I'm writing a script in Python and have a bit of a problem:
class LightDMUser(QObject):
def __init__(self, user):
super(LightDMUser, self).__init__()
self.user = user
@pyqtProperty(QVariant)
def background(self): return self.user.get_background()
@pyqtProperty(QVariant)
def display_name(self): return self.user.get_display_name()
@pyqtProperty(QVariant)
def has_messages(self): return self.user.get_has_messages()
@pyqtProperty(QVariant)
def home_directory(self): return self.user.get_home_directory()
@pyqtProperty(QVariant)
def image(self): return self.user.get_image()
@pyqtProperty(QVariant)
def language(self): return self.user.get_language()
@pyqtProperty(QVariant)
def layout(self): return self.user.get_layout()
@pyqtProperty(QVariant)
def layouts(self): return self.user.get_layouts()
@pyqtProperty(QVariant)
def logged_in(self): return self.user.get_logged_in()
@pyqtProperty(QVariant)
def name(self): return self.user.get_name()
@pyqtProperty(QVariant)
def real_name(self): return self.user.get_real_name()
@pyqtProperty(QVariant)
def session(self): return self.user.get_session()
As you can see, this code is horribly redundant. I tried condensing it like this:
class LightDMUser(QObject):
attributes = ['background', 'display_name', 'has_messages', 'home_directory', 'image', 'language', 'layout', 'layouts', 'logged_in', 'name', 'real_name', 'session']
def __init__(self, user):
super(LightDMUser, self).__init__()
self.user = user
for attribute in self.attributes:
setattr(self, attribute, pyqtProperty(QVariant, getattr(self.user, 'get_' + attribute)))
PyQt4, however, expects the class methods to be present for the class itself, not an instance. Moving the setattr
code out of the __init__
block didn't work either because self
wasn't defined for the class, so I don't really know what to do.
Can anyone see a way to condense this code?
There are number of ways to do it: class decorator, metaclass, Mixin.
Common helper function:
def set_pyqtproperties(klass, properties, proxy='user'):
def make_prop(prop):
def property_(self):
return getattr(getattr(self, proxy), 'get_' + prop)
property_.__name__ = prop
return property_
if isinstance(properties, basestring):
properties = properties.split()
for prop in properties:
setattr(klass, prop, pyqtProperty(QVariant, make_prop(prop)))
def set_properties(properties):
def decorator(klass):
set_pyqtproperties(klass, properties)
return klass
return decorator
Usage
@set_properties("display background")
class LightDMUser(QObject): pass
if there is no support for class decorators then you could try:
class LightDMUser(QObject):
pass
LightDMUser = set_properties("display background")(LightDMUser)
def set_properties_meta(properties):
def meta(name, bases, attrs):
cls = type(name, bases, attrs)
set_pyqtproperties(cls, properties)
return cls
return meta
Usage
class LightDMUser(QObject):
__metaclass__ = set_properties_meta("display background")
Note: you could reuse the same metaclass if you set the list of properties as a class attribute:
def MetaClass(name, bases, attrs):
cls = type(name, bases, attrs)
set_pyqtproperties(cls, attrs.get('properties', ''))
return cls
class LightDMUser(QObject):
properties = "display background"
__metaclass__ = MetaClass
Also you could manipulate attrs
directly: attrs[name] = value
before calling type()
instead of setattr(cls, name, value)
.
The above assumes that QObject.__class__ is type
.
def properties_mixin(classname, properties):
#note: create a new class by whatever means necessary
# e.g., even using exec() as namedtuple does
# http://hg.python.org/cpython/file/3.2/Lib/collections.py#l235
# reuse class decorator here
return set_properties(properties)(type(classname, (), {}))
Usage
PropertiesMixin = properties_mixin('PropertiesMixin', 'display background')
class LightDMUser(PropertiesMixin, QObject): pass
I haven't tried any of it. The code is here to show the amount and the kind of code it might require to implement the feature.
You could attach these methods from еру outside of the class definition:
class LightDMUser(QObject):
def __init__(self, user):
super(LightDMUser, self).__init__()
self.user = user
The simplest way is to create a closure for each property, override its __name__
(just for case if @pyqtProperty
needs it) and to bind it to the class:
for attribute in [
'background',
'display_name',
'has_messages',
'home_directory',
'image',
'language',
'layout',
'layouts',
'logged_in',
'name',
'real_name',
'session'
]:
def delegating(self):
return getattr(self.user, 'get_' + attribute)()
delegating.__name__ = attribute
delegating = pyqtProperty(QVariant)(delegating)
setattr(LightDMUser, attribute, delegating)
I'm pretty sure this can work if you move your loop out of the class, and create a closure to hold each of the attribute names:
class LightDMUser(QObject):
attributes = ['background', 'display_name', 'has_messages',
'home_directory', 'image', 'language', 'layout',
'layouts', 'logged_in', 'name', 'real_name', 'session']
def __init__(self, user):
super(LightDMUser, self).__init__()
self.user = user
for attribute in LightDMUser.attributes:
closure = lambda self, attribute=attribute : getattr(self.user,
'get_' + attribute)()
setattr(LightDMUser, attribute, pyqtProperty(QVariant, closure))
I've not tested this with the actual QT based classes you're dealing with, but a simpler version using regular Python property
instances worked perfectly. I'm also not sure this is a good idea, since it would be pretty hard to figure out what's going on if you are not already familiar with it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With