Python metaclass(元类)[未完成]

简介

元编程这个术语指的是程序了解或操作自身的潜力。Python支持一种名为元类的元编程形式。
metaclasses是一个深奥的OOP(面向对象编程)概念,隐藏在几乎所有Python代码的后面。你无论你有没有意识到,都使用过它们。大部分,你不需要直到它。大部分的Python程序员没有考虑过它。
然而,当需要时,Python提供了并非所有面向对象语言都支持的功能:您可以深入其中并定义自定义meta类。使用自定义的meta类是有一定争议的。

“Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).”

— Tim Peters

很多人相信我们永不不应该用meta类。
这可能有点太过了,但是自定义meta类在大多数情况下是不必要的,这可能是事实。如果一个问题对它们的调用不是很明显,那么如果用一种更简单的方法解决,它可能会更清晰、更易于阅读。

它有助于我们理解Python的内部
尽管如此,理解Python meta类仍然是有价值的,因为它通常可以更好地理解Python类的内部。您永远不会知道:有一天您可能会发现自己处于这样一种情况中,您只知道自定义meta类是您想要的。

Python类的新/老形式

老形式

对于旧式类,类和类型并不完全相同。旧式类的实例总是由一个名为instance的内置类型实现。如果obj是旧式类的实例,则obj。指定类,但是类型(obj)总是实例。下面的例子摘自Python 2.7:

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

新形式

新型类统一了类和类型的概念。如果obj是一个新类型类的实例,那么类型(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

类型和类

在python3中,所有类都是新样式的类。因此,在python3中,可以合理地交替引用对象的类型及其类。

**注意:**在python2中,默认情况下类是旧式的。在Python 2.2之前,根本不支持新样式的类。从Python 2.2开始,可以创建它们,但必须显式声明为new-style。

记住,在Python中,所有东西都是一个对象。类也是对象。因此,类必须具有类型。类的类型是什么?
考虑以下:

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

>>> type(x)
<class '__main__.Foo'>

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

x的类型是Foo类,正如您所期望的那样。但是Foo的类型,类本身,是类型。通常,任何新样式类的类型都是type。

你熟悉的内置类的类型也是:

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

就此而言,type of type也是type:

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

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

在上面的例子中:

  • x是类foo的实例。
  • foo是type元类的实例。
  • type也是type元类的实例,所以它是一个它自己的实例。

动态定义一个类

内联的type()函数,当传递一个参数的时候,它返回对象的类型。对于新形式的class,这大体上跟对象的__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__属性。
  • 指定了一个命名空间包含类主体的定义。这变成类中的__dict__属性。

以这种方式调用type()将创建元类类型的新实例。换句话说,它动态地创建一个新类。
在下面的每个示例中,上面的代码片段使用type()动态定义一个类,下面的代码片段使用class语句以通常的方式定义类。在每种情况下,这两个片段在功能上是等价的。

例子 1

在第一个示例中,传递给type()的和参数都是空的。没有指定来自任何父类的继承,也没有最初放置在名称空间字典中。这是最简单的类定义:

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

例子 2

这里,是一个元组,包含一个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

这一次,再次为空。通过参数将两个对象放入名称空间字典。第一个是一个名为attr的属性,第二个是一个名为attr_val的函数,它成为定义类的一个方法:

>>> Foo = type(
...     'Foo',
...     (),
...     {
...         'attr': 100,
...         'attr_val': lambda x : x.attr
...     }
... )

>>> x = Foo()
>>> x.attr
100
>>> x.attr_val()
100

例子 4

只有非常简单的函数可以用Python中的lambda定义。在下面的例子中,一个稍微复杂一点的函数在外部定义,然后通过名称f 在名称空间字典中分配给 attr_val:

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

自定义meta类

再想想这个老生常谈的例子:

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

表达式Foo()创建类Foo的一个新实例。当解释器遇到Foo()时,会发生以下情况:

  • 调用Foo的父类的__call__()方法。由于Foo是一个标准的新型类,它的父类是类型元类,所以调用类型的__call__()方法。
  • call()方法包括:
    • new()
    • init()

如果Foo没有定义__new__()和__init__(),默认方法将继承自Foo的祖先。但是,如果Foo确实定义了这些方法,它们将覆盖来自祖先的方法,这允许在实例化Foo时定制行为。

在下面,一个名为new()的自定义方法被定义并赋值为Foo的__new__()方法:

>>> 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。(像这样的代码通常出现在方法中,而不是在方法中。此示例是为演示目的而设计的。)

现在,正如前面所重申的,类也是对象。假设您希望在创建像Foo这样的类时以类似的方式定制实例化行为。如果您要遵循上面的模式,您将再次定义一个自定义方法,并将其指定为Foo作为实例的类的_new__()方法。Foo是元类类型的一个实例,所以代码看起来是这样的:

# 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'

但是,正如您所看到的,您不能重新分配元类类型的_new__()方法。Python不允许这样做。

这或许也无妨。类型是派生所有新样式类的meta类。无论如何,你真的不应该浪费时间。但是,如果您想自定义类的实例化,有什么办法呢?

一个可能的解决方案是自定义meta类。本质上,您可以定义您自己的meta类,它派生自type,而不是摆弄类型meta类,然后您可以使用它。

第一步是定义一个派生自type的meta类,如下:

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

定义头类元(类型):指定元派生自类型。因为类型是元类,所以元类也是元类。

**注意:**已经为Meta定义了一个定制的_new__()方法。不可能直接对类型元类执行此操作。方法的作用如下:

  • 通过super()委托给父元类(type)的_new__()方法来实际创建一个新类
  • 将自定义属性attr分配给该类,其值为100
  • 返回新创建的类
    现在,voodoo的另一半:定义一个新的类Foo,并指定它的元类是自定义meta类meta数据,而不是标准的元类类型。这是通过使用类定义中的元类关键字完成的,如下所示:
>>> 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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值