如何装饰一堂课?

问题:如何装饰一堂课?

在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仅将decoratorif condition求值应用于True

使用getattrcallable测试和@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

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

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

我实际上认为这是一个非常有趣的实现。因为它继承了它装饰的类的子类,所以在诸如isinstance检查。

它还有另外一个好处:它并不罕见__init__在自定义声明Django表单进行修改或补充,self.fields因此,最好的改变self.fields发生后,所有的__init__已经跑了有问题的类。

非常聪明。

但是,在您的类中,您实际上希望装饰更改构造函数,但我认为这不是类装饰器的好用例。

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

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

https://github.com/django/django/blob/50cf183d219face91822c75fa0a15fe2fe3cb32d/django/utils/decorators.py#L53

https://docs.djangoproject.com/zh-CN/3.0/topics/class-based-views/intro/#decorating-the-class

Django has method_decorator which is a decorator that turns any decorator into a method decorator, you can see how it’s implemented in 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