-1

In the website How does the @property decorator work in Python? you can find many answers about the meaning of the @property decorator, but no answer about the corresponding @propertyname.setter decorator.

My question is the following. In the code below, is it possible to replace the @x.setter decorator with a single statement?

class D:
    def __init__(self):
        self._x = 'Hi'

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

    @x.setter #delete this decorator
    def x(self, value):
       self._x = value
    # Your code, one line!
  • The very first answer on the question you linked explains what `setter` doex. – user2357112 May 27 '23 at 20:10
  • I know what it does. My question is simply: Is it as easy to do without this decorator as it is to do without the @property decorator? – Dietrich Baumgarten May 27 '23 at 20:16
  • This answer has a worked example of how to build the same class with decorator-style and non-decorator-style properties: https://stackoverflow.com/a/44166208/765091 – slothrop May 27 '23 at 20:19

2 Answers2

2

There's no way to replace the decorator syntax for your setter method with exactly one line. That's because in this specific situation, the little detail of how applying the decorator manually differs from @decorator syntax matters.

That difference is that with @decorator syntax, the function to be decorated is never bound to a name before it's passed to the decorator. In your code, the x name is already in use in our namespace, for the property object decorating the getter function. If we temporarily bind x to the undecorated setter function, we'll lose access to that object, and we need it, since the decorator we want to apply is one of its attributes!

You can work around this by giving the setter function a different name:

class D:
    def __init__(self):
        self._x = 'Hi'

    def x(self):
        return self._x
    x = property(x)   # this is the previous x value, which we will eventually overwrite
    
    def x_setter(self, value):   # rename this function, so we don't clobber x quite yet
       self._x = value
    x = x.setter(x_setter)       # then apply the decorator manually, overwriting x

    # we might want to `del x_setter` here, to avoid cluttering the namespace

As my comments show, the previous x value does eventually get overwritten, by our new property that wraps both the getter and setter methods. But we can't do it too soon, or we won't be able to access x.setter using the old version of x.

A more natural way to call property manually with both getter and setter methods is to pass both of them (and maybe a deleter too) in a single property call:

class D:
    def __init__(self):
        self._x = 'Hi'

    def x_getter(self):
        return self._x

    def x_setter(self, value):
       self._x = value

    x = property(x_getter, x_setter)   # wrap both getter and setter in one call
user2357112
  • 260,549
  • 28
  • 431
  • 505
Blckknght
  • 100,903
  • 11
  • 120
  • 169
1

Here,

class Test:
    x = property(
        lambda self: getattr(self, "_x"),  # getter
        lambda self, value: setattr(
            self, "_x", f"{value} From Setter"
        ),  # setter
    )

    def __init__(self):
        print("init called")
        self.x = "Hi"


test = Test()
test.x = "Hello"
print(test.x)

Output:

init called
Hello From Setter

The property class has three methods within it getter, setter, and deleter. You can guess by the name what they do. You can either use a init method to initialize whatever method you need or you can explicitly call the methods later and set the functions.

May I know why you wanna do that?

  • My question is pure curiosity, without practical purpose, because of course I use the decorators in my code. I just want to understand the magic of the setter decorator, which allows to use the identifier of the property again for the setter function. Without the decorator you have to use a new identifier for the setter function, which is of course very clunky. – Dietrich Baumgarten May 27 '23 at 21:21
  • Thank you for pointing out my mistake. I have corrected the code according to your comment @Blckknght. – Pratik Thakare May 28 '23 at 00:01
  • Thanks for your solution, which is similar to what you would do in JavaScript: use anonymous functions when you need them only once, and pass them directly as arguments to another function. Your solution works in this example because the setter has only one line of code, but if it is more complex, the setattr trick fails. This trick is only necessary because anonymous functions in Python are only allowed to have one expression for reasons unknown to me. – Dietrich Baumgarten May 28 '23 at 05:48