【问题标题】:__repr__ decorator fails on subclasses__repr__ 装饰器在子类上失败
【发布时间】:2018-03-09 15:31:26
【问题描述】:

背景

我编写了一个装饰器函数来修改给定类的__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

问题

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

【问题讨论】:

  • 我不知道装饰器不会被继承。现在这开始有意义了。
  • @IgnacioVergaraKausel 元类在这里太过分了。而“非继承”并不是对正在发生的事情的准确描述。装饰的类对象被__repr__dec 中定义的call 函数替换。换句话说,Container 实际上是一个函数,而不是一个类。 这就是为什么试图继承它会引发异常。
  • 为什么不能在Container 中定义__repr__
  • 你的装饰器不过是一个函数。你不能子类化函数。
  • 是否可以制作一个返回类而不是函数的装饰器?

标签: python python-3.x decorator subclassing


【解决方案1】:

问题是你的装饰器把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__ 方法。

【讨论】:

    【解决方案2】:
    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
    

    【讨论】:

      猜你喜欢
      • 2010-12-13
      • 1970-01-01
      • 2018-01-12
      • 2011-11-20
      • 2018-12-16
      • 2015-03-09
      • 1970-01-01
      • 1970-01-01
      • 2014-01-14
      相关资源
      最近更新 更多