1

I am coding for a Python plugin to some planetarium software. This plugin comes with functions for accessing objects within the planetarium software's namespace, but they are cumbersome and not OOP. I am therefore trying to create a class that would overload attribute access to streamline coding. I would like to be able to do things such as,

rocket = RemoteObject('rocket')
rocket.color = blue

to set the color of the rocket object in the planetarium software's namespace to blue.

How to define properties in __init__ comes quite close. One difficulty I'm having is that I need to determine the names of my properties when the instance is created. Another difficulty is due to my poor understanding of descriptors in general: attribute calls are returning or overwriting my property object itself instead of calling its getter and setter.

Here is what I have so far:

class RemoteObject(object):
    def __init__(self,remote_object_name):
        self.normalattr = 'foo'
        self.normalmethod = lambda: 'spam'
        for attrname in get_remote_object_attrnames(remote_object_name):
            def _get(self):
                return fetch_remote_attr_value(remote_object_name,attrname)
            def _set(self,value):
                set_remote_attr_value(remote_object_name,attrname,value)
            setattr(self,attrname,property(_get,_set))

if __name__ == '__main__':
    get_remote_object_attrnames = lambda name: {'apple','banana','cherry'}
    fetch_remote_attr_value = lambda o,a: 'Reading %s.%s' % (o,a)
    set_remote_attr_value = lambda o,a,v: 'Writing %s.%s = %s' % (o,a,v)

    scene = RemoteObject('scene')
    for x in scene.__dict__.items(): print x
    print '-----'
    print scene.normalattr
    print scene.normalmethod()
    print scene.apple
    scene.banana = '42'
    print '-----'
    for x in scene.__dict__.items(): print x

When run, it returns this:

('cherry', <property object at 0x00CB65A0>)
('normalmethod', <function <lambda> at 0x00CB8FB0>)
('banana', <property object at 0x00CB65D0>)
('normalattr', 'foo')
('apple', <property object at 0x00CB6600>)
-----
foo
spam
<property object at 0x00CB6600>
-----
('cherry', <property object at 0x00CB65A0>)
('normalmethod', <function <lambda> at 0x00CB8FB0>)
('banana', '42')
('normalattr', 'foo')
('apple', <property object at 0x00CB6600>)

Is there a better way to handle the dynamic set of attrnames needing properties fro each instance? Why are the instance attibutes matching the property names returning the property object itself instead of executing its getter or setter?

Community
  • 1
  • 1
MainiacJoe
  • 11
  • 2

2 Answers2

5

You cannot define properties on an instance; property objects are descriptors, and descriptor __get__ and __set__ hooking is only supported on classes.

You'll have to either define all these properties on the class and disable them (throw an AttributeError perhaps from a disabled property), or use __getattr__ and __setattr__ instead.

The 'define them all' approach:

class Foo(object):
    __enabled_properties = ()

    def __init__(self):
        self.__enabled_properties = ('bar',)

    @property
    def bar(self):
        if 'bar' not in self.__enabled_properties:
            raise AttributeError('bar')
        ...

    @bar.setter
    def bar(self, value):
        if 'bar' not in self.__enabled_properties:
            raise AttributeError('bar')
        ...

The __getattr__ and __setattr__ approach:

class Foo(object):
    __enabled_properties = ()
    __dynamic_properties = ('bar', 'spam', 'eggs')

    def __init__(self):
        self.__enabled_properties = ('bar',)

    def __getattr__(self, attr):
        if attr in self.__dynamic_properties:
            if attr not in self.__enabled_properties:
                raise AttributeError(attr)
            # Fetch a remote value here and return it
        if attr not in self.__dict__:
            raise AttributeError(attr)
        return self.__dict__[attr]

    def __setattr__(self, attr, value):
        if attr in self.__dynamic_properties:
            if attr not in self.__enabled_properties:
                raise AttributeError(attr)
            # Set a remote value here
        self.__dict__[attr] = value
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • There are scores of classes in the remote software, so a single class constructor for the Python object would have a very long __dynamic_properties. Sounds instead that I need a factory class. It's beyond my ken to throw Python code for that together quickly, but I can pseudocode what I'm thinking: – MainiacJoe Nov 09 '12 at 15:43
  • @MainiacJoe: I've updated the `__getattr__`... version a little; I think you'll find that one easier to handle in a base class. All you have to do really is to know that a given attribute is a remote attribute, and then handle it. – Martijn Pieters Nov 09 '12 at 15:45
  • Each remote object class has its own python RemoteObjectClass; RemoteObject takes an remote_object_name and a remote_class_name and creates an instance of the appropriate RemoteObjectClass. [Sorry for the edits and deleted comment, I don't know how to add line breaks in comments yet] – MainiacJoe Nov 09 '12 at 15:50
  • @MainiacJoe: You cannot create line breaks in comments... If you have another question, better post a new question instead, comments are not really meant to carry a conversation. – Martijn Pieters Nov 09 '12 at 15:53
  • Can I just set __dynamic_properties in __init__ also, and dispense with the enabled/dynamic dichotomy? – MainiacJoe Nov 09 '12 at 15:57
  • Of course you can, it's just a standard attribute, so it can be set for either the class or the instance. – Martijn Pieters Nov 09 '12 at 15:59
0

You might have an easier time using a namedtuple. Here's another question with a similar answer: How to add property to a class dynamically?

Community
  • 1
  • 1
Matt Williamson
  • 39,165
  • 10
  • 64
  • 72
  • A named tuple could suffice for reading the arguments in the proxied object - but it could not be used to set them, so I think it would be quite useless in this case. What namedtuples have in common with the problem at hand is that to"namedtuple" is actually a class factory. – jsbueno Nov 09 '12 at 15:36