Python元类与类装饰器

45

Python元类和类装饰器的主要区别是什么?有没有一些只能通过其中一种方式实现的功能?

2个回答

49

装饰器(Decorators)更加简单和有限,因此只要所需效果可以用元类或者类装饰器来实现的话,就应该优先考虑使用装饰器。

用类装饰器能做到的事情,在自定义元类中当然也能实现(只需要在元类的__new____init__ 方法中应用“装饰器函数”的功能--即接受一个类对象并修改它的那个函数!)。

虽然自定义元类能做很多类装饰器不能做的事情(除非类装饰器内部生成并应用了自定义元类,当然这样做是作弊的;-)……而且即使如此,在Python3 中仍然有一些事情只能通过自定义元类来实现……但这属于比较高级的子领域,让我来给你举几个更简单的例子吧。

例如,假设你想创建一个类对象X,使得print X(或者在 Python 3 中则是print(X))输出peekaboo!。你没有办法不用自定义元类来实现,因为这里关键的角色是元类中对__str__方法的重载。也就是说,你需要在类X的自定义元类中加入def __str__(cls): return "peekaboo!"

同样的道理适用于所有的魔法方法,即所有应用于类对象本身(而不是应用于其实例的操作,后者使用类中定义的魔法方法)的操作都会使用在元类中定义的魔法方法。


9
嗯...已经过去4年了,我在谷歌上找到了这个问题。我只想就你的例子做个注释。我定义了一个名为foo(cls)的装饰器,其中有两个语句:cls.__str__ = lambda self: "peekaboo!",然后紧接着是return cls,它确实起作用了 - 我使用了这个装饰器为某个类设置了__str__方法,而不是使用复杂的元类..我只是想说明一下,并可能获得一些评论,以了解是否漏掉了什么。 - rboy
@AlexMartelli 我也对rboy所说的感兴趣。我仍然找不到一个清晰明确的定义,说明元类和类装饰器在功能上的区别。 - antitoxic
16
@antitoxic,rboy所做的影响了print(X()),即打印类X实例,并没有影响我在答案中非常明确提到的print(X),即打印类本身。请重新阅读我答案的最后一段。 - Alex Martelli

4

根据书籍“流畅的Python”第21章所述,一个差异与继承有关。请参见以下两个脚本。Python版本为3.5。其中一点是使用元类(metaclass)会影响其子类,而装饰器(decorator)仅影响当前类。

该脚本使用类装饰器(class-decorator)来替换/覆盖方法'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 Galantin
据我所了解,当Cls2具有Deco4cls的元类时,在Deco4cls的__init__调用中,Cls2_1继承Cls2时,会有一个lambda表达式将继承类的方法设置为返回2。(cls.func1 = lambda self : 2)。使用super后,如果Cls2_1调用super,则func1应该仍然返回3,尚未测试,只是猜测。 - Aaron B
关于元类示例。我认为你可以用装饰器做同样的事情?你需要修改装饰器中的 __init__ 方法。换句话说:这两个示例的行为不同,因为它们执行不同的操作。 - Gerrit Begher
1
@GerritBegher,这不起作用,因为继承类(Cls2_1)仍将在其类主体中覆盖func1(self)。元类示例不会修改类本身的__init __()方法,而是修改元类实例(即类和继承类)创建后。这意味着该函数甚至在类的实例创建之外(obj2_1)被覆盖。要使用装饰器做同样的事情,您必须将装饰器应用于Cls2_1,而不是应用于Cls2。 - Aprillomat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接