Можно изменить атрибут __str __() Python?

Я хотел бы изменить атрибут __str__() одного из методов класса .

(Примечание. Не путать с "попыткой изменить метод __str__()".)

У меня есть класс MyClass, у которого есть метод 'some_method'. Я могу изменить способ отображения MyClass:

class MyClass():
    def __init__(self): pass
    def some_method(self): pass
    def __str__(self): return "I'm an instance of MyClass!"

Когда я создаю экземпляр и печатаю MyClass:

print(my_class)

Я получаю:

I'm an instance of MyClass!

Когда я

print(my_class.some_method)

Я получаю:

<bound method my_class.some_method of <gumble margle object at mumble-jumble>>

Вместо этого я хотел бы видеть:

some_method in the instance my_class you Dope!

Я попытался переопределить метод str some_method:

def some_method(self):
    def __str__(self):
        return "You dope!"

Но нет любви.

Попытка принудительного перенаправления в IPython оказалась не лучше:

my_class.some_method.__str__ = lambda: "You Dope!"

дал

AttributeError: 'method' object attribute '__str__' is read-only

Есть ли простой способ сделать это программно (желательно в Python 3)?

Ответ 1

Вам нужно использовать неклассический класс вместо

class CustomFunction(object):
    def __init__(self, name):
        self.name = name

    def __call__(func, self):
        # This is the actual function
        pass

    def __get__(func, instance=None, type_=None):
        class CustomMethod(object):
            def __init__(self, instance, type_):
                self.im_self = instance
                self.im_class = type_
            def __call__(self, *args, **kw):
                return func(self.im_self, *args, **kw)
            def __str__(self):
                return '{} in the instance {} you Dope!'.format(func.name, self.im_self)

        return CustomMethod(instance, type_)

то используйте это в своем классе:

class MyClass():
    def __init__(self): pass

    some_method = CustomFunction('some_method')

Демо:

>>> print MyClass().some_method
some_method in the instance <__main__.MyClass instance at 0x106b5ccb0> you Dope!

Это работает, потому что функции дескрипторы; они возвращают методы, когда вызывается их метод __get__.

Ответ 2

Просто добавьте @toStr("This is %s You Dope! :P") над методом.

class MyClass():
    @toStr("This is %s You Dope! :P")
    def some_method(self):
        print("method is doing something... Here is an attrbute... "+str(self.kk))
    def __str__(self): return "I'm an instance of MyClass!"
    def __init__(self):
        self.some_method.real_self = self
        self.kk = [":D"]
c = MyClass()
print(c)
c.some_method()
print(c.some_method)

Вывод:

I'm an instance of MyClass!
method is doing something... Here is an attrbute... [':D']
This is some_method You Dope! :P

Чтобы создать аннотацию, добавьте следующее над классом (возможно, отдельный файл):

def toStr(str):
    def decorator(f):
        class _temp:
            def __call__(self, *args, **kwargs):
                return f(self.real_self, *args, **kwargs)
            def __str__(self):
                return str%f.__name__
        return _temp()
    return decorator

Обратите внимание, что в __init__ требуется self.some_method.real_self = self, чтобы гарантировать, что правый self будет передан обернутому методу.

Ответ 3

Я столкнулся с этой же проблемой, и я не был доволен ни одним из решений здесь. Решение Martijn с использованием дескрипторов является правильным подходом, но оно не так элегантно, как решение, предоставляющее декоратор (и некоторые из вариантов имен аргументов, а также структура его решения излишне запутывают). Решение Navin не является хорошим подходом, поскольку для его установки вручную требуется "real_self"; это и есть цель дескрипторов. Здесь я хотел переопределить __repr__ вместо __str__, но это всего лишь деталь, решение одинаков.

Вот мой декоратор, который возвращает дескрипторное решение:

from functools import update_wrapper

# the usual outer function to allow the decorator to accept an argument
def custom_repr(repr_text):
    # the decorator itself
    def method_decorator(method):

        # Wrap the method in our own descriptor.
        class CustomReprDescriptor(object):

            def __get__(self, instance, owner):
                # Return our wrapped method when we call instance.method
                # This class encapsulates the method
                class MethodWrapper(object):
                    # Callable with custom __repr__ method
                    # Capture the instance and owner (type) from the __get__ call
                    def __init__(self):
                        self.im_self = instance
                        self.im_class = owner
                        self.im_func = method

                    # Call the wrapped method using the captured instance
                    def __call__(self, *args, **kwargs):
                        return self.im_func(self.im_self, *args, **kwargs)

                    # Capture the custom __repr__ text from the decorator call
                    def __repr__(self):
                        return repr_text
                # The call to __get__ returns our wrapped method with custom __repr__
                return update_wrapper(MethodWrapper(), method)
        # The decorator returns our custom descriptor
        return CustomReprDescriptor()
    return method_decorator

class TestClass(object):

    @custom_repr("Test of custom repr.")
    def re_repr_method(self):
        print "Called re-repred method."

tc = TestClass
tc.re_repr_method()
print "rc.re_repr_method.__name__ = " + tc.re_repr_method.__name__
print repr(tc.re_repr_method)

Вывод:

Called re-repred method.
rc.re_repr_method.__name__ = re_repr_method
Test of custom repr.

Ключом к пониманию всего этого является то, что когда вы пишете метод в объявлении класса в python, вы не делаете ничего особенного - просто определяете функцию в пространстве имен этого класса. Однако тогда какой-то синтаксический сахат (или, по крайней мере, я считаю, это происходит): Python затем обертывает этот метод внутри дескриптора, который обрабатывает вызов этой функции с экземпляром класса как аргумент self. Итак, все, что нам нужно сделать, это сделать этот шаг самостоятельно; вместо того, чтобы позволить Python преобразовывать нашу функцию уровня класса в метод, просто оберните его самим в дескриптор, метод __get__ которого возвращает вызываемый метод, метод __call__ которого вызывает функцию, которую мы хотим в качестве нашего метода, но который имеет метод __repr__ по нашему выбору.