Creating a class that behaves like any variable, but has a callback when changing / reading

I would like to create a class that behaves like a python variable, but calls some callback function when the "variable" changes / is read.

In other words, I would like to be able to use the class as follows:

x=myClass(change_callback, read_callback) 

defines x as an instance of myclass. The constructor ( INIT ) accepts 2 functions as a paramater: a function that needs to be called every time x changes, and a function that needs to be called every time x is "read"

The following statement:

 x=1 

saves 1 and calls change_callback(1) , which can do something.

The following statement:

 a=x 

will retrieve the stored value and call read_callback() , which may change the stored value and do other things.

I would like this to work with any type, for example. to write things like:

x=[1 2 3] , which will cause change_callback([1 2 3])

x.append(4) runs change_callback([1 2 3 4]) (and possibly calling read_callback() before)

x={'a':1} will call change_callback({'a':1})

print(x) will call read_callback () ... and, of course, will return the last stored value for printing. A.

The idea is that any access to a variable can be logged or generate other calculations ... without visible results.

I have a feeling that it should be possible, but I really don’t see what my object should inherit from ... If I need to limit me to one type, for example. list, is there a way to override all assignment operators (including methods such as append () ...) "once", preserving the original behavior (base class method) and adding a callback ...

Or are there more suitable ways (modules ...) to achieve the same goals ...?

Thanks in advance,

+4
source share
4 answers

Objects do not know when they are assigned to a variable. Writing x = a adds a dict (or locals) entry that points to a. The object does not receive notifications (although in CPython its reference count is incremented).

The part that receives the notification is the container in which the object is assigned. In the case of a global assignment, the module dictionary is updated. In the case of updates to instance variables of type ab = x there is a point search and storage in the instance dictionary.

You can force these containers to call arbitrary code when searching or storing. The Python property provides this opportunity to new-style classes. The exec and eval operations allow you to specify a custom dict that can provide this feature for regular assignments. In both cases, you are in full control of what happens during the appointment.

Summary Search and repository behavior is controlled through the destination, not the destination.

An example :

 from collections import namedtuple CallbackVar = namedtuple('CallbackVar', ['change_callback', 'read_callback']) class CallbackDict(dict): 'Variant of dict that does callbacks for instances of CallbackVar' def __getitem__(self, key): value = dict.__getitem__(self, key) if isinstance(value, CallbackVar): return value.read_callback(key) def __setitem__(self, key, value): try: realvalue = dict.__getitem__(self, key) if isinstance(realvalue, CallbackVar): return realvalue.change_callback(key, value) except KeyError: pass return dict.__setitem__(self, key, value) stmts = ''' x = CallbackVar(setter, getter) # Attach getter() and setter() to "x" x = 1 # Invoke the setter() x # Invoke the getter() ''' def getter(key): print 'Getting', key return 42 def setter(key, value): print 'Setting', key, 'to', value exec stmts in globals(), CallbackDict() 
+8
source

You might like to describe the descriptors: http://docs.python.org/howto/descriptor.html

This will only work for object properties, although this is probably enough for you.

+4
source

You cannot do this with the = operator - because by definition it is intended to overwrite the left side, no matter what the current contents of this file receive notification that this is happening. (And the assignment operator is not redefined in Python.)

However, you can use the .set() and .get() methods for your class. It would not bring you exactly the same β€œmagic”, but, frankly, this is probably good - it makes it more clear what is happening.

+2
source

A variable is a reference to a python object. It is not true to say that "a class behaves like a variable." when you do

 x = [1, 2, 3] 

the variable x will be a link ( redirected) to the new object of the list [1, 2, 3] , and not the old object was "modified". He just lost the link.

Do what you may wish for. Try calling change_callback on the __init__ method of your class.

To call read_callback . Do something like this

 class A(object): def __init__(self): self._x = 3 @property def x(): read_callback() # called! return self._x .... a = A() print ax 

If this is what you are what.

+1
source

Source: https://habr.com/ru/post/1403252/


All Articles