如何装饰一个类?

Rob*_*and 118 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()
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*ven 194

除了类装饰器是否是您的问题的正确解决方案的问题:

在Python 2.6及更高版本中,有一些带有@ -syntax的类装饰器,因此您可以编写:

@addID
class Foo:
    pass
Run Code Online (Sandbox Code Playgroud)

在旧版本中,您可以采用另一种方式:

class Foo:
    pass

Foo = addID(Foo)
Run Code Online (Sandbox Code Playgroud)

但请注意,这与函数装饰器的工作方式相同,并且装饰器应该返回新的(或修改过的原始)类,这不是您在示例中所做的.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
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用适合您的Python版本的语法,如上所述.

但我同意其他人的意见,如果你想覆盖,继承更适合__init__.

  • 抱歉linesep乱七八糟,但代码示例在评论中并不严格...:def addID(original_class):original_class .__ orig__init__ = original_class .__ init__ def __init __(self,*args,**kws):print"decorator" self.id = 9 original_class .__ orig__init __(self,*args,**kws)original_class .__ init__ = __init__ return original_class @addID class Foo:def __init __(self):print"Foo"a = Foo()print a.id (5认同)
  • @Day:感谢提醒,我之前没有注意到这些评论.但是:我使用Geralds代码也超出了最大递归深度.我会尝试制作一个正常工作的版本;-) (3认同)
  • ...即使问题特别提到Python 2.5 :-) (2认同)
  • @Steven,@ Gerald是对的,如果你能更新你的答案,那么阅读他的代码要容易得多;) (2认同)

Jar*_*die 73

我认为你可能希望考虑一个子类而不是你概述的方法.但是,不知道您的具体情况,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
Run Code Online (Sandbox Code Playgroud)

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

  • 元类曾经是在 Python2.5 或更早版本中执行此类操作的首选方法,但现在您可以经常使用类装饰器(请参阅 Steven 的答案),这要简单得多。 (3认同)

and*_*oke 17

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

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
Run Code Online (Sandbox Code Playgroud)

哪个可以在Python 2中使用(Blckknght的注释,它解释了为什么你应该继续在2.6+中这样做),像这样:

class Foo:
    pass

FooId = addId(Foo)
Run Code Online (Sandbox Code Playgroud)

在这样的Python 3中(但要小心super()在你的类中使用):

@addId
class Foo:
    pass
Run Code Online (Sandbox Code Playgroud)

所以,你可以有你的蛋糕吃它-继承装饰!

  • 装饰器中的子类在Python 2.6+中是危险的,因为它打破了原始类中的`super`调用.如果`Foo`有一个名为`foo`的方法,它调用`super(Foo,self).foo()`它将无限递归,因为名称`Foo`绑定到装饰器返回的子类,而不是原始类(任何名称都无法访问).Python 3无争议的`super()`避免了这个问题(我假设通过相同的编译器魔法允许它完全工作).您还可以通过在不同名称下手动装饰类来解决此问题(如在Python 2.5示例中所做的那样). (3认同)

mpe*_*son 12

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

查看课程文档.

一个小例子:

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)
Run Code Online (Sandbox Code Playgroud)

这种方式Boss拥有一切Employee,还有他自己的__init__方法和自己的成员.

  • @ S.Lott:一般来说,多继承是一个坏主意,即使过多的继承级别也是不好的.我建议你远离多重继承. (7认同)
  • @Robert Gowland:这就是为什么Python有多重继承.是的,您应该从各种父类继承各个方面. (4认同)
  • mpeterson:python中的多重继承比这种方法更糟糕吗?python的多重继承有什么问题? (4认同)
  • 我想我想要的是Boss与它所包含的类不可知.也就是说,我想要将Boss功能应用到几十个不同的类中.我离开了从Boss继承了这十几个类吗? (3认同)
  • @Arafangion:多重继承通常被认为是一个麻烦的警告信号.它使复杂的层次结构和难以理解的关系成为可能.如果你的问题域很适合多继承,(它可以按层次建模吗?)对许多人来说它是一个自然的选择.这适用于允许多重继承的所有语言. (2认同)

Chr*_*ris 10

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

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

这是基于其他答案的另外几个示例,包括继承如何影响 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...
Run Code Online (Sandbox Code Playgroud)

通常您想向装饰器添加参数:

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()
Run Code Online (Sandbox Code Playgroud)

输出:

@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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

检查这些括号的更强大的版本,如果装饰类中不存在这些方法,则可以工作:

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
Run Code Online (Sandbox Code Playgroud)

assert该装饰尚未使用支票没有括号。如果是,则将被装饰的类传递给msg装饰器的参数,从而引发AssertionError.

@decorate_if仅适用decoratorifcondition评估为True

getattrcallable测试和@decorate_if使用,这样,如果该装饰不破foo()的方法不会对类正在装修存在。


Jor*_*ter 6

实际上,这里有一个非常好的类装饰器实现:

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

我实际上认为这是一个非常有趣的实现。因为它是它所修饰的类的子类,所以它在检查等方面的行为与该类完全相同isinstance

__init__它还有一个额外的好处:自定义 django 表单中的语句进行修改或添加并不罕见,self.fields因此最好在所有相关类运行之后self.fields进行更改。__init__

非常聪明。

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