2012-09-26 64 views
19

屬性的問題:描述符的情況下在Python

爲什麼不能描述符是實例的屬性?

它一直answered說:

描述對象需要生活中的類,而不是實例

,因爲這是該__getattribute__的實現方式。

一個簡單的例子。考慮一個描述:

class Prop(object): 

    def __get__(self, obj, objtype=None): 
     if obj is None: 
      return self 
     return obj._value * obj._multiplier 

    def __set__(self, obj, value): 
     if obj is None: 
      return self 
     obj._value = value 

class Obj(object): 

    val = Prop() 

    def __init__(self): 
     self._value = 1 
     self._multiplier = 0 

考慮每個OBJ具有多個支柱的情況:我需要使用唯一的名稱標識值和乘數(像here有每個實例描述符對象將允許存儲。 。_multiplier(和_value)在描述符本身,簡化了一些東西

每個實例描述實現屬性,你需要:

  1. 創建一個每個實例類See here
  2. 覆蓋__getattribute__See here

我知道,類似的問題之前已經提出了,但我還沒有找到一個真正的解釋:

  1. 爲什麼Python是這樣設計?
  2. 什麼是建議的方式來存儲描述符需要的信息,但是每個實例?

回答

9

大量的高級功能僅適用於在類而不是實例上定義的功能;例如,所有特殊的方法。除了使代碼評估效率更高之外,這還可以明確實例和類型之間的分離,否則這些實例和類型會傾向於崩潰(因爲當然所有類型都是對象)。

我不知道如何推薦這個,但你可以在實例存儲從描述實例映射屬性值:

class Prop(object): 
    def __get__(self, obj, objtype=None): 
     if obj is None: 
      return self 
     return obj._value * obj._multiplier[self] 

    def __set__(self, obj, value): 
     if obj is None: 
      return self 
     obj._value = value 

class Obj(object): 
    val = Prop() 

    def __init__(self): 
     self._value = 1 
     self._multiplier = {Obj.val: 0} 

這相對其他兩個建議方案明顯的優點:

  1. 每個實例類打破對象方向並增加內存使用量;
  2. 覆蓋__getattribute__效率低下(因爲所有的屬性訪問必須經過重寫的特殊方法)並且是脆弱的。

作爲替代方案,你可以使用代理屬性:

class PerInstancePropertyProxy(object): 
    def __init__(self, prop): 
     self.prop = prop 
    def __get__(self, instance, owner): 
     if instance is None: 
      return self 
     return instance.__dict__[self.prop].__get__(instance, owner) 
    def __set__(self, instance, value): 
     instance.__dict__[self.prop].__set__(instance, value) 
class Prop(object): 
    def __init__(self, value, multiplier): 
     self.value = value 
     self.multiplier = multiplier 
    def __get__(self, instance, owner): 
     if instance is None: 
      return self 
     return self.value * self.multiplier 
    def __set__(self, instance, value): 
     self.value = value 
class Obj(object): 
    val = PerInstancePropertyProxy('val') 
    def __init__(self): 
     self.__dict__['val'] = Prop(1.0, 10.0) 
    def prop(self, attr_name): 
     return self.__dict__[attr_name] 
+0

您可以通過** break object orientation **擴展您的意思嗎?其次,我用這種方法看到的問題是如何提供一個簡單的API來改變乘數。用戶將不得不像'obj._multiplier [Obj.val] = 10'這樣做。這可以包含在一個函數'def change_multiplier(self,attr_name,new_value)'中,但如果Prop屬性有多個屬性,則不會很好地縮放。像def prop(self,attr_name):返回自我。__dict __ [attr_name]'可以用來做'obj.prop('val')。multiplier = 10'。 – Hernan

+0

@ Hernan有一個通常的假設,即實例具有相同的類型;違反了這一點,各種事情將會破裂。就改變乘數而言,也許是代理財產? - 見上面的編輯。 – ecatmur

+0

確實所有實例的類型都不相同,但是您可能會創建子類,因此isinstance仍然可以工作。關於代理人,我寫了類似的東西,但我不確定這是一個好主意。基本上,'obj.prop('val')'返回一個知道'obj'和'val'的代理對象。當你執行'obj.prop('val')。multiplier = 10'時,它會寫入'obj._multiplier [val] = 10'。我只是不確定它會如何維護。 – Hernan

15

這個確切的問題是今年早些時候的raised on Python-list。我只想報價Ian G. Kelly's response

該行爲是由設計。首先,保持類定義中的對象行爲簡化了實現,並使實例 檢查更有意義。借用你的註冊示例,如果「M」描述符是由某些實例定義的,而不是由類定義的,那麼知道對象「reg」是註冊實例的 並不告訴 我有關「reg.M 「是一個有效的屬性或錯誤。由於 的結果,我需要防止「reg.M」的每個訪問都是 try-except結構,以防「reg」是錯誤的寄存器類型。

二,類與實例的分離還有助於保持對象行爲與對象數據分離。考慮以下 類:

class ObjectHolder(object): 
    def __init__(self, obj): 
     self.obj = obj 

不要擔心這是什麼類可能是很有用的。只知道 它的意思是保持和提供不受限制地任意Python 對象:

>>> holder = ObjectHolder(42) 
>>> print(holder.obj) 42 
>>> holder.obj = range(5) 
>>> print(holder.obj) [0, 1, 2, 3, 4] 

由於類是爲了保存任意對象,它甚至有效 有人可能要存儲描述符對象有:

>>> holder.obj = property(lambda x: x.foo) 
>>> print(holder.obj) <property object at 0x02415AE0> 

現在假設的Python調用存儲在例如用於 描述符描述符協議屬性:

>>> holder = ObjectHolder(None) 
>>> holder.obj = property(lambda x: x.foo) 
>>> print(holder.obj) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'ObjectHolder' object has no attribute 'foo' 

在這種情況下,ObjectHolder將無法簡單地將屬性 對象保存爲數據。僅將屬性對象 描述符分配給實例屬性的行爲將更改ObjectHolder的行爲 。與其將「holder.obj」視爲簡單的數據屬性,它將開始在訪問 時調用描述符協議以「holder.obj」並最終將其重定向到不存在的和 無意義的「holder.foo」屬性,這當然不是該課程的作者所期望的。

如果您希望能夠支持描述符的多個實例,只需使該描述符的構造函數具有一個名稱參數(前綴),並將添加的屬性作爲該名稱的前綴。您甚至可以在類實例內創建一個名稱空間對象(字典)來容納所有新的屬性實例。

+0

到Python列表的鏈接都死了。新鏈接是https://mail.python.org/pipermail/python-list/2012-January/631340.html? –

+0

是的。我將編輯它。 – nneonneo

相關問題