术语元编程是指程序具备理解或操纵本身的后劲。Python反对一种称为metaclasses的类的元编程模式。

元类是一个深奥的OOP概念,简直暗藏在所有Python代码之后。无论您是否晓得,都在应用它们。在大多数状况下,您无需意识到这一点。大多数Python程序员很少(即便有的话)也不用思考元类。

然而,当须要时,Python提供了并非所有面向对象的语言都反对的性能:您能够深刻理解并自定义元类。自定义元类的应用引起了一些争议,正如Python 禅意作者Tim Peters所援用的那样:

元类具备比99%的用户应该放心的更深的魔力。如果您想晓得是否须要它们,则不须要(实际上须要它们的人必定会晓得他们须要它们,并且不须要解释起因)。”

蒂姆·彼得斯

有一些使用者(Pythonista - 家喻户晓的Python爱好者)认为永远不要应用自定义元类。这可能有点远,然而很可能不须要自定义元类。如果不是很显著有问题须要解决,那么如果以更简略的形式解决问题,它可能会更洁净,更易读。

尽管如此,了解 Python 元类还是值得的,因为通过元类能够更好地了解Python 类的外部。可能有一天会遇到一种状况:只须要一个自定义元类即能够解决问题。

新式与旧式类

在Python畛域中,类能够是两个变体之一。尚未确定官网术语,因而将它们非正式地称为旧类和新类。

老式类

对于老式的类,类和类型不是一回事。老式类的实例始终由称为的单个内置类型实现instance。如果obj是老式类的实例,则obj.__class__指定该类,但type(obj)始终为instance。以下示例取自 Python 2.7:

>>> class Foo:...     pass...>>> x = Foo()>>> x.__class__<class __main__.Foo at 0x000000000535CC48>>>> type(x)<type 'instance'>

新型类

新型类对立了类和类型的概念。如果obj是新型类的实例,type(obj)则与雷同obj.__class__

>>> class Foo:...     pass>>> obj = Foo()>>> obj.__class__<class '__main__.Foo'>>>> type(obj)<class '__main__.Foo'>>>> obj.__class__ is type(obj)True
>>> n = 5>>> d = { 'x' : 1, 'y' : 2 }>>> class Foo:...     pass...>>> x = Foo()>>> for obj in (n, d, x):...     print(type(obj) is obj.__class__)...TrueTrueTrue

类型type和类别class

在Python 3中,所有类都是新型类。因而,在Python 3中,能够调换地援用对象的类型及其类是正当的。

留神:在Python 2中,默认状况下,类为旧款式。在Python 2.2之前,基本不反对新型类。从Python 2.2开始,能够创立它们,但必须将其显式申明为new-style。

请记住,在Python中,一切都是对象。类也是对象。后果,一个类必须具备一个类型。什么是课程类型?

思考以下:

>>> class Foo:...     pass...>>> x = Foo()>>> type(x)<class '__main__.Foo'>>>> type(Foo)<class 'type'>

如您所料,type x是class Foo。然而Foo,类自身的类型是type。通常,任何旧式类的类型都是type

您相熟的内置类的类型也是type

>>> for t in int, float, dict, list, tuple:...     print(type(t))...<class 'type'><class 'type'><class 'type'><class 'type'><class 'type'>

就此而言,类型type也是type如此(是的,的确):

>>> type(type)<class 'type'>

type是一个元类,其中的类是实例。就像一般对象是类的实例一样,Python中的任何旧式类以及Python 3中的任何类都是type元类的实例。

在上述情况下:

  • x是class的实例Foo
  • Footype元类的实例。
  • type也是type元类的实例,因而它也是本身的实例。

动静定义类

type()当传递一个参数时,内置函数将返回对象的类型。对于新型类,通常与对象的__class__属性雷同:

>>> type(3)<class 'int'>>>> type(['foo', 'bar', 'baz'])<class 'list'>>>> t = (1, 2, 3, 4, 5)>>> type(t)<class 'tuple'>>>> class Foo:...     pass...>>> type(Foo())<class '__main__.Foo'>

还能够应用三个参数进行调用type(<name>, <bases>, <dct>)

  • <name>指定类名称。这成为__name__该类的属性。
  • <bases>指定从其继承的基类的元组。这成为__bases__该类的属性。
  • <dct>指定一个蕴含类主体定义的名称空间字典。这成为__dict__该类的属性。

type()以这种形式进行调用会创立该type元类的新实例。换句话说,它动态创建一个新类

在以下每个示例中,最下面的代码段应用来动静定义一个类type(),而上面的代码段则应用该class语句以通常的形式定义该类。在每种状况下,这两个代码段在性能上是等效的。

例子1

在第一个示例中,传递给的<bases><dct>参数type()均为空。没有指定任何父类的继承,并且最后在命名空间字典中未搁置任何内容。这是最简略的类定义:

>>> Foo = type('Foo', (), {})>>> x = Foo()>>> x<__main__.Foo object at 0x04CFAD50>
>>> class Foo:...     pass...>>> x = Foo()>>> x<__main__.Foo object at 0x0370AD50>

例子2

这里<bases>是一个具备单个元素的元组Foo,指定Bar从其继承的父类。属性attr最后搁置在名称空间字典中:

>>> Bar = type('Bar', (Foo,), dict(attr=100))>>> x = Bar()>>> x.attr100>>> x.__class__<class '__main__.Bar'>>>> x.__class__.__bases__(<class '__main__.Foo'>,)
>>> class Bar(Foo):...     attr = 100...>>> x = Bar()>>> x.attr100>>> x.__class__<class '__main__.Bar'>>>> x.__class__.__bases__(<class '__main__.Foo'>,)

例子3

这次,又<bases>是空的。通过<dct>参数将两个对象放入名称空间字典中。第一个是名为的属性attr,第二个是名为的函数attr_val,该函数成为已定义类的办法:

>>> Foo = type(...     'Foo',...     (),...     {...         'attr': 100,...         'attr_val': lambda x : x.attr...     }... )>>> x = Foo()>>> x.attr100>>> x.attr_val()100
>>> class Foo:...     attr = 100...     def attr_val(self):...         return self.attr...>>> x = Foo()>>> x.attr100>>> x.attr_val()100

例子4

lambda在Python中只能定义非常简单的函数。在上面的示例中,在内部定义了一个略微简单一点的函数,而后attr_val通过名称在名称空间字典中将其调配给f

>>> def f(obj):...     print('attr =', obj.attr)...>>> Foo = type(...     'Foo',...     (),...     {...         'attr': 100,...         'attr_val': f...     }... )>>> x = Foo()>>> x.attr100>>> x.attr_val()attr = 100
>>> def f(obj):...     print('attr =', obj.attr)...>>> class Foo:...     attr = 100...     attr_val = f...>>> x = Foo()>>> x.attr100>>> x.attr_val()attr = 100

自定义元类

再次思考这个古老的示例:

>>> class Foo:...     pass...>>> f = Foo()

该表达式Foo()创立class的新实例Foo。解释器遇到时Foo(),将产生以下状况:

  • 的父类的__call__()办法Foo被调用。因为Foo是规范的新型类,因而其父类是type元类,因而调用type__call__()办法。
  • __call__()办法顺次调用以下内容:

    • __new__()
    • __init__()

如果Foo未定义__new__()__init__(),则默认办法继承自Foo的先人。然而,如果Foo的确定义了这些办法,则它们会笼罩先人中的办法,从而在实例化时容许自定义行为Foo

在上面,定义了一个自定义办法,并将new()其指定为__new__()用于的办法Foo

>>> def new(cls):...     x = object.__new__(cls)...     x.attr = 100...     return x...>>> Foo.__new__ = new>>> f = Foo()>>> f.attr100>>> g = Foo()>>> g.attr100

这会批改类的实例化行为Foo:每次Foo创立实例时,默认状况下都会应用名为的属性对其进行初始化,该属性attr的值为100。(这样的代码通常会呈现在__init__()办法中,而通常不会呈现在办法中__new__(),这个示例是为演示目标而设计的)

当初,正如曾经重申的,类也是对象。假如您要在创立相似的类时,能够以相似的自定义形式实现Foo实例化行为。如果要遵循上述模式,须要再次定义一个自定义办法,并将其调配__new__()为该类Foo是实例的办法。Footype元类的实例,因而代码如下所示:

# Spoiler alert:  This doesn't work!>>> def new(cls):...     x = type.__new__(cls)...     x.attr = 100...     return x...>>> type.__new__ = newTraceback (most recent call last):  File "<pyshell#77>", line 1, in <module>    type.__new__ = newTypeError: can't set attributes of built-in/extension type 'type'

如您所见,除了不能重新分配元类type__new__()办法。Python不容许这样做。

这可能也是一样。type是从其派生所有新款式类的元类。无论如何,您真的不应该对此乱搞。然而,如果要自定义类的实例化,那又有什么方法?

一种可能的解决方案是自定义元类。实质上,您不用定义type元类,而能够定义本人的元类,该元类是从派生的type,而后您就能够应用元类。

第一步是定义一个从派生的元类,type如下所示:

>>> class Meta(type):...     def __new__(cls, name, bases, dct):...         x = super().__new__(cls, name, bases, dct)...         x.attr = 100...         return x...

定义class Meta(type):申明,指定Metatype派生。因为type是一个元类,因而也形成Meta了一个元类。

请留神,__new__()已为定义了自定义办法Meta。无奈type间接对元类执行此操作。该__new__()办法执行以下操作:

  • 由父元类(即type)的代理--super()__new__()办法创立一个新的类
  • 将自定义属性调配attr给类,其值为100
  • 返回新创建的类

当初,巫毒教的另一半:定义一个新类Foo,并指定其元类是自定义元类Meta,而不是规范元类type。应用metaclass类定义中的关键字来实现此操作,如下所示:

>>> class Foo(metaclass=Meta):...     pass...>>> Foo.attr100

瞧! Foo曾经拿到Meta元类的attr主动属性。当然,相似定义的任何其余类也将这样做:

>>> class Bar(metaclass=Meta):...     pass...>>> class Qux(metaclass=Meta):...     pass...>>> Bar.attr, Qux.attr(100, 100)

与类充当创立对象的模板的形式雷同,元类充当创立类的模板。元类有时称为类工厂)。

比拟以下两个示例:

对象工厂:

>>> class Foo:...     def __init__(self):...         self.attr = 100...>>> x = Foo()>>> x.attr100>>> y = Foo()>>> y.attr100>>> z = Foo()>>> z.attr100

类工厂:

>>> class Meta(type):...     def __init__(...         cls, name, bases, dct...     ):...         cls.attr = 100...>>> class X(metaclass=Meta):...     pass...>>> X.attr100>>> class Y(metaclass=Meta):...     pass...>>> Y.attr100>>> class Z(metaclass=Meta):...     pass...>>> Z.attr100

这真的有必要吗?

就像下面的类工厂示例一样简略,这是元类如何工作的实质。它们容许自定义类如何实例化。

尽管如此,attr在每个新创建的类上赋予自定义属性依然有很多麻烦。您真的须要一个元类吗?

在 Python 中,至多有几种其余办法能够无效地实现同一件事:

简略继承:

>>> class Base:...     attr = 100...>>> class X(Base):...     pass...>>> class Y(Base):...     pass...>>> class Z(Base):...     pass...>>> X.attr100>>> Y.attr100>>> Z.attr100

类装璜器:

>>> def decorator(cls):...     class NewClass(cls):...         attr = 100...     return NewClass...>>> @decorator... class X:...     pass...>>> @decorator... class Y:...     pass...>>> @decorator... class Z:...     pass...>>> X.attr100>>> Y.attr100>>> Z.attr100

论断

正如蒂姆·彼得斯(Tim Peters)所倡议的那样,元类很容易进入“从问题中寻找解决方案”的境界。通常不须要创立自定义元类。如果眼前的问题能够用更简略的办法解决,那就应该这样解决。尽管如此,了解元类还是有益处的,这样您就能够大抵了解Python类,并能够辨认何时才真正适宜应用元类。

????Python技巧????

对于作者:
约翰·斯图兹
奥尔登·桑托斯
丹·巴德
乔安娜·贾布隆斯基


❤️高兴Pythoning!

彩蛋

>>> import this