[PyQt] Use pyqtProperty on QObject instance

Kyle Altendorf sda at fstab.net
Fri May 31 15:34:52 BST 2019


Definitely look at attrs.org.  It will give a commonly expected 
declarative form for classes and you can add your own metadata to the 
fields as needed.  But, I'm not really clear what variable you are 
creating a closure on.  And, have you looked at three-arg type() 
calls?  And one more, the @ decorator syntax is just a shortcut.

     @d
     def f():
         pass

Is basically the same as:

     def f():
         pass
     f = d(f)

I'll note that I did something at least vaguely similar to this, though 
I bet it ought to be rewritten.

https://github.com/altendky/stlib/blob/b532ae346b88e2465a54fb96d31639de0c73bff1/epyqlib/utils/qt.py#L914-L996

used like

https://github.com/altendky/stlib/blob/master/epyqlib/tests/utils/test_qt.py#L13-L34

Though admittedly I did not make anything an actual slot.  It just turns 
the attrs attributes into properties (or optionally pyqtProperty's) and 
creates associated on-change signals.

Cheers,
-kyle


On 2019-05-31 03:37, Patrick Stinson wrote:

> Well, I have no idea what kind of trouble I am asking for, but after 
> spending a bunch fo time in the sip and PyQt5 code I came up with the 
> idea to hack a slot in a closure, which seems to work:
> 
> @staticmethod
> def _setup_pyqtProperties(classAttrs, fields):
> def closure(attr, kind):
> signalName = '%sChanged' % attr
> getterName = attr
> setterName = 'set' + attr[0].upper() + attr[1:]
> #
> qsignal = pyqtSignal()
> getter = lambda self: self.get(attr)
> def setter(self, x):
> self.set(attr, x)
> getattr(self, signalName).emit()
> #
> qproperty = pyqtProperty(kind,
> fget=getter,
> fset=setter,
> notify=qsignal)
> @pyqtSlot(kind, name=setterName)
> def qslot(self, x):
> setter(self, x)
> #
> ret = {}
> ret[signalName] = qsignal
> ret[getterName] = qproperty
> ret[setterName] = qslot
> return ret
> for k, (attr, kind, label) in enumerate(fields):
> propAttrs = closure(attr, kind)
> classAttrs.update(propAttrs)
> 
> On May 30, 2019, at 9:34 AM, Patrick Stinson <patrickkidd at gmail.com> 
> wrote:
> 
> Being able to call pyqtSlot as a function and not a decorator as you 
> can with pyqtProperty would allow me to avoid having to add an explicit 
> setter for every property and simply generate them from the FIELDS 
> class attribute.
> 
> @pyqtSlot(str)
> def setFirstName(self, x): self.set('firstName', x)
> 
> On May 30, 2019, at 9:18 AM, Patrick Stinson <patrickkidd at gmail.com> 
> wrote:
> 
> I am playing with dynamically adding signals, slots, and properties at 
> class-declaration time using locals(). The following code works with 
> pyqtSignal and pyqtProperty, but not pyqtSlot? It looks like 
> pyqtProperty can read the attributes from fset and fget, but pyqtSlot 
> doesn't read the attribute from name?
> 
> class PropertySheet(QObject):
> 
> @staticmethod
> def _setup_pyqtProperties(classAttrs, fields):
> def closure(attr, kind):
> qsignal = pyqtSignal()
> getterName = attr
> setterName = 'set' + attr[0].upper() + attr[1:]
> getter = lambda self: self.get(attr)
> setter = lambda self, x: self.set(attr, x)
> qproperty = pyqtProperty(kind,
> fget=getter,
> fset=setter,
> notify=qsignal)
> qslot = pyqtSlot(kind, name=setterName)
> ret = {}
> ret['%sChanged' % attr] = qsignal
> ret[getterName] = qproperty
> ret[setterName] = qslot
> return ret
> for k, (attr, kind, label) in enumerate(fields):
> propAttrs = closure(attr, kind)
> classAttrs.update(propAttrs)
> 
> class QmlPersonProperties(PropertySheet):
> 
> FIELDS = [('firstName', str, 'First Name'),
> ('middleName', str, 'Middle Name'),
> ]
> PropertySheet._setup_pyqtProperties(locals(), FIELDS)
> 
> On May 27, 2019, at 11:29 AM, Kyle Altendorf <sda at fstab.net> wrote:
> 
> When do you figure out what the properties should be and when do you 
> need to create the class?  You can dynamically create classes with 
> three-arg type() calls.
> 
> I went another approach for my needs with signals.  A simplified 
> explanation of the idea is that instead of adding signals dynamical to 
> a class you can instead add other QObject instances with signals on 
> them as attributes of your primary class.  I wrapped this up with a 
> descriptor which I can use pretty much as a drop in replacement for 
> pyqtSignal but without having to inherit from QObject.  Not sure if 
> that approach ends up helpful here though.  Of course there is overhead 
> associated with extra objects etc but... sometimes that doesn't matter.
> 
> https://github.com/altendky/stlib/blob/f779e9c4d5ca4015eafe946b3281a9dcdd7a9fd9/epyqlib/utils/qt.py#L1172-L1210
> 
> Cheers,
> -kyle
> 
> On May 27, 2019 2:38:04 PM EDT, Patrick Stinson <patrickkidd at gmail.com> 
> wrote:
> So they are. I wonder if this can be done with attached properties.
> 
> On May 27, 2019, at 9:59 AM, Phil Thompson 
> <phil at riverbankcomputing.com> wrote:
> 
> On 27/05/2019 17:41, Patrick Stinson wrote:
> Ok, that answers my question. I will figure out a way to add the
> properties to the class the first time they are added to one of the
> instances. They are always the same, after all.
> Properties (like signals) are part of the class *definition* (as far
  as Qt is concerned). You can't add them dynamically.

> Phil
> 
> On May 27, 2019, at 9:11 AM, Phil Thompson
  <phil at riverbankcomputing.com> wrote:

> On 27/05/2019 16:33, Patrick Stinson wrote:
> I have a custom object property system that adds properties to
  QObject

> instances and am trying to expose those [dynamic] properties to
  qml.

> Is it possible to add a qt property to a QObject instance, as
  opposed

> to adding it using pyqtProperty as a decorator in the class
> declaration?
> The PyQt5 docs say that you can use pyqtProperty in the same way
  as

> the python property() function apart from the decorator, but I
  haven't

> had much success with this:
> def test_property():
> class A(QObject):
> def __init__(self):
> self._mine = 12
> self.mine = pyqtProperty(int, self.get_mine,
  self.set_mine)

> def get_mine(self):
> return self._mine
> def set_mine(self, x):
> self._mine = x
> a = A()
> print(a.mine)
> print(a.mine())
> turin:pkdiagram patrick$ python test.py
> <PyQt5.QtCore.pyqtProperty object at 0x114ea1840>
> Traceback (most recent call last):
> File "test.py", line 743, in <module>
> test_property()
> File "test.py", line 740, in test_property
> print(a.mine())
> TypeError: Required argument 'fget' (pos 1) not found
> turin:pkdiagram patrick$ Properties are class objects not instance 
> objects.
> Phil
  _______________________________________________
PyQt mailing list    PyQt at riverbankcomputing.com
https://www.riverbankcomputing.com/mailman/listinfo/pyqt


More information about the PyQt mailing list