动态添加/覆盖属性属性的setter和getter



我需要使用/模仿语法糖语法动态修饰子类中的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的用法如下:

    一个属性对象有gettersetterdeleter方法可用作装饰符

  • .fset的用法如下:

    fset是用于设置属性值的函数…返回的属性对象也具有属性fgetfset,和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>
  • doc/gh>
  • 文档属性
  • 文档描述符
  • 重要

    • 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的回答,因为他们提供了有用的信息

    相关内容

    • 没有找到相关文章

    最新更新