[英]Dynamically add/overwrite the setter and getter of property attributes
我需要使用/模仿语法糖语法动态装饰子类中的 getter 和 setter 对方法。
我正在为 setter 实现而苦苦挣扎。
class A:
def __init__(self, x):
print('init')
self.__x = x
@property
def x(self):
print('getter')
return self.__x
@x.setter
def x(self, v):
print('setter')
self.__x = v
class Dec:
def __init__(self):
print('init - dec')
def __call__(self, cls):
c = type('A_Dec', (cls,), {})
# super-init
setattr(c, '__init__', lambda sub_self, x: super(type(sub_self), sub_self).__init__(x))
# getter
setattr(c, 'x', property(lambda sub_self: super(type(sub_self), sub_self).x))
# setter - see below
return c
dec_A = Dec()(A)
dec_a = dec_A('p')
print(dec_a.x)
输出
init - dec
init
getter
p
如果我尝试在Dec
实现 setter 方法, dec_a.x = 'p'
,使用以下方法我会收集以下错误:
# setter-statements of __call__
# Attempt 1
setattr(c, 'x', property(fset=lambda sub_self, v: super(type(sub_self), sub_self).x(v)))
# AttributeError: unreadable attribute
# Attempt 2 - auxiliary function
def m(sub_self, v):
print('--> ', sf, super(type(sub_self), sub_self))
super(type(sub_self), sub_self).x = v
# Attempt 2.A
setattr(c, 'x', eval('x.setter(m)'))
# NameError: name 'x' is not defined
# Attempt 2.B
setattr(c, 'x', property(fset=lambda sf, v: m(sf, v)))
# AttributeError: unreadable attribute
# Attempt 2.C: !! both at once, `fget`and `fset` so, in case, comment the getter in the above code to avoid conflicts
setattr(c, 'x', property(fget=lambda sub_self: super(type(sub_self), sub_self).x, fset=m))
# AttributeError: 'super' object has no attribute 'x'
# Attempt 2.D
p = property(fget=lambda sub_self: super(type(sub_self), sub_self).x, fset=m)
setattr(c, 'x', p)
# AttributeError: 'super' object has no attribute 'x'
尝试 1引发错误,因为(我猜)用括号设置属性。 所以在尝试 2 中,我使用了一个辅助函数,因为lambda
不允许初始化,'=' 语句,同样没有成功。
有没有办法动态模仿属性 getter/setter 装饰器? (可能没有额外的进口)还有另一种方法吗?
额外:为什么 super 在没有属性的情况下不起作用? super().x(v)
-> TypeError: super(type, obj): obj must be an instance or subtype of type
编辑:
未正确设置属性设置器。 为了形象化这一点,如果没有为属性显式设置 setter,则该属性将变为只读,如文档所示。
class Parrot: def __init__(self): self._voltage = 100000 @property def voltage(self): """Get the current voltage.""" return self._voltage
@property 装饰器将电压()方法转换为具有相同名称的只读属性的“getter”
假设我们有这个:
class A:
def __init__(self, x):
self.__x = x
@property
def x(self):
return self.__x
a = A(123)
print(a.x) # will display "123"
a.x = 456 # will display "AttributeError: can't set attribute"
在您的原始代码中,您创建了一个新类型A_Dec
。 您明确设置了 getter:
# getter
setattr(c, 'x', property(lambda sub_self: super(type(sub_self), sub_self).x))
但是您没有明确设置任何 setter,从而使x
属性为只读。 这导致此代码中的错误:
dec_a.x = 'new value!' # will display "AttributeError: can't set attribute"
不要明确定义 getter。 这样,对x
所有访问都将委托给实际的类A
。
如果您定义了 getter,那么还要定义 setter。
...
class Dec:
...
def __call__(self, cls):
...
# setter
x_property = getattr(c, 'x')
x_setter = getattr(x_property, 'setter')
setattr(c, 'x', x_setter(lambda sub_self, v: super(type(sub_self), type(sub_self)).x.fset(sub_self, v)))
...
...
cxsetter
的用法如文档所示:
属性对象具有可用作装饰器的
getter
、setter
和deleter
方法
.fset
的用法如文档所示:
fset
是用于设置属性值的函数...返回的属性对象还具有与构造函数参数对应的属性fget
、fset
和fdel
。
所以添加以下几行将是成功的:
dec_a.x = 'new value!'
print(dec_a.x)
输出:
setter
getter
new value!
进一步参考:
参考
重要的
python 3.9
(否则可能会出现super
或其他问题)简而言之,案例2.3代表了问题的解决方案,其他案例研究显示了可能的替代方案和(也许)关于如何达到它的有用信息
Step 0:父类(引用类配备描述符协议)
class A:
def __init__(self, x): self.__x = x
@property
def x(self): return self.__x
@x.setter
def x(self, v): self.__x = v
第 1 步:审查 - 静态覆盖 getter/setter ,两种方式
方式 1.1 - 语法糖
class B(A):
def __init__(self, x): super().__init__(x)
@property
def x(self): return super().x # still not sure if there isn't a better way
@x.setter
def x(self, v): super(B, B).x.fset(self, v)
b = B('x')
print(b.x)
b.x = 'xx'
print(b.x)
# Output
x
xx
方式 1.2 - 属性作为类属性
class C(A):
def __init__(self, x): super().__init__(x)
def x_read(self): return super().x
def x_write(self, v): super(C, C).x.fset(self, v) # notice the arguments of super!!!
x = property(x_read, x_write)
c = C('x')
print(c.x)
c.x = 'xx'
print(c.x)
# Output
x
xx
第 2 步:动态覆盖 getter/setter - 两种方式
方式 2.1静态装饰器(这是解决方案在静态设置中的样子)
class DecStatic:
def __init__(self):
print('init - static dec')
def __call__(self, cls):
class A_Dec(cls):
def __init__(sub_self, x):
super().__init__(x)
@property
def x(sub_self):
return super().x
@x.setter
def x(sub_self, v):
super(type(sub_self), type(sub_self)).x.fset(sub_self, v)
return A_Dec
dec_A = DecStatic()(A)
dec_a = dec_A('x')
print(dec_a.x)
dec_a.x = 'xx'
print(dec_a.x)
# Output
init - static dec
x
xx
方式 2.2动态装饰器 - 将属性覆盖为类属性
class DecDynamicClsAttr:
def __init__(self):
print('init - dynamic dec - class attr')
def __call__(self, cls):
DecA = type('DecA', (cls,), {})
# super-init
setattr(DecA, '__init__', lambda sub_self, x: super(type(sub_self), sub_self).__init__(x))
# property
setattr(DecA, 'x', property(fget=lambda sub_self: super(type(sub_self), sub_self).x, fset=lambda sub_self, v: super(type(sub_self), type(sub_self)).x.fset(sub_self, v)))
return DecA
dec_A = DecDynamicClsAttr()(A)
dec_a = dec_A('x')
print(dec_a.x)
dec_a.x = 'xx'
print(dec_a.x)
# Output
init - dynamic dec - class attr
x
xx
方式 2.3动态装饰器——(苦涩的)语法糖语法(<-解决方案)
class DecDynamicSS:
def __init__(self):
print('init - dynamic dec - syntactic sugar')
def __call__(self, cls):
DecA = type('A_Dec', (cls,), {})
# super-init
setattr(DecA, '__init__', lambda sub_self, x: super(type(sub_self), sub_self).__init__(x))
# getter
setattr(DecA, 'x', property(lambda sub_self: super(type(sub_self), type(sub_self)).x.fget(sub_self)))
# setter
setattr(DecA, 'x', DecA.x.setter(lambda sub_self, v: super(type(sub_self), type(sub_self)).x.fset(sub_self, v)))
return DecA
dec_A = DecDynamicSS()(A)
dec_a = dec_A('x')
print(dec_a.x)
dec_a.x = 'xx'
print(dec_a.x)
# Output
init - dynamic dec - syntactic sugar
x
xx
评论
从 2.2 很明显,以 getter/setter 描述符为特征的属性被绑定到类而不是实例,所以在 2.3 中,我们也应该跟踪类:
`x.setter` --> `cls.x.setter`
我感谢@chepner的评论和@Niel Godfrey Ponciano 的回答,感谢他们提供的有用信息
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.