Можно изменить атрибут __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__ по нашему выбору.