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
元类的一个实例,因此它本身就是一个实例。
动态定义类
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_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()
创建了一个新的类实例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中,至少有几种方法可以有效地完成同样的事情:
简单继承:
>>> 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类,并且可以识别元类真正适合使用的工具。