如何装饰一个班级?
- 2025-03-05 09:14:00
- admin 原创
- 108
问题描述:
如何创建适用于类的装饰器?
具体来说,我想使用装饰器向类addID
添加成员,并更改构造函数以接受该成员的参数。__id
`__init__`id
def getId(self): return self.__id
classdecorator addID(cls):
def __init__(self, id, *args, **kws):
self.__id = id
self.getId = getId
cls.__init__(self, *args, **kws)
@addID
class Foo:
def __init__(self, value1):
self.value1 = value1
以上应该相当于:
class Foo:
def __init__(self, id, value1):
self.__id = id
self.value1 = value1
def getId(self): return self.__id
解决方案 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__
。
解决方案 2:
我赞同你的观点,你可能希望考虑子类而不是你概述的方法。但是,不知道你的具体情况,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
替换构造函数可能有点戏剧性,但语言确实为这种深度自省和动态修改提供了支持。
解决方案 3:
没有人解释过你可以动态定义类。因此,你可以有一个定义(并返回)子类的装饰器:
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)
super()
在 Python 3 中是这样的(但在课堂上使用时要小心):
@addId
class Foo:
pass
所以你可以鱼与熊掌兼得——继承和装饰器!
解决方案 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
如果condition
评估为True
。
使用getattr
、callable
测试和,@decorate_if
以便当foo()
被装饰的类中不存在该方法时,装饰器不会中断。
解决方案 5:
这不是一个好的做法,因此也没有机制可以做到这一点。实现你想要的正确方法是继承。
查看课程文档。
举个小例子:
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__
方法和自己的成员。
解决方案 6:
这里实际上有一个相当不错的类装饰器的实现:
https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py
我实际上认为这是一个非常有趣的实现。因为它将它修饰的类子类化,所以它在检查等方面的行为与该类完全相同isinstance
。
__init__
它还有一个额外的好处:自定义 django 表单中的语句进行修改或添加并不罕见,self.fields
因此最好在相关类的全部运行之后self.fields
进行更改。__init__
非常聪明。
然而,在您的类中,您实际上希望装饰器能够改变构造函数,我认为这不是类装饰器的一个好用例。
解决方案 7:
这是一个回答返回类的参数问题的示例。此外,它仍然遵循继承链,即只返回类本身的参数。该函数get_params
作为一个简单的示例添加,但可以通过检查模块添加其他功能。
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']
解决方案 8:
Django 有method_decorator
一个装饰器,它可以将任何装饰器变成方法装饰器,你可以看到它是如何实现的django.utils.decorators
:
https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class
扫码咨询,免费领取项目管理大礼包!