我试图在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/