Do your own Python's @property

mzsrtgzr2

MoRoth

Posted on May 18, 2020

Do your own Python's @property

After reading this chapter in "Expert Python Programming" -
Advanced Attrbiute Access patterns - https://subscription.packtpub.com/book/application_development/9781789808896/4/ch04lvl1sec39/advanced-attribute-access-patterns
decided to implement @property partially.
Here is the final result - https://gist.github.com/mzsrtgzr2/46975900b8d7a8e6f8cc51d1a40ca940

class myproperty:
    def __init__(self, func):
        self.f = func

    def setter(self, func):
        self.setter_func = func
        return self # (1) why do we need this? read below on 

    def __get__(self, instance, klass):
        return self.f(instance or klass)

    def __set__(self, instance, value):
        self.setter_func(instance, value)


class Foo:
    def __init__(self):
        self._val=0

    @myproperty
    def temperature(self):
        return self._val

    @temperature.setter
    def temperature(self, value):  # (2) why need same name as getter?
        print('setting to', value)
        self._val = value

ins = Foo()

print(int.temperature)  # 0
ins.temperature = 6
print(int.temperature)  # 6
Enter fullscreen mode Exit fullscreen mode

It's important to understand how decorators work. This is the key to understand this code. The decorated function is REPLACED with an instance of your decorator (or a function in case you use a decorator function and not class). That being said, you can add functionality to your decorator class like __get__ and __set__, like in the code above.
__get__ - doing a "read" operation on the decorator class.
__set__ - doing a "write" operation on the decorator class.

This is another way to look at decorators - it can be used for "data descriptors" (when you access a field, as in ins.temperature) and not just as function wrappers (implemented with __call__, as in ins.temperature()).

Important - why is the decorated set method should also be named temperature?

A decorator works like this:

@dec 
def func1:
   pass
Enter fullscreen mode Exit fullscreen mode

actually converts to

func1 = dec(func1)
Enter fullscreen mode Exit fullscreen mode

so the name of the function is important.

What happens if we don't use the same name in (2) or drop the return self statement at the end of our decorator: what used to be temperature in our class is overridden to None - losing all the functionality we thought we built. We really happens:

class Foo:
    def __init__(self):
        self._val=0

    def temperature(self):
        return self._val
    temperature = myproperty(temperature)  # instantiating myproperty class

    def temperature(self, value):  # (2) why need same name as getter?
        print('setting to', value)
        self._val = value

    temperature = temperature.setter(temperature)

Enter fullscreen mode Exit fullscreen mode

In this example, the property field temperature is always the reference name and can't change it.

đź’– đź’Ş đź™… đźš©
mzsrtgzr2
MoRoth

Posted on May 18, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related