Давайте предположим, что мы хотим создать семейство классов, которые являются различными реализациями или специализациями всеобъемлющей концепции. Давайте предположим, что существует правдоподобная реализация по умолчанию для некоторых производных свойств. Мы хотели бы поместить это в базовый класс
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
Таким образом, подкласс автоматически сможет посчитать свои элементы в этом довольно глупом примере.
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
Но что, если подкласс не хочет использовать это значение по умолчанию? Это не работает:
import math
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
Square_Integers_Below(7)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute
Я понимаю, что есть способы переопределить свойство свойством, но я бы хотел этого избежать. Поскольку цель базового класса состоит в том, чтобы максимально облегчить жизнь его пользователю, а не добавлять вздор, навязывая (с узкой точки зрения подкласса) сложный и избыточный метод доступа.
Это можно сделать? Если нет, каково следующее лучшее решение?
источник
len(self.elements)
в качестве реализации. Это очень конкретная реализация накладывает контракт на все экземпляры класса, а именно , что они обеспечивают ,elements
который является контейнером размера . Однако вашSquare_Integers_Below
класс, похоже, не ведет себя таким образом (возможно, он генерирует своих членов динамически), поэтому он должен определить свое собственное поведение.len(self.elements)
не является подходящей реализацией по умолчанию для математических наборов, поскольку существует «множество» наборов, которые даже не имеют конечной мощности. В общем, если подкласс не хочет использовать поведение своих базовых классов, ему необходимо переопределить его на уровне класса. Атрибуты экземпляра, как следует из названия, работают на уровне экземпляра и, следовательно, регулируются поведением класса.Ответы:
Свойство - это дескриптор данных, который имеет приоритет над атрибутом экземпляра с тем же именем. Вы можете определить дескриптор без данных с помощью уникального
__get__()
метода: атрибут экземпляра имеет приоритет над дескриптором без данных с тем же именем, см. Документацию . Проблема здесь заключается в том, чтоnon_data_property
приведенное ниже описание предназначено только для вычислительных целей (вы не можете определить установщик или средство удаления), но, похоже, это имеет место в вашем примере.Однако это предполагает, что у вас есть доступ к базовому классу для внесения этих изменений.
источник
__get__()
,__set__()
и__delete__()
), и они вызывают,AttributeError
если вы не предоставляете им какую-либо функцию. Смотрите Python эквивалент реализации свойства .setter
или__set__
собственности, это будет работать. Однако я хотел бы предупредить вас, что это также означает, что вы можете легко перезаписать свойство не только на уровне класса (self.size = ...
), но даже на уровне экземпляра (напримерConcrete_Math_Set(1, 2, 3).size = 10
, будет одинаково допустимым). Пища для размышлений :)Square_Integers_Below(9).size = 1
также действует как простой атрибут. В этом конкретном случае использования «size» это может показаться неудобным (вместо этого используйте свойство), но в общем случае «легко переопределить вычисляемый реквизит в подклассе», в некоторых случаях это может быть полезно. Вы также можете контролировать доступ к атрибутам с помощью,__setattr__()
но это может быть огромным.Это будет многословный ответ, который может послужить лишь дополнительным ... но ваш вопрос заставил меня прокатиться по кроличьей норе, поэтому я хотел бы также поделиться своими выводами (и болью).
В конечном итоге этот ответ может оказаться бесполезным для вашей реальной проблемы. На самом деле, мой вывод таков: я бы этого не делал вообще. Сказав это, фон к этому выводу может вас развлечь немного, так как вы ищете больше деталей.
Решение некоторых заблуждений
Первый ответ, хотя и правильный в большинстве случаев, не всегда так. Например, рассмотрим этот класс:
inst_prop
будучиproperty
безвозвратно атрибутом экземпляра:Все зависит от того, где ваш
property
определяется в первую очередь. Если ваш@property
определяется в пределах класса «видимость» (или действительноnamespace
), он становится атрибутом класса. В моем примере сам класс не знает о них,inst_prop
пока не будет создан экземпляр. Конечно, это не очень полезно, как свойство здесь вообще.Но сначала давайте обратимся к вашему комментарию о разрешении наследования ...
Так как именно фактор наследования входит в этот вопрос? Эта следующая статья немного углубляется в тему, и порядок разрешения методов несколько связан, хотя в нем обсуждается в основном широта наследования, а не глубина.
В сочетании с нашими выводами, приведенными ниже настройками:
Представьте, что происходит, когда выполняются эти строки:
Результат таков:
Обратите внимание, как:
self.world_view
удалось применить, покаself.culture
не удалосьculture
не существует вChild.__dict__
(mappingproxy
класса, не путать с экземпляром__dict__
)culture
существует в немc.__dict__
, на него нет ссылок.Возможно, вы сможете догадаться, почему -
world_view
был переписанParent
классом как не свойство, поэтомуChild
смог перезаписать его. В то же время, так какculture
наследуется, она существует только в пределахmappingproxy
отGrandparent
:На самом деле, если вы попытаетесь удалить
Parent.culture
:Вы заметите, что это даже не существует для
Parent
. Потому что объект напрямую ссылается наGrandparent.culture
.Итак, что насчет Порядка разрешения?
Поэтому мы заинтересованы в том, чтобы соблюсти фактический порядок разрешения, попробуем
Parent.world_view
вместо этого удалить :Интересно, каков результат?
Это вернулось к бабушке и дедушке
world_view
property
, хотя мы успешно сумели назначитьself.world_view
раньше! Но что, если мы решительно изменимworld_view
на уровне класса, как и другой ответ? Что если мы удалим это? Что если мы присвоим атрибуту текущего класса свойство?Результат:
Это интересно, потому что
c.world_view
восстанавливается его атрибут экземпляра, аChild.world_view
тот, который мы присвоили. После удаления атрибута экземпляра он возвращается к атрибуту класса. И после переназначенияChild.world_view
свойства мы немедленно теряем доступ к атрибуту экземпляра.Следовательно, мы можем предположить следующий порядок разрешения :
property
, извлеките его значение с помощьюgetter
илиfget
(подробнее об этом позже). Текущий класс первым до базового класса последним.property
атрибут не класса. Текущий класс первым до базового класса последним.В этом случае давайте удалим рут
property
:Который дает:
Ta-д!
Child
теперь имеет свои собственные,culture
основанные на принудительной вставке вc.__dict__
.Child.culture
конечно, не существует, так как он никогда не был определен в атрибутеParent
илиChild
классе иGrandparent
был удален.Это коренная причина моей проблемы?
На самом деле нет . Ошибка, которую вы получаете, которую мы все еще наблюдаем при назначении
self.culture
, совершенно другая . Но порядок наследования устанавливает фон для ответа - самогоproperty
себя.Помимо ранее упомянутого
getter
метода,property
также есть несколько аккуратных хитростей в рукавах. Наиболее актуальным в этом случае является методsetter
илиfset
, который запускаетсяself.culture = ...
строкой. Поскольку выproperty
не реализовали никакой функцииsetter
илиfget
функции, python не знает, что делать, иAttributeError
вместо этого выдает (т.е.can't set attribute
).Однако, если вы реализовали
setter
метод:При создании экземпляра
Child
класса вы получите:Вместо того, чтобы получить
AttributeError
, вы теперь фактически вызываетеsome_prop.setter
метод. Что дает вам больший контроль над вашим объектом ... с нашими предыдущими результатами мы знаем, что нам нужно перезаписать атрибут класса, прежде чем он достигнет свойства. Это может быть реализовано в базовом классе как триггер. Вот свежий пример:Что приводит к:
TA-Д! Теперь вы можете перезаписать свой собственный атрибут экземпляра поверх унаследованного свойства!
Итак, проблема решена?
... На самом деле, нет. Проблема этого подхода в том, что теперь у вас не может быть правильного
setter
метода. Есть случаи, когда вы хотите установить значения на вашемproperty
. Но теперь всякий раз, когда вы устанавливаетеself.culture = ...
его, он всегда будет перезаписывать любую функцию, которую вы определили вgetter
(которая в данном случае на самом деле является просто@property
обернутой частью. Вы можете добавить больше нюансов, но так или иначе, это всегда будет включать больше, чем простоself.culture = ...
. например:Это waaaaay более сложные , чем другой ответ,
size = None
на уровне класса.Вы также можете написать собственный дескриптор, чтобы обрабатывать
__get__
и__set__
или дополнительные методы. Но в конце дня, когда наself.culture
него ссылаются,__get__
всегда будет запускаться первым, а когда наself.culture = ...
него ссылаются,__set__
всегда будет запускаться первым. Там нет возможности обойти это, насколько я пытался.Суть вопроса, ИМО
Проблема, которую я вижу здесь, - ты не можешь есть свой пирог и есть его тоже.
property
подразумевается как дескриптор с удобным доступом из таких методов, какgetattr
илиsetattr
. Если вы также хотите, чтобы эти методы достигли другой цели, вы просто напрашиваетесь на неприятности. Я бы, возможно, переосмыслил подход:property
для этого?property
, есть ли какая-то причина, по которой мне нужно было бы перезаписать это?property
не применяются?property
s, будет ли отдельный метод мне лучше, чем просто переназначение, так как переназначение может случайно аннулироватьproperty
s?Для пункта 5, мой подход будет иметь
overwrite_prop()
метод в базовом классе, который перезаписывает атрибут текущего класса, так чтоproperty
больше не будет срабатывать:Как вы можете видеть, хотя он все еще немного надуман, он, по крайней мере, более явный, чем загадочный
size = None
. Тем не менее, в конечном счете, я бы вообще не перезаписывал свойство и пересмотрел бы мой дизайн с самого начала.Если вы зашли так далеко - спасибо, что прошли это путешествие со мной. Это было забавное маленькое упражнение.
источник
А
@property
определяется на уровне класса. Документация содержит подробные сведения о том, как она работает, но достаточно сказать, что установка или получение свойства приводят к вызову определенного метода. Однакоproperty
объект, который управляет этим процессом, определяется собственным определением класса. То есть он определен как переменная класса, но ведет себя как переменная экземпляра.Одним из следствий этого является то, что вы можете свободно переназначить его на уровне класса :
И, как и любое другое имя уровня класса (например, методы), вы можете переопределить его в подклассе, просто явно определив его по-другому:
Когда мы создаем фактический экземпляр, переменная экземпляра просто затеняет переменную класса с тем же именем. Обычно
property
объект использует некоторые махинации для управления этим процессом (т. Е. Применяя методы получения и установки), но когда имя уровня класса не определено как свойство, ничего особенного не происходит, и поэтому он действует так, как вы ожидаете от любой другой переменной.источник
__dict__
? Кроме того, есть ли способы автоматизировать это? Да, это всего лишь одна строчка, но это та вещь, которую очень загадочно читать, если вы не знакомы с кровавыми подробностями собственности и т. Д.# declare size to not be a @property
Вам не нужно назначение (для
size
) вообще.size
это свойство в базовом классе, поэтому вы можете переопределить это свойство в дочернем классе:Вы можете (микро) оптимизировать это, предварительно вычислив квадратный корень:
источник
size
это свойство, а не атрибут экземпляра, вам не нужно делать ничего особенного .Похоже, вы хотите определить
size
в своем классе:Другой вариант - сохранить
cap
в своем классе и рассчитать его,size
определив его как свойство (которое переопределяет свойство базового классаsize
).источник
Я предлагаю добавить сеттер так:
Таким образом, вы можете переопределить
.size
свойство по умолчанию следующим образом:источник
Также вы можете сделать следующее
источник
_size
или_size_call
там. Вы могли бы запечь в вызове функции вsize
качестве условия и использоватьtry... except...
для проверки_size
вместо того, чтобы брать дополнительную ссылку на класс, которая не будет использоваться. Несмотря на это, я чувствую, что это даже более загадочно, чем простаяsize = None
перезапись в первую очередь.Я думаю, что можно установить свойство базового класса для другого свойства из производного класса внутри производного класса, а затем использовать свойство базового класса с новым значением.
В исходном коде существует своего рода конфликт между именем
size
из базового класса и атрибутомself.size
из производного класса. Это может быть видно, если мы заменим имяself.size
из производного класса наself.length
. Это выведет:Тогда, если мы заменим имя метода
size
сlength
во всех вхождений по всей программе, это приведет к тем же исключением:Фиксированный код или, в любом случае, версия, которая каким-то образом работает, должна сохранять код точно таким же, за исключением класса
Square_Integers_Below
, который установит методsize
из базового класса в другое значение.И затем, когда мы запустим всю программу, вывод будет:
Я надеюсь, что это было так или иначе полезно.
источник