这个抽象基类是否具有“更好”的__repr __()危险?

时间:2012-11-18 20:56:21

标签: python pickle signature abc repr

让我觉得一个类的默认__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__()方法比预期(但不要求)定义{{{}更加繁重1}}方法。

我想知道的是:它有多危险和/或脆弱?另一方面,我想不出任何可能出现严重错误的事情,除非如果一个__repr__()实例包含自己,你将获得无限递归......但这是可以解决的,但代价是制作上面的代码丑陋。

我还错过了什么?

2 个答案:

答案 0 :(得分:4)

如果您遇到这种情况,为什么不通过使用装饰器从__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__方法。

答案 1 :(得分:2)

这个整体想法的一个问题是,某些类型的对象的状态并不完全依赖于赋予其构造函数的参数。对于一个简单的案例,考虑一个具有随机状态的类:

import random

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

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

这就是为什么__repr__可以编码来做任何你想要的事情。如果您希望内部状态可见,则可以让您的班级__repr__执行此操作。如果对象应该是不透明的,那么你也可以这样做。对于上面的课程,我可能会像这样实现__repr__

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