Какой питонный способ использовать геттеры и сеттеры?

341

Я делаю это так:

def set_property(property,value):  
def get_property(property):  

или

object.property = value  
value = object.property

Я новичок в Python, так что я все еще изучаю синтаксис, и я хотел бы несколько советов по этому вопросу.

Хорхе Губерте
источник

Ответы:

684

Попробуйте это: свойство Python

Пример кода:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        print("getter of x called")
        return self._x

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called
Grissiom
источник
2
Вызывается ли setter для x в инициализаторе при создании экземпляра _x?
Кейси
7
@Casey: Нет. Ссылки на ._x(который не является свойством, просто атрибут) обходят propertyперенос. Только ссылки, чтобы .xпройти property.
ShadowRanger
278

Какой питонный способ использовать геттеры и сеттеры?

«Pythonic» - это не использование «получателей» и «сеттеров», а использование простых атрибутов, как показано в вопросе, и delдля удаления (но имена изменяются для защиты невинных ... встроенных):

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute

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

class Obj:
    """property demo"""
    #
    @property            # first decorate the getter method
    def attribute(self): # This getter method name is *the* name
        return self._attribute
    #
    @attribute.setter    # the property decorates with `.setter` now
    def attribute(self, value):   # name, e.g. "attribute", is the same
        self._attribute = value   # the "value" name isn't special
    #
    @attribute.deleter     # decorate with `.deleter`
    def attribute(self):   # again, the method name is the same
        del self._attribute

(Каждое использование декоратора копирует и обновляет предыдущий объект свойства, поэтому обратите внимание, что вы должны использовать одно и то же имя для каждого набора, получения и удаления функции / метода.

После определения вышеизложенного исходная настройка, получение и удаление кода остаются прежними:

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute

Вам следует избегать этого:

def set_property(property,value):  
def get_property(property):  

Во-первых, вышеприведенное не работает, потому что вы не предоставляете аргумент для экземпляра, для которого свойство будет установлено (обычно self), а именно:

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...

Во-вторых, это дублирует назначение двух специальных методов __setattr__и __getattr__.

В- третьих, мы также имеем setattrи getattrвстроенные функции.

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

@propertyДекоратор для создания методов получения и установки.

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

class Protective(object):

    @property
    def protected_value(self):
        return self._protected_value

    @protected_value.setter
    def protected_value(self, value):
        if acceptable(value): # e.g. type or range check
            self._protected_value = value

В общем, мы хотим избежать использования propertyи просто использовать прямые атрибуты.

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

демонстрация

Например, скажем, нам нужно, чтобы атрибут защищенного объекта был целым числом от 0 до 100 включительно, и предотвращали его удаление с соответствующими сообщениями, информирующими пользователя о его правильном использовании:

class Protective(object):
    """protected property demo"""
    #
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    # 
    @property
    def protected_value(self):
        return self._protected_value
    #
    @protected_value.setter
    def protected_value(self, value):
        if value != int(value):
            raise TypeError("protected_value must be an integer")
        if 0 <= value <= 100:
            self._protected_value = int(value)
        else:
            raise ValueError("protected_value must be " +
                             "between 0 and 100 inclusive")
    #
    @protected_value.deleter
    def protected_value(self):
        raise AttributeError("do not delete, protected_value can be set to 0")

(Обратите внимание, что это __init__относится к self.protected_valueметодам свойств, к которым они относятся self._protected_value. Это делается для того, чтобы __init__использовать свойство через открытый API, гарантируя, что оно «защищено».)

И использование:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0

Имена имеют значение?

Да, они делают . .setterи .deleterсделайте копии оригинальной собственности. Это позволяет подклассам корректно изменять поведение, не изменяя поведения в родительском.

class Obj:
    """property demo"""
    #
    @property
    def get_only(self):
        return self._attribute
    #
    @get_only.setter
    def get_or_set(self, value):
        self._attribute = value
    #
    @get_or_set.deleter
    def get_set_or_delete(self):
        del self._attribute

Теперь, чтобы это работало, вы должны использовать соответствующие имена:

obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'  
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error

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

Вывод

Начните с простых атрибутов.

Если вам позже понадобится функциональность, связанная с настройкой, получением и удалением, вы можете добавить ее с помощью декоратора свойств.

Избегайте именованных функций set_...и get_...- вот для чего нужны свойства.

Аарон Холл
источник
Помимо дублирования доступных функций, почему следует избегать написания собственных сеттеров и геттеров? Я понимаю, что это может быть не путь Pythonic, но есть ли действительно серьезные проблемы, с которыми можно столкнуться в противном случае?
user1350191
4
В вашей демоверсии __init__метод относится к методу , а к методу self.protected_valuegetter и setters self._protected_value. Не могли бы вы объяснить, как это работает? Я проверил ваш код, и он работает как есть - так что это не опечатка.
Codeforester,
2
@codeforester Я надеялся ответить в своем ответе ранее, но пока я не могу, этого комментария должно быть достаточно. Я надеюсь, что вы видите, что он использует собственность через общедоступные API, гарантируя, что она «защищена». Не имеет смысла «защищать» его свойством, а затем использовать непубличный API вместо этого, __init__не так ли?
Аарон Холл
2
Да, @AaronHall понял это сейчас. Я не осознавал, self.protected_value = start_protected_valueчто на самом деле вызывает функцию-установщик; Я думал, что это было задание.
Codeforester
1
IMHO, это должен быть принятый ответ, если я правильно понял, Python принимает противоположную точку зрения по сравнению, например, с Java. Вместо того, чтобы делать все по умолчанию приватным и писать какой-то дополнительный код, когда это необходимо публично, в python, вы можете сделать все публичным и позже добавить конфиденциальность
idclev 463035818
27
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20
Autoplectic
источник
17

Использование @propertyи @attribute.setterпомогает вам не только использовать «питонический» способ, но и проверять действительность атрибутов как при создании объекта, так и при его изменении.

class Person(object):
    def __init__(self, p_name=None):
        self.name = p_name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if type(new_name) == str: #type checking for name property
            self._name = new_name
        else:
            raise Exception("Invalid value for name")

Таким образом, вы фактически «скрываете» _nameатрибут от разработчиков клиента, а также выполняете проверки типа свойства name. Обратите внимание, что следуя этому подходу, даже во время инициации вызывается установщик. Так:

p = Person(12)

Приведет к:

Exception: Invalid value for name

Но:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception
Фарзад Головокружение
источник
16

Проверьте @propertyдекоратор .

Кевин Литтл
источник
34
Это в значительной степени только для ссылки.
Аарон Холл
7
Как это полный ответ? Ссылка не является ответом.
Andy_A̷n̷d̷y̷
Я думаю, что это хороший ответ, потому что в документации четко указано, как его использовать (и он останется актуальным, если реализация Python изменится, и он направит OP к методу, который предлагает ответчик. @ Jean-FrançoisCorbett четко заявил «как» это полный ответ.
HunnyBear
В любом случае этот ответ не добавляет ничего полезного другим ответам и в идеале должен быть удален.
Георгий
5

Вы можете использовать аксессоры / мутаторы (то есть @attr.setterи @property) или нет, но самое главное, чтобы быть последовательным!

Если вы используете @propertyдля доступа к атрибуту, например,

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

используйте его для доступа к каждому * атрибуту! Было бы плохой практикой обращаться к некоторым атрибутам, используя @propertyи оставлять некоторые другие свойства общедоступными (например, имя без подчеркивания) без доступа, например , не делать

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

Обратите внимание, что self.bздесь нет явного средства доступа, даже если оно общедоступно.

Точно так же с сеттерами (или мутаторами ), не стесняйтесь использовать, @attribute.setterно будьте последовательны! Когда вы делаете, например,

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

Мне трудно угадать ваше намерение. С одной стороны, вы говорите, что оба aи bявляются общедоступными (без подчеркивания в их именах), поэтому мне теоретически должно быть разрешено получить доступ / изменить (получить / установить) оба. Но тогда вы указываете явный мутатор только для a, который говорит мне, что, возможно, я не должен быть в состоянии установить b. Поскольку вы предоставили явный мутатор, я не уверен, @propertyозначает ли отсутствие явного accessor ( ), что я не смогу получить доступ ни к одной из этих переменных, или вы просто экономно использовали @property.

* Исключение составляют случаи, когда вы явно хотите сделать некоторые переменные доступными или изменяемыми, но не оба, или вы хотите выполнить некоторую дополнительную логику при доступе или изменении атрибута. Это когда я лично использую @propertyи @attribute.setter(в противном случае нет явных acessors / mutators для открытых атрибутов).

Наконец, предложения PEP8 и Google Style Guide:

PEP8, Проектирование для Наследования говорит:

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

С другой стороны, в соответствии с Руководством по стилю Google Python Language Rules / Properties рекомендуется:

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

Плюсы этого подхода:

Читаемость повышается за счет исключения явных вызовов методов get и set для простого доступа к атрибутам. Позволяет вычислениям быть ленивым. Рассматривается Pythonic способ поддерживать интерфейс класса. С точки зрения производительности, разрешение свойств обходится без необходимости тривиальных методов доступа, когда прямой доступ к переменным является разумным. Это также позволяет добавлять методы доступа в будущем без нарушения интерфейса.

и минусы:

Должен наследоваться от objectPython 2. Может скрывать побочные эффекты, такие как перегрузка операторов. Может быть запутанным для подклассов.

Томаш Бартковяк
источник
1
Я категорически не согласен. Если у меня есть 15 атрибутов на моем объекте, и я хочу, чтобы один из них был вычислен @property, использование остальных также @propertyкажется плохим решением.
Quelklef
Согласитесь, но только если есть что-то конкретное в этом конкретном атрибуте, для которого вам нужно @property(например, выполнение некоторой специальной логики перед возвратом атрибута). В противном случае, почему вы бы украшали один атрибут, @properyа не другие?
Томаш Бартковяк
@Quelklef см. Заметку в сообщении (отмечена звездочкой).
Томаш Бартковяк
Ну ... Если вы не делаете одну из вещей, упомянутых sidenote, то вам не следует использовать @propertyдля начала, верно? Если ваш геттер есть, return this._xа ваш сеттер есть this._x = new_x, то использовать его @propertyвообще глупо.
Quelklef
1
Хм, возможно. Лично я бы сказал, что это не хорошо - это совершенно лишнее. Но я вижу, откуда ты. Думаю, я просто прочитал ваш пост, в котором говорится, что «самое важное при использовании @property- это быть последовательным».
Quelklef
-1

Вы можете использовать магические методы __getattribute__и __setattr__.

class MyClass:
    def __init__(self, attrvalue):
        self.myattr = attrvalue
    def __getattribute__(self, attr):
        if attr == "myattr":
            #Getter for myattr
    def __setattr__(self, attr):
        if attr == "myattr":
            #Setter for myattr

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

Пика Волшебник китов
источник