是否可以找到给定 class 变量的 class 名称

is it possible to find class name given class variable

class A:
    var = 'hello'


type(A.var)               # returns: <class 'str'>
A.var.__class__.__name__  # returns: 'str'
vars(A)                   # returns: mappingproxy({'__module__': '__main__', 'var': 'hello', '__dict__': <attribute ' __dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, '__getattribute__': <slot wrapper '__getattribute__' of 'object' objects>})

给定 A.var 是否有可能找到 class A,或者只是 var 与 class A 相关,可能类似于 'qualname' 用于方法。

已编辑

我想找到 class 名称的原因是我正在实现微型 ORM,类似于使用 declarative mapping.

的 SQLAlchemy

如您在此示例中所见。

import sqlalchemy as sa
import sqlalchemy.ext.declarative

meta = sa.MetaData()
DeclarativeBase = sa.ext.declarative.declarative_base(metadata=meta)


class User(DeclartiveBase):
    __tablename__ = 'user'

    id            = sa.Column(Integer, primary_key=True)
    name          = sa.Column(String)
    fullname      = sa.Column(String)

如您所见,class 用作真实 table 的抽象,有趣的是您仍然可以使用 vars(Users) 访问所有列并使用 [= 过滤列20=].

在进行查询时,查询如何能够知道该行来自哪个 table,这与我上面提出的问题类似,可能与 sa.Column.[= 有关24=]

Session = sa.orm.sessionmaker(bind=engine)
session = Session()

rows = session.query(
    User.id,
    User.name,
).all()

样本

这是实现的示例,它能够进行基本的 table 创建、插入和更新。

class Col(str):
    pass

class Base:
    @classmethod
    def childs(cls):
        subclasses = set()
        work = [cls]
        while work:
            parent = work.pop()
            for child in parent.__subclasses__():
                if child not in subclasses:
                    subclasses.add(child)
                    work.append(child)
        return subclasses


    @classmethod
    def create(cls, cursor):
        query = 'CREATE TABLE IF NOT EXISTS {} ({})'.format(
            cls.__tablename__,
            ', '.join(
                k + ' ' + v
                for k, v in vars(cls).items()
                if isinstance(v, Col)
            )
        )
        cursor.execute(query)


    @staticmethod
    def create_all_tables(cursor):
        for cls in __class__.childs():
            cls.create(cursor)


    @classmethod
    def drop(cls, cursor):
        query = 'DROP TABLE IF EXISTS {}'.format(
            cls.__tablename__,
        )
        cursor.execute(query)


    @classmethod
    def insert(cls, cursor, kvRow):
        query = 'INSERT INTO {} ({}) VALUES(:{})'.format(
            cls.__tablename__,
            ', '.join(kvRow.keys()),
            ', :'.join(kvRow.keys())
        )
        cursor.execute(query, kvRow)

使用它

class User(Base):
    __tablename__ = 'user'

    id            = Col('INTEGER PRIMARY KEY')
    name          = Col('VARCHAR')
    fullname      = Col('VARCHAR')

现在创建 table 类似于 sqlalchemy meta.create_all(bind=engine, checkfirst=True)

import sqlite3

conn   = sqlite3.connect('./sqlite.db')
cursor = conn.cursor()
Base.create_all_tables(cursor)

我不明白 sqlchemy session.query 是如何理解 table 该行的来源。

嗯,不可能将 class A 关联到变量,因为您正在创建一个 class A,但实例化了一个与该变量无关的字符串 class. 如果您创建 class 并关联一个属性,而不是仅在 class 中创建原始字符串,那么您可以 return class.

class A:
    def __init__(self, value):
        self.__value = value
    def __str__(self):
        return self.__value
    def __eq__(self, other):
        return self.__value == other
    def retunclass(self):
        return self.__class__.__name__

var = A('hello')             #var = 'hello'
print(var.retunclass())      #>>> 'A'

if var == 'hello':
    print(True)              #>>> True

如果我对你的问题理解正确的话,你处理的主要问题是:如何创建一个对象,当它被定义为一个class作为它的属性时,"知道”class它属于什么?

为了实现这一点,SQLAlchemy 使用 metaclasses(高级主题...),但也有更简单的技术,尤其是在现代版本的 Python。其中之一,我认为对你来说特别方便,是让你的 Col class 实现 描述符协议的特定部分 .

一个简单的方法——基于__set_name__

参考Python docs,当涉及到您的用例时,描述符协议的相关部分是方法:

__set_name__(self, owner, name)

Called at the time the owning class owner is created. The descriptor has been assigned to name.

例如,您可以按如下方式实现此方法:

class Col(str):

    owner = None
    name = None

    def __set_name__(self, owner, name):
        self.owner = owner
        self.name = name

然后,如果您在 class 定义中放置一些 Col 的实例作为 class 的属性,他们将通过调用他们的 __set_name__,就在创建 class 之后。多亏了这一点,他们每个人都会“知道” class 和作为 class 的属性获得的名称:

>>> class ArbitraryClass:
...     id            = Col('INTEGER PRIMARY KEY')
...     name          = Col('VARCHAR')
...     fullname      = Col('VARCHAR')
... 
>>> ArbitraryClass.id
'INTEGER PRIMARY KEY'
>>> ArbitraryClass.id.owner
<class '__main__.ArbitraryClass'>
>>> ArbitraryClass.id.name
'id'
>>> ArbitraryClass.name.owner
<class '__main__.ArbitraryClass'>
>>> ArbitraryClass.name.name
'name'
>>> ArbitraryClass.fullname.owner
<class '__main__.ArbitraryClass'>
>>> ArbitraryClass.fullname.name
'fullname'

另一种方法 - 基于 __init_subclass__

# We keep `Col` very simple:
class Col(str):
    owner = None
    name = None

# ...as the responsibility of detecting `Col` attributes
# is taken by the `Base` class:
class Base:
    def __init_subclass__(cls, /, **kwargs):
        super().__init_subclass__(**kwargs)
        for name, obj in vars(cls).items():
            if isinstance(obj, Col):
                obj.owner = cls
                obj.name = name
    # And below the rest of your implementation of `Base`...

参见docs of __init_subclass__

上述定义的使用示例:

>>> class Table(Base):
...     id            = Col('INTEGER PRIMARY KEY')
...     name          = Col('VARCHAR')
...     fullname      = Col('VARCHAR')
... 
>>> Table.id
'INTEGER PRIMARY KEY'
>>> Table.id.owner
<class '__main__.Table'>
>>> Table.id.name
'id'
>>> Table.name.owner
<class '__main__.Table'>
>>> Table.name.name
'name'
>>> Table.fullname.owner
<class '__main__.Table'>
>>> Table.fullname.name
'fullname'

另一种替代方法 - metaclass-based

另一种可能性是使用 metaclass —— 其方式与我们在上面对 __init_subclass__ 所做的非常相似。

# Again, we keep `Col` very simple:
class Col(str):
    owner = None
    name = None

# ...because a metaclass will do the work:
class BaseMeta(type):
    def __new__(metacls, name, bases, namespace, **kwargs):
        cls = super().__new__(metacls, name, bases, namespace, **kwargs)
        for name, obj in vars(cls).items():
            if isinstance(obj, Col):
                obj.owner = cls
                obj.name = name
        return cls

class Base(metaclass=BaseMeta):
    # Note: ↑ by passing `metaclass=BaseMeta` we say:
    # the class of the `Base` class is `BaseMeta`.
    # That's the essence: *metaclass* == *class of a class*.

    # And below the rest of your implementation of `Base`...

用法与基于 __init_subclass__() 的方法相同...

注意:metaclasses 是一个非常强大的工具,因为您可以使用它们自定义 class 创建过程的各个阶段。然而,在这里,我认为让他们参与会有点矫枉过正。此外,当您需要多个 metaclass 提供的功能时,使用此技术会变得很麻烦。编写不同的现有元classes 可能会变得困难甚至不可能。

所以我宁愿建议前面描述的一种更简单的方法。

如果要查找查询中的列“属于”的模型 class,可以使用查询的 column_descriptions 属性:

q = session.query(User.id, User.name)
q.column_descriptions

产出

[{
     'name': 'id',
     'type': Integer(),
     'aliased': False,
     'expr': <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x7fa18b87dcc0>,
     'entity': <class '__main__.User'>
 }, 
 {
     'name': 'name',
     'type': String(),
     'aliased': False,
     'expr': <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x7fa18b87dd60>,
     'entity': <class '__main__.User'>
}]

因此可以在每列的 entity 键的值中找到模型 class。这适用于 1.x session.query 语法和 2.0 sa.select(User.id, User.name) 语法。