繁体   English   中英

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

[英]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

编辑:

  • 额外答案:来自文档:零参数形式仅适用于类定义[...]
  • 使用 python3.9

未正确设置属性设置器。 为了形象化这一点,如果没有为属性显式设置 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"

解决方案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)))
        ...
...
  • cxsetter的用法如文档所示

    属性对象具有可用作装饰器的gettersetterdeleter方法

  • .fset的用法如文档所示

    fset是用于设置属性值的函数...返回的属性对象还具有与构造函数参数对应的属性fgetfsetfdel

所以添加以下几行将是成功的:

dec_a.x = 'new value!'
print(dec_a.x)

输出:

setter
getter
new value!

进一步参考:

参考

重要的

  • 使用python 3.9 (否则可能会出现super或其他问题)
  • 从文档中回忆零参数形式 [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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM