Дескрипторы в качестве атрибутов экземпляра в 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
) в самом дескрипторе, упрощая несколько вещей.
для реализации атрибутов дескриптора экземпляра необходимо либо:
- создать один экземпляр класса посмотреть здесь
- переопределить
__getattribute__
посмотреть вот!--13-->
Я знаю, что подобные вопросы поднимались и раньше, но я не нашел реального объяснения:
- почему Python разработан таким образом?
- каков предлагаемый способ хранения информации, необходимой дескриптору, но на каждый экземпляр?
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}
Это имеет очевидные преимущества перед двумя другими предлагаемыми вариантами:
- классы для каждого экземпляра нарушают объектную ориентацию и увеличивают использование памяти;
- переопределение
__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'
отметим, что это не полная реализация дескрипторов и, конечно, если вы решите использовать его, это на свой страх и риск.