关于python:cls在装饰类的继承classmethod中的行为

cls behaviour in inherited classmethod of a decorated class

我正在尝试使用调用类方法时使用的参数之一对类的类方法进行验证。

为此,我将为类使用一个decorator,该类将对所需方法应用decorator,该方法将使用函数中的一个参数执行验证函数。

这对于基类(在本例中,我将称之为Parent)都很好地工作。

但是,如果我创建另一个继承Parent的类(例如,我将称它为Child),则继承的修饰类方法将不再正常工作。

Child类的classmethod中的cls参数不是预期的Child,而是Parent

举个例子

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import inspect


def is_number(word):
    if word.isdigit():
        print('Validation passed')
    else:
        raise Exception('Validation failed')


class ClassDecorator(object):

    def __init__(self, *args):
        self.validators = args

    def __decorateMethod(self):
        def wrapped(method):
            def wrapper(cls, word, *args, **kwargs):
                for validator in self.validators:
                    validator(word)
                return method(word, *args, **kwargs)
            return wrapper
        return wrapped

    def __call__(self, cls):
        for name, method in inspect.getmembers(cls):
            if name == 'shout':
                decoratedMethod = self.__decorateMethod()(method)
                setattr(cls, name, classmethod(decoratedMethod))
        return cls


@ClassDecorator(is_number)
class Parent(object):

    @classmethod
    def shout(cls, word):
        print('{} is shouting {}'.format(cls, word))

    @classmethod
    def say(cls):
        print('{} is talking'.format(cls))


class Child(Parent):
    pass


Parent.shout('123')
Child.shout('321')

将产生以下输出:

1
2
3
4
Validation passed
<class '__main__.Parent'> is shouting 123
Validation passed
<class '__main__.Parent'> is shouting 321

我的问题是:

  • 为什么用Parent作为cls调用Child的classmethod
  • 是否有可能使用这种设计来获得想要的行为?

P.S.:我在python 2.7.10和python 3.5.2上都试过,并且得到了相同的行为


您正在修饰绑定类方法;正是这个对象持有Parent并在调用时将其传递到原始shout函数;wrapper()方法中绑定到的任何cls都不会传入和忽略。

先解包classmethods,就可以得到具有__func__属性的底层函数对象:

1
2
3
4
5
6
def __call__(self, cls):
    for name, method in inspect.getmembers(cls):
        if name == 'shout':
            decoratedMethod = self.__decorateMethod()(method.__func__)
            setattr(cls, name, classmethod(decoratedMethod))
    return cls

现在必须考虑包装器也在处理未绑定的函数,因此传递cls参数或手动绑定:

1
2
3
4
5
# pass in cls explicitly:
return method(cls, word, *args, **kwargs)

# or bind the descriptor manually:
return method.__get__(cls)(word, *args, **kwargs)