Який пітонічний спосіб використовувати геттери та сетери?


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

2
Чи викликається сеттер для x в ініціалізаторі при інстанціюванні _x?
Кейсі

7
@Casey: Ні. Посилання на ._x(що не є властивістю, а просто атрибут) обходять propertyобгортку. Тільки посилання, щоб .xпройти через property.
ShadowRanger

278

Який пітонічний спосіб використовувати геттери та сетери?

"Пітонічний" спосіб полягає не у використанні "гетерів" та "сеттерів", а у використанні простих атрибутів, як це демонструє питання, та 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_...- ось для чого властивості.


Окрім дублювання доступної функціональності, чому слід уникати написання власних сетерів та геттерів? Я розумію, що це може бути не піфонічний шлях, але чи є справді серйозні проблеми, з якими можна зіткнутися інакше?
користувач1350191

4
У вашій демонстрації __init__метод посилається на, self.protected_valueале на них посилаються геттер і сетери self._protected_value. Чи можете ви поясніть, як це працює? Я перевірив ваш код, і він працює як є - тож це не помилка друку.
codeforester

2
@codeforester Я сподівався відповісти на свою відповідь раніше, але поки не зможу, цього коментаря має бути достатньо. Я сподіваюся, що ви можете побачити, що він використовує майно через громадські програми, забезпечуючи його "захист". Не було б сенсу "захищати" його власністю, а потім використовувати непублічні 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

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атрибут від розробників клієнта, а також здійснюєте перевірку типу властивостей імені. Зауважте, що слідуючи такому підходу навіть під час ініціації, сетер викликає. Тому:

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, і він спрямовує ОП на метод, який пропонує відповідь-ер. @ 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) означає, що я не маю змоги отримати доступ до жодної з цих змінних, або ви просто нерозумно користуєтесь @property.

* Виняток становить, коли ви явно хочете зробити деякі змінні доступними або зміненими, але не обидва, або ви хочете виконати додаткову логіку під час доступу до або змінити атрибут. Це коли я особисто використовую @propertyі @attribute.setter(інакше немає явних аксесуарів / мутаторів для публічних атрибутів).

Нарешті, пропозиції PEP8 та Посібник зі стилів Google:

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

Для простих атрибутів загальнодоступних даних найкраще розкрити лише ім’я атрибута без складних методів доступу / мутатора . Майте на увазі, що Python забезпечує простий шлях до майбутнього вдосконалення, якщо ви виявите, що простий атрибут даних потребує зростання функціональної поведінки. У цьому випадку використовуйте властивості, щоб приховати функціональну реалізацію за простим синтаксисом доступу до атрибутів даних.

З іншого боку, згідно з Посібником зі стилів Google, правилами / властивостями Python щодо мов, рекомендується:

Використовуйте властивості в новому коді для доступу чи встановлення даних там, де зазвичай використовувались прості, легкі методи доступу чи налаштування. Властивості слід створити за допомогою @propertyдекоратора.

Плюси цього підходу:

Читання збільшується за рахунок виключення явних викликів методу отримання та встановлення для простого доступу до атрибутів. Дозволяє розрахункам бути ледачими. Розглянуто пітонічний спосіб підтримки інтерфейсу класу. З точки зору продуктивності дозволення властивостей обходить необхідні тривіальні методи доступу, коли прямий змінний доступ є розумним. Це також дозволяє в подальшому додавати методи аксесуара, не порушуючи інтерфейс.

і мінуси:

Потрібно успадкувати від objectPython 2. Може сховати побічні ефекти, як перевантаження оператора. Може бути заплутаним для підкласів.


1
Я категорично не згоден. Якщо у мене на об’єкті є 15 атрибутів, і я хочу, щоб один був обчислений @property, а також використання решти @propertyздається поганим рішенням.
Квеклеф

Погодьтеся, але тільки якщо є щось конкретне щодо цього конкретного атрибута, який вам потрібен @property(наприклад, виконайте якусь особливу логіку перед поверненням атрибута). Інакше чому б ви прикрашали один атрибут, @properyа не інші?
Томаш Бартковіак

@Quelklef див. Сторонній знак у пості (позначений зірочкою).
Томаш Бартковяк

Ну ... Якщо ви не займаєтесь однією з речей, про які згадує sidenote, то для початку вам не слід користуватися @property, правда? Якщо ваш геттер є, return this._xа ваш сетер є this._x = new_x, то використання @propertyвзагалі якось нерозумно.
Quelklef

1
Хм, мабуть. Я особисто сказав, що це не добре --- це абсолютно зайве. Але я бачу, звідки ти родом. Я думаю, що я просто прочитав вашу публікацію, яка сказала, що "найголовніше, коли користуватися @property- це послідовність".
Квеклеф

-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__викликається лише тоді, коли атрибут не знайдено.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.