装饰classmethod时出错

时间:2017-05-16 13:32:06

标签: python

我正在研究装饰这个类的装饰器。它对于实例方法来说很好,但是为类方法提供了TypeError。代码如下:

def deco_method(fn):
  def wrapper(*arg, **kwarg):
    """
    Function: Wrapper
    """
    print "Calling function {}".format(fn.__name__)
    print arg, kwarg
    ret_val = fn(*arg, **kwarg)
    print "Executed function {}".format(fn.__name__)
    return ret_val
  return wrapper

def clsdeco(cls):
  attributes = cls.__dict__.keys()

  for attribute in attributes:
    # Do not decorate private methods
    if '__' in attribute:
        continue 

    # Get the method
    value = getattr(cls, attribute)
    if not hasattr(value, '__call__'):
        continue

    # CHeck if method is a class method or normal method and decoate accordingly
    if value.im_self is None:# non class method
        setattr(cls, attribute, deco_method(value))
    elif value.im_self is cls: # CHeck if the method is class method 
        setattr(cls, attribute, classmethod(deco_method(value)))
    else: 
        assert False
  return cls # return decorated class



@clsdeco
class Person:
  message = "Hi Man"
  def __init__(self, first_name, last_name):
    self.fname = first_name
    self.lname = last_name
    self.age = None

  def get_name(self):
    print "Name is '{} {}'".format(self.fname, self.lname)

  @classmethod
  def greet_person(cls):
     print cls.message

p = Person('John', 'snow')
p.greet_person()

它出错了:

TypeError: greet_person() takes exactly 1 argument (2 given)

如果我删除@clsdeco,它的效果非常好。

知道我在这里缺少什么吗?

1 个答案:

答案 0 :(得分:1)

如果添加显示的行,它将起作用。这是因为在类定义中应用的@classmethod装饰器改变了getattr(cls, attribute)返回的内容 - 它将是命名方法的描述符,它添加cls参数然后调用实际方法。

您需要做的是检索" raw"属性的值,它只是一个常规函数,然后通过显式调用classmethod将其转换为类方法。这需要" raw" value存储在与相同属性名称关联的类字典__dict__中,因此需要添加value = cls.__dict__[attribute].__func__行。

正确处理静态方法也需要类似的东西。 this answer问题Decorating a method that's already a classmethod?中描述了如何对所有不同类型的方法执行此操作。其他一些答案也比我在此更详细地描述了所发生的事情。 / p>

def clsdeco(cls):
  attributes = cls.__dict__.keys()

  for attribute in attributes:
    # Do not decorate private methods
    if '__' in attribute:
        continue

    # Get the method
    value = getattr(cls, attribute)
    if not hasattr(value, '__call__'):
        continue

    # Check if method is a class method or normal method and decoate accordingly
    if value.im_self is None:# non class method
        setattr(cls, attribute, deco_method(value))
    elif value.im_self is cls: # Check if the method is class method
        value = cls.__dict__[attribute].__func__                        # ADDED
        setattr(cls, attribute, classmethod(deco_method(value)))
    else:
        assert False

  return cls # return decorated class