Reputation: 167
I'm writing multiple classes with a common logic added to a bunch of attributes. This is a simplified version of the code:
class FooAspect:
_bar_prop = 'bar'
def __init__(self, bar_value: int):
self._bar = bar_value
@property
def bar(self) -> int:
return self._bar
@bar.setter
def bar(self, value: int) -> None:
self._bar = value
perfrom_action(self._bar_prop, value)
perform_action
always has similar form, and I would like to encapsulate it with a decorator. Essentially I'm looking for a way to write something like this:
# ... define @my_setter
class FooAspect:
# ...
@property
def bar(self) -> int:
return self._bar
@bar.my_setter(key='bar')
def bar(self, value: int) -> None:
self._bar = value
Is it possible to extend @property
or @prop.setter
to achieve this?
Upvotes: 1
Views: 1814
Reputation: 107134
While copying and pasting the reference code of property
and making minor modifications would work as demonstrated by your answer, in the interest of code reuse you can subclass the property
class and call super()
to access methods of the parent class instead.
Also, the setter
function in your implementation is unnecessarily instantiating a new instance of MyProperty
, when it can reuse the current object by returning self._setter
:
class MyProperty(property):
def __set__(self, obj, value):
super().__set__(obj, value)
perform_action(self.key, value)
def _setter(self, fset):
obj = super().setter(fset)
obj.key = self.key
return obj
def setter(self, key):
self.key = key
return self._setter
so that:
class FooAspect:
@MyProperty
def bar(self) -> int:
return self._bar
@bar.setter(key='bar')
def bar(self, value: int) -> None:
self._bar = value
def perform_action(key, value):
print(key, value)
f = FooAspect()
f.bar = 'foo'
outputs:
bar foo
Upvotes: 1
Reputation: 96350
My point wasn't to copy the property
implementation, that is very generic on purpose, which probably isn't needed if your logic is always the same for your getter and setter. So it would be better to do something like:
def perform_action(key, value):
print(key, value)
class PerformKeyAction:
def __init__(self, key):
self.key = key
def __get__(self, instance, owner):
# maybe common getter behavior
return getattr(instance, self.attr_name)
def __set__(self, instance, value):
perform_action(self.key, value)
return setattr(instance, self.attr_name, value)
def __set_name__(self, owner, name):
self.attr_name = f'_{name}'
class FooAspect:
bar = PerformAction(key='bar')
class BazAspect:
buzz = PerformAction(key='buzz_key')
class FizzAspect:
fang = PerformAction(key='fang_key')
And this way you avoid the boilerplate when writing your various classes instead of repeating it in various getters/setters in various classes.
Upvotes: 4
Reputation: 167
Thanks to @juanpa.arrivillaga I came up with this solution implementing my own descriptor:
# myproperty.py
class MyProperty:
def __init__(self, fget=None, fset=None, key=None):
self.fget = fget
self.fset = fset
self.key = key
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
perfrom_action(self.key, value)
self.fset(obj, value)
def getter(self, fget):
return type(self)(fget, self.fset, self.key)
def _setter(self, fset):
return type(self)(self.fget, fset, self.key)
def setter(self, key):
return type(self)(self.fget, self.fset, key=key)._setter
# foo_aspect.py
from myproperty import MyProperty
class FooAspect:
# ...
@MyProperty
def bar(self) -> int:
return self._bar
@bar.setter(key='bar')
def bar(self, value: int) -> None:
self._bar = value
Upvotes: 1