Предпочитают свойства . Это то, что они там.
Причина в том, что все атрибуты являются общедоступными в Python. Начиная имена с подчеркивания или два, это всего лишь предупреждение о том, что данный атрибут является деталью реализации, которая может не остаться прежней в будущих версиях кода. Это не мешает вам фактически получить или установить этот атрибут. Следовательно, стандартный доступ к атрибутам - это нормальный, Pythonic способ доступа к атрибутам.
Преимущество свойств заключается в том, что они синтаксически идентичны доступу к атрибутам, поэтому вы можете переходить от одного к другому без каких-либо изменений в коде клиента. Вы могли бы даже иметь одну версию класса, которая использует свойства (скажем, для кода по контракту или отладку), и ту, которая не предназначена для производства, без изменения кода, который ее использует. В то же время вам не нужно писать геттеры и сеттеры для всего на тот случай, если вам, возможно, понадобится лучше контролировать доступ позже.
В Python вы не используете геттеры, сеттеры или свойства просто для удовольствия. Сначала вы просто используете атрибуты, а затем, только при необходимости, в конечном итоге переходите к свойству без необходимости изменять код с помощью ваших классов.
Действительно, существует много кода с расширением .py, который использует методы получения и установки, а также наследование и бессмысленные классы везде, где, например, подойдет простой кортеж, но это код от людей, пишущих на C ++ или Java с использованием Python.
Это не код Python.
источник
Используя свойства, вы можете начать с обычного доступа к атрибутам, а затем, при необходимости, сделать для них резервные копии с использованием методов получения и установки .
источник
Краткий ответ: свойства выигрывают руки вниз . Всегда.
Иногда нужны геттеры и сеттеры, но даже тогда я бы «спрятал» их для внешнего мира. Есть много способов сделать это в Python (
getattr
,setattr
,__getattribute__
и т.д ..., но очень краткий и чистые одно:Вот краткая статья, которая представляет тему методов получения и установки в Python.
источник
property
. (Это выглядит как простое присвоение, но вызывает функцию.) Следовательно, «Pythonic» по сути является бессмысленным термином, за исключением тавтологического определения: «Pythonic условные обозначения - это вещи, которые мы определили как Pythonic».property
особенность угрожает сделать идею аксиом Питона почти бесполезной. Итак, все, что нам осталось, это контрольный список.self
объекта , явные установщики могут быть полезны. Например,user.email = "..."
не похоже, что это может вызвать исключение, потому что это похоже на установку атрибута, тогдаuser.set_email("...")
как ясно, что могут быть побочные эффекты, такие как исключения.[ TL; DR? Вы можете перейти к концу для примера кода .]
Я на самом деле предпочитаю использовать другую идиому, которая немного сложна для использования как единичная, но это хорошо, если у вас есть более сложный вариант использования.
Сначала немного предыстории.
Свойства полезны тем, что они позволяют нам обрабатывать как установку, так и получение значений программным способом, но при этом позволяют обращаться к атрибутам как к атрибутам. Мы можем превратить «получает» в «вычисления» (по сути), и мы можем превратить «множества» в «события». Допустим, у нас есть следующий класс, который я кодировал с помощью Java-подобных методов получения и установки.
Вы можете быть удивлены, почему я не позвонил
defaultX
иdefaultY
в__init__
методе объекта . Причина в том, что в нашем случае я хочу предположить, чтоsomeDefaultComputation
методы возвращают значения, которые меняются во времени, например, отметку времени, и всякий раз, когдаx
(илиy
) не установлено (где для целей данного примера «не установлено» означает «установлено») в None ") Я хочу, чтобы значениеx
'(илиy
) вычислений по умолчанию.Так что это хромает по ряду причин, описанных выше. Я перепишу его, используя свойства:
Что мы получили? Мы получили возможность ссылаться на эти атрибуты как на атрибуты, хотя за кулисами мы заканчиваем тем, что запускаем методы.
Конечно, реальная сила свойств заключается в том, что мы обычно хотим, чтобы эти методы делали что-то помимо того, что просто получали и устанавливали значения (иначе нет смысла использовать свойства). Я сделал это в моем примере получения. Мы в основном запускаем тело функции, чтобы выбрать значение по умолчанию, когда значение не установлено. Это очень распространенная модель.
Но что мы теряем и что мы не можем сделать?
Основное раздражение, на мой взгляд, заключается в том, что если вы определяете геттер (как мы делаем здесь), вы также должны определить сеттер. [1] Это дополнительный шум, который загромождает код.
Еще одна неприятность в том , что мы все еще должны инициализировать
x
иy
значения__init__
. (Ну, конечно, мы могли бы добавить их, используя,setattr()
но это более дополнительный код.)В-третьих, в отличие от Java-подобного примера, получатели не могут принимать другие параметры. Теперь я уже слышу, как вы говорите, ну, если он принимает параметры, это не добытчик! В официальном смысле это правда. Но в практическом смысле нет причин, по которым мы не можем параметризовать именованный атрибут, например,
x
и установить его значение для некоторых конкретных параметров.Было бы хорошо, если бы мы могли сделать что-то вроде:
например. Самое близкое, что мы можем получить, это переопределить присвоение, чтобы подразумевать некоторую особую семантику:
и, конечно, убедитесь, что наш установщик знает, как извлечь первые три значения в качестве ключа к словарю и установить его значение в число или что-то еще.
Но даже если бы мы сделали это, мы все равно не смогли бы поддерживать его с помощью свойств, потому что нет способа получить значение, потому что мы вообще не можем передавать параметры получателю. Поэтому нам пришлось все вернуть, введя асимметрию.
Метод получения / установки в стиле Java позволяет нам справиться с этим, но мы вернулись к необходимости получения / установки.
На мой взгляд, то, что мы действительно хотим, это то, что соответствует следующим требованиям:
Пользователи определяют только один метод для данного атрибута и могут указать там, является ли атрибут только для чтения или для чтения-записи. Свойства не проходят этот тест, если атрибут доступен для записи.
Пользователю не нужно определять дополнительную переменную, лежащую в основе функции, поэтому нам не нужен
__init__
ниsetattr
код, ни код. Переменная существует только благодаря тому факту, что мы создали этот атрибут нового стиля.Любой код по умолчанию для атрибута выполняется в самом теле метода.
Мы можем установить атрибут как атрибут и ссылаться на него как на атрибут.
Мы можем параметризовать атрибут.
С точки зрения кода, мы хотим написать:
и сможет тогда сделать:
и так далее.
Нам также нужен способ сделать это для особого случая параметризуемого атрибута, но все же разрешить работу с назначением по умолчанию. Вы увидите, как я справился с этим ниже.
Теперь к делу (ууу! Суть!). Решение, к которому я пришел, заключается в следующем.
Мы создаем новый объект, чтобы заменить понятие свойства. Объект предназначен для хранения значения переменной, установленной для него, но также поддерживает дескриптор кода, который знает, как рассчитать значение по умолчанию. Его задача - сохранить набор
value
или запустить,method
если это значение не установлено.Давайте назовем это
UberProperty
.Я предполагаю, что
method
здесь есть метод класса,value
это значениеUberProperty
, и я добавил,isSet
потому что онNone
может быть реальным значением, и это позволяет нам четко заявить, что на самом деле «нет значения». Другой способ - это какой-то часовой.Это в основном дает нам объект, который может делать то, что мы хотим, но как мы на самом деле помещаем его в наш класс? Ну, свойства используют декораторы; почему мы не можем? Давайте посмотрим, как это может выглядеть (с этого момента я собираюсь использовать только один «атрибут»,
x
).Это на самом деле пока не работает, конечно. Мы должны реализовать
uberProperty
и убедиться, что он обрабатывает как получает, так и устанавливает.Давайте начнем с получения.
Моей первой попыткой было просто создать новый объект UberProperty и вернуть его:
Конечно, я быстро обнаружил, что это не работает: Python никогда не привязывает вызываемое к объекту, и мне нужен объект для вызова функции. Даже создание декоратора в классе не работает, поскольку, хотя теперь у нас есть класс, у нас все еще нет объекта для работы.
Так что нам нужно быть в состоянии сделать больше здесь. Мы знаем, что метод должен быть представлен только один раз, поэтому давайте продолжим и оставим наш декоратор, но изменим,
UberProperty
чтобы хранить толькоmethod
ссылку:Это также не вызывается, поэтому на данный момент ничего не работает.
Как мы закончим картину? Хорошо, что мы получим, когда создадим пример класса, используя наш новый декоратор:
в обоих случаях мы возвращаем то,
UberProperty
что, конечно, не вызывается, так что это не очень полезно.Нам нужен какой-то способ динамической привязки
UberProperty
экземпляра, созданного декоратором после того, как класс был создан, к объекту класса до того, как этот объект был возвращен этому пользователю для использования. Хм, да, это__init__
звонок, чувак.Давайте напишем, что мы хотим, чтобы наш результат поиска был первым. Мы привязываем
UberProperty
экземпляр к экземпляру, поэтому очевидной вещью, которую нужно вернуть, будет BoundUberProperty. Это где мы на самом деле будем поддерживать состояние дляx
атрибута.Теперь мы представительство; как передать это объекту? Есть несколько подходов, но самый простой для объяснения, просто использует
__init__
метод для этого отображения. К моменту__init__
вызова наши декораторы уже запущены, так что просто нужно просмотреть объект__dict__
и обновить любые атрибуты, для которых значение атрибута имеет типUberProperty
.Теперь uber-свойства хороши, и мы, вероятно, захотим использовать их много, поэтому имеет смысл просто создать базовый класс, который делает это для всех подклассов. Я думаю, вы знаете, как будет называться базовый класс.
Мы добавили это, изменили наш пример для наследования
UberObject
и ...После изменения
x
быть:Мы можем запустить простой тест:
И мы получаем результат, который мы хотели:
(Ну и дела, я работаю поздно.)
Обратите внимание , что я использовал
getValue
,setValue
иclearValue
здесь. Это потому, что я еще не связал средства для их автоматического возврата.Но я думаю, что сейчас это хорошее место, чтобы остановиться, потому что я устаю. Вы также можете видеть, что основная функциональность, которую мы хотели, уже на месте; остальное оформление витрин. Важна юзабилити оформления витрин, но это может подождать, пока у меня не появятся изменения для обновления поста.
Я закончу пример в следующей публикации, обращаясь к этим вещам:
Нам нужно убедиться, что UberObject
__init__
всегда вызывается подклассами.Нам нужно убедиться, что мы обрабатываем общий случай, когда кто-то «псевдоним» функции к чему-то другому, например:
Нам нужно
e.x
вернутьe.x.getValue()
по умолчанию.e.x.getValue()
. (Это очевидно, если вы еще не исправили это.)Нам нужно поддержать настройку
e.x directly
, как вe.x = <newvalue>
. Мы можем сделать это и в родительском классе, но нам нужно обновить наш__init__
код, чтобы справиться с этим.Наконец, мы добавим параметризованные атрибуты. Должно быть совершенно очевидно, как мы это сделаем.
Вот код, как он существует до сих пор:
[1] Я могу отстать от того, так ли это до сих пор.
источник
return self.x or self.defaultX()
это опасный код. Что происходит когдаself.x == 0
?__getitem__
метод которого вы переопределили . Это было бы странно, потому что тогда у вас был бы совершенно нестандартный питон.Я думаю, что у обоих есть свое место. Одна проблема с использованием
@property
заключается в том, что трудно расширить поведение методов получения или установки в подклассах, используя стандартные механизмы классов. Проблема в том, что фактические функции получения / установки скрыты в свойстве.Вы можете на самом деле получить функции, например, с
вы можете получить доступ к функциям получения и установки как
C.p.fget
иC.p.fset
, но вы не можете легко использовать обычные средства наследования методов (например, супер) для их расширения. Немного покопавшись в тонкостях super, вы действительно можете использовать super таким образом:Использование super (), однако, довольно неуклюже, так как свойство должно быть переопределено, и вы должны использовать слегка нелогичный механизм super (cls, cls), чтобы получить несвязанную копию p.
источник
Использование свойств для меня более интуитивно понятно и лучше вписывается в большинство кода.
Сравнение
против
для меня вполне очевидно, что легче читать. Также свойства позволяют для частных переменных гораздо проще.
источник
Я предпочел бы использовать ни в большинстве случаев. Проблема со свойствами заключается в том, что они делают класс менее прозрачным. Особенно это проблема, если вы должны поднять исключение из сеттера. Например, если у вас есть свойство Account.email:
тогда пользователь класса не ожидает, что присвоение значения свойству может вызвать исключение:
В результате исключение может остаться необработанным и либо распространяться слишком высоко в цепочке вызовов, чтобы быть обработанным должным образом, либо приводить к очень бесполезной трассировке, представляемой пользователю программы (что, к сожалению, слишком распространено в мире python и java ).
Я бы также избегал использования геттеров и сеттеров:
Вместо свойств и методов получения / установки я предпочитаю выполнять сложную логику в четко определенных местах, таких как метод проверки:
или подобный метод Account.save.
Обратите внимание, что я не пытаюсь сказать, что не бывает случаев, когда свойства полезны, только то, что вам может быть лучше, если вы можете сделать свои классы достаточно простыми и прозрачными, чтобы они вам не нужны.
источник
validate()
метод в классе. Свойство просто используется, когда у вас есть сложная логика за простымobj.x = y
присваиванием, и это зависит от того, что такое логика.Я чувствую, что свойства позволяют вам получать накладные расходы на написание геттеров и сеттеров только тогда, когда они вам действительно нужны.
Культура Java-программирования настоятельно рекомендует никогда не предоставлять доступ к свойствам, а вместо этого проходить через геттеры и сеттеры, и только те, которые действительно необходимы. Немного многословно всегда писать эти очевидные фрагменты кода и заметить, что в 70% случаев они никогда не заменяются какой-то нетривиальной логикой.
В Python люди действительно заботятся о таких накладных расходах, так что вы можете принять следующую практику:
@property
для их реализации без изменения синтаксиса остальной части вашего кода.источник
Я удивлен, что никто не упомянул, что свойства являются связанными методами класса дескриптора, Adam Donohue и NeilenMarais достигают именно этой идеи в своих постах - что геттеры и сеттеры являются функциями и могут использоваться для:
Это представляет собой умный способ скрыть детали реализации и перебор кода, такой как регулярное выражение, приведение типов, попытка ... за исключением блоков, утверждений или вычисленных значений.
В целом выполнение CRUD для объекта часто может быть довольно обыденным, но рассмотрим пример данных, которые будут сохранены в реляционной базе данных. ORM может скрыть детали реализации конкретных национальных SQL-выражений в методах, связанных с fget, fset, fdel, определенных в классе свойств, который будет управлять ужасными лестницами if .. elif .. else, которые настолько уродливы в OO-коде - разоблачая простые и элегантный
self.variable = something
и избавиться от деталей для разработчика с помощью ORM.Если кто-то думает о свойствах только как о каком-то мрачном пережитке языка Бондаж и Дисциплины (то есть Java), они упускают точку дескрипторов.
источник
В сложных проектах я предпочитаю использовать свойства только для чтения (или геттеры) с явной функцией установки:
В долгоживущих проектах отладка и рефакторинг занимают больше времени, чем написание самого кода. Есть несколько недостатков для использования
@property.setter
которые делают отладку еще сложнее:1) python позволяет создавать новые атрибуты для существующего объекта. Это делает следующую опечатку очень трудно отследить:
Если ваш объект представляет собой сложный алгоритм, то вы потратите немало времени, пытаясь выяснить, почему он не сходится (обратите внимание на дополнительную букву 't' в строке выше)
2) иногда сеттер может развиться до сложного и медленного метода (например, попадание в базу данных). Другому разработчику было бы довольно сложно понять, почему следующая функция работает очень медленно. Он может потратить много времени на
do_something()
метод профилирования , хотяmy_object.my_attr = 4.
на самом деле это является причиной замедления:источник
И у
@property
традиционных, и у сеттеров есть свои преимущества. Это зависит от вашего варианта использования.Преимущества от
@property
Вам не нужно менять интерфейс при изменении реализации доступа к данным. Когда ваш проект небольшой, вы, вероятно, захотите использовать прямой доступ к атрибутам для доступа к члену класса. Например, допустим, у вас есть объект
foo
типаFoo
, который имеет членnum
. Тогда вы можете просто получить этот член сnum = foo.num
. По мере роста вашего проекта вы можете почувствовать необходимость проверок или отладок простого доступа к атрибутам. Тогда вы можете сделать это с@property
в класса. Интерфейс доступа к данным остается прежним, поэтому нет необходимости изменять код клиента.Цитируется из PEP-8 :
Использование
@property
для доступа к данным в Python рассматривается как Pythonic :Это может усилить вашу самоидентификацию как программиста на Python (не Java).
Это может помочь вашему собеседованию, если ваш собеседник считает, что методы получения и установки в стиле Java являются анти-шаблонами .
Преимущества традиционных геттеров и сеттеров
Традиционные методы получения и установки обеспечивают более сложный доступ к данным, чем простой доступ к атрибутам. Например, когда вы устанавливаете члена класса, иногда вам нужен флаг, указывающий, где вы хотели бы принудительно выполнить эту операцию, даже если что-то выглядит не идеально. Хотя не совсем очевидно, как расширить прямой доступ к членам, например
foo.num = num
, вы можете легко дополнить свой традиционный установщик дополнительнымforce
параметром:Традиционные методы получения и установки ясно указывают, что доступ к членам класса осуществляется через метод. Это означает:
То, что вы получите в результате, может отличаться от того, что хранится в этом классе.
Даже если доступ выглядит как простой атрибут доступа, производительность может сильно отличаться от этого.
Если пользователи вашего класса не ожидают
@property
сокрытия за каждым оператором доступа к атрибутам, то такие явные выражения могут помочь минимизировать неожиданности для ваших учеников.Как уже упоминалось @NeilenMarais и в этом посте , расширение традиционных методов получения и установки в подклассах проще, чем расширение свойств.
Традиционные геттеры и сеттеры уже давно широко используются на разных языках. Если в вашей команде есть люди разного происхождения, они выглядят более знакомыми, чем
@property
. Кроме того, по мере роста вашего проекта, если вам может понадобиться перейти с Python на другой язык, который не имеет@property
, использование традиционных методов получения и установки сделает миграцию более плавной.Предостережения
Ни
@property
традиционные, ни методы получения и установки не делают члена класса приватным, даже если перед его именем используется двойное подчеркивание:источник
Вот выдержки из «Эффективного Python: 90 конкретных способов написать лучший Python» (Удивительная книга. Я очень рекомендую это).
источник