共计 6382 个字符,预计需要花费 16 分钟才能阅读完成。
术语 元编程 是指程序具备理解或操纵本身的后劲。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__)
...
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
。Foo
是type
元类的实例。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.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
是实例的办法。Foo
是 type
元类的实例,因而代码如下所示:
# 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):
申明,指定 Meta
从type
派生。因为 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 技巧????
对于作者:
约翰·斯图兹
奥尔登·桑托斯
丹·巴德
乔安娜·贾布隆斯基
❤️高兴 Pythoning!
彩蛋
>>> import this