admin管理员组文章数量:1122832
I have a class that performs some useful job in my project
class Test:
def __init__(self):
self.__value = None
def set_value(self, value):
print(f"set_value(): value={value}")
self.__value = value
def get_value(self):
print(f"get_value()")
return self.__value
value = property(get_value, set_value)
Among regular functions it has some getters/setters that is handy to group into properties for simpler client code. E.g.
t.value = 123
Now, in some parts of the application I need to do some extra actions (a print() call in my case) on every access to the Test object, on each method call. So I did this:
class Wrapper:
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, attr):
print(f"Wrapper::__getattr__(): {attr}")
return getattr(self.wrapped, attr)
This is very similar to what decorators do, but this approach has 2 benefits:
- Original class is untouched, and I can use it separately from the Wrapper
- I can add as many new methods to the Test class, without having to add new decorators in Wrapper
Now the usage code:
t = Test()
w = Wrapper(t)
w.set_value(123)
print(f"Value: {w.get_value()}")
w.value = 234
print(f"Value: {w.value}")
Direct set/get calls work as expected. Unfortunately w.value syntax no longer work, and silently assign a value of 234 to the value
name, instead of calling getters and setters.
How this issue can be fixed, so that Wrapper respects Test class properties?
I have a class that performs some useful job in my project
class Test:
def __init__(self):
self.__value = None
def set_value(self, value):
print(f"set_value(): value={value}")
self.__value = value
def get_value(self):
print(f"get_value()")
return self.__value
value = property(get_value, set_value)
Among regular functions it has some getters/setters that is handy to group into properties for simpler client code. E.g.
t.value = 123
Now, in some parts of the application I need to do some extra actions (a print() call in my case) on every access to the Test object, on each method call. So I did this:
class Wrapper:
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, attr):
print(f"Wrapper::__getattr__(): {attr}")
return getattr(self.wrapped, attr)
This is very similar to what decorators do, but this approach has 2 benefits:
- Original class is untouched, and I can use it separately from the Wrapper
- I can add as many new methods to the Test class, without having to add new decorators in Wrapper
Now the usage code:
t = Test()
w = Wrapper(t)
w.set_value(123)
print(f"Value: {w.get_value()}")
w.value = 234
print(f"Value: {w.value}")
Direct set/get calls work as expected. Unfortunately w.value syntax no longer work, and silently assign a value of 234 to the value
name, instead of calling getters and setters.
How this issue can be fixed, so that Wrapper respects Test class properties?
Share Improve this question asked Nov 22, 2024 at 19:29 Oleksandr MasliuchenkoOleksandr Masliuchenko 8452 gold badges9 silver badges19 bronze badges 2 |2 Answers
Reset to default 3You can define a __setattr__
method to manage assignments. Note that it has to treat the wrapped
attribute separately to avoid infinite recursion in __init__
.
class Test:
def __init__(self):
self.__value = None
def set_value(self, value):
print(f"set_value(): value={value}")
self.__value = value
def get_value(self):
print(f"get_value()")
return self.__value
value = property(get_value, set_value)
class Wrapper:
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, attr):
print(f"Wrapper::__getattr__(): {attr}")
return getattr(self.wrapped, attr)
def __setattr__(self, attr, value):
if attr == 'wrapped':
super().__setattr__('wrapped', value)
else:
print(f"Wrapper::__setattr__(): {attr}")
setattr(self.wrapped, attr, value)
t = Test()
w = Wrapper(t)
w.set_value(123)
print(f"Value: {w.get_value()}")
print()
w.value = 234
print(f"Value: {w.value}")
Output:
Wrapper::__getattr__(): set_value
set_value(): value=123
Wrapper::__getattr__(): get_value
get_value()
Value: 123
Wrapper::__setattr__(): value
set_value(): value=234
Wrapper::__getattr__(): value
get_value()
Value: 234
Properties are descriptors and descriptors are looked up in the class, not in an instance --> data model / descriptors.
Instance Binding
If binding to an object instance, a.x is transformed into the call:
type(a).__dict__['x'].__get__(a, type(a))
.
The easiest way to have Test
descriptors (properties) in the Wrapper
is to inherit them.
Further, there is a better place to monitor almost all access than __getattr__
. It's the __getattribute__
--> data model / attribute access
object.__getattribute__(self, name)
Called unconditionally to implement attribute accesses for instances of the class. If the class also defines
__getattr__()
, the latter will not be called unless__getattribute__()
either calls it explicitly or raises an AttributeError.
Result:
...
class Wrapper(Test):
def __getattribute__(self, attr):
print(f"Wrapper::__getattribute__(): {attr}")
return super().__getattribute__(attr)
w = Wrapper()
...
本文标签: decoratorHow can getattr() respect python class propertiesStack Overflow
版权声明:本文标题:decorator - How can getattr() respect python class properties? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736301260a1931114.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
@property
and@value.setter
decorators? – Barmar Commented Nov 22, 2024 at 19:39__setattr__
magic function. Otherwise, when you setvalue
on wrapper it just uses normal semantics to set the name on your wrapper and not your wrapped/test object. There will be some edge cases where the behaviour is not exactly reproduced. eg.Test.value = ...
vsWrapper.value = ...
(ie class attributes). – Dunes Commented Nov 22, 2024 at 19:51