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 __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
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.
Top comments (2)
Nice explanation! This is indeed an important feature to understand. I believe "replaced" might not be the best description here, though. Perhaps "wrapped with" would be a better descriptor of what's going on?
thanks mate!
regarding the "replaced"... it looks like "wrapping" indeed but it's important to understand that the value of this class attribute that used to be what you wrote as 'temperature' is replaced with the value of what's returned from the decorator. this is why i used "replaced". i think the word "wrapping" is not strict enough.