2

I know the questions about: copy properties, or dynamic creation of properties has already been posted and also been answered (here, here and here). You could also find an excellent description, how the property function works here.

But I think, that my question is a bit more specific. I do not only want to copy the property from one class to another. No, I also want the specific getter, setter and deleter functions to be copied to the destination class. After a whole day of searching for an answer, I decided to create an new post for this question.

So let me get a bit more in detail. A have an attribute class which is more a class group and stores property-classes:

class AttrContainer():  
    class a():
        ATTR=1
        @property
        def a(self):
            return self.ATTR

        @a.setter
        def a(self, n):
            self.ATTR = n + 3.021

    class b():
        ATTR=None
        @property
        def b(self):
            return "Something"

    class c():
        ATTR=None
        @property
        def c(self):
            return 3

        @c.setter
        def c(self, n):
            self.ATTR = n - 8.5201

As you can see, I have different getter, setter (not in the example: deleter) definitions of each property.

I want to use those properties with my item "wrapper" objects. But not all of item objects needs all properties, thats why I want to copy them dynamically into my wrapper classes.

So, this is how my item "wrapper" classes looks like:

class Item01Object():
    properties = ["a","c"]
    ATTR = None
    #[...]

class Item02Object():
    properties = ["b","c"]
    ATTR = None
    #[...]

#[...]

Because I can't set the properties dynamically while the item class will be instanced, I have to set them before I instance the class:

def SetProperties( ItemObject ):
    for propName, cls in AttrContainer.__dict__.iteritems():
        if propName in ItemObject.properties:
            prop = cls.__dict__[propName]
            fget = prop.fget if prop.fget else None
            fset = prop.fset if prop.fset else None
            fdel = prop.fdel if prop.fdel else None
            ItemObject.__dict__[propName] = property(fget,fset,fdel)
    return ItemObject()

In the end, i would instance my ItemObjects like this:

item = SetProperties(Item01Object)

I would expect, that this will work...

>>> print item
<__builtin__.Item01Object instance at 0x0000000003270F88>
>>> print item.a
None

This is result is right, because I do not update my property ATTR.. Lets change the property:

>>> item.a = 20
>>> print item.a 
20

But this result is wrong, it should be 23.021 and NOT 20 . It looks like my properties do not using the setter functions from its classes.

Why? What do I wrong in my code?

Edit: Sorry, I forgot to remove the inherited object of the ItemObject classes.. Now the code works.

Community
  • 1
  • 1
MagSec
  • 346
  • 4
  • 17
  • Your code doesn't actually work *at all* because you cannot assign to `ItemObject.__dict__` if `ItemObject` is really a class. What are you *really* passing in? The exception I see is `TypeError: 'dictproxy' object does not support item assignment` – Martijn Pieters Feb 19 '15 at 18:03
  • I edited my code. now it works. Sorry, I forgot to remove the `object` class in the `ItemXXObject` class definitions. – MagSec Feb 19 '15 at 18:21
  • 1
    in Python 2 using `object` is **required** if you want setters and deleters to work. – Martijn Pieters Feb 19 '15 at 18:24
  • Thanks!! I didn't saw it in the documentation: `class property([fget[, fset[, fdel[, doc]]]])` **Return a property attribute for new-style classes (classes that derive from object).** [Python Docs 2.7](https://docs.python.org/2/library/functions.html#property) – MagSec Feb 19 '15 at 18:48

1 Answers1

2

For properties with setters and deleters to work properly, your classes need to inherit from object: Why does @foo.setter in Python not work for me?

You can just copy the property object itself over to the new class. It'll hold references to the getter, setter and deleter functions and there is no need to copy those across.

For new-style classes, your code is not working; you cannot assign to a class __dict__ attribute:

>>> item = SetProperties(Item01Object)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in SetProperties
TypeError: 'dictproxy' object does not support item assignment

Use setattr() instead to set attributes on new-style classes:

def SetProperties( ItemObject ):
    for propName, cls in AttrContainer.__dict__.iteritems():
        if propName in ItemObject.properties:
            setattr(ItemObject, propName, cls.__dict__[propName])
    return ItemObject()

Note that the property object is copied across wholesale.

Demo:

>>> class Item01Object(object):
...     properties = ["a","c"]
...     ATTR = None
...
>>> def SetProperties( ItemObject ):
...     for propName, cls in AttrContainer.__dict__.iteritems():
...         if propName in ItemObject.properties:
...             setattr(ItemObject, propName, cls.__dict__[propName])
...     return ItemObject()
... 
>>> item = SetProperties(Item01Object)
>>> item
<__main__.Item01Object object at 0x108205850>
>>> item.a
>>> item.a = 20
>>> item.a
23.021

You only have to copy across property objects to the target class once though; that your function returns an instance implies you are planning to use it for all instances created.

I'd make it a decorator instead:

def set_properties(cls):
    for name, propcls in vars(AttrContainer).iteritems():
        if name in cls.properties:
            setattr(cls, name, vars(propcls)[name])
    return cls

then use this on each of your Item*Object classes:

@set_properties
class Item01Object(object):
    properties = ["a","c"]
    ATTR = None

@set_properties
class Item02Object(object):
    properties = ["b","c"]
    ATTR = None

Demo:

>>> def set_properties(cls):
...     for name, propcls in vars(AttrContainer).iteritems():
...         if name in cls.properties:
...             setattr(cls, name, vars(propcls)[name])
...     return cls
... 
>>> @set_properties
... class Item01Object(object):
...     properties = ["a","c"]
...     ATTR = None
... 
>>> @set_properties
... class Item02Object(object):
...     properties = ["b","c"]
...     ATTR = None
... 
>>> item01 = Item01Object()
>>> item01.c = 20
>>> item01.c
3
>>> item02 = Item02Object()
>>> item02.b = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> item02.b
'Something'
Community
  • 1
  • 1
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Nice, that works fine. Thank you!! but How and why is `object` influencing the property? Your definitions: `class Item01Object(object):#[...]` , My definition: `class Item01Object():#[...]`. – MagSec Feb 19 '15 at 18:35
  • 1
    @MagSec: old-style classes simply do not have the plumbing to support full-on descriptors. – Martijn Pieters Feb 19 '15 at 18:42
  • Do you know if it is possible to get a static variable from my class (which is overwritten) and use this as the base of a new class to replace the existing? the question therefore is here: [link](http://stackoverflow.com/questions/29413046/python-how-to-rebase-or-dynamically-replace-a-class-with-a-different-base-class) – MagSec Apr 02 '15 at 14:33