如何装饰一个班级?

如何创建应用于类的装饰器?

具体来说,我希望使用装饰器 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
154663 次浏览

这不是一个好的做法,因为没有机制来做到这一点。完成你想要的事情的正确方法是继承。

看看 类别文件

举个小例子:

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__方法和自己的成员。

我赞成您可能希望考虑子类而不是您所概述的方法的观点。然而,不知道你的具体情况,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

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

除了班级装饰是否是解决你问题的正确方法这个问题:

在 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__,继承更适合。

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

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

实际上我认为这是一个非常有趣的实现。因为它是它所修饰的类的子类,所以它的行为和这个类在 isinstance检查中的行为完全一样。

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

非常聪明。

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

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

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中使用(Blcknightt 的评论解释了为什么你应该在2.6 + 中继续这样做) ,如下所示:

class Foo:
pass


FooId = addId(Foo)

在 Python 3中,如下所示(但要注意在类中使用 super()) :

@addId
class Foo:
pass

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

我同意继承遗产更能解决问题。

我发现这个问题真的很方便,虽然在装饰类,谢谢大家。

下面是另外两个基于其他答案的例子,包括继承如何影响 Python 2.7(以及 @ Waps,它维护原始函数的 docstring,等等) :

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

如果 condition的计算结果为 True,则 @decorate_if仅应用 decorator

使用了 getattrcallable测试和 @decorate_if,以便在所修饰的类上不存在 foo()方法时,修饰符不会中断。

下面的示例回答了返回类的参数的问题。此外,它仍然尊重继承链,即只返回类本身的参数。函数 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']


Django 有 method_decorator,它是一个装饰器,可以将任何装饰器转换为方法装饰器,你可以看到它是如何在 django.utils.decorators中实现的:

Https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#l53

Https://docs.djangoproject.com/en/3.0/topics/class-based-views/intro/#decorating-the-class