我需要使用/模仿语法糖语法动态修饰子类中的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
,dec_a.x = 'p'
中实现setter方法,使用以下方法,我收集以下错误:
# 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装饰器?(可能不需要额外导入)还有别的办法吗?
Extra:为什么没有属性的super不能工作?
super().x(v)
→TypeError: super(type, obj): obj must be an instance or subtype of type
编辑:
- 额外的回答:来自doc:0参数形式只在类定义中工作[…]
- 使用python3.9
属性设置不正确。为了更直观地理解这一点,如果没有显式地为属性设置setter,则该属性变为只读。
class Parrot: def __init__(self): self._voltage = 100000 @property def voltage(self): """Get the current voltage.""" return self._voltage
@property修饰器将voltage()方法转换为只读属性的"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"
解决方案1
不要显式定义getter。这样,对x
的所有访问将被委托给实际的类A
。
解决方案2
如果你定义了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)))
...
...
c.x.setter
的用法如下:一个属性对象有
getter
,setter
和deleter
方法可用作装饰符.fset
的用法如下:fset
是用于设置属性值的函数…返回的属性对象也具有属性fget
,fset
,和fdel
对应构造函数参数。
所以添加以下行将是成功的:
dec_a.x = 'new value!'
print(dec_a.x)
输出:
setter
getter
new value!
进一步引用:
- https://newbedev.com/how-to-call-a-property-of-the-base-class-if-this-property-is-being-overwritten-in-the-derived-class
- https://gist.github.com/Susensio/979259559e2bebcd0273f1a95d7c1e79
参考资料
超级<<ul>重要
- 与
python 3.9
一起工作(否则可能与super
或其他问题) - 从文档中回收0参数形式[super()]只在类定义中起作用[…]
短暂案例2.3代表问题的解决方案,其他案例研究显示可能的替代方案和(也许)如何达到它的有用信息
step0:父类(带有描述符协议的引用类)
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, two ways
方式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动态装饰器——(苦的)语法糖语法(<- solution)
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的回答,因为他们提供了有用的信息