0

让我感到__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实例包含自己,你会得到无限递归......但这是可以解决的,代价是使上面的代码更丑陋。

我还错过了什么?

4

2 回答 2

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__以这种方式自动装饰方法的元类。

于 2012-11-18T21:40:59.687 回答
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
于 2012-11-18T22:36:22.407 回答