簡體   English   中英

描述符作為python中的實例屬性

[英]Descriptors as instance attributes in python

對於這個問題:

為什么描述符不能是實例屬性?

得到的答復是:

描述符對象需要存在於類中,而不是存在於實例中

因為這是__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 有多個 Prop 的情況:我需要使用唯一的名稱來標識值和乘數(就像這里。擁有每個實例描述符對象將允許在描述符本身中存儲_multiplier (和_value ),簡化一些事情。

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

  1. 創建每個實例類請參見此處
  2. 覆蓋__getattribute__ 見這里

我知道之前也有人提出過類似的問題,但我還沒有找到真正的解釋:

  1. 為什么 Python 是這樣設計的?
  2. 存儲描述符需要但每個實例的信息的建議方法是什么?

今年早些時候在 Python-list 上提出了這個確切的問題。 我只想引用Ian G. Kelly 的回應

該行為是設計使然。 首先,在類定義中保留對象行為可以簡化實現並使實例檢查更有意義。 借用您的 Register 示例,如果“M”描述符是由某些實例而不是類定義的,那么知道對象“reg”是 Register 的實例並不能告訴我有關“reg.M”是否為有效屬性或錯誤。 因此,我需要使用 try-except 構造來保護幾乎所有對“reg.M”的訪問,以防“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”屬性,這當然是不是這門課的作者想要的。

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

許多高級功能僅在定義在類而不是實例上時才有效; 例如,所有特殊方法。 除了使代碼評估更有效之外,這還明確了實例和類型之間的分離,否則它們將趨於崩潰(因為當然所有類型都是對象)。

我不確定這是多么推薦,但是您可以在實例中存儲從描述符實例到屬性值的映射:

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]

在 Python 3.6 中,這可以很容易地完成。 也許它不像預期的那樣,但是嘿,如果它有效,對嗎? ;)

Python 3.6 添加了__set_name__方法

object.__set_name__(self, owner, name)

在創建擁有類所有者時調用。 描述符已分配給名稱。

3.6 版中的新功能。

使用此名稱將內部值存儲在實例的 dict 中似乎工作正常。

>>> class Prop:
...     def __set_name__(self, owner, name):
...         self.name = name
...     def __get__(self, instance, owner):
...         print('get')
...         return instance.__dict__.setdefault(self.name, None)
...     def __set__(self, instance, value):
...         print('set')
...         instance.__dict__[self.name] = value
... 
>>> class A:
...     prop = Prop()
... 
>>> a = A()
>>> a.prop = 'spam'
set
>>> a.prop
get
'spam'

請注意,這不是完整的描述符實現,當然,如果您決定使用它,則風險自負。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM