Python 的元类与元编程
2020/8/26 5:04:49
本文主要是介绍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__ <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
这篇关于Python 的元类与元编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-21Python编程基础教程
- 2024-11-20Python编程基础与实践
- 2024-11-20Python编程基础与高级应用
- 2024-11-19Python 基础编程教程
- 2024-11-19Python基础入门教程
- 2024-11-17在FastAPI项目中添加一个生产级别的数据库——本地环境搭建指南
- 2024-11-16`PyMuPDF4LLM`:提取PDF数据的神器
- 2024-11-16四种数据科学Web界面框架快速对比:Rio、Reflex、Streamlit和Plotly Dash
- 2024-11-14获取参数学习:Python编程入门教程
- 2024-11-14Python编程基础入门