0
点赞
收藏
分享

微信扫一扫

Python元类详解


新式类

新式类统一了类和类型,如果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 and Class

在Python 3中,所有类都是新式类。因此,在Python 3中,可以互换地引用对象的类型及其类是合理的。

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

请记住,在Python中,一切都是对象。类也是对象。因此,类必须具有类型。class的类型是什么?

考虑以下:

>>> 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是一个类的实例Foo。
  • Foo是type元类的一个实例。
  • 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()用三个参数调用 ​​- 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_va​​l,它成为已定义类的方法:

>>> 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()创建了一个新的类实例Foo。解释器遇到时Foo(),会发生以下情况:

调用Foo的父类方法​​__call__()​​​。由于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​​​属性对其进行初始化,该属性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__()​​方法执行以下操作:

  • 经由代表super()的​​__new__()​​父元类的方法(type)实际创建一个新的类
  • 将自定义属性分配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中,至少有几种方法可以有效地完成同样的事情:

简单继承:

'''
学习中遇到问题没人解答?小编创建了一个Python学习交流群:711312441
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
>>> 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

结论

元类很容易转向成为“寻找问题的解决方案”的领域。通常不需要创建自定义元类。如果手头的问题可以用更简单的方式解决,那么它应该是。尽管如此,理解元类是有益的,这样你就可以理解Python类,并且可以识别元类真正适合使用的工具。


举报

相关推荐

0 条评论