0

I am creating a python class out of raw data as follows:

Class Test:

def __init__(self, raw_number):
    self._raw_number = raw_number

I am then computing _raw_number as a property to return the actual number I would like to have:

@property
def number(self):
    return self._raw_number[0]

However, when trying to set this property on a Test object using

t = Test([1, 2, 3]) # this will set the number property to 1
t.number = 5 # this is supposed to set the number property to 5

I am running into an error that says property number cannot be set.

I am wondering why I can solve this?

marco
  • 656
  • 2
  • 6
  • 22
  • 1
    You defined a "get" property, but no "set" property. Check the [`property`](https://docs.python.org/3/library/functions.html#property) documentation to see how that works. – Matthias Mar 04 '20 at 15:41
  • https://stackoverflow.com/q/1684828/8014793 – hurlenko Mar 04 '20 at 15:42
  • This last link doesn't apply to my case as I am not defining the property (or even a private property with the same name) in my `init` function . – marco Mar 04 '20 at 15:43

1 Answers1

2

@property by itself (at least, when used as shown) only provides a getter. Unless you provide a setter as well, the property is read-only.

class Test:

    def __init__(self, raw_number):
        self.number = raw_number

    @property
    def number(self):
        return self._raw_number[0]

    @number.setter
    def number(self, value):
        self._raw_number = value

Keep in mind that the setter should be responsible for ensuring that the value of _raw_number is, in fact, indexable. Also, the __init__ method can make use of the setter in initializing the property; only the getter and setter themselves should be accessing the underlying _raw_number attribute.


property can be used to set the getter and setter simultaneously.

class Test:
    def __init__(self, raw_number):
        self.number = raw_number

    def _getter(self):
        return self._raw_number[0]

    def _setter(self, value):
        try:
            value[0]
        except IndexError:
            raise TypeError("Value must be indexable")
        self._raw_number = value

    number = property(_getter, _setter)

    # Clean up the namespace before creating the class
    del _getter
    del _setter
chepner
  • 497,756
  • 71
  • 530
  • 681
  • So this is similar to what I was trying to do. Howver, the issue that I ran into is that the first time `number` is computed, it is based on an array. But the next time that I setting number, say `number = number * 5`, I am passing in two integers. Would this not affect the getter? – marco Mar 04 '20 at 15:47
  • Yes; the getter and setter need to be designed together to ensure that the setter provides a value that the getter can use. Whether that means the setter should *require* or *reject* a list is a design issue. – chepner Mar 04 '20 at 15:48
  • okay so basically what you're saying is that this is a design issue and nothing that I can solve by code? I'll have to redesign how my class deals with the property? – marco Mar 04 '20 at 15:49
  • 1
    Yes; it's not clear why you are initializing it to a list when the getter only wants the first value. In `_setter`, I just make it raise an error if the given value can't be used with `self._raw_number[0]`. – chepner Mar 04 '20 at 15:52
  • Another option would be a setter that simply does something like `self._raw_number[0] = value`. But ultimately, the first step is to decide how you want the property to behave; after you do that, you can implement the getter and setter to provide that behavior. – chepner Mar 04 '20 at 15:53