Extending @property decorator

hello,

Is there a way that you can extends the @property behavior or rewrite it ?
I want to call ‘update_ui()’ everytime a property is set.

I would like to do this because I don’t want to write it in every setter method.

Is this possible while keeping the same behaviour of property ?

thanks!


class Job(object):
    def __init__(self):
        self._name = None

    @property_update
    def name(self):
        return self._name

    @property_update.setter
    def name(self, value):
        self._name = value

    def update_ui(self):
        print 'updating ui'

job = Job()
job.name = 'new'
# output : 'update ui'


Properties are descriptors and as such can be subclassed. You’d then want to overload the set method.

Maybe something like this:


class property_update(property):
    def __set__(self, obj, value):
        super(property_update, self).__set__(obj, value)
        try:
            obj.update_ui()
        except AttributeError as exc:
            # Possibly do something when a class doesn't have an update_ui method?
            pass

class Job(object):
    def __init__(self):
        self._name = None

    @property_update
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

    def update_ui(self):
        print 'updating ui'

job = Job()
job.name = 'new'
# output : 'update ui'

Also noticed a bug in your code, the @property_update.setter, should be @name.setter for it to work like a property.

You can also do this with a completely custom descriptor class. Some background on the idea here and a pretty detailed example here.

If you’re trying to keep a UI element in sync with a class variable, you can use a descriptor to just disguise getting and setting the actual UI value to look like a property on your class: that’s pretty much what both examples above do

Thanks, I never heard about the term descriptors but that is indeed what I need.

I tried to do a setup with custom descriptor class from the details as in given examples with the examples of Theodox.
But I don’t achieve to let the ‘JobProperty’ work on it s own.

Example 1 : It works when I create the JobProperty in the ‘class space’ but that all jobs use the same instance.
Example 2 : Creating the JobProperty in the init does not work because it returns the object not the’ get

How come that example 1 returns the ‘get’ and not ‘<main.JobProperty object at 0x7f242e36ebd0>’ ?


# example 1
class JobProperty( object ):
    def __init__( self ):
        self.value = None
                  
    def __get__( self, obj, objtype ):
        return self.value
         
    def __set__( self, obj, value ):
        self.value = value

class Job(object):
    name = JobProperty()
    
    def __init__(self):
        pass
        

# create two jobs
job_a = Job()
job_b = Job()

# print names
print job_a.name
print job_b.name
# output : None
#          None

# set name
job_a.name = 'demo'

# print names again
print job_a.name
print job_b.name

# output : 'demo'
#          'demo'

# both names are changed


# example 2
class JobProperty( object ):
    def __init__( self ):
        self.value = None
                  
    def __get__( self, obj, objtype ):
        return self.value
         
    def __set__( self, obj, value ):
        self.value = value

class Job(object):
    
    def __init__(self):
        self.name = JobProperty()
        

# create two jobs
job_a = Job()
job_b = Job()

# print names
print job_a.name
print job_b.name
# output : <__main__.JobProperty object at 0x7f242e36ebd0>
#          <__main__.JobProperty object at 0x7f242e36ec50>

Your first example is on the right track, but your descriptor is storing the data inside itself. There is only one descriptor instance PER CLASS, that’s why you’re getting the shared data. In a case like that you want will need to create some storage mechanism that’s not part of the descriptor to hold the reeal data.

Here’s two different examples for different situations:

  1. The data lives in an outside object. Here’ the class wraps a maya object so you get and set the property from there:

class XformProperty( object ):

    def __init__( self, flag ):
        self.Flag = flag
        self._q_args = {'q':True, flag:True}
        self._e_args = {flag: 0}
 
 
    def __get__( self, obj, objtype ):
        return cmds.xform( obj, **self._q_args )
 
    def __set__( self, obj, value ):
        self._e_args[self.Flag] = value
        cmds.xform( obj.Object, **self._e_args )
 
class Xform( object ):
 
    def __init__( self, obj ):
        self.Object = cmds.ls( obj.Object, l=True )[0]
  
    translation = XformProperty( 'translation' )


In that example, the Xform class holds a maya transform name. When you get or set .translation, it’s checking the stored object name and then calling cmds.xform. You see it uses the stored object name when called. This lets it work on different objects, since the differentiating factor is the obj.Object field.

  1. data lives in the class object, but you want to simplify the storage and avoid boilerplate (or do something else when the value is touched)

class LateBoundProperty(object):
    def __init__(self, name, class_default="IGNORE"):
        self._class_default_string = class_default
        self._name = "_" + name

    def __create_backstore(self, instance, owner=None):
        if not hasattr(instance, self._name):
            default = None
            if owner and hasattr(owner, self._class_default_string):
                default = copy.copy(getattr(owner, self._class_default_string))
            setattr(instance, self._name, default)

    def __get__(self, instance, owner):
        self.__create_backstore(instance, owner)
        return getattr(instance, self._name)

    def __set__(self, instance, val):
        self.__create_backstore(instance)
        setattr(instance, self._name, val)

This is a property descriptor that’s useful for mixin classes that need to create instance properties but which won’t get called in the init of a class they are tacked onto. As you can see it actually adds a field with the same name as the property and a leading underscore to an object it’s attached to and then manages the per-object data there. It also specifes the name of class-level default that can be set if needed.


 class Mixin(object):
     example = LateBoundProperty('example', '_EX')

class Target (Mixin):
    _EX = 99

 print Target().example
 # 99

descriptor are super usefull.
You can have a look at my shader lib about descriptor.
I use them heavily: