Как сделать, чтобы метод вызывался у свойства экземпляра?
Решаю задачу, в которой необходимо создать декоратор класса, который бы обладал методом get_change для определения было ли изменено свойство экземпляра класса или нет. Я накарябал вот такую ерунду, и в принципе оно даже можно сказать работает.
Я бы хотел узнать, можно ли как-то сделать, чтобы данный метод работал напрямую, т.е. вызывать его следующим образом: экземпляр_класса.свойство_экземпляра_класса.get_change() .Без всяких аргументов внутри метода.
def change_detection(cls):
class NewClass:
changes = {}
def __init__(self, *args, **kwargs):
pass
def __setattr__(self, key, value):
if not hasattr(self, key):
super().__setattr__(key, value)
type(self).changes[key] = 'I am newborn'
else:
super().__setattr__(key, value)
type(self).changes[key] = 'I have been modified already'
def get_change(self, key):
return type(self).changes[key]
return NewClass
@change_detection
class DecorateMe:
def __init__(self):
pass
a = DecorateMe()
a.y = 5
a.y = 6
a.x = 'some string'
print(a.get_change('y')) #I have been modified already
print(a.get_change('x')) #I am newborn
Ответы (2 шт):
Не знаю, может в последних версиях Python и добавили методы расширения для стандартных классов, но насколько пишут в старых ответах на английском SO так сделать невозможно: ссылка1, ссылка2.
You can add whatever methods you like on class objects defined in Python code (AKA monkey patching):
>>> class A(object):
>>> pass
>>> def stuff(self):
>>> print self
>>> A.test = stuff
>>> A().test()
This does not work on builtin types, because their dict is not writable (it's a dictproxy). So no, there is no "real" extension method mechanism in Python.
Проблема в том, что у вас в свойствах лежат стандартные типы Python - например, int или str и метод вы хотите вызывать от них, а это значит, что вы хотите сделать метод расширения для стандартного класса, что невозможно.
По идее, если вы сделаете класс-обёртку, и будете класть в свойства экземпляра класса не исходные данные (стандартные классы Python), а эту обёртку, то у неё можно написать такой метод. Не знаю, может правда потом возникнут проблемы при использовании таких свойств, если от них будет ожидаться поведение и свойства стандартных классов, а там придёт эта обёртка со своим поведением и свойствами. В общем, дальше надо ещё думать, я так не соображу сразу можно ли сделать эту обёртку достаточно прозрачной. Но простого метода тут вроде нет.
Это можно реализовать с помощью дескрипторов.
class StateMon:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
cur_value = instance.__dict__.get(self.name)
instance.__dict__[f"old_{self.name}"] = cur_value
instance.__dict__[self.name] = value
class Foo():
a = StateMon()
f = Foo()
f.a = 10
print("Old:", f.old_a, "Current: ", f.a)
# Old: None Current: 10
f.a = 90
print("Old:", f.old_a, "Current: ", f.a)
# Old: 10 Current: 90
print(type(f.a))
# <class 'int'>
Таким образом мы можем контролировать доступ к атрибуту так как нам вздумается.
Еще пример когда мы храним историю изменения объекта и можем по ней откатываться
import functools
class StateMon:
def __set_name__(self, owner, name):
self._states = []
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
cur_value = instance.__dict__.get(self.name)
self._states.append(cur_value)
instance.__dict__[f"prev_{self.name}"] = functools.partial(self.prev, instance, self.name)
instance.__dict__[self.name] = value
def prev(self, instance, name):
if not self._states:
return
instance.__dict__[self.name] = self._states.pop()
class Foo:
a = StateMon()
f = Foo()
f.a = 10
f.a = 90
print(f.a)
# 90
f.prev_a()
print(f.a)
# 10