python元类编程_Python 的元类与元编程

术语元编程是指程序具有了解或操纵自身的潜力。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__

>>> type(x)

新型类

新型类统一了类和类型的概念。如果obj是新型类的实例,type(obj)则与相同obj.__class__:>>> class Foo:

... pass

>>> obj = Foo()

>>> obj.__class__

>>> type(obj)

>>> 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)

>>> type(Foo)

如您所料,type x是class Foo。但是Foo,类本身的类型是type。通常,任何新式类的类型都是type。

您熟悉的内置类的类型也是type:>>> for t in int, float, dict, list, tuple:

... print(type(t))

...

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

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

在上述情况下:x是class的实例Foo。

Foo是type元类的实例。

type也是type元类的实例,因此它也是自身的实例。

动态定义类

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

>>> type(['foo', 'bar', 'baz'])

>>> t = (1, 2, 3, 4, 5)

>>> type(t)

>>> class Foo:

... pass

...

>>> type(Foo())

还可以使用三个参数进行调用type(, , ):指定类名称。这成为__name__该类的属性。

指定从其继承的基类的元组。这成为__bases__该类的属性。

指定一个包含类主体定义的名称空间字典。这成为__dict__该类的属性。

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

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

例子1

在第一个示例中,传递给的和参数type()均为空。没有指定任何父类的继承,并且最初在命名空间字典中未放置任何内容。这是最简单的类定义:>>> Foo = type('Foo', (), {})

>>> x = Foo()

>>> x

<__main__.foo object at>>>> class Foo:

... pass

...

>>> x = Foo()

>>> x

例子2

这里是一个具有单个元素的元组Foo,指定Bar从其继承的父类。属性attr最初放置在名称空间字典中:>>> Bar = type('Bar', (Foo,), dict(attr=100))

>>> x = Bar()

>>> x.attr

100

>>> x.__class__

>>> x.__class__.__bases__

(,)>>> class Bar(Foo):

... attr = 100

...

>>> x = Bar()

>>> x.attr

100

>>> x.__class__

>>> x.__class__.__bases__

(,)

例子3

这次,又是空的。通过参数将两个对象放入名称空间字典中。第一个是名为的属性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 "", line 1, in

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

你可能感兴趣的:(python元类编程)