如何装饰一个类?

180 投票
8 回答
183685 浏览
提问于 2025-04-15 11:03

我该如何创建一个可以应用于类的装饰器呢?

具体来说,我想用一个叫做 addID 的装饰器,给一个类添加一个成员 __id,并且修改构造函数 __init__,让它可以接收一个 id 参数来为这个成员赋值。

def getId(self): return self.__id

classdecorator addID(cls):
    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        cls.__init__(self, *args, **kws)

@addID
class Foo:
    def __init__(self, value1):
        self.value1 = value1

上面的内容应该等同于:

class Foo:
    def __init__(self, id, value1):
        self.__id = id
        self.value1 = value1

    def getId(self): return self.__id

8 个回答

35

没有人解释过你可以动态地定义类。也就是说,你可以创建一个装饰器,它可以定义(并返回)一个子类:

def addId(cls):

    class AddId(cls):

        def __init__(self, id, *args, **kargs):
            super(AddId, self).__init__(*args, **kargs)
            self.__id = id

        def getId(self):
            return self.__id

    return AddId

在Python 2中可以这样使用(Blckknght的评论解释了为什么你应该在2.6+版本中继续这样做):

class Foo:
    pass

FooId = addId(Foo)

在Python 3中可以这样使用(但要注意在你的类中使用super()):

@addId
class Foo:
    pass

所以你可以两全其美,既能享受继承,又能使用装饰器!

244

除了讨论类装饰器是否是解决你问题的正确方法之外:

在Python 2.6及更高版本中,有一种使用@符号的类装饰器,你可以这样写:

@addID
class Foo:
    pass

在旧版本中,你可以用另一种方式来实现:

class Foo:
    pass

Foo = addID(Foo)

不过要注意,这种方式和函数装饰器的工作原理是一样的,装饰器应该返回一个新的(或修改过的原始)类,而在你的例子中并没有做到这一点。addID装饰器应该是这样的:

def addID(original_class):
    orig_init = original_class.__init__
    # Make copy of original __init__, so we can call it without recursion

    def __init__(self, id, *args, **kws):
        self.__id = id
        self.getId = getId
        orig_init(self, *args, **kws) # Call the original __init__

    original_class.__init__ = __init__ # Set the class' __init__ to the new one
    return original_class

然后你可以根据你使用的Python版本,使用上面描述的正确语法。

但我同意其他人的看法,如果你想重写__init__方法,继承会更合适。

87

我同意你可能想考虑使用子类,而不是你提到的方法。不过,由于我不知道你具体的情况,结果可能会有所不同哦 :-)

你想到的是元类。元类中的 __new__ 函数会接收到完整的类定义,然后可以在类创建之前对其进行修改。这个时候,你可以把构造函数换成一个新的。

举个例子:

def substitute_init(self, id, *args, **kwargs):
    pass

class FooMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['__init__'] = substitute_init
        return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

    __metaclass__ = FooMeta

    def __init__(self, value1):
        pass

替换构造函数可能有点夸张,但这个语言确实支持这种深层次的检查和动态修改。

撰写回答