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
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
__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
Important - why is the
decorated set method should also be named
A decorator works like this:
@dec def func1: pass
actually converts to
func1 = dec(func1)
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)
In this example, the property field
temperature is always the
reference name and can't change it.