Подтвердить что ты не робот

Дескрипторы как атрибуты экземпляра в 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) в самом дескрипторе, упрощая несколько вещей.

Чтобы реализовать атрибуты дескриптора экземпляра, вам необходимо:

Я знаю, что подобные вопросы были подняты раньше, но я не нашел реального объяснения:

  • Почему Python разработан таким образом?
  • Каков предложенный способ хранения информации, необходимой для дескриптора, но для каждого экземпляра?
4b9b3361

Ответ 1

Множество расширенных функций работает только тогда, когда они определены в классе, а не в экземпляре; все специальные методы, например. Кроме того, чтобы сделать оценку кода более эффективной, это позволяет четко разделить между экземплярами и типами, которые в противном случае будут иметь тенденцию к краху (из-за того, что все типы являются объектами).

Я не уверен, как это рекомендуется, но вы можете в экземпляре сохранить сопоставление из экземпляра дескриптора для значения атрибута:

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}

Это имеет очевидные преимущества по сравнению с двумя другими предложенными опциями:

  • классы-экземпляры нарушают ориентацию объектов и увеличивают использование памяти;
  • overriding __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]

Ответ 2

Этот точный вопрос был поднят в Python-списке в начале этого года. Я просто приведу ответ Джона Г. Келли:

Поведение по дизайну. Во-первых, сохранение поведения объекта в определение класса упрощает реализацию, а также делает экземпляр проверяет более значимые. Чтобы заимствовать ваш регистр, если "M" дескриптор определяется некоторыми экземплярами, а не классом, тогда зная, что объект "рег" является экземпляром Регистра, не говорит мне ничего о том, является ли "reg.M" допустимым атрибутом или ошибкой. В виде в результате мне нужно будет защищать практически каждый доступ "reg.M" с помощью try-except construct только в том случае, если регистр "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", что, безусловно, не является тем, что автор предполагаемого класса.

Если вы хотите поддерживать несколько экземпляров дескриптора, просто создайте конструктор дескриптора для аргумента имени (префикс) и префикс добавленных атрибутов с этим именем. Вы даже можете создать объект пространства имен (словарь) в экземпляре класса для хранения всех новых экземпляров свойств.