【问题标题】:Is this abstract base class with a "better" __repr__() dangerous?这个具有“更好” __repr__() 的抽象基类是否危险?
【发布时间】:2012-11-06 19:48:12
【问题描述】:

让我感到困扰的是,一个类的默认 __repr__() 信息量太少了:

>>> class Opaque(object): pass
... 
>>> Opaque()
<__main__.Opaque object at 0x7f3ac50eba90>

...所以我一直在考虑如何改进它。经过一番考虑,我想出了这个利用pickle 协议的__getnewargs__() 方法的抽象基类:

from abc import abstractmethod

class Repro(object):

    """Abstract base class for objects with informative ``repr()`` behaviour."""

    @abstractmethod
    def __getnewargs__(self):
        raise NotImplementedError

    def __repr__(self):
        signature = ", ".join(repr(arg) for arg in self.__getnewargs__())
        return "%s(%s)" % (self.__class__.__name__, signature)

这是一个简单的用法示例:

class Transparent(Repro):

    """An example of a ``Repro`` subclass."""

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

    def __getnewargs__(self):
        return self.args

... 以及由此产生的 repr() 行为:

>>> Transparent("an absurd signature", [1, 2, 3], str)
Transparent('an absurd signature', [1, 2, 3], <type 'str'>)
>>> 

现在,我可以看到 Python 默认不立即执行此操作的一个原因 - 要求每个类定义 __getnewargs__() 方法比预期(但不要求)它定义 __repr__() 方法更繁重.

我想知道的是:它有多危险和/或脆弱?顺便说一句,我想不出任何可能出错的地方,除非Repro 实例包含自身,你会得到无限递归......但这是可以解决的,代价是使上面的代码更丑陋。

我还错过了什么?

【问题讨论】:

  • repr 在存在诸如 __getnewargs__ 之类的东西之前就已经存在 way 了。你也在滥用一种方法。它是由一个库模块(不是 python 语言核心本身)引入的,目的是做完全不同的事情。还有关键字参数呢?在 python3 中,构造函数只能有关键字参数。此外,我没有看到任何真正的额外好处。可以具有简单字符串表示的对象应该实现__repr__ 和/或__str__。使用您的方法,您仍然必须重新实现它,或者您必须重新实现__getnewargs__
  • 没有真正的理由为此使用__getnewargs__ 本身,是吗?你可以定义一个约定,对象在任意属性中存储一些识别信息(比如_reprInfo),然后__repr__ 只打印self._reprInfo
  • @Bakuriu 我怎么滥用它呢?正确实现的Repro 子类将具有正确的泡菜__getnewargs__(),这甚至可能被认为是一个优势。我意识到这不适用于带有关键字参数构造函数的类,但我不会将它与那些......实际上,如果可以的话,我会完全避免在构造函数中使用关键字参数。
  • @BrenBarn 是的,最初我只是想使用类似_reprInfo 属性的东西......但是这样做有什么害处吗?
  • @ZeroPiraeus:没有真正的“伤害”,但正如@Bakuriu 所说,它只会在__repr____getnewargs__ 之间造成不必要的依赖。您也可以重载任何其他随机方法,例如__pos__,并声明现在应该使用该方法返回__repr__ 数据,但为什么呢?它没有任何优势,而且如果你以后想同时使用__repr__ 和重载方法来实现各自不同的目的,它只会让人头疼。

标签: python pickle signature abc repr


【解决方案1】:

如果您喜欢这种事情,为什么不使用装饰器自动从__init__ 提取参数?然后,您不需要手动处理它们给用户带来负担,并且您可以透明地处理具有多个参数的普通方法签名。这是我想出的快速版本:

def deco(f):
    def newFunc(self, *args, **kwargs):
        self._args = args
        self._kwargs = kwargs
        f(self, *args, **kwargs)
    return newFunc
class AutoRepr(object):
    def __repr__(self):
        args = ', '.join(repr(arg) for arg in self._args)
        kwargs = ', '.join('{0}={1}'.format(k, repr(v)) for k, v in self._kwargs.iteritems())
        allArgs = ', '.join([args, kwargs]).strip(', ')
        return '{0}({1})'.format(self.__class__.__name__, allArgs)

现在您可以正常定义 AutoRepr 的子类,使用正常的__init__ 签名:

class Thingy(AutoRepr):
    @deco
    def __init__(self, foo, bar=88):
        self.foo = foo
        self.bar = bar

__repr__ 自动工作:

>>> Thingy(1, 2)
Thingy(1, 2)
>>> Thingy(10)
Thingy(10)
>>> Thingy(1, bar=2)
Thingy(1, bar=2)
>>> Thingy(bar=1, foo=2)
Thingy(foo=2, bar=1)
>>> Thingy([1, 2, 3], "Some junk")
Thingy([1, 2, 3], 'Some junk')

@deco 放在__init__ 上比写一个完整的__getnewargs__ 要容易得多。如果您甚至不想这样做,您可以编写一个元类,以这种方式自动装饰__init__ 方法。

【讨论】:

  • 相当优雅,是的......但这意味着你必须在修改对象时更新_args_kwargs。对我来说,使用__getnewargs__() 的优势在于,为了对pickle 正确,它必须对repr 正确,反之亦然,我认为它可能不那么脆弱(或者至少更有可能破坏早且显眼)。
  • @ZeroPiraeus:元类可以检查是否定义了__getnewargs__,并使用它来生成__repr__(),否则假定装饰器已用于__init__()
  • @martineau hmmm ... 结合上面 BrenBarn 的最终建议,我们将拥有一个元类,它要么装饰一个特殊方法,要么创建另一个,基于第三个方法的存在与否。这真是太神奇了……
  • @ZeroPiraeus:如果您真的想动态跟踪对象的状态(以便属性更改反映在 __repr____getnewargs__ 中),只需创建自己的管理方法这个,跟踪状态变化,然后让__repr____getnewargs__ 调用该方法以获取有关当前状态的独家新闻。
  • @BrenBarn 有趣的想法……我得走开想想。谢谢:-)
【解决方案2】:

整个想法的一个问题是,可能有一些对象的状态不完全依赖于其构造函数的参数。对于一个简单的情况,考虑一个具有随机状态的类:

import random

def A(object):
    def __init__(self):
        self.state = random.random()

这个类没有办法正确实现__getnewargs__,所以你植入__repr__也是不可能的。可能是像上面这样的类设计得不好。但是pickle 可以毫无问题地处理它(我假设使用从object 继承的__reduce__ 方法,但我的泡菜还不足以肯定地说)。

这就是为什么__repr__ 可以被编码为做任何你想做的事。如果您希望内部状态可见,您可以让您的班级的__repr__ 这样做。如果对象应该是不透明的,您也可以这样做。对于上面的类,我可能会像这样实现__repr__

def __repr__(self):
    return "<A object with state=%f>" % self.state

【讨论】:

  • 这是一个很好的反例,我没有考虑过 - 谢谢!我会在接受之前等待,看看是否有其他人出现......你永远不知道......
  • 在我看来,如果这个类还实现了一个接受状态参数的__new__ 方法,那么它可以实现__getnewargs
猜你喜欢
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-05
  • 1970-01-01
  • 2019-12-26
  • 1970-01-01
相关资源
最近更新 更多