Какой питонный способ использовать геттеры и сеттеры?
Я делаю это так:
def set_property(property,value):
def get_property(property):
или же
object.property = value
value = object.property
Я новичок в Python, так что я все еще изучаю синтаксис, и я хотел бы несколько советов по этому вопросу.
11 ответов
Попробуйте это: свойство 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
Какой питонный способ использовать геттеры и сеттеры?
"Pythonic" - это не использование методов "получения" и "установки", а использование простых атрибутов, как показано в вопросе, и del
для разыменования (но имена меняются для защиты невинных... встроенных):
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_...
- это то, что свойства для.
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
С помощью @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
атрибут от разработчиков клиента, а также выполнять проверку типа свойства имени. Обратите внимание, что следуя этому подходу даже во время инициации вызывается установщик. Так:
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
Это старый вопрос, но тема очень важна и всегда актуальна. Если кто-то хочет выйти за рамки простых геттеров / сеттеров, я написал статью о сверхмощных свойствах в Python с поддержкой слотов, наблюдаемости и сокращенного стандартного кода.
from objects import properties, self_properties
class Car:
with properties(locals(), 'meta') as meta:
@meta.prop(read_only=True)
def brand(self) -> str:
"""Brand"""
@meta.prop(read_only=True)
def max_speed(self) -> float:
"""Maximum car speed"""
@meta.prop(listener='_on_acceleration')
def speed(self) -> float:
"""Speed of the car"""
return 0 # Default stopped
@meta.prop(listener='_on_off_listener')
def on(self) -> bool:
"""Engine state"""
return False
def __init__(self, brand: str, max_speed: float = 200):
self_properties(self, locals())
def _on_off_listener(self, prop, old, on):
if on:
print(f"{self.brand} Turned on, Runnnnnn")
else:
self._speed = 0
print(f"{self.brand} Turned off.")
def _on_acceleration(self, prop, old, speed):
if self.on:
if speed > self.max_speed:
print(f"{self.brand} {speed}km/h Bang! Engine exploded!")
self.on = False
else:
print(f"{self.brand} New speed: {speed}km/h")
else:
print(f"{self.brand} Car is off, no speed change")
Этот класс можно использовать так:
mycar = Car('Ford')
# Car is turned off
for speed in range(0, 300, 50):
mycar.speed = speed
# Car is turned on
mycar.on = True
for speed in range(0, 350, 50):
mycar.speed = speed
Этот код выдаст следующий результат:
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Car is off, no speed change
Ford Turned on, Runnnnnn
Ford New speed: 0km/h
Ford New speed: 50km/h
Ford New speed: 100km/h
Ford New speed: 150km/h
Ford New speed: 200km/h
Ford 250km/h Bang! Engine exploded!
Ford Turned off.
Ford Car is off, no speed change
Более подробная информация о том, как и почему, здесь: https://mnesarco.github.io/blog/2020/07/23/python-metaprogramming-properties-on-steroids
Свойства очень полезны, поскольку вы можете использовать их с присваиванием, но затем также можете включать проверку. Вы можете увидеть этот код, в котором вы используете декоратор @property, а также @ <property_name> .setter для создания методов:
# Python program displaying the use of @property
class AgeSet:
def __init__(self):
self._age = 0
# using property decorator a getter function
@property
def age(self):
print("getter method called")
return self._age
# a setter function
@age.setter
def age(self, a):
if(a < 18):
raise ValueError("Sorry your age is below eligibility criteria")
print("setter method called")
self._age = a
pkj = AgeSet()
pkj.age = int(input("set the age using setter: "))
print(pkj.age)
В этом посте я тоже об этом писал: https://pythonhowtoprogram.com/how-to-create-getter-setter-class-properties-in-python-3/
Вы можете использовать аксессоры / мутаторы (т.е. @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
) означает, что я не смогу получить доступ ни к одной из этих переменных, или вы просто экономно использовали @property
.
* Исключение составляют случаи, когда вы явно хотите сделать некоторые переменные доступными или изменяемыми, но не то и другое, или вы хотите выполнить некоторую дополнительную логику при доступе или изменении атрибута. Это когда я лично использую@property
а также @attribute.setter
(в противном случае нет явных обработчиков / мутаторов для общедоступных атрибутов).
Наконец, предложения PEP8 и Google Style Guide:
PEP8, Проектирование с учетом наследования говорит:
Для простых атрибутов общедоступных данных лучше всего предоставлять только имя атрибута без сложных методов доступа / мутатора. Имейте в виду, что Python предоставляет простой путь к будущему усовершенствованию, если вы обнаружите, что простой атрибут данных нуждается в расширении функционального поведения. В этом случае используйте свойства, чтобы скрыть функциональную реализацию за простым синтаксисом доступа к атрибутам данных.
С другой стороны, в соответствии с правилами и свойствами языка Python в Google Style Guide рекомендуется:
Используйте свойства в новом коде для доступа или установки данных там, где вы обычно использовали бы простые и легкие методы доступа или установки. Свойства должны быть созданы с
@property
декоратор.
Плюсы такого подхода:
Удобочитаемость повышается за счет исключения явных вызовов методов get и set для простого доступа к атрибутам. Позволяет лениться вычислениям. Рассмотрел питонический способ поддержки интерфейса класса. С точки зрения производительности, разрешение свойств обходит необходимость в тривиальных методах доступа, когда прямой доступ к переменной является разумным. Это также позволяет добавлять методы доступа в будущем без нарушения интерфейса.
и минусы:
Должен наследовать от
object
в Python 2. Может скрывать побочные эффекты так же, как перегрузка оператора. Может сбивать с толку подклассы.
class ChangingPassword(object):
def __init__(self, username, password):
"""use _ for change to read only type(protected)."""
self.username = username
self._password = password
def username(self):
return self.username
@property
def password(self):
return self._password
@password.setter
def password(self, new_password: int):
if isinstance(new_password, int):
if self._password != new_password:
self._password = new_password
else:
raise ValueError('Enter different value!')
user01 = ChangingPassword('Herment', 1321)
print(user01.password)
user01.password = 6301
print(user01.password)
Вы можете использовать магические методы __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__
вызывается только когда атрибут не найден.
Примечание: свойство, вероятно, лучший способ сделать это, посмотрите на другие ответы
определять __setattr__(self, name, value)
и / или __getattr__(self, name)
: obj.abc
-> type(obj).__getattr__(obj, 'abc')