Python 元类与类装饰器

Posted

技术标签:

【中文标题】Python 元类与类装饰器【英文标题】:Python metaclasses vs class decorators 【发布时间】:2010-12-19 06:13:25 【问题描述】:

Python 元类和类装饰器之间的主要区别是什么?有什么我可以用一个做而另一个不能做的事情吗?

【问题讨论】:

【参考方案1】:

装饰器要简单得多,也受到更多限制——因此,只要可以使用元类或类装饰器来实现所需的效果,就应该首选装饰器。

任何你可以用类装饰器做的事情,你当然可以用一个自定义元类来做(只需应用“装饰器函数”的功能,即接受一个类对象并修改它的那个,在元类的 __new____init__ 构成类对象!-)。

您可以在自定义元类中做很多事情,但不能在装饰器中执行(当然,除非装饰器在内部生成并应用自定义元类,但那是作弊;-)... 即便如此,在 Python 中3,有些事情你只能用自定义元类来做,而不是事后......但这是你问题的一个非常高级的子领域,所以让我举个更简单的例子)。

例如,假设您想要创建一个类对象X,这样print X(或者在Python 3 中当然是print(X);-)显示peekaboo!。如果没有自定义元类,您不可能做到这一点,因为元类对 __str__ 的覆盖是这里的关键角色,即,您需要在类 X 的自定义元类中使用 def __str__(cls): return "peekaboo!"

这同样适用于所有魔术方法,即应用于类对象本身的各种操作(与应用于其实例的操作相反,它们使用定义在类——对类对象本身的操作使用元类中定义的魔法方法)。

【讨论】:

嗯.. 已经 4 年了,我在谷歌找到了这个问题。我只想记下您的示例。我已经定义了一个装饰器foo(cls),它有两个语句:cls.__str__ = lambda self: "peekaboo!" 和紧随其后的return cls,它确实有效 - 我已经使用装饰器而不是复杂的设置了某个类的 __str__ 方法元类..我只是想注意这一点,也许会得到一些评论以了解我是否遗漏了什么。 @AlexMartelli 我也对 rboy 注意到的内容感兴趣。我仍然无法很好地明确定义元类和类装饰器的功能差异。 @antitoxic,rboy 所做的会影响print(X())X 类的 instances 的打印,不是我非常明确地提到的在我的回答中,print(X)类本身的打印。重新阅读我回答的最后一段。【参考方案2】:

正如“fluent python”一书的第 21 章所述,一个区别与继承有关。请看这两个脚本。 python版本是3.5。一点是metaclass 的使用会影响其子类,而装饰器仅影响当前类。

脚本使用类装饰器来替换/覆盖方法“func1”。

def deco4cls(cls):
    cls.func1 = lambda self: 2
    return cls


@deco4cls
class Cls1:
    pass


class Cls1_1(Cls1):
    def func1(self):
        return 3


obj1_1 = Cls1_1()
print(obj1_1.func1())  # 3

脚本使用元类替换/覆盖方法'func1'。

class Deco4cls(type):
    def __init__(cls, name, bases, attr_dict):
        # print(cls, name, bases, attr_dict)
        super().__init__(name, bases, attr_dict)
        cls.func1 = lambda self: 2


class Cls2(metaclass=Deco4cls):
    pass


class Cls2_1(Cls2):
    def func1(self):
        return 3


obj2_1 = Cls2_1()
print(obj2_1.func1())  # 2!! the original Cls2_1.func1 is replaced by metaclass

【讨论】:

您能解释一下为什么给定的代码表现不同吗? Marine,所以据我所知,当具有 Deco4cls 元类的 Cls2 在 Deco4cls 的 init 调用中被 Cls2_1 继承时,一个 lambda 表达式,它将继承类的方法设置为返回 2。 (cls.func1 = lambda self : 2) 。使用 super 它会改变,如果 Cls2_1 调用 super func1 应该仍然返回 3,没有测试只是推测。 关于元类示例。我想你可以用装饰器做同样的事情吗?您必须修改装饰器中的 __init__ 方法。换句话说:两个两个示例的行为不同,因为它们做的事情不同。

以上是关于Python 元类与类装饰器的主要内容,如果未能解决你的问题,请参考以下文章

Python - 元类装饰器 - 如何使用 @classmethod

Python之路:描述符,类装饰器,元类

python面向对象:组合封装property装饰器多态

Python 中的纯静态类 - 使用元类、类装饰器还是其他东西?

python(描述符应用与类的装饰器)

Python基础(30)——上下文管理,描述符,类装饰器,元类