什么是 Python 中的元类?
元类是类的类。一个类定义了类的实例(即对象)的行为,而元类定义了类的行为。一个类是元类的实例。
虽然在Python中你可以使用任意可调用对象作为元类(就像 Jerub 所示),但更好的方法是将其作为一个实际的类。在Python中,type
是通常使用的元类。 type
本身是一个类,也是自己的类型。你不会仅使用Python就能够重新创建像 type
这样的东西,但是Python有点欺骗。要在Python中创建自己的元类,您只需要将其作为type
的子类即可。
元类最常用作类工厂。当您通过调用类创建对象时,Python通过调用元类创建一个新的类(当执行 'class' 语句时)。结合普通的__init__
和__new__
方法,元类因此允许您在创建类时执行“额外的事情”,比如将新类注册到某个注册表中或将类替换为完全不同的东西。
当执行 class
语句时,Python 首先将 class
语句体作为普通代码块执行。产生的命名空间(一个字典)保存着将要创建的类的属性。元类通过查看将要创建的类的基类(元类是继承而来的)、类的 metaclass
属性(如果有)或全局变量 metaclass
来确定。然后,元类用类的名称、基类和属性来调用,从而实例化该类。
然而,元类实际上定义了类的类型,而不仅仅是一个工厂。因此,你可以通过元类做更多的事情。例如,你可以在元类上定义普通方法。这些元类方法类似于类方法,因为它们可以在没有实例的情况下在类上调用,但它们也不像类方法,因为它们不能在类的实例上调用。 type.__subclasses__()
是 type
元类的一个方法示例。你也可以定义普通的“魔法”方法,例如 __add__
、__iter__
和 __getattr__
,以实现或更改类的行为方式。
这是一个聚合示例的代码段:
def make_hook(f): """Decorator to turn 'foo' method into '__foo__'""" f.is_hook = 1 return f class MyType(type): def __new__(mcls, name, bases, attrs): if name.startswith('None'): return None # Go over attributes and see if they should be renamed. newattrs = {} for attrname, attrvalue in attrs.iteritems(): if getattr(attrvalue, 'is_hook', 0): newattrs['__%s__' % attrname] = attrvalue else: newattrs[attrname] = attrvalue return super(MyType, mcls).__new__(mcls, name, bases, newattrs) def __init__(self, name, bases, attrs): super(MyType, self).__init__(name, bases, attrs) # classregistry.register(self, self.interfaces) print "Would register class %s now." % self def __add__(self, other): class AutoClass(self, other): pass return AutoClass # Alternatively, to autogenerate the classname as well as the class: # return type(self.__name__ + other.__name__, (self, other), {}) def unregister(self): # classregistry.unregister(self) print "Would unregister class %s now." % self class MyObject: __metaclass__ = MyType class NoneSample(MyObject): pass # Will print "NoneType None" print type(NoneSample), repr(NoneSample) class Example(MyObject): def __init__(self, value): self.value = value @make_hook def add(self, other): return self.__class__(self.value + other.value) # Will unregister the class Example.unregister() inst = Example(10) # Will fail with an AttributeError #inst.unregister() print inst + inst class Sibling(MyObject): pass ExampleSibling = Example + Sibling # ExampleSibling is now a subclass of both Example and Sibling (with no # content of its own) although it will believe it's called 'AutoClass' print ExampleSibling print ExampleSibling.__mro__
类作为对象
在理解元类之前,了解 Python 类更深入的知识将会有所帮助。Python 对于类有一个非常特殊的想法,这个想法来自于 Smalltalk 语言。
在大多数语言中,类只是描述如何生成对象的一些代码片段。在 Python 中也有这种情况:
>>> class ObjectCreator(object): ... pass >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
但是在 Python 中,类不仅仅是这些。类本身也是对象。
没错,就是对象。
当 Python 脚本运行时,每一行代码都会从上到下执行。当 Python 解释器遇到 class
关键字时,Python 会根据接下来的类的“描述”创建一个 对象。因此,以下指令
>>> class ObjectCreator(object): ... pass
...创建了一个名称为 ObjectCreator
的对象!
这个对象(即类)本身也能够创建对象(称为实例)。
但是它仍然是一个对象。因此,像所有对象一样:
- 你可以将其赋值给一个变量1
JustAnotherVariable = ObjectCreator
- 你可以将属性附加到它上面
ObjectCreator.class_attribute = 'foo'
- 你可以将其作为函数参数传递
print(ObjectCreator)
1 注意,仅仅将其赋值给另一个变量并不会改变类的 __name__
,也就是说,
>>> print(JustAnotherVariable)>>> print(JustAnotherVariable()) <__main__.ObjectCreator object at 0x8997b4c>
动态创建类
由于类也是对象,因此可以像任何对象一样在运行时创建它们。
首先,可以使用 class
在函数中创建一个类:
>>> def choose_class(name): ... if name == 'foo': ... class Foo(object): ... pass ... return Foo # return the class, not an instance ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class('foo') >>> print(MyClass) # the function returns a class, not an instance>>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c>
但是这并不是很动态,因为你仍然需要自己编写整个类。
由于类也是对象,因此它们必须由某些东西生成。
当你使用 class
关键字时,Python 会自动创建这个对象。但像 Python 中的大多数东西一样,它也给你提供了手动创建这个对象的方法。
还记得函数 type
吗?那个能让你知道一个对象类型的老函数:
>>> print(type(1))>>> print(type("1")) >>> print(type(ObjectCreator)) >>> print(type(ObjectCreator()))
好吧,type
还有一个完全不同的能力:它可以动态地创建类。 type
可以将一个类的描述作为参数,返回一个类。
(我知道,同一个函数根据你传入的参数可以有两个完全不同的用法,这有点儿傻。这是由于 Python 中的向后兼容问题)
type
的工作方式如下:
type(name, bases, attrs)
其中:
name
:类的名称bases
:父类的元组(用于继承,可以为空)attrs
:包含属性名称和值的字典
例如:
>>> class MyShinyClass(object): ... pass
可以手动创建如下:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object >>> print(MyShinyClass)>>> print(MyShinyClass()) # create an instance with the class <__main__.MyShinyClass object at 0x8997cec>
你会发现我们使用 MyShinyClass
作为类名和保存类引用的变量名。它们可以不同,但没有理由让事情变得复杂。
type
接受一个字典来定义类的属性。所以:
>>> class Foo(object): ... bar = True
可以翻译为:
>>> Foo = type('Foo', (), {'bar':True})
并且可以像普通类一样使用:
>>> print(Foo)>>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True
当然,你可以从中继承,例如:
>>> class FooChild(Foo): ... pass
可以写成:
>>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild)>>> print(FooChild.bar) # bar is inherited from Foo True
最终,你可能想要给你的类添加方法。只需要定义一个带有正确签名的函数,并将其分配为属性即可。
>>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') False >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
你甚至可以在动态创建类之后添加更多方法,就像向通常创建的类对象添加方法一样。
>>> def echo_bar_more(self): ... print('yet another method') ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more') True
你看到了吗:在Python中,类也是对象,你可以动态地创建类。
这就是当你使用关键字class
时,Python所做的事情,它使用了一个元类。
什么是元类(终于)
元类就是创建类的'东西'。
你定义类是为了创建对象,对吗?
但我们已经知道Python类也是对象。
那么,元类就是创建这些对象的东西。它们是类的类,可以这样形象地描述它们:
MyClass = MetaClass() my_object = MyClass()
你已经看到了type
让你做了下面的事情:
MyClass = type('MyClass', (), {})
这是因为函数type
实际上是一个元类。type
是Python在幕后创建所有类所使用的元类。
现在你会想:"为什么它写成小写,而不是Type
?"
嗯,我猜这是为了与创建字符串对象的str
类和创建整数对象的int
类保持一致。 type
只是创建类对象的类。
你可以通过检查__class__
属性来查看所有东西,我是指所有东西,在Python中都是对象。 这包括整数、字符串、函数和类。 它们都是对象,而且它们都是从类创建的:
>>> age = 35 >>> age.__class__>>> name = 'bob' >>> name.__class__ >>> def foo(): pass >>> foo.__class__ >>> class Bar(object): pass >>> b = Bar() >>> b.__class__
现在,任何__class__
的__class__
是什么呢?
>>> age.__class__.__class__>>> name.__class__.__class__ >>> foo.__class__.__class__ >>> b.__class__.__class__
因此,元类只是创建类对象的东西。
你可以称它为“类工厂”,如果你愿意的话。
type
是Python使用的内置元类,但是当然,你可以创建自己的元类。
__metaclass__
属性
在 Python 2 中,当你定义一个类时,可以添加 __metaclass__
属性(Python 3 的语法见下一部分):
class Foo(object): __metaclass__ = something... [...]
如果你这样做,Python 将使用元类来创建类 Foo
。
但请小心,这很棘手。
首先你要写 class Foo(object)
,但类对象 Foo
还没有在内存中创建。
Python 会查找类定义中的 __metaclass__
。如果找到了,它就会用它来创建对象类 Foo
。如果没有找到,它就会使用 type
来创建类。
多读几遍。
当你这样做时:
class Foo(Bar): pass
Python会执行以下操作:
Foo
中是否有__metaclass__
属性?
如果是,则使用__metaclass__
的内容在内存中创建一个类对象(请跟着我理解) ,并将其命名为Foo
。
如果Python找不到__metaclass__
,则会在模块级别上查找__metaclass__
,并尝试做相同的事情(但只针对那些没有继承任何东西的类,基本上是旧式类)。
然后,如果它找不到任何__metaclass__
,它将使用Bar
的(第一个父类)自己的元类(可能是默认的type
)来创建类对象。
要注意的是,__metaclass__
属性不会被继承,父类(Bar.__class__
)的元类会被继承。如果Bar
使用了一个创建Bar
为type()
的__metaclass__
属性(而不是type.__new__()
),那么子类将不会继承这个行为。
现在的大问题是,你可以在__metaclass__
里放什么?
答案是一些可以创建类的东西。
那么什么可以创建类呢?type
或任何子类或使用它的东西。
Python 3中的元类
在Python 3中,设置元类的语法已经改变:
class Foo(object, metaclass=something): ...
也就是说,不再使用__metaclass__
属性,而是使用基类列表中的关键字参数。
然而,元类的行为基本上保持不变。
Python 3中添加到元类的一件事是,你还可以将属性作为关键字参数传递给元类,像这样:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2): ...
请阅读下面的部分,了解Python如何处理此问题。
自定义元类
元类的主要目的是在创建类时自动更改类。
通常在 API 中,您会使用元类来创建与当前上下文匹配的类。
想象一个愚蠢的例子,您决定模块中的所有类都应该使用大写字母编写其属性。 有几种方法可以实现这一点,但一种方法是在模块级别设置__metaclass__
。
这样,该模块的所有类都将使用此元类创建,我们只需告诉元类将所有属性转换为大写字母即可。
幸运的是,__metaclass__
实际上可以是任何可调用对象,它不需要是正式的类(我知道,一个带有“class”名称的东西不需要是类,有点难以理解...但这很有用)。
因此,我们将从一个简单的例子开始,使用一个函数。
# the metaclass will automatically get passed the same argument # that you usually pass to `type` def upper_attr(future_class_name, future_class_parents, future_class_attrs): """ Return a class object, with the list of its attribute turned into uppercase. """ # pick up any attribute that doesn't start with '__' and uppercase it uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in future_class_attrs.items() } # let `type` do the class creation return type(future_class_name, future_class_parents, uppercase_attrs) __metaclass__ = upper_attr # this will affect all classes in the module class Foo(): # global __metaclass__ won't work with "object" though # but we can define __metaclass__ here instead to affect only this class # and this will work with "object" children bar = 'bip'
让我们检查一下:
>>> hasattr(Foo, 'bar') False >>> hasattr(Foo, 'BAR') True >>> Foo.BAR 'bip'
现在,让我们完全相同地使用一个真正的类作为元类:
# remember that `type` is actually a class like `str` and `int` # so you can inherit from it class UpperAttrMetaclass(type): # __new__ is the method called before __init__ # it's the method that creates the object and returns it # while __init__ just initializes the object passed as parameter # you rarely use __new__, except when you want to control how the object # is created. # here the created object is the class, and we want to customize it # so we override __new__ # you can do some stuff in __init__ too if you wish # some advanced use involves overriding __call__ as well, but we won't # see this def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in future_class_attrs.items() } return type(future_class_name, future_class_parents, uppercase_attrs)
现在我们已经知道这些变量的含义,让我们使用更短和更现实的变量名来重写上面的代码:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in attrs.items() } return type(clsname, bases, uppercase_attrs)
您可能已经注意到了额外的参数cls
。这并没有什么特别之处:__new__
总是作为第一个参数接收它所定义的类。就像您对于普通方法有self
作为第一个参数接收实例,或者对于类方法有定义类作为第一个参数。
但这不是正确的面向对象编程方式。我们直接调用了type
而没有重写或调用父类的__new__
。让我们改正这一点:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in attrs.items() } return type.__new__(cls, clsname, bases, uppercase_attrs)
我们可以使用super
进一步简化代码,这将有助于继承(因为是的,您可以有继承自元类的元类,继承自type
):
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in attrs.items() } # Python 2 requires passing arguments to super: return super(UpperAttrMetaclass, cls).__new__( cls, clsname, bases, uppercase_attrs) # Python 3 can use no-arg super() which infers them: return super().__new__(cls, clsname, bases, uppercase_attrs)
哦,在 Python 3 中,如果您使用关键字参数进行此调用,如下所示:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1): ...
它会在元类中转换为:
class MyMetaclass(type): def __new__(cls, clsname, bases, dct, kwargs1=default): ...
就这样。关于元类真的没有更多内容了。
使用元类的代码复杂性的原因并不是因为元类本身,而是因为您通常使用元类来做一些仰仗内省、操作继承、变量(如__dict__
)等神秘操作。
的确,元类尤其适用于进行黑魔法和因此复杂的操作。但它们本身很简单:
- 拦截类的创建
- 修改类
- 返回修改后的类
为什么要使用元类而不是函数?
由于__metaclass__
可以接受任何可调用对象,那么为什么要使用类,因为显然它更加复杂?
有几个原因可以这样做:
- 意图清晰。当你看到
UpperAttrMetaclass(type)
,你知道接下来会发生什么 - 你可以使用 OOP。元类可以继承元类,覆盖父类方法。元类甚至可以使用元类。
- 类的子类如果指定了元类类,则将是其元类的实例,但使用元类函数则不会。
- 你可以更好地组织你的代码。你永远不会为像上面的示例那样微不足道的事情使用元类。通常是用于复杂的事情。能够制作几个方法并将它们分组在一个类中,这非常有用,可以使代码更易于阅读。
- 你可以钩住
__new__
,__init__
和__call__
。这将允许你做不同的事情,即使通常你可以在__new__
中完成所有这些,但有些人更喜欢使用__init__
。 - 这些被称为元类,该死!这一定有什么含义!
为什么要使用元类?
现在是一个大问题。为什么要使用一些晦涩难懂的功能呢?
通常情况下,您不需要使用元类:
元类是更深层次的魔法,99% 的用户都不需要担心它。
如果您想知道自己是否需要它们,那么您不需要(那些真正需要它们的人知道他们需要它们,而且不需要关于为什么需要的解释)。
Python大师Tim Peters
元类的主要用途是创建API。一个典型的例子是Django ORM。它允许您定义类似于以下内容:
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
但是如果您这样做:
person = Person(name='bob', age='35') print(person.age)
它不会返回一个IntegerField
对象。它将返回一个int
,甚至可以直接从数据库中获取它。
这是可能的,因为models.Model
定义了__metaclass__
并使用了一些魔法,将刚刚用简单语句定义的Person
转换为一个连接到数据库字段的复杂钩子。
Django通过公开简单的API并使用元类来重新创建代码,从而使复杂的事情看起来简单。
最后的话
首先,您知道类是可以创建实例的对象。
实际上,类本身也是元类的实例。
>>> class Foo(object): pass >>> id(Foo) 142630324
在Python中,所有东西都是对象,它们都是类的实例或元类的实例。
除了type
。
type
实际上是它自己的元类。这不是您可以在纯Python中复制的内容,而是在实现层面上稍微作弊。
其次,元类很复杂。您可能不想将其用于非常简单的类修改。您可以使用两种不同的技术来更改类:
- 猴子补丁
- 类装饰器
99%的时间,您需要类修改,最好使用这些方法。
但是实际上有98%的情况下,你根本不需要对类进行修改。