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版本使用适当的语法,如上所述。
但我同意其他人的观点,如果你想推翻
我建议您考虑一个子类,而不是您概述的方法。但是,不知道您的具体情况,YMMV:-)
你所想的是一个超类。元类中的
例子:
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的代码中(但在类中要小心使用
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) |
这样,老板就拥有了
这里实际上有一个很好的类修饰器实现:
https://github.com/agiliq/django-parsley/blob/master/parsley/decorators.py
实际上,我认为这是一个非常有趣的实现。因为它对它所修饰的类进行了子类化,所以它在类似于
它还有一个附加的好处:在自定义的django形式中,
非常聪明。
但是,在您的类中,您实际上希望装饰更改构造函数,我认为这对于类装饰器来说不是一个好的用例。
我同意继承更适合这个问题。
不过,我发现这个问题在装饰课上非常有用,谢谢大家。
下面是另两个基于其他答案的示例,包括继承如何影响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 |
号
使用