我试图在sqlacalchemy的类构造过程中注入一些自己的代码。为了理解代码,我对元类的实现有些困惑。以下是相关的代码片段:
sqlacalchemy的默认“元类”:

class DeclarativeMeta(type):
    def __init__(cls, classname, bases, dict_):
        if '_decl_class_registry' in cls.__dict__:
            return type.__init__(cls, classname, bases, dict_)
        else:
            _as_declarative(cls, classname, cls.__dict__)
        return type.__init__(cls, classname, bases, dict_)

    def __setattr__(cls, key, value):
        _add_attribute(cls, key, value)

declarative_base的实现方式如下:
def declarative_base(bind=None, metadata=None, mapper=None, cls=object,
                     name='Base', constructor=_declarative_constructor,
                     class_registry=None,
                     metaclass=DeclarativeMeta):
     # some code which should not matter here
     return metaclass(name, bases, class_dict)

使用方法如下:
Base = declarative_base()

class SomeModel(Base):
    pass

现在我已经派生出了我自己的元类,如下所示:
class MyDeclarativeMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        result = DeclarativeMeta.__init__(cls, classname, bases, dict_)
        print result
        # here I would add my custom code, which does not work
        return result

像这样使用:
Base = declarative_base(metaclass=MyDeclarativeMeta)

好吧,现在我的问题是:
在我自己的班级里,总是打印。
不管怎么说,代码似乎还是有效的!?
为什么元类使用print result而不是None
__init__返回此类的实例。它是否应该返回一个属性为__new__且值为declarative_base的类?
所以我想知道为什么代码是有效的。因为sqlacalchemy的人显然知道他们在做什么,所以我认为我完全走错了道路。有人能解释一下这是怎么回事吗?

最佳答案

第一件事。需要返回__init__。在python中,Python docs说“不可能返回任何值”,但是在函数的“dropping off the end”而不命中返回语句就等于None。因此显式返回return None(作为文本或通过返回导致None的表达式的值)也不会造成任何伤害。
所以你引用的None方法对我来说有点奇怪,但它没有做任何错误。这里是我添加的一些评论:

def __init__(cls, classname, bases, dict_):
    if '_decl_class_registry' in cls.__dict__:
        # return whatever type's (our superclass) __init__ returns
        # __init__ must return None, so this returns None, which is okay
        return type.__init__(cls, classname, bases, dict_)
    else:
        # call _as_declarative without caring about the return value
        _as_declarative(cls, classname, cls.__dict__)
    # then return whatever type's __init__ returns
    return type.__init__(cls, classname, bases, dict_)

这可以更简洁明了地写为:
def __init__(cls, classname, bases, dict_):
    if '_decl_class_registry' not in cls.__dict__:
        _as_declarative(cls, classname, cls.__dict__)
    type.__init__(cls, classname, bases, dict_)

我不知道为什么sqlAlchemy开发人员认为需要返回任何返回的__init__值(限制为DeclarativeMeta)。当type.__init__可能返回某些内容时,它可能是对未来的一种防御。也许这只是为了与其他方法的一致性,在这些方法中,核心实现是通过推迟到超类来实现的;通常,除非您希望对超类调用进行后处理,否则您将返回任何超类调用返回的值。但是它确实没有做任何事情。
因此,您的None打印__init__只是表明一切都按预期工作。
接下来,让我们仔细看看元类的实际含义。元类只是类的一个类。与任何类一样,您可以通过调用元类来创建元类(即类)的实例。类块语法并不是创建类的真正原因,它只是定义字典然后将字典传递给元类调用以创建类对象的非常方便的语法工具。
print result属性并不是什么魔法,它实际上只是一个巨大的黑客,用来传递信息“我希望这个类块创建一个这个元类的实例,而不是通过一个后台通道来传递None的实例,因为没有合适的通道来传递该信息。向口译员问话。1
通过一个例子,这可能会更清楚。采用以下类块:
class MyClass(Look, Ma, Multiple, Inheritance):
    __metaclass__ = MyMeta

    CLASS_CONST = 'some value'

    def __init__(self, x):
        self.x = x

    def some_method(self):
        return self.x - 76

这是做以下工作的大致句法糖分2:
dict_ = {}

dict_['__metaclass__'] = MyMeta
dict_['CLASS_CONST'] = 'some value'

def __init__(self, x):
    self.x = x
dict_['__init__'] = __init__

def some_method(self):
    return self.x - 76
dict_['some_method'] = some_method

metaclass = dict_.get('__metaclass__', type)
bases = (Look, Ma, Multiple, Inheritance)
classname = 'MyClass'

MyClass = metaclass(classname, bases, dict_)

因此,“具有属性__metaclass__且[元类]为值的类”是元类的一个实例!它们是完全一样的。唯一的区别是,如果直接(通过调用元类)创建类,而不是使用class块和type属性,那么它不必将__metaclass__作为属性。3
末尾对__metaclass__的调用与其他类调用完全相同。它将调用__metaclass__以获取创建类对象,然后对生成的对象调用metaclass以初始化它。
默认的元类仅在metaclass.__new__(classname, bases, dict_)中执行任何有趣的操作。我在示例中看到的元类的大多数用途实际上只是实现类修饰符的一种复杂的方法;它们希望在创建类时进行一些处理,之后就不必担心了。所以他们使用__init__是因为它允许他们在type之前和之后执行。最终的结果是,每个人都认为__new__是在元类中实现的。
但实际上您可以有一个__new__方法;在创建新的类对象之后,它将被调用。如果您需要在某个注册表中的某个地方添加类的一些属性,或者记录类对象,那么这实际上是一个比type.__new__更方便的地方(以及逻辑上正确的地方)。
1在python3中,这是通过在基类列表中添加__new__作为“关键字参数”而不是作为类的属性来解决的。
2实际上,由于所构造的类和所有基之间需要元类兼容性,这稍微复杂一些,但这是核心思想。
3即使是具有元类(而不是__init__的类)的类创建了通常的方法,也不一定要将__new__作为属性;检查类的正确方法与检查任何其他类的方法相同;请使用metaclass或应用type

关于python - SqlAlchemy元类混淆,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12664385/

10-12 18:27