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