Python中的 метaclass 是什么?

7368 投票
25 回答
1166115 浏览
提问于 2025-04-11 09:24

什么是 metaclasses?它们有什么用?

25 个回答

491

注意,这个回答是针对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
9017

类也是对象

在深入了解 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% 的时候,你根本不需要类的修改。

3429

元类就是类的类。一个类定义了这个类的实例(也就是对象)是怎么工作的,而元类则定义了一个类是怎么工作的。简单来说,类是元类的一个实例。

在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__

撰写回答