Как мы можем получить поведение по умолчанию __repr __()?

Если кто-то пишет класс в python и не может указать свой собственный метод __repr__(), то для них предоставляется по умолчанию. Однако предположим, что мы хотим написать функцию, которая имеет то же или подобное поведение по умолчанию __repr__(). Однако мы хотим, чтобы эта функция имела поведение метода __repr__() по умолчанию, даже если фактический __repr__() для класса был перегружен. То есть предположим, что мы хотим написать функцию, которая имеет такое же поведение, что и значение по умолчанию __repr__(), независимо от того, перегрузил ли кто-либо метод __repr__() или нет. Как мы можем это сделать?

class DemoClass:
    def __init__(self):
        self.var = 4
    def __repr__(self):
        return str(self.var)

def true_repr(x):
    # [magic happens here]
    s = "I'm not implemented yet"
    return s

obj = DemoClass()

print(obj.__repr__())

print(true_repr(obj))

Требуемый выход:

print(obj.__repr__()) выводит 4, но print(true_repr(obj)) печатает что-то вроде:
<__main__.DemoClass object at 0x0000000009F26588>

Ответ 1

Вы можете использовать object.__repr__(obj). Это работает, потому что поведение по умолчанию repr определено в object.__repr__.

Ответ 2

Обычно для этого мы можем использовать object.__repr__, но это будет для "объекта для каждого элемента", поэтому:

>>> object.__repr__(4)
'<int object at 0xa6dd20>'

Так как a int является object, но с __repr__ overriden.

Если вы хотите перейти на один уровень перезаписи, мы можем использовать super(..):

>>> super(type(4), 4).__repr__()  # going up one level
'<int object at 0xa6dd20>'

Для int, что, таким образом, снова означает, что мы будем печатать <int object at ...>, но если бы мы использовали подкласс int, то он снова использовал бы __repr__ of int, например:

class special_int(int):

    def __repr__(self):
        return 'Special int'

Затем он будет выглядеть так:

>>> s = special_int(4)
>>> super(type(s), s).__repr__()
'4'

Что мы здесь делаем, это создать прокси-объект с super(..). Super выполнит порядок разрешения метода (MRO) объекта и попытается найти первую функцию (из суперкласса s), которая превзошла эту функцию. Если мы используем одиночное наследование, то это самый близкий родитель, который переопределяет функцию, но если это связано с несколькими множественными наследованиями, то это более сложно. Таким образом, мы выбираем __repr__ этого родителя и вызываем эту функцию.

Это также довольно странное приложение super, так как обычно класс (здесь type(s)) является фиксированным и не зависит от типа самого s, так как в противном случае несколько таких вызовов super(..) приведет к бесконечному циклу.

Но обычно это плохая идея сломать все-таки. Причина, по которой программист переопределяет функцию, - это изменение поведения. Несоблюдение этого может, конечно, иногда приводить к некоторым полезным функциям, но часто это приводит к тому, что кодовые контракты больше не выполняются. Например, если программист переопределяет __eq__, он также переопределит __hash__, если вы используете хэш другого класса и реальный __eq__, тогда все начнет ломаться.

Вызов функции магии напрямую также часто рассматривается как антипаттерн, поэтому вам лучше избегать этого.

Ответ 3

Обратите внимание: лучший ответ - это просто использовать object.__repr__ напрямую, как указывали другие. Но можно реализовать ту же функциональность примерно так:

>>> def true_repr(x):
...     type_ = type(x)
...     module = type_.__module__
...     qualname = type_.__qualname__
...     return f"<{module}.{qualname} object at {hex(id(x))}>"
...

Так....

>>> A()
hahahahaha
>>> true_repr(A())
'<__main__.A object at 0x106549208>'
>>>