簡體   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