Python中的 метaclass 是什么?
什么是 metaclasses?它们有什么用?
25 个回答
注意,这个回答是针对Python 2.x的,因为它是在2008年写的,3.x中的元类稍有不同。
元类是让“类”能够工作的秘密武器。新式对象的默认元类叫做“type”。
class type(object)
| type(object) -> the object's type
| type(name, bases, dict) -> a new type
元类需要三个参数:'name'(名字)、'bases'(基类)和'dict'(字典)。
这里就是秘密开始的地方。看看在这个示例类定义中,名字、基类和字典是从哪里来的。
class ThisIsTheName(Bases, Are, Here):
All_the_code_here
def doesIs(create, a):
dict
让我们定义一个元类,来演示“class:”是如何调用它的。
def test_metaclass(name, bases, dict):
print 'The Class Name is', name
print 'The Class Bases are', bases
print 'The dict has', len(dict), 'elems, the keys are', dict.keys()
return "yellow"
class TestName(object, None, int, 1):
__metaclass__ = test_metaclass
foo = 1
def baz(self, arr):
pass
print 'TestName = ', repr(TestName)
# output =>
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName = 'yellow'
现在,给出一个实际有意义的例子,这将自动将列表中的变量“属性”设置在类上,并且初始值为None。
def init_attributes(name, bases, dict):
if 'attributes' in dict:
for attr in dict['attributes']:
dict[attr] = None
return type(name, bases, dict)
class Initialised(object):
__metaclass__ = init_attributes
attributes = ['foo', 'bar', 'baz']
print 'foo =>', Initialised.foo
# output=>
foo => None
注意,Initialised
通过使用元类init_attributes
获得的神奇行为,并不会传递给Initialised
的子类。
这里有一个更具体的例子,展示了如何子类化'type'来创建一个元类,当类被创建时执行某个操作。这是相当复杂的:
class MetaSingleton(type):
instance = None
def __call__(cls, *args, **kw):
if cls.instance is None:
cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
return cls.instance
class Foo(object):
__metaclass__ = MetaSingleton
a = Foo()
b = Foo()
assert a is b
类也是对象
在深入了解 metaclasses(元类)之前,先掌握 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)
<class '__main__.ObjectCreator'>
>>> 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
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
但这并不是特别动态,因为你仍然需要自己写完整个类。
由于类是对象,它们必须由某种东西生成。
当你使用 class
关键词时,Python 会自动创建这个对象。但是,像 Python 中的许多事情一样,它也提供了一种手动创建类的方法。
还记得 type
函数吗?这个老朋友可以告诉你一个对象是什么类型:
>>> print(type(1))
<class 'int'>
>>> print(type("1"))
<class 'str'>
>>> print(type(ObjectCreator))
<class 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.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)
<class '__main__.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)
<class '__main__.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)
<class '__main__.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__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
那么,任何 __class__
的 __class__
是什么呢?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
所以,元类就是创建类对象的东西。
你可以把它称为“类工厂”。
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
使用了一个 __metaclass__
属性来创建 Bar
,使用的是 type()
(而不是 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__
实际上可以是任何可调用的东西,它不需要是一个正式的类(我知道,某些带有“类”字样的东西不需要是类,这很奇怪……但这很有用)。
所以我们将从一个简单的例子开始,使用一个函数。
# 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
使其更简洁,这将简化继承(因为是的,你可以有元类,继承自元类,继承自类型):
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)
时,你就知道接下来会发生什么 - 你可以使用面向对象编程。元类可以继承自元类,重写父类的方法。元类甚至可以使用元类。
- 类的子类将是其元类的实例,如果你指定了一个元类类,但使用元类函数则不会。
- 你可以更好地组织代码。你不会将元类用于像上面那样简单的事情。通常是用于复杂的事情。将多个方法组合在一个类中是使代码更易读的一个很有用的方式。
- 你可以挂钩
__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% 的时候,你根本不需要类的修改。
元类就是类的类。一个类定义了这个类的实例(也就是对象)是怎么工作的,而元类则定义了一个类是怎么工作的。简单来说,类是元类的一个实例。
在Python中,你可以用任意可调用的东西作为元类(就像Jerub所示),但更好的做法是把它做成一个真正的类。type
是Python中常用的元类。type
本身也是一个类,而且它还是它自己的类型。你不能单纯用Python重建像type
这样的东西,但Python在这方面有点小聪明。要在Python中创建自己的元类,你只需要继承type
就可以了。
元类最常用的方式是作为类的工厂。当你通过调用一个类来创建对象时,Python会在执行'类'语句时调用元类来创建一个新类。结合普通的__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__