关于python:如何装饰一个类?

How to decorate a class?

在Python2.5中,有没有一种方法可以创建一个装饰类的装饰器?具体地说,我希望使用一个修饰器向类中添加一个成员,并更改构造函数以获取该成员的值。

正在查找类似以下内容的内容(在"class foo:上有语法错误):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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接口之类的功能的方法。我想我需要改变我的模式。


除了问题外,类修饰符是否是解决问题的正确方法:

在Python2.6及更高版本中,有带有@-语法的类修饰符,因此您可以编写:

1
2
3
@addID
class Foo:
    pass

在旧版本中,您可以用另一种方式执行此操作:

1
2
3
4
class Foo:
    pass

Foo = addID(Foo)

但是请注意,这与函数decorator的工作原理相同,decorator应该返回新的(或修改过的原始)类,这不是您在示例中所做的。AddID装饰器如下所示:

1
2
3
4
5
6
7
8
9
10
11
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__,继承更适合。


我建议您考虑一个子类,而不是您概述的方法。但是,不知道您的具体情况,YMMV:-)

你所想的是一个超类。元类中的__new__函数通过了类的完整建议定义,然后可以在创建类之前重写该定义。此时,您可以将构造函数分包给一个新的构造函数。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

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


没有人解释过您可以动态定义类。因此,您可以有一个定义(并返回)子类的修饰符:

1
2
3
4
5
6
7
8
9
10
11
12
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+中继续这样做),如下所示:

1
2
3
4
class Foo:
    pass

FooId = addId(Foo)

在类似于python 3的代码中(但在类中要小心使用super()):

1
2
3
@addID
class Foo:
    pass

所以你可以吃你的蛋糕-继承和装饰!


这不是一个好的实践,因此没有任何机制可以做到这一点。完成你想要的事情的正确方法是继承。

查看类文档。

举个小例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

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


这里实际上有一个很好的类修饰器实现:

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

实际上,我认为这是一个非常有趣的实现。因为它对它所修饰的类进行了子类化,所以它在类似于isinstance检查的事情中的行为将与这个类完全相同。

它还有一个附加的好处:在自定义的django形式中,__init__语句对self.fields进行修改或添加并不少见,因此在__init__的所有内容都运行到相关类之后,对self.fields进行更改更好。

非常聪明。

但是,在您的类中,您实际上希望装饰更改构造函数,我认为这对于类装饰器来说不是一个好的用例。


我同意继承更适合这个问题。

不过,我发现这个问题在装饰课上非常有用,谢谢大家。

下面是另两个基于其他答案的示例,包括继承如何影响Python2.7中的内容(以及@wrapps,它维护原始函数的docstring等):

1
2
3
4
5
6
7
8
9
10
11
12
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...

通常,您希望向装饰器添加参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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()

输出:

1
2
3
4
5
6
7
8
9
10
11
@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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

一个更健壮的版本,用于检查这些圆括号,并在修饰类上不存在方法时工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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检查装饰器没有使用括号。如果有,则将要修饰的类传递给decorator的msg参数,该参数将引发AssertionError

@decorate_if仅在conditionTrue进行评价时适用decorator

使用getattrcallable测试和@decorate_if测试,以便在被修饰类上不存在foo()方法的情况下,装饰器不会损坏。