-1

Before this is flagged as a duplicate, I know this question has been answered before, but the solutions provided there don't seem to apply to my case. I'm trying to programmatically set class properties. I know I can use property for that, so I thought about doing this:

class Foo:
    def __init__(self, x):
        self._x = x
        def getx(): return self._x
        def setx(y): self._x = y
        self.x = property(fget=getx, fset=setx)

However, when I run this interactively, I get:

>>> f = Foo(42)
>>> f.x
<property object at 0x0000000>
>>> f._x
42
>>> f.x = 1
>>> f.x
1

Is there any way to solve this?

Edit:

I feel I may have left out too much, so here's what I am actually trying to reach. I have a class with a class variable called config, which contains configuration values to set as properties. The class should be subclassed to implement the config variable:

class _Base:
    config = ()

    def __init__(self, obj, **kwargs):
        self._obj = obj()
        for kwarg in kwargs:
            # Whatever magic happens here to make these properties

# Sample implementation
class Bar(_Base):
     config = (
         "x",
         "y"
     )

     def __init__(self, obj, x, y):
         super().__init__(obj, x=x, y=y)

Which now allows for manipulation:

>>> b = Bar(x=3, y=4)
>>> b.x
3
>>> # Etc.

I'm trying to keep this as DRY as possible because I have to subclass _Base a lot.

Daniel
  • 769
  • 8
  • 21

1 Answers1

4

property objects are descriptors, and descriptors are only invoked when defined on the class or metaclass. You can't put them directly on an instance; the __getattribute__ implementation for classes simply don't invoke the binding behaviour needed.

You need to put the property on the class, not on each instance:

class Foo:
    def __init__(self, x):
        self._x = x

    @property
    def x(self): return self._x

    @x.setter
    def x(self, y): self._x = y

If you have to have a property that only works on some instances, you'll have to alter your getter and setter methods to vary behaviour (like raise an AttributeError for when the state of the instance is such that the attribute should 'not exist').

class Bar:
    def __init__(self, has_x_attribute=False):
        self._has_x_attribute = has_x_attribute
        self._x = None

    @property
    def x(self):
        if not self._has_x_attribute:
            raise AttributeError('x')
        return self._x

    @x.setter
    def x(self, y):
        if not self._has_x_attribute:
            raise AttributeError('x')
        self._x = y

The property object still exists and is bound, but behaves as if the attribute does not exist when a flag is set to false.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343