__repr__装饰器在子类

时间:2018-03-09 15:31:26

标签: python python-3.x decorator subclassing

背景

我写了一个装饰器函数来修改给定类的__repr__,这样当调用类实例时,它的所有属性都会打印给用户。在下面示例中的Container类中使用时,装饰器__repr__dec的行为符合预期。

输入

def __repr__wrapper(self):
    """Show all attributes."""
    return "Attributes: "+", ".join(list(self.__dict__.keys()))

def __repr__dec(func):
    """Replaces the __repr__ function of a class with __repr__wrapper"""
    def call(*args, **kwargs):
        func.__repr__ = __repr__wrapper
        result = func(*args, **kwargs)
        return result
    return call


@__repr__dec
class Container(object):
    def __init__(self, *args, **kwargs):
        self.metadata = args[0]
        for k,v in kwargs.items():
            self.__dict__[k] = v

occ = Container(42, how="now")
occ

输出

Attributes: metadata, how

但是在尝试子类Container时,我收到一条TypeError消息:

输入

class Handle(Container):
    def __init__(self, *args, **kwargs):
        Container.__init__(self, *args, **kwargs)

han = Handle(42)

输出

TypeError                                 Traceback (most recent call last)
<ipython-input-17-b4c252411c1f> in <module>()
----> 1 class Handle(Container):
      2     def __init__(self, *args, **kwargs):
      3         Container.__init__(self, *args, **kwargs)
      4 
      5 han = Handle(42)

TypeError: function() argument 1 must be code, not str

问题

使用Conatainer函数时,为什么子类__repr__dec会失败?有可能解决这个问题吗?

2 个答案:

答案 0 :(得分:4)

问题在于你的装饰者使Container成为一个函数而不再是一个类。你可以非常简单地控制它:

>>> type(Container)
<class 'function'>

这是因为您对装饰器的使用以下结尾:

  • 宣布未修饰的课程

    class Container:
        ...
    
  • 使用装饰器:

    Container = __repr__dec(Container)
    

__repr__dec返回一个函数时,你确实将Container更改为一个能够返回具有预期__repr__成员的对象的函数,但它不再是一个类。

如果您希望以后能够将它子类化,那么您的装饰器必须返回一个类:

def repr_dec(cls):
    cls.__repr__ = __repr__wrapper
    return cls

然后一切都很好:

>>> Container
<class '__main__.Container'>
>>> occ=Container(42, how="now")
>>> occ
Attributes: metadata, how

您可以成功地将其子类化:

>>> class Handle(Container):
    def __init__(self, *args, **kwargs):
        Container.__init__(self, *args, **kwargs)

>>> han = Handle(42, foo="now")
>>> han
Attributes: metadata, foo

Handle类已从其父级继承了__repr__方法。

答案 1 :(得分:1)

def replace_str(cls):
    class Wrapper:
        def __init__(self, *args, **kargs):
            self.wrapped = cls(*args, **kargs)
        def __getattr__(self, attrname):
            return getattr(self.wrapped, attrname)
        def __str__(self):
            return "Attributes: " + ", ".join(list(self.wrapped.__dict__.keys()))
    return Wrapper

@replace_str
class Container(object):
    def __init__(self, *args, **kwargs):
        self.metadata = args[0]
        for k,v in kwargs.items():
            self.__dict__[k] = v

使用代理类可以轻松实现此目的。 另外,metaclass可以做到这一点:

class PrintKey(type):
    def __new__(meta, classname, bases, namespace):
        namespace['__str__'] = lambda self: "Attributes: " + ", ".join(list(self.__dict__.keys()))
        return type.__new__(meta, classname, bases, namespace)

class Container(object, metaclass=PrintKey):
    def __init__(self, *args, **kwargs):
        self.metadata = args[0]
        for k,v in kwargs.items():
            self.__dict__[k] = v