Опасен ли этот абстрактный базовый класс с "лучшим" __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__() метод будет более обременительным, чем ожидание (но не обязательное), что он определяет __repr__() метод.

Что я хотел бы знать: насколько это опасно и / или хрупко? Я не могу придумать ничего такого, что могло бы пойти не так, кроме как если бы Repro Экземпляр сам по себе, вы получите бесконечную рекурсию... но это решаемо за счет того, что код выше будет более уродливым.

Что еще я пропустил?

2 ответа

Решение

Одна проблема с этой идеей состоит в том, что могут быть некоторые виды объектов, состояние которых не полностью зависит от аргументов, данных его конструктору. Для тривиального случая рассмотрим класс со случайным состоянием:

import random

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

Для этого класса нет способа правильно реализовать __getnewargs__и так ваша имплантация __repr__ тоже невозможно. Может случиться так, что класс, подобный приведенному выше, не очень хорошо разработан. Но pickle может справиться с этим без проблем (я предполагаю, используя __reduce__ метод, унаследованный от object, но моего рассола-фу недостаточно, чтобы сказать это с уверенностью).

Вот почему хорошо, что __repr__ можно кодировать, чтобы делать все, что вы хотите. Если вы хотите, чтобы внутреннее состояние было видимым, вы можете сделать свой класс __repr__ сделай это. Если объект должен быть непрозрачным, вы можете сделать это тоже. Для класса выше, я бы, вероятно, реализовать __repr__ как это:

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

Если вы в такой вещи, почему бы не получить аргументы автоматически __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__ метод таким образом.

Другие вопросы по тегам