问题:如何装饰一堂课?
在Python 2.5中,有没有办法创建装饰类的装饰器?具体来说,我想使用装饰器将成员添加到类中,并更改构造函数以获取该成员的值。
寻找类似以下的内容(在“ 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#接口的方法。我想我应该改变我的范式。
In Python 2.5, is there a way to create a decorator that decorates a class? Specifically, I want to use a decorator to add a member to a class and change the constructor to take a value for that member.
Looking for something like the following (which has a syntax error on ‘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()
I guess what I’m really after is a way to do something like a C# interface in Python. I need to switch my paradigm I suppose.
回答 0
我的观点是您可能希望考虑一个子类,而不是您概述的方法。但是,不知道您的特定情况,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
替换构造函数可能有点麻烦,但是语言确实为这种深入的内省和动态修改提供了支持。
I would second the notion that you may wish to consider a subclass instead of the approach you’ve outlined. However, not knowing your specific scenario, YMMV :-)
What you’re thinking of is a metaclass. The __new__
function in a metaclass is passed the full proposed definition of the class, which it can then rewrite before the class is created. You can, at that time, sub out the constructor for a new one.
Example:
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
Replacing the constructor is perhaps a bit dramatic, but the language does provide support for this kind of deep introspection and dynamic modification.
回答 1
除了类装饰器是否是您的问题的正确解决方案的问题之外:
在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__
。
Apart from the question whether class decorators are the right solution to your problem:
In Python 2.6 and higher, there are class decorators with the @-syntax, so you can write:
@addID
class Foo:
pass
In older versions, you can do it another way:
class Foo:
pass
Foo = addID(Foo)
Note however that this works the same as for function decorators, and that the decorator should return the new (or modified original) class, which is not what you’re doing in the example. The addID decorator would look like this:
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
You could then use the appropriate syntax for your Python version as described above.
But I agree with others that inheritance is better suited if you want to override __init__
.
回答 2
没有人解释过您可以动态定义类。因此,您可以使用一个装饰器来定义(并返回)一个子类:
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
所以,你可以有你的蛋糕和吃它-继承和装饰!
No one has explained that you can dynamically define classes. So you can have a decorator that defines (and returns) a subclass:
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
Which can be used in Python 2 (the comment from Blckknght which explains why you should continue to do this in 2.6+) like this:
class Foo:
pass
FooId = addId(Foo)
And in Python 3 like this (but be careful to use super()
in your classes):
@addId
class Foo:
pass
So you can have your cake and eat it – inheritance and decorators!
回答 3
这不是一个好习惯,因此,没有机制可以做到这一点。完成所需内容的正确方法是继承。
查看类文档。
一个小例子:
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__
方法和自己的成员。
That’s not a good practice and there is no mechanism to do that because of that. The right way to accomplish what you want is inheritance.
Take a look into the class documentation.
A little example:
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)
This way Boss has everything Employee
has, with also his own __init__
method and own members.
回答 4
我同意继承更适合提出的问题。
我发现这个问题在装饰类上确实很方便,谢谢大家。
这是另外两个基于其他答案的示例,包括继承如何影响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
if condition
求值应用于True
。
使用getattr
,callable
测试和@decorate_if
可以使装饰器foo()
在被装饰的类上不存在该方法时也不会中断。
I’d agree inheritance is a better fit for the problem posed.
I found this question really handy though on decorating classes, thanks all.
Here’s another couple of examples, based on other answers, including how inheritance affects things in Python 2.7, (and @wraps, which maintains the original function’s docstring, etc.):
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...
Often you want to add parameters to your decorator:
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()
Outputs:
@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
I’ve used a function decorator, as I find them more concise. Here’s a class to decorate a class:
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
A more robust version that checks for those parentheses, and works if the methods don’t exist on the decorated class:
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
The assert
checks that the decorator has not been used without parentheses. If it has, then the class being decorated is passed to the msg
parameter of the decorator, which raises an AssertionError
.
@decorate_if
only applies the decorator
if condition
evaluates to True
.
The getattr
, callable
test, and @decorate_if
are used so that the decorator doesn’t break if the foo()
method doesn’t exist on the class being decorated.
回答 5
There’s actually a pretty good implementation of a class decorator here:
https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py
I actually think this is a pretty interesting implementation. Because it subclasses the class it decorates, it will behave exactly like this class in things like isinstance
checks.
It has an added benefit: it’s not uncommon for the __init__
statement in a custom django Form to make modifications or additions to self.fields
so it’s better for changes to self.fields
to happen after all of __init__
has run for the class in question.
Very clever.
However, in your class you actually want the decoration to alter the constructor, which I don’t think is a good use case for a class decorator.
回答 6
这是一个示例,它回答了返回类参数的问题。而且,它仍然尊重继承链,即仅返回类本身的参数。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']
Here is an example which answers the question of returning the parameters of a class. Moreover, it still respects the chain of inheritance, i.e. only the parameters of the class itself are returned. The function get_params
is added as a simple example, but other functionalities can be added thanks to the inspect module.
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