Python 的元类与元编程

Python元类和元编程

术语元编程是指程序具备了解或操纵自身的潜力。Python支持一种称为metaclasses的类的元编程形式。html

元类是一个深奥的OOP概念,几乎隐藏在全部Python代码以后。不管您是否知道,都在使用它们。在大多数状况下,您无需意识到这一点。大多数Python程序员不多(即便有的话)也没必要考虑元类。python

可是,当须要时,Python提供了并不是全部面向对象的语言都支持的功能:您能够深刻了解并自定义元类。自定义元类的使用引发了一些争议,正如Python 禅意做者Tim Peters所引用的那样程序员

元类具备比99%的用户应该担忧的更深的魔力。若是您想知道是否须要它们,则不须要(实际上须要它们的人确定会知道他们须要它们,而且不须要解释缘由)。”

蒂姆·彼得斯shell

有一些使用者(Pythonista - 众所周知的Python爱好者)认为永远不要使用自定义元类。这可能有点远,可是极可能不须要自定义元类。若是不是很明显有问题须要解决,那么若是以更简单的方式解决问题,它可能会更干净,更易读。编程

尽管如此,理解 Python 元类仍是值得的,由于经过元类能够更好地理解Python 类的内部。可能有一天会遇到一种状况:只须要一个自定义元类便可以解决问题。函数

旧式与新式类

在Python领域中,类能够是两个变体之一。还没有肯定官方术语,所以将它们非正式地称为旧类和新类。ui

老式类

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

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

新型类

新型类统一了类和类型的概念。若是obj是新型类的实例,type(obj)则与相同obj.__class__spa

>>> 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__)
...
True
True
True

类型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元类的实例,所以它也是自身的实例。

Python类链

动态定义类

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.attr
100
>>> x.__class__
<class '__main__.Bar'>
>>> x.__class__.__bases__
(<class '__main__.Foo'>,)
>>> class Bar(Foo):
...     attr = 100
...

>>> x = Bar()
>>> x.attr
100
>>> 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.attr
100
>>> x.attr_val()
100
>>> class Foo:
...     attr = 100
...     def attr_val(self):
...         return self.attr
...

>>> x = Foo()
>>> x.attr
100
>>> 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.attr
100
>>> x.attr_val()
attr = 100
>>> def f(obj):
...     print('attr =', obj.attr)
...
>>> class Foo:
...     attr = 100
...     attr_val = f
...

>>> x = Foo()
>>> x.attr
100
>>> 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.attr
100

>>> g = Foo()
>>> g.attr
100

这会修改类的实例化行为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__ = new
Traceback (most recent call last):
  File "<pyshell#77>", line 1, in <module>
    type.__new__ = new
TypeError: 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.attr
100

瞧! 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.attr
100

>>> y = Foo()
>>> y.attr
100

>>> z = Foo()
>>> z.attr
100

类工厂:

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

>>> class Y(metaclass=Meta):
...     pass
...
>>> Y.attr
100

>>> class Z(metaclass=Meta):
...     pass
...
>>> Z.attr
100

这真的有必要吗?

就像上面的类工厂示例同样简单,这是元类如何工做的本质。它们容许自定义类如何实例化。

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

在 Python 中,至少有几种其余方法能够有效地完成同一件事:

简单继承:

>>> class Base:
...     attr = 100
...

>>> class X(Base):
...     pass
...

>>> class Y(Base):
...     pass
...

>>> class Z(Base):
...     pass
...

>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100

类装饰器:

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

>>> X.attr
100
>>> Y.attr
100
>>> Z.attr
100

结论

正如蒂姆·彼得斯(Tim Peters)所建议的那样,元类很容易进入“从问题中寻找解决方案”的境界。一般不须要建立自定义元类。若是眼前的问题能够用更简单的方法解决,那就应该这样解决。尽管如此,理解元类仍是有好处的,这样您就能够大体理解Python类,并能够识别什么时候才真正适合使用元类。

🐍Python技巧💌

Python技巧字典合并

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


❤️快乐Pythoning!

彩蛋

>>> import this
相关文章
相关标签/搜索