Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

condense pyqtproperties

Tags:

python

class

pyqt

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?

like image 738
Blender Avatar asked Aug 12 '12 09:08

Blender


3 Answers

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)))

Class decorator

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)

Metaclass

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.

Mixin

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.

like image 167
jfs Avatar answered Oct 25 '22 15:10

jfs


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)
like image 21
Eldar Abusalimov Avatar answered Oct 25 '22 15:10

Eldar Abusalimov


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.

like image 28
Blckknght Avatar answered Oct 25 '22 16:10

Blckknght