Когда и как использовать свойство встроенной функции () в Python - PullRequest
68 голосов
/ 12 октября 2009

Мне кажется, что кроме небольшого синтаксического сахара, свойство () ничего хорошего не дает.

Конечно, приятно иметь возможность писать a.b=2 вместо a.setB(2), но сокрытие того факта, что ab = 2 - это не простое задание, похоже на рецепт проблемы, потому что может произойти какой-то неожиданный результат, например, a.b=2 на самом деле a.b означает 1. Или исключение поднято. Или проблема с производительностью. Или просто сбивает с толку.

Можете ли вы дать мне конкретный пример для хорошего его использования? (использование его для исправления проблемного кода не считается; -)

Ответы [ 7 ]

128 голосов
/ 12 октября 2009

В языках, которые используют геттеры и сеттеры, как, например, Java, они не должны и не должны делать ничего, кроме того, что они говорят - было бы удивительно, если бы x.getB() сделал что-то, кроме возврата текущего значения логического атрибута b, или если x.setB(2) сделал что-то, кроме небольшого объема внутренней работы, необходимого для x.getB() возврата 2.

Тем не менее, нет никаких навязанных языком гарантий об этом ожидаемом поведении, т. Е. Наложенных компилятором ограничений на тело методов, имена которых начинаются с get или set: скорее, оно осталось до здравого смысла, социального соглашения, "руководств по стилю" и тестирования.

Поведение x.b обращений и назначений, таких как x.b = 2, в языках, которые имеют свойства (набор языков, который включает, но не ограничивается Python), является точно таким же, как для методов получения и установки, например, в Java: те же ожидания, то же самое отсутствие гарантированных языком гарантий.

Первый выигрыш для свойств - это синтаксис и удобочитаемость. Необходимость писать, например,

x.setB(x.getB() + 1)

вместо очевидного

x.b += 1

взывает к мести богам. В языках, которые поддерживают свойства, нет абсолютно никаких веских причин заставлять пользователей класса проходить через такие византийские шаблоны, влияя на читабельность их кода без каких-либо дополнительных преимуществ.

В частности, в Python есть еще один большой плюс в использовании свойств (или других дескрипторов) вместо геттеров и сеттеров: если и когда вы реорганизуете свой класс так, что базовый сеттер и геттер больше не нужны, вы можете (без нарушая опубликованный API класса), просто исключите эти методы и свойство, которое на них полагается, сделав b обычным «хранимым» атрибутом класса x, а не «логическим», полученным и установленным в вычислительном отношении.

В Python выполнение действий напрямую (когда это возможно), а не с помощью методов, является важной оптимизацией, а систематическое использование свойств позволяет выполнять эту оптимизацию всякий раз, когда это возможно (всегда выставляя «обычные хранимые атрибуты» напрямую и только те, которые действительно нужны). вычисления при доступе и / или настройке с помощью методов и свойств).

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

Ваш единственный аргумент против свойств, например, что «внешний пользователь обычно не ожидает никаких побочных эффектов в результате назначения»; но вы упускаете тот факт, что один и тот же пользователь (в языке, таком как Java, где методы getter и setters распространены) не будет ожидать (наблюдаемых) «побочных эффектов» в результате вызова метода setter (и даже меньше для метода getter) ;-). Это разумные ожидания, и вы, как автор класса, должны попытаться приспособить их - независимо от того, используются ли ваш установщик и получатель напрямую или через свойство, без разницы. Если у вас есть методы с важными наблюдаемыми побочными эффектами, не назовите их getThis, setThat и не используйте их через свойства.

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

31 голосов
/ 12 октября 2009

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

Итак, для начала напишите:

class MyClass(object):
    def __init__(self):
        self.myval = 4

Очевидно, что теперь вы можете написать myobj.myval = 5.

Но позже вы решаете, что вам нужен сеттер, поскольку вы хотите сделать что-то умное одновременно. Но вам не нужно менять весь код, который использует ваш класс - поэтому вы помещаете установщик в @property декоратор, и все это просто работает.

14 голосов
/ 12 октября 2009

но скрывать тот факт, что a.b = 2 не является простое задание выглядит как рецепт за неприятности

Вы не скрываете этот факт, хотя; этого факта никогда не было с самого начала. Это python - язык высокого уровня; не сборка. Немногие из «простых» операторов в нем сводятся к одиночным инструкциям ЦП. Прочитать простоту в задании означает прочитать вещи, которых там нет.

Когда вы говорите x.b = c, вероятно, все, что вы должны думать, это то, что «что бы ни случилось, x.b теперь должно быть c».

5 голосов
/ 12 октября 2009

Основная причина в том, что все выглядит лучше. Это более питонический. Особенно для библиотек. thing.getValue () выглядит менее красиво, чемthing.value

В plone (довольно большой CMS) у вас был document.setTitle (), который делает много вещей, таких как сохранение значения, его индексация и так далее. Просто делать document.title = 'что-то' лучше. Вы знаете, что в любом случае многое происходит за кулисами.

3 голосов
/ 13 октября 2009

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

Учтите, что у вас есть класс Foo, который широко используется в вашем приложении. Теперь это приложение стало довольно большим, и, скажем так, это веб-приложение, которое стало очень популярным.

Вы указываете, что Foo вызывает узкое место. Возможно, можно добавить некоторое кэширование в Foo, чтобы ускорить его. Использование свойств позволит вам сделать это без изменения какого-либо кода или тестов за пределами Foo.

Да, конечно, это проблемный код, но вы сэкономили много $$, быстро его исправив.

Что, если Foo находится в библиотеке, для которой у вас есть сотни или тысячи пользователей? Ну, вы избавили себя от необходимости указывать им делать дорогостоящий рефакторинг при обновлении до последней версии Foo.

В примечаниях к выпуску вместо руководства по переносу абзацев есть строка о Foo.

Опытные программисты на Python не ожидают многого от a.b=2, кроме a.b==2, но они знают даже, что это может быть неправдой. То, что происходит внутри класса, это его личное дело.

2 голосов
/ 12 октября 2009

Вот мой старый пример. Я обернул библиотеку C, в которой были такие функции, как «void dt_setcharge (int atom_handle, int new_charge)» и «int dt_getcharge (int atom_handle)». Я хотел на уровне Python сделать "atom.charge = atom.charge + 1".

Декоратор "свойства" делает это легко. Что-то вроде:

class Atom(object):
    def __init__(self, handle):
        self.handle = handle
    def _get_charge(self):
        return dt_getcharge(self.handle)
    def _set_charge(self, charge):
        dt_setcharge(self.handle, charge)
    charge = property(_get_charge, _set_charge)

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

class Atom:
    def __init__(self, handle):
        self.handle = handle
    def __getattr__(self, name):
        if name == "charge":
            return dt_getcharge(self.handle)
        raise AttributeError(name)
    def __setattr__(self, name, value):
        if name == "charge":
            dt_setcharge(self.handle, value)
        else:
            self.__dict__[name] = value
0 голосов
/ 02 января 2016

геттеры и сеттеры необходимы для многих целей и очень полезны, потому что они прозрачны для кода. Имея у объекта Something свойство height, вы присваиваете значение Something.height = 10, но если высота имеет методы получения и установки, тогда, когда вы присваиваете это значение, в процедурах можно делать много вещей, например, проверять мин или макс. значение, например, запуск события из-за изменения высоты, автоматическая установка других значений в зависимости от нового значения высоты, все, что может произойти в момент присвоения значения Something.height. Помните, вам не нужно вызывать их в своем коде, они автоматически выполняются в тот момент, когда вы читаете или записываете значение свойства. В некотором смысле они похожи на процедуры обработки событий, когда свойство X меняет значение и когда значение свойства X читается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...