Дескрипторы в качестве атрибутов экземпляра в Python

на вопрос:

почему дескрипторы не могут быть атрибутами экземпляра?

это был ответил что:

объекты дескриптора должны жить в классе, а не в экземпляре

потому что это путь, что есть.

простой пример. Рассмотрим дескриптор:

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

рассмотрим случай, когда каждый объект имеет несколько Prop: I нужно будет использовать уникальные имена для идентификации значений и множителей (например,здесь. Наличие объекта дескриптора на экземпляр позволит сохранить _multiplier_value) в самом дескрипторе, упрощая несколько вещей.

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

  1. создать один экземпляр класса посмотреть здесь
  2. переопределить __getattribute__ посмотреть вот!--13-->

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

  1. почему Python разработан таким образом?
  2. каков предлагаемый способ хранения информации, необходимой дескриптору, но на каждый экземпляр?

3 ответов


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

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

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-list в начале этого года. Я просто процитирую ответ Яна г. Келли:

поведение по дизайну. Во-первых, сохранение поведения объекта в определение класса упрощает реализацию, а также делает экземпляр проверки более значимые. Позаимствовать пример регистра, если "M" дескриптор определяется некоторыми экземплярами, а не классом, затем зная, что объект "reg" является экземпляром Register не говорит мне что-нибудь о том "Редж".M " является допустимым атрибутом или ошибкой. Как в результате мне нужно будет охранять практически каждый доступ " reg.М" с попробуйте-кроме конструкции на случай, если" 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 не просто держите свойство объект как данные. Простой акт присвоения объекта собственности, дескриптор, к атрибуту экземпляра будет изменить поведение of в ObjectHolder. Вместо лечения " Холдер.obj " как простые данные атрибут, он начнет вызывать протокол дескриптора при доступе к " Холдеру.параметр obj" и в конечном итоге перенаправить их на несуществующие и бессмысленный "Холдер".ФОО" атрибут, который, конечно, не то, что автор класса предназначенный.

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


в 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'

отметим, что это не полная реализация дескрипторов и, конечно, если вы решите использовать его, это на свой страх и риск.