How to create PyQt Properties dynamically - python

I am currently looking into a way to create GUI desktop applications with Python and HTML/CSS/JS using PyQt5's QWebEngineView.
In my little demo application, I use a QWebChannel to publish a Python QObject to the JavaScript side, so that data can be shared and passed back and forth. Sharing and connecting slots and signals so far works fine.
I'm having difficulties though with the synchronisation of simple (property) values. From what I've read, the way to go is to implement a pyqtProperty in the shared QObject via decorated getter and setter functions, with an additional signal emitted in the setter, used to notify JavaScript when the value has changed. The code below shows that and so far this works fine:
import sys
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
class HelloWorldHtmlApp(QWebEngineView):
html = '''
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script>
var backend;
new QWebChannel(qt.webChannelTransport, function (channel) {
backend = channel.objects.backend;
});
</script>
</head>
<body> <h2>HTML loaded.</h2> </body>
</html>
'''
def __init__(self):
super().__init__()
# setup a page with my html
my_page = QWebEnginePage(self)
my_page.setHtml(self.html)
self.setPage(my_page)
# setup channel
self.channel = QWebChannel()
self.backend = self.Backend(self)
self.channel.registerObject('backend', self.backend)
self.page().setWebChannel(self.channel)
class Backend(QObject):
""" Container for stuff visible to the JavaScript side. """
foo_changed = pyqtSignal(str)
def __init__(self, htmlapp):
super().__init__()
self.htmlapp = htmlapp
self._foo = "Hello World"
#pyqtSlot()
def debug(self):
self.foo = "I modified foo!"
#pyqtProperty(str, notify=foo_changed)
def foo(self):
return self._foo
#foo.setter
def foo(self, new_foo):
self._foo = new_foo
self.foo_changed.emit(new_foo)
if __name__ == "__main__":
app = QApplication.instance() or QApplication(sys.argv)
view = HelloWorldHtmlApp()
view.show()
app.exec_()
Starting this with the Debugger connected, I can call the backend.debug() slot in the JavaScript console, which leads to the value of backend.foo being "I modified foo!" afterwards, which means the Python code succesfully changed the JavaScript variable.
This is kind of tedious though. For every value I'd want to share, I'd have to
create an internal variable (here self._foo)
create a getter function
create a setter function
create a signal in the QObject's body
emit this signal explicitly in the setter function
Is there any simpler way to achieve this? Ideally some sort of one-liner declaration? Maybe using a class or function to pack that all up? How would I have to bind this to the QObject later on? I'm thinking of something like
# in __init__
self.foo = SyncedProperty(str)
Is this possible? Thanks for your ideas!

One way to do this is by using a meta-class:
class Property(pyqtProperty):
def __init__(self, value, name='', type_=None, notify=None):
if type_ and notify:
super().__init__(type_, self.getter, self.setter, notify=notify)
self.value = value
self.name = name
def getter(self, inst=None):
return self.value
def setter(self, inst=None, value=None):
self.value = value
getattr(inst, '_%s_prop_signal_' % self.name).emit(value)
class PropertyMeta(type(QObject)):
def __new__(mcs, name, bases, attrs):
for key in list(attrs.keys()):
attr = attrs[key]
if not isinstance(attr, Property):
continue
value = attr.value
notifier = pyqtSignal(type(value))
attrs[key] = Property(
value, key, type(value), notify=notifier)
attrs['_%s_prop_signal_' % key] = notifier
return super().__new__(mcs, name, bases, attrs)
class HelloWorldHtmlApp(QWebEngineView):
...
class Backend(QObject, metaclass=PropertyMeta):
foo = Property('Hello World')
#pyqtSlot()
def debug(self):
self.foo = 'I modified foo!'

Thanks for your metaclass idea, I modified it slightly to work properly with classes that contain more than one instance. The problem I faced was that the value was stored into the Property itself, not in the class instance attributes. Also I split up the Property class into two classes for clarity.
class PropertyMeta(type(QtCore.QObject)):
def __new__(cls, name, bases, attrs):
for key in list(attrs.keys()):
attr = attrs[key]
if not isinstance(attr, Property):
continue
initial_value = attr.initial_value
type_ = type(initial_value)
notifier = QtCore.pyqtSignal(type_)
attrs[key] = PropertyImpl(
initial_value, name=key, type_=type_, notify=notifier)
attrs[signal_attribute_name(key)] = notifier
return super().__new__(cls, name, bases, attrs)
class Property:
""" Property definition.
This property will be patched by the PropertyMeta metaclass into a PropertyImpl type.
"""
def __init__(self, initial_value, name=''):
self.initial_value = initial_value
self.name = name
class PropertyImpl(QtCore.pyqtProperty):
""" Actual property implementation using a signal to notify any change. """
def __init__(self, initial_value, name='', type_=None, notify=None):
super().__init__(type_, self.getter, self.setter, notify=notify)
self.initial_value = initial_value
self.name = name
def getter(self, inst):
return getattr(inst, value_attribute_name(self.name), self.initial_value)
def setter(self, inst, value):
setattr(inst, value_attribute_name(self.name), value)
notifier_signal = getattr(inst, signal_attribute_name(self.name))
notifier_signal.emit(value)
def signal_attribute_name(property_name):
""" Return a magic key for the attribute storing the signal name. """
return f'_{property_name}_prop_signal_'
def value_attribute_name(property_name):
""" Return a magic key for the attribute storing the property value. """
return f'_{property_name}_prop_value_'
Demo usage:
class Demo(QtCore.QObject, metaclass=PropertyMeta):
my_prop = Property(3.14)
demo1 = Demo()
demo2 = Demo()
demo1.my_prop = 2.7

Building on the excellent answers by ekhumoro and Windel (y'all are lifesavers), I've made a modified version that:
Is specified via type, with no initial value
Can correctly handle properties that are Python lists or dictionaries
[Edit: now notifies when list/dict is modified in place, not just when it's reassigned]
Just as with Windel's version, to use it, simply specify the properties as class attributes, but with their types rather than values. (For a custom user-defined class that inherits from QObject, use QObject.) Values can be assigned in intializer methods, or wherever else you need.
from PyQt5.QtCore import QObject
# Or for PySide2:
# from PySide2.QtCore import QObject
from properties import PropertyMeta, Property
class Demo(QObject, metaclass=PropertyMeta):
number = Property(float)
things = Property(list)
def __init__(self, parent=None):
super().__init__(parent)
self.number = 3.14
demo1 = Demo()
demo2 = Demo()
demo1.number = 2.7
demo1.things = ['spam', 'spam', 'baked beans', 'spam']
And here's the code. I've gone with Windel's structure to accommodate instances, simplified a couple of things that had remained as artefacts of ekhumoro's version, and added a new class to enable notification of in-place modifications.
# properties.py
from functools import wraps
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
# Or for PySide2:
# from PySide2.QtCore import QObject, Property as pyqtProperty, Signal as pyqtSignal
class PropertyMeta(type(QObject)):
"""Lets a class succinctly define Qt properties."""
def __new__(cls, name, bases, attrs):
for key in list(attrs.keys()):
attr = attrs[key]
if not isinstance(attr, Property):
continue
types = {list: 'QVariantList', dict: 'QVariantMap'}
type_ = types.get(attr.type_, attr.type_)
notifier = pyqtSignal(type_)
attrs[f'_{key}_changed'] = notifier
attrs[key] = PropertyImpl(type_=type_, name=key, notify=notifier)
return super().__new__(cls, name, bases, attrs)
class Property:
"""Property definition.
Instances of this class will be replaced with their full
implementation by the PropertyMeta metaclass.
"""
def __init__(self, type_):
self.type_ = type_
class PropertyImpl(pyqtProperty):
"""Property implementation: gets, sets, and notifies of change."""
def __init__(self, type_, name, notify):
super().__init__(type_, self.getter, self.setter, notify=notify)
self.name = name
def getter(self, instance):
return getattr(instance, f'_{self.name}')
def setter(self, instance, value):
signal = getattr(instance, f'_{self.name}_changed')
if type(value) in {list, dict}:
value = make_notified(value, signal)
setattr(instance, f'_{self.name}', value)
signal.emit(value)
class MakeNotified:
"""Adds notifying signals to lists and dictionaries.
Creates the modified classes just once, on initialization.
"""
change_methods = {
list: ['__delitem__', '__iadd__', '__imul__', '__setitem__', 'append',
'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'],
dict: ['__delitem__', '__ior__', '__setitem__', 'clear', 'pop',
'popitem', 'setdefault', 'update']
}
def __init__(self):
if not hasattr(dict, '__ior__'):
# Dictionaries don't have | operator in Python < 3.9.
self.change_methods[dict].remove('__ior__')
self.notified_class = {type_: self.make_notified_class(type_)
for type_ in [list, dict]}
def __call__(self, seq, signal):
"""Returns a notifying version of the supplied list or dict."""
notified_class = self.notified_class[type(seq)]
notified_seq = notified_class(seq)
notified_seq.signal = signal
return notified_seq
#classmethod
def make_notified_class(cls, parent):
notified_class = type(f'notified_{parent.__name__}', (parent,), {})
for method_name in cls.change_methods[parent]:
original = getattr(notified_class, method_name)
notified_method = cls.make_notified_method(original, parent)
setattr(notified_class, method_name, notified_method)
return notified_class
#staticmethod
def make_notified_method(method, parent):
#wraps(method)
def notified_method(self, *args, **kwargs):
result = getattr(parent, method.__name__)(self, *args, **kwargs)
self.signal.emit(self)
return result
return notified_method
make_notified = MakeNotified()

Related

how to access outer class properties inside the inner classes?

class Remote:
aa=7
def __init__(self):
self.name="Lenovo"
self.b=self.Battery()
print("this is outer",self.b.t)
class Battery:
def __init__(self):
self.name="Hp"
self.t="df"
self.c=self.Cover()
class Cover:
def __init__(self):
self.name="Arplastic"
c1=Remote()
I knew today about inner class but i don't know how to i access properties and methods of outer class into inner class please let me know anyone.
Change the constructor(s) of the inner class(es) to accept a parent argument and have the creating instance pass itself to it:
class Remote:
aa=7
def __init__(self):
self.name="Lenovo"
self.b=self.Battery(self)
print("this is outer",self.b.t)
class Battery:
def __init__(self,parent):
self.name="Hp"
self.t="df"
self.c=self.Cover(self)
self.parent=parent
class Cover:
def __init__(self,parent):
self.name="Arplastic"
self.parent=parent
c1=Remote()
print(c1.b.c.parent.parent.name) # prints 'Lenovo'
One approach is to make a metaclass that automatically creates self.parent attributes for nested classes. Note that there is a trade-off between readability and boilerplate here - many programmers would rather you just manually pass parents as arguments and add them to __init__ methods. This is more fun though, and there is something to be said for having less cluttered code.
Here is the code:
import inspect
def inner_class(cls):
cls.__is_inner_class__ = True
return cls
class NestedClass(type):
def __new__(metacls, name, bases, attrs, parent=None):
attrs = dict(attrs.items())
super_getattribute = attrs.get('__getattribute__', object.__getattribute__)
inner_class_cache = {}
def __getattribute__(self, attr):
val = super_getattribute(self, attr)
if inspect.isclass(val) and getattr(val, '__is_inner_class__', False):
if (self, val) not in inner_class_cache:
inner_class_cache[self, val] = NestedClass(val.__name__, val.__bases__, val.__dict__, parent=self)
return inner_class_cache[self, val]
else:
return val
attrs['__getattribute__'] = __getattribute__
attrs['parent'] = parent
return type(name, bases, attrs)
class Remote(metaclass=NestedClass):
aa = 7
def __init__(self):
self.name = "Lenovo"
self.b = self.Battery()
print("this is outer", self.b.t)
#inner_class
class Battery:
def __init__(self):
self.name = "Hp"
self.t = "df"
self.c = self.Cover()
#inner_class
class Cover:
def __init__(self):
self.name = "Arplastic"
print(f'{self.parent=}, {self.parent.parent=}')
c1 = Remote()
print(f'{c1.b.c.parent.parent is c1=}')
print(f'{isinstance(c1.b, c1.Battery)=}')
Output:
self.parent=<__main__.Battery object at 0x7f11e74936a0>, self.parent.parent=<__main__.Remote object at 0x7f11e7493730>
this is outer df
c1.b.c.parent.parent is c1=True
isinstance(c1.b, c1.Battery)=True
The way this works is by storing the parent as a class attribute (which is None by default), and replacing the __getattribute__ method so that all inner classes are replaced with NestedClasses with the parent attribute correctly filled in.
The inner_class decorator is used to mark a class as an inner class by setting the __is_inner_class__ attribute.
def inner_class(cls):
cls.__is_inner_class__ = True
return cls
This is not strictly necessary if all attributes that are classes should be treated as inner classes, but it's good practice to do something like this to prevent Bar.foo being treated as an inner class in this example:
class Foo:
pass
class Bar(metaclass=NestedClass):
foo = Foo
All the NestedClass metaclass does is take the description of the class and modify it, adding the parent attribute:
class NestedClass(type):
def __new__(metacls, name, bases, attrs, parent=None):
attrs = dict(attrs.items())
...
attrs['parent'] = parent
return type(name, bases, attrs)
...and modifying the __getattribute__ method. The __getattribute__ method is a special method that gets called every time an attribute is accessed. For example:
class Foo:
def __init__(self):
self.bar = "baz"
def __getattribute__(self, item):
return 1
foo = Foo()
# these assert statements pass because even though `foo.bar` is set to "baz" and `foo.remote` doesn't exist, accessing either of them is the same as calling `Foo.__getattribute(foo, ...)`
assert foo.bar == 1
assert foo.remote == 1
So, by modifying the __getattribute__ method, you can make accessing self.Battery return a class that has its parent attribute equal to self, and also make it into a nested class:
class NestedClass(type):
def __new__(metacls, name, bases, attrs, parent=None):
attrs = dict(attrs.items())
# get the previous __getattribute__ in case it was not the default one
super_getattribute = attrs.get('__getattribute__', object.__getattribute__)
inner_class_cache = {}
def __getattribute__(self, attr):
# get the attribute
val = super_getattribute(self, attr)
if inspect.isclass(val) and getattr(val, '__is_inner_class__', False):
# if it is an inner class, then make a new version of it using the NestedClass metaclass, setting the parent attribute
if (self, val) not in inner_class_cache:
inner_class_cache[self, val] = NestedClass(val.__name__, val.__bases__, val.__dict__, parent=self)
return inner_class_cache[self, val]
else:
return val
attrs['__getattribute__'] = __getattribute__
attrs['parent'] = parent
return type(name, bases, attrs)
Note that a cache is used to ensure that self.Battery will always return the same object every time rather than re-making the class every time it is called. This ensures that checks like isinstance(c1.b, c1.Battery) work correctly, since otherwise c1.Battery would return a different object to the one used to create c1.b, causing this to return False, when it should return True.
And that's it! You can now enjoy nested classes without boilerplate!

Multiprocessing proxy: let getters return proxies themselves

I have a complex unpickable object that has properties (defined via getters and setters) that are of complex and unpickable type as well. I want to create a multiprocessing proxy for the object to execute some tasks in parallel.
The problem: While I have succeeded to make the getter methods available for the proxy object, I fail to make the getters return proxies for the unpickable return objects.
My setup resembles the following:
from multiprocessing.managers import BaseManager, NamespaceProxy
class A():
#property
def a(self):
return B()
#property
def b(self):
return 2
# unpickable class
class B():
def __init__(self, *args):
self.f = lambda: 1
class ProxyBase(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__')
class AProxy(ProxyBase): pass
class BProxy(ProxyBase): pass
class MyManager(BaseManager):pass
MyManager.register('A', A, AProxy)
if __name__ == '__main__':
with MyManager() as manager:
myA = manager.A()
print(myA.b) # works great
print(myA.a) # raises error, because the object B is not pickable
I know that I can specify the result type of a method when registering it with the manager. That is, I can do
MyManager.register('A', A, AProxy, method_to_typeid={'__getattribute__':'B'})
MyManager.register('B', B, BProxy)
if __name__ == '__main__':
with MyManager() as manager:
myA = manager.A()
print(myA.a) # works great!
print(myA.b) # returns the same as myA.a ?!
It is clear to me that my solution does not work since the __getattr__ method applies to all properties, whereas I only want it to return a proxy for B when property a is accessed. How could I achieve this?
As a side question: if I remove the *args argument from the __init__ method of B, I get an error that it is called with the wrong number of arguments. Why? How could I resolve this?
I don't this is possible without some hacks, since the choice to return a value or proxy is made based on the method name alone, and not the type of the return value (from Server.serve_client):
try:
res = function(*args, **kwds)
except Exception as e:
msg = ('#ERROR', e)
else:
typeid = gettypeid and gettypeid.get(methodname, None)
if typeid:
rident, rexposed = self.create(conn, typeid, res)
token = Token(typeid, self.address, rident)
msg = ('#PROXY', (rexposed, token))
else:
msg = ('#RETURN', res)
Also keep in mind exposing __getattribute__ in an unpickable class's proxy basically breaks the proxy functionality when calling methods.
But if you're willing to hack it and just need attribute access, here is a working solution (note calling myA.a.f() still won't work, the lambda is an attribute and is not proxied, only methods are, but that's a different problem).
import os
from multiprocessing.managers import BaseManager, NamespaceProxy, Server
class A():
#property
def a(self):
return B()
#property
def b(self):
return 2
# unpickable class
class B():
def __init__(self, *args):
self.f = lambda: 1
self.pid = os.getpid()
class HackedObj:
def __init__(self, obj, gettypeid):
self.obj = obj
self.gettypeid = gettypeid
def __getattribute__(self, attr):
if attr == '__getattribute__':
return object.__getattribute__(self, attr)
obj = object.__getattribute__(self, 'obj')
result = object.__getattribute__(obj, attr)
if isinstance(result, B):
gettypeid = object.__getattribute__(self, 'gettypeid')
# This tells the server that the return value of this method is
# B, for which we've registered a proxy.
gettypeid['__getattribute__'] = 'B'
return result
class HackedDict:
def __init__(self, data):
self.data = data
def __setitem__(self, key, value):
self.data[key] = value
def __getitem__(self, key):
obj, exposed, gettypeid = self.data[key]
if isinstance(obj, A):
gettypeid = gettypeid.copy() if gettypeid else {}
# Now we need getattr to update gettypeid based on the result
# luckily BaseManager queries the typeid info after the function
# has been invoked
obj = HackedObj(obj, gettypeid)
return (obj, exposed, gettypeid)
class HackedServer(Server):
def __init__(self, registry, address, authkey, serializer):
super().__init__(registry, address, authkey, serializer)
self.id_to_obj = HackedDict(self.id_to_obj)
class MyManager(BaseManager):
_Server = HackedServer
class ProxyBase(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__')
class AProxy(ProxyBase): pass
class BProxy(ProxyBase): pass
MyManager.register('A', callable=A, proxytype=AProxy)
MyManager.register('B', callable=B, proxytype=BProxy)
if __name__ == '__main__':
print("This process: ", os.getpid())
with MyManager() as manager:
myB = manager.B()
print("Proxy process, using B directly: ", myB.pid)
myA = manager.A()
print('myA.b', myA.b)
print("Proxy process, via A: ", myA.a.pid)
The key to the solution is to replace the _Server in our manager, and then wrap the id_to_obj dict with the one that performs the hack for the specific method we need.
The hack consists on populating the gettypeid dict for the method, but only after it has been evaluated and we know the return type to be one that we would need a proxy for. And we're lucky in the order of evaluations, gettypeid is accessed after the method has been called.
Also luckily gettypeid is used as a local in the serve_client method, so we can return a copy of it and modify it and we don't introduce any concurrency issues.
While this was a fun exercise, I have to say I really advise against this solution, if you're dealing with external code that you cannot modify, you should simply create your own wrapper class that has explicit methods instead of #property accessors, proxy your own class instead, and use method_to_typeid.

Receiving pyqtSignal from Singleton

There's singleton class in python:
from PyQt5.QtCore import QObject, pyqtSignal
import logging
class Singleton(QObject):
_instance = None
def __new__(cls, *args, **kwargs):
if not isinstance(cls._instance, cls):
cls._instance = QObject.__new__(cls, *args, **kwargs)
return cls._instance
class DataStatus(Singleton, QObject):
'''
'''
dataChanged = pyqtSignal(str)
__val = 'init'
def __init__(self):
super().__init__()
def setVal(self, val):
self.dataChanged.emit('emit: ' + val)
logging.debug('emit: ' + val)
self.__val = val
def getVal(self):
return self.__val
The idea is to have one single data store accessible from allover the program. Every time a set Method is called, a signal should be emitted telling all instances that from somewhere the data was changed and should be re-read.
Cool plan, but if you look at the test code
def test(self):
self.ds1 = DataStatus()
self.ds1.dataChanged.connect(self.windowaction)
print(self.ds1)
print(self.ds1.getVal())
self.ds1.setVal('ds1.first')
self.ds2 = DataStatus()
#self.ds2.dataChanged.connect(self.windowaction)
print(self.ds2)
print(self.ds2.getVal())
self.ds2.setVal('ds2.second')
print(self.ds1.getVal())
def windowaction(self, q):
print(q)
And the console output it get's strange (at least for me):
<DataStatus.DataStatus.DataStatus object at 0x03207580>
init
emit: ds1.first
<DataStatus.DataStatus.DataStatus object at 0x03207580>
ds1.first
ds2.second
Both instances do have the same address, cool the singleton does it's job.
To ds1 if've connected the "dataChange" signal which works properly if from ds1 data is updated.
BUT no signal is received by ds1 if I change the data with ds2.set......
Does anybody have an explanation about what happens here. Data is shared properly across the instances, but not the signals:-/
Although your Singleton class complies that the same object is always returned but that does not imply that it is correctly implemented, in your case in new the new object is created but you return the first object created (fulfilling what you apparently want) but the signal "dataChanged "belongs to the new object and not to the first object causing the problem. The solution in this case is to use metaclasses as this library points out:
class Singleton(type(QObject), type):
def __init__(cls, name, bases, dict):
super().__init__(name, bases, dict)
cls._instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class DataStatus(QObject, metaclass=Singleton):
dataChanged = pyqtSignal(str)
__val = "init"
def __init__(self):
super().__init__()
def setVal(self, val):
self.dataChanged.emit("emit: " + val)
logging.debug("emit: " + val)
self.__val = val
def getVal(self):
return self.__val

Using decorators to implement Observer Pattern in Python3

This question is not in general about the observer pattern. It is focused on the use of decorators in that pattern. The question is based on the answer of a similar question.
#!/usr/bin/env python3
class Observable:
"""
The object that need to be observed. Alternative names are 'Subject'.
In the most cases it is a data object.
"""
def __init__(self):
self._observers = []
def register_observer(self, callback):
self._observers.append(callback)
return callback
def _broadcast_observers(self, *args, **kwargs):
for callback in self._observers:
callback(*args, **kwargs)
class TheData(Observable):
"""
Example of a data class just for demonstration.
"""
def __init__(self, data):
Observable.__init__(self)
self._data = data
#property
def data(self):
return self._data
#data.setter
def data(self, data):
self._data = data
self._broadcast_observers()
class TheGUIElement:
"""
Example of a gui class (Widget) just for demonstration.
e. g. it could be a text field in GUI.
"""
def __init__(self, data):
self._data = data
#data.register_observer(self._data_updated)
self._redraw()
def _redraw(self):
print('in _redraw(): ' + data.data)
#Observable.register_observer
def _data_updated(self, **kwargs):
"""
This is the callback that is called by the Observable if the
data changed.
"""
print('in _data_updated() - kwargs: {}'.format(kwargs))
self._redraw()
if __name__ == '__main__':
data = TheData('DATA')
gui = TheGUIElement(data)
data.data = 'SECOND DATA'
This code doesn't work because of this error.
Traceback (most recent call last):
File "./o.py", line 42, in <module>
class TheGUIElement:
File "./o.py", line 55, in TheGUIElement
#Observable.register_observer
TypeError: register_observer() missing 1 required positional argument: 'callback'
It is unclear to me how to use a decorator for to register the observers (e.g. TheGUIElement).
To register the callback, you need to have an actual object. In your code, how is #Observable.register_observer supposed to find which instance is should register on?
Please drop that Observable thing that's a javaism, cumbersome in python.
Look at this.
#!/usr/bin/env python
class SomeData(object):
def __init__(self, value):
self.callbacks = []
self.foo = value
def register(self, callback):
self.callbacks.append(callback)
return callback
def notify(self, *args, **kwargs):
for callback in self.callbacks:
callback(self, *args, **kwargs)
class SomeGUI(object):
def redraw(self, obj, key, newvalue):
print('redrawing %s with value %s' % (self, newvalue))
if __name__ == '__main__':
my_data = SomeData(42)
# Register some function using decorator syntax
#my_data.register
def print_it(obj, key, value):
print('Key %s changed to %s' % (key, value))
# Register the SomeGUI element
my_gui = SomeGUI()
my_data.register(my_gui.redraw)
# Try changing it. Note my_data is dumb for now, notify manually.
my_data.foo = 10
my_data.notify("foo", 10)
I intentionally removed automatic notifications to illustrate registration by itself.
Let's add it back. But there is no point using that Observable class. Let's make it lighter, simply defining an event class.
#!/usr/bin/env python3
class Event(object):
def __init__(self):
self.callbacks = []
def notify(self, *args, **kwargs):
for callback in self.callbacks:
callback(*args, **kwargs)
def register(self, callback):
self.callbacks.append(callback)
return callback
class SomeData(object):
def __init__(self, foo):
self.changed = Event()
self._foo = foo
#property
def foo(self):
return self._foo
#foo.setter
def foo(self, value):
self._foo = value
self.changed.notify(self, 'foo', value)
class SomeGUI(object):
def redraw(self, obj, key, newvalue):
print('redrawing %s with value %s' % (self, newvalue))
if __name__ == '__main__':
my_data = SomeData(42)
# Register some function using decorator syntax
#my_data.changed.register
def print_it(obj, key, value):
print('Key %s changed to %s' % (key, value))
# Register the SomeGUI element
my_gui = SomeGUI()
my_data.changed.register(my_gui.redraw)
# Try changing it.
my_data.foo = 10
As you probably noted now, the decorator syntax is useful in those circumstances:
You have a single registry. Either a singleton or the class itself class are first-order objects, and most are singletons.
You dynamically define the function and register it as you go.
Now, those manual getters/setters you have are cumbersome as well, if you have many why not factor them out?
#!/usr/bin/env python3
class Event(object):
def __init__(self):
self.callbacks = []
def notify(self, *args, **kwargs):
for callback in self.callbacks:
callback(*args, **kwargs)
def register(self, callback):
self.callbacks.append(callback)
return callback
#classmethod
def watched_property(cls, event_name, key):
actual_key = '_%s' % key
def getter(obj):
return getattr(obj, actual_key)
def setter(obj, value):
event = getattr(obj, event_name)
setattr(obj, actual_key, value)
event.notify(obj, key, value)
return property(fget=getter, fset=setter)
class SomeData(object):
foo = Event.watched_property('changed', 'foo')
def __init__(self, foo):
self.changed = Event()
self.foo = foo
class SomeGUI(object):
def redraw(self, obj, key, newvalue):
print('redrawing %s with value %s' % (self, newvalue))
if __name__ == '__main__':
my_data = SomeData(42)
# Register some function using decorator syntax
#my_data.changed.register
def print_it(obj, key, value):
print('Key %s changed to %s' % (key, value))
# Register the SomeGUI element
my_gui = SomeGUI()
my_data.changed.register(my_gui.redraw)
# Try changing it.
my_data.foo = 10
For reference, all three programs output the exact same thing:
$ python3 test.py
Key foo changed to 10
redrawing <__main__.SomeGUI object at 0x7f9a90d55fd0> with value 10
Even though the thread is kinda old (probably the problem is already solved), I would like to share a solution of mine to the "Decorated Observer Pattern" problem:
https://pypi.org/project/notifyr/
I created a package that implements decorators which add the observer-observed methods/attributes to python classes. I managed to use the package in a Django project too, but with a few adaptations (the .observers attribute is not persisted in the database, so I had to load the list of observers into it every time I expected to notify them).
Here is an implementation example:
Original Code:
class Dog(object):
def __init__(self, name):
self.name = name
def bark(self):
print('Woof')
def sleep(self):
print(self.name, 'is now asleep: ZZzzzzZzzZ...')
class Person(object):
def __init__(self, name):
self.name = name
def educate_dog(self, dog):
print(self.name + ':','Sleep,', dog.name)
dog.sleep()
Suppose we want a person to educate a dog every time the animal barks:
from notifyr.agents import observed, observer
from notifyr.functions import target
#observed
class Dog(object):
def __init__(self, name):
self.name = name
#target
def bark(self):
print('Woof')
def sleep(self):
print(self.name, 'is now asleep: ZZzzzzZzzZ...')
#observer('educate_dog')
class Person(object):
def __init__(self, name):
self.name = name
def educate_dog(self, dog):
print(self.name + ':','Sleep,', dog.name)
dog.sleep()
Given the decorated classes, it is possible to achieve the following result:
d = Dog('Tobby')
p = Person('Victor')
d.attach(p) # Victor is now observing Tobby
d.bark()
# Woof
# Victor: Sleep, Tobby
# Tobby is now asleep: ZZzzzzZzzZ...
The package is still very primitive, but it presents a working solution to this type of situation.
I was recently looking for something similar and here's what I came up with. It works by intercepting the __setattr__ method -- a useful stunt I plan on keeping in my pocket for later.
def watchableClass(cls):
"""
Class Decorator!
* If the class has a "dirty" member variable, then it will be
automatically set whenever any class value changes
* If the class has an "onChanged()" method, it will be called
automatically whenever any class value changes
* All this only takes place if the value is different from what it was
that is, if myObject.x is already 10 and you set myObject.x=10
nothing happens
* DOES NOT work with getter/setter functions. But then, you are
already in a function, so do what you want!
EXAMPLE:
#watchableClass
class MyClass:
def __init__(self):
self.dirty=False
def onChanged(self):
print('class has changed')
"""
if hasattr(cls,'__setattr__'):
cls.__setattr_unwatched__=cls.__setattr__
cls.__setattr__=_setObjValueWatchedCascade
else:
cls.__setattr__=_setObjValueWatched
return cls
def _setObjValueWatched(ob,k,v):
"""
called when an object value is set
"""
different=not k in ob.__dict__ or ob.__dict__[k]!=v
if different:
ob.__dict__[k]=v
if k not in ('dirty'):
if hasattr(ob,'dirty'):
ob.dirty=True
if hasattr(ob,'onChanged'):
ob.onChanged()
def _setObjValueWatchedCascade(ob,k,v):
"""
called when an object value is set
IF the class had its own __setattr__ member defined!
"""
different=not k in ob.__dict__ or ob.__dict__[k]!=v
ob.__setattr_unwatched__(k,v)
if different:
if k not in ('dirty'):
if hasattr(ob,'dirty'):
ob.dirty=True
if hasattr(ob,'onChanged'):
ob.onChanged()

Python shared property parent/child

Embarrassed to ask but I am using webapp2 and I am templating out a solution to make it easier to define routesbased on this google webapp2 route function. But it all depends on being able to define TYPE_NAME at the child level. The idea is the parent sets everything up and the child just needs to implement the _list function. The issue I ran into is TYPE_NAME is None and I need it to be the child.
#main WSGI is extended to have this function
class WSGIApplication(webapp2.WSGIApplication):
def route(self, *args, **kwargs):
def wrapper(func):
self.router.add(webapp2.Route(handler=func, *args, **kwargs))
return func
return wrapper
from main import application
class ParentHandler(RequestHandler):
TYPE_NAME = None
#application.route('/', name="list_%s" %TYPE_NAME)
def list(self):
return self._list()
class ChildHandler(ParentHandler):
TYPE_NAME = 'child'
def _list(self):
return []
I have tried a couple solutions using "class properties" but they didn't pan out. Open to other ideas, I basically just need the child class to inherit the decorated properties and execute them.
Edit:
For all of those on the edge of their seats wondering how I fix this,I was not able to get everything I needed out of the decorator so I ended up using a meta. I also added a _URLS parameter to allow for adding additional "routes". It maps custom function to the route. Really wanted to use a decorator but couldn't get it to work.
class RequestURLMeta(type):
def __new__(mcs, name, bases, dct):
result = super(RequestURLMeta, mcs).__new__(mcs, name, bases, dct)
urls = getattr(result, '_URLS', {}) or {}
for k,v in urls.iteritems():
template = v.pop('template')
app.route(getattr(result, k), template, **v)
if getattr(result, 'TYPE_NAME', None):
app.route(result.list, result.ROOT_PATH, methods=['GET'],name="%s" % result.TYPE_NAME)
#other ones went here..
return result
class ParentHandler(RequestHandler):
__metaclass__ = RequestURLMeta
class ChildHandler(ParentHandler):
TYPE_NAME = 'child'
_URLS = { 'custom': '/custom', 'TYPE_NAME': 'custom_test' }
def _list(self):
return []
def custom(self): pass
I think to get this to work you are going to need to use a metaclass. It might look something like the following (untested):
from main import application
class RouteMeta(type):
def __new__(mcs, name, bases, dct):
type_name = dct.get("TYPE_NAME")
if type_name is not None:
#application.route('/', type_name)
def list(self):
return self._list()
dct["list"] = list
return super(RouteMeta, mcs).__new__(mcs, name, bases, dct)
class ParentHandler(RequestHandler):
__metaclass__ = RouteMeta
class ChildHandler(ParentHandler):
TYPE_NAME = 'child'
def _list(self):
return []
Instead of having the list() method an attribute of ParentHandler, it is dynamically created for classes that inherit from ParentHandler and have TYPE_NAME defined.
If RequestHandler also uses a custom metaclass, have RouteMeta inherit from RequestHandler.__metaclass__ instead of type.
This code:
#application.route('/', name="list_%s" %TYPE_NAME)
def list(self):*emphasized text*
...
is semantically identical to this one:
def list(self):
...
list = application.route('/', name="list_%s" %TYPE_NAME)(list)
i.e. the method route is called inside the ParentHandler scope and
whatever lazy method you try, it will not work. You should try something
different:
from main import application
def route_list(klass):
klass.list = application.route('/',
name="list_%s" % klass.TYPE_NAME)(klass.list)
return klass
class ParentHandler(RequestHandler):
def list(self):
return self._list()
class ChildHandler(ParentHandler):
TYPE_NAME = 'child'
def _list(self):
return []
# in python3 would be:
# #route_list
# class ChildHandler(ParentHandler):
# ...
ChildHandler = route_list(ChildHandler)

Categories