如何装饰一个类?

时间:2009-03-25 14:52:03

标签: python decorator python-2.5

在Python 2.5中,有没有办法创建一个装饰类的装饰器?具体来说,我想使用装饰器将成员添加到类中,并更改构造函数以获取该成员的值。

寻找类似下面的内容(在'class Foo:':

上有语法错误
def getId(self): return self.__id

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

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

if __name__ == '__main__':
    foo1 = Foo(5,1)
    print foo1.value1, foo1.getId()
    foo2 = Foo(15,2)
    print foo2.value1, foo2.getId()

我想我真正想要的是在Python中做一些类似C#接口的方法。我想我需要改变我的范例。

8 个答案:

答案 0 :(得分:187)

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

在Python 2.6及更高版本中,有一些带有@ -syntax的类装饰器,所以你可以这样写:

@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__,继承更适合。

答案 1 :(得分:71)

我认为您可能希望考虑子类而不是您概述的方法。但是,不知道您的具体情况,YMMV: - )

你在想的是一个元类。元类中的__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

替换构造函数可能有点戏剧性,但该语言确实为这种深度内省和动态修改提供了支持。

答案 2 :(得分:14)

没有人解释过你可以动态定义类。所以你可以有一个定义(并返回)子类的装饰器:

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

所以你可以把你的蛋糕吃掉 - 继承装饰者!

答案 3 :(得分:12)

这不是一个好习惯,因此没有机制可以做到这一点。实现目标的正确方法是继承。

查看class documentation

一个小例子:

class Employee(object):

    def __init__(self, age, sex, siblings=0):
        self.age = age
        self.sex = sex    
        self.siblings = siblings

    def born_on(self):    
        today = datetime.date.today()

        return today - datetime.timedelta(days=self.age*365)


class Boss(Employee):    
    def __init__(self, age, sex, siblings=0, bonus=0):
        self.bonus = bonus
        Employee.__init__(self, age, sex, siblings)

这样Boss拥有Employee所拥有的一切,还有他自己的 __init__ 方法和自己的成员。

答案 4 :(得分:4)

这里实际上是一个很好的类装饰器实现:

https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py

我实际上认为这是一个非常有趣的实现。因为它是它所装饰的类的子类,所以它在isinstance检查之类的行为中的行为与此类完全相同。

它还有一个额外的好处:自定义django表单中__init__语句对self.fields进行修改或添加的情况并不少见,因此更改为 所有self.fields已经为相关课程运行后发生。{/ p>

非常聪明。

然而,在你的课堂上,你实际上想要装饰来改变构造函数,我认为这不是一个类装饰器的好用例。

答案 5 :(得分:3)

我同意继承更适合提出的问题。

我发现这个问题在装饰课上非常方便,谢谢大家。

以下是另外两个例子,基于其他答案,包括继承如何影响Python 2.7中的内容,(和@wraps,它维护原始函数的文档字符串等):

def dec(klass):
    old_foo = klass.foo
    @wraps(klass.foo)
    def decorated_foo(self, *args ,**kwargs):
        print('@decorator pre %s' % msg)
        old_foo(self, *args, **kwargs)
        print('@decorator post %s' % msg)
    klass.foo = decorated_foo
    return klass

@dec  # No parentheses
class Foo...

通常你想为你的装饰者添加参数:

from functools import wraps

def dec(msg='default'):
    def decorator(klass):
        old_foo = klass.foo
        @wraps(klass.foo)
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass
    return decorator

@dec('foo decorator')  # You must add parentheses now, even if they're empty
class Foo(object):
    def foo(self, *args, **kwargs):
        print('foo.foo()')

@dec('subfoo decorator')
class SubFoo(Foo):
    def foo(self, *args, **kwargs):
        print('subfoo.foo() pre')
        super(SubFoo, self).foo(*args, **kwargs)
        print('subfoo.foo() post')

@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
    def foo(self, *args, **kwargs):
        print('subsubfoo.foo() pre')
        super(SubSubFoo, self).foo(*args, **kwargs)
        print('subsubfoo.foo() post')

SubSubFoo().foo()

输出:

@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator

我使用了函数装饰器,因为我发现它们更简洁。这是一个装饰类的类:

class Dec(object):

    def __init__(self, msg):
        self.msg = msg

    def __call__(self, klass):
        old_foo = klass.foo
        msg = self.msg
        def decorated_foo(self, *args, **kwargs):
            print('@decorator pre %s' % msg)
            old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)
        klass.foo = decorated_foo
        return klass

一个更健壮的版本,用于检查这些括号,并且如果装饰类上不存在这些方法,则可以使用

from inspect import isclass

def decorate_if(condition, decorator):
    return decorator if condition else lambda x: x

def dec(msg):
    # Only use if your decorator's first parameter is never a class
    assert not isclass(msg)

    def decorator(klass):
        old_foo = getattr(klass, 'foo', None)

        @decorate_if(old_foo, wraps(klass.foo))
        def decorated_foo(self, *args ,**kwargs):
            print('@decorator pre %s' % msg)
            if callable(old_foo):
                old_foo(self, *args, **kwargs)
            print('@decorator post %s' % msg)

        klass.foo = decorated_foo
        return klass

    return decorator

assert检查装饰器是否在没有括号的情况下使用。如果有,则将要装饰的类传递给装饰器的msg参数,该参数会引发AssertionError

@decorate_if仅在decorator评估为condition时才适用True

使用getattrcallable测试和@decorate_if,以便如果正在修饰的类上不存在foo()方法,装饰器不会中断

答案 6 :(得分:0)

这里是一个示例,它回答了返回类参数的问题。而且,它仍然尊重继承链,即仅返回类本身的参数。作为简单示例,添加了功能get_params,但是借助inspect模块,可以添加其他功能。

import inspect 

class Parent:
    @classmethod
    def get_params(my_class):
        return list(inspect.signature(my_class).parameters.keys())

class OtherParent:
    def __init__(self, a, b, c):
        pass

class Child(Parent, OtherParent):
    def __init__(self, x, y, z):
        pass

print(Child.get_params())
>>['x', 'y', 'z']

答案 7 :(得分:0)

Django具有method_decorator,它是一个装饰器,可以将任何装饰器转换为方法装饰器,您可以在django.utils.decorators中查看其实现方式:

https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#L53

https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class