Related
Trying to fix an issue I have with with a Utilities class I am creating where one instance of it will work across many different stock classes. Below is an example of what I am testing with.
class Singleton():
_instance = None
def __new__(cls: type[typing.Self], *args, **kwags) -> typing.Self:
if cls._instance is None:
cls._instance: typing.Self = super(Singleton, cls).__new__(cls, *args, **kwags)
return cls._instance
def __init__(self, *args, **kwargs) -> None:
self.data: str = ""
pass
def setData(self, data: str) -> None:
self.data = data
pass
class Subclass(Singleton):
def __init__(self, arg: int) -> None:
super().__init__()
self.arg: int = arg
pass
def method(self):
return self.arg, self.data
def main() -> None:
single: Singleton = Singleton()
single.setData("Hello")
sub = Subclass(1)
print(sub.method())
pass
Instead of printing 1 hello which you would expect the terminal spits out an AttributeError
print(sub.method())
^^^^^^^^^^
AttributeError: 'Singleton' object has no attribute 'method'
I am not sure why this is happening as the subclass was the one calling the method and not the singleton class.
I have looked into replacing this with a medthod or passing the instance into each of the classes, both would work, however, would like it to just be easier to write by only calling the class and having everything as parent and child classes. Just curious at this point.
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()
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()
I want to keep track of object instances of a given class/subclasses in Python given my particular framework I cannot use constructors. As an alternative I could use either decorators or Singletons objects.
Are there any other alternatives except using global variables ?
You could use a descriptor. That is, it's an object that can and will keep track of what things are where with the caveat that it will only do so if you've assigned a value to do it. Here's an example:
class RevealAccess(object):
"""
A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print 'Retrieving', self.name
return self.val
def __set__(self, obj, val):
print 'Updating', self.name
self.val = val
This is taken out from the documentation. However, add the following from this SO Q&A:
_instances = []
def __new__(cls, *args, **kw):
instance = object.__new__(cls)
cls._instances.append(instance)
return instance
#classmethod
def get_instances(cls):
return self._instances
and you've achieved your goals:
class Foo(object):
desc = MyDesc('yo')
foo = Foo()
Foo.get_instances().length
My problem is:
I would like to add to a Composite class Leaf objects created at runtime inside
a Composite routine like this:
def update(self, tp, msg, stt):
"""It updates composite objects
"""
d = Leaf()
d.setDict(tp, msg, stt)
self.append_child(d)
return self.status()
Inside main:
import lib.composite
c = Composite()
for i in range(0,10):
c.update(str(i), msg, stt)
and the Composite is:
class Composite(Component):
def __init__(self, *args, **kw):
super(Composite, self).__init__()
self.children = []
def append_child(self, child):
self.children.append(child)
def update(self, tp, msg, stt):
d = Leaf()
d.setDict(tp, msg, stt)
self.append_child(d)
return self.status()
def status(self):
for child in self.children:
ret = child.status()
if type(child) == Leaf:
p_out("Leaf: %s has value %s" % (child, ret))
class Component(object):
def __init__(self, *args, **kw):
if type(self) == Component:
raise NotImplementedError("Component couldn't be "
"instantiated directly")
def status(self, *args, **kw):
raise NotImplementedError("Status method "
"must be implemented")
class Leaf(Component):
def __init__(self):
super(Leaf, self).__init__()
self._dict = {}
def setDict(self, type, key, value)
self._dict = { type : { key : value } }
def status(self):
return self._dict
But in this way I found always that my composite has just one leaf ("d") added even if
update was called many times.
How can I code such a routine such to be able to fill composite at runtime?
"But in this way I found always that my composite has just one leaf ("d") added even if update was called many times."
No, that code makes Composite having ten children.
>>> c.children
[<__main__.Leaf object at 0xb7da77ec>, <__main__.Leaf object at 0xb7da780c>,
<__main__.Leaf object at 0xb7da788c>, <__main__.Leaf object at 0xb7da78ac>,
<__main__.Leaf object at 0xb7da78cc>, <__main__.Leaf object at 0xb7da792c>,
<__main__.Leaf object at 0xb7da794c>, <__main__.Leaf object at 0xb7da798c>,
<__main__.Leaf object at 0xb7da79ac>, <__main__.Leaf object at 0xb7da79cc>]
So why you think it only has one is strange.
What is doing the append_child? I think it should store the leafs in a list. Does it?
Update: you shouldn't pass self as first argument in the main function. I think that it raises an exception.
See code below that seems to work ok
class Component(object):
def __init__(self, *args, **kw):
pass
def setDict(self, *args, **kw):
pass
class Leaf(Component):
def __init__(self, *args, **kw):
Component.__init__(self, *args, **kw)
class Composite(Component):
def __init__(self, *args, **kw):
Component.__init__(self, *args, **kw)
self.children = []
def update(self, tp, msg, stt):
"""It updates composite objects
"""
d = Leaf()
d.setDict(tp, msg, stt)
self.append_child(d)
return 0
def append_child(self, child):
self.children.append(child)
def remove_child(self, child):
self.children.remove(child)
c =Composite()
for i in range(0,10):
c.update(str(i), "", 0)
print len(c.children)