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

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

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

или

object.property = value  
value = object.property

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

Ответ 1

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

Ответ 2

Попробуйте это: свойство 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

Ответ 3

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

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

value = 'something'

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

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

class Obj:
    """property demo"""
    #
    @property
    def attribute(self): # implements the get - this name is *the* name
        return self._attribute
    #
    @attribute.setter
    def attribute(self, value): # name must be the same
        self._attribute = value
    #
    @attribute.deleter
    def attribute(self): # again, name must be 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):
    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")

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

>>> 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_... - то, для чего предназначены свойства.

Ответ 4

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

Ответ 6

Использование @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

Ответ 7

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

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

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

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

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

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

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

и минусы:

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

Ответ 8

Определите __setattr__(self, name, value) и/или __getattr__(self, name).

Ответ 9

Вы можете использовать магические методы __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__ вызывается только когда атрибут не найден.