Приклад реального світу про те, як використовувати властивість властивості в python?


143

Мене цікавить, як користуватися @propertyPython. Я прочитав документи python, і приклад, на мою думку, - це лише іграшковий код:

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

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

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Я не знаю, яку користь (и) я можу отримати від обгортання _xзаповненого декоратором майна. Чому б просто не реалізувати як:

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

Думаю, властивість властивості може бути корисною в деяких ситуаціях. Але коли? Може хтось, будь ласка, надати мені приклади з реального світу?

Дякую.


10
Це найкраще і найяскравіше пояснення, яке я знайшов про декоратора майна [натисніть тут ]
Сумуду,

2
@Anubis в останньому прикладі у наданому вами посиланні, встановивши c = Цельсій (-500), не кинув жодного ValueError, що, на мою думку, не досягає наміченого результату.
Саджук

Погодьтеся з @Anubis. Тут реалізовано правильно: python-course.eu/python3_properties.php
anon01

Відповіді:


92

Іншими прикладами можуть бути валідація / фільтрація заданих атрибутів (примушування їх до меж або прийнятним) та ледача оцінка складних або швидко мінливих термінів.

Складний обчислення, прихований за атрибутом:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Перевірка:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value

1
Мені подобається приклад PDB_Calculator - складні речі відбираються, вся справа працює, і користувач може насолоджуватися простотою!
Адам Куркевич

2
можливо, з професійної точки зору, це дуже хороші приклади. Але, як ноубі, я вважаю ці приклади досить неефективними. мій поганий ... :(
kmonsoor

78

Одним простим випадком використання буде встановлення атрибута екземпляра лише для читання, оскільки ви знаєте, що ім'я змінної з одним підкресленням _xу python зазвичай означає, що це приватне (внутрішнє використання), але іноді ми хочемо мати можливість читати атрибут екземпляра, а не писати це ми можемо використовувати propertyдля цього:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute

7
Можна все-таки встановити c._x, якщо користувач хоче. Python насправді не має реальних приватних атрибутів.

21

Погляньте на цю статтю для дуже практичного використання. Коротше кажучи, це пояснює, як у Python зазвичай можна викопати явний метод getter / setter, оскільки якщо вам знадобляться на певному етапі, ви можете використовувати їх propertyдля безпроблемної реалізації.


15

Одне, для чого я використовував це кешування повільних, але незмінних значень, що зберігаються в базі даних. Це узагальнює будь-яку ситуацію, коли ваші атрибути вимагають обчислень або іншої тривалої роботи (наприклад, перевірка бази даних, мережевий зв’язок), яку ви хочете робити лише на вимогу.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

Це було у веб-додатку, де для будь-якого поданого перегляду сторінки може знадобитися лише один конкретний атрибут такого типу, але самі основні об'єкти можуть мати кілька таких атрибутів - ініціалізація їх усіх на будівництві була б марною, а властивості дозволяють мені бути гнучкими, в яких атрибути ліниві, а ні.


1
Ви не можете використати @cached_propertyдля цього?
adarsh

@adarsh ​​- Звучить цікаво. Де це?
detly

Я використовую його , але я забув , що це був не побудований, але ви можете використовувати його з цим, pypi.python.org/pypi/cached-property/0.1.5
Адарш

2
Цікаво. Я думаю, що вона була вперше опублікована після цієї відповіді, але кожен, хто читає це, мабуть, використовує її замість цього.
detly

10

Читаючи відповіді та коментарі, головна тема, здається, відповіді, здається, не вистачає простого, але корисного прикладу. Я включив сюди дуже простий, який демонструє просте використання @propertyдекоратора. Це клас, який дозволяє користувачеві вказувати та отримувати вимірювання відстані за допомогою безлічі різних одиниць, тобто in_feetабо in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Використання:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14

Особисто для мене найкращі приклади геттерів / сетерів - це показати людям такі зміни, які потрібно внести згодом, але очевидно, що це займе трохи більше часу.
dtc

7

Властивість - це лише абстракція навколо поля, яка дає вам більше контролю над способами, якими можна керувати конкретним полем, і робити обчислення середнього програмного забезпечення. Мало хто із звичаїв, які виникають на увазі, - це перевірка та попередня ініціалізація та обмеження доступу

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x

6

Так, для опублікованого оригінального прикладу, властивість буде працювати точно так само, як просто мати змінну екземпляра 'x'.

Це найкраще про властивості пітона. Зовні вони працюють точно як змінні екземпляра! Що дозволяє використовувати змінні екземпляри поза класом.

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

Багато інших мов та курсів програмування вказуватимуть, що програміст ніколи не повинен виставляти змінні екземпляри, а замість цього використовувати "getters" та "setters" для будь-яких значень, до яких можна отримати доступ поза межами класу, навіть простий випадок, зазначений у питанні.

Код поза класом з використанням багатьох мов (наприклад, Java)

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

При впровадженні класу існує безліч "getters" і "setters", які точно виконуються як ваш перший приклад: повторити просто змінну екземпляра. Ці геттери та сеттери необхідні, тому що якщо зміна реалізації класу, весь код поза класом потрібно буде змінити. Але властивості python дозволяють коду поза класом бути таким же, як у змінних екземпляра. Тому код за межами класу не потрібно змінювати, якщо ви додаєте властивість або маєте просту змінну екземпляра. Отже, на відміну від більшості об'єктно-орієнтованих мов, для простого прикладу ви можете використовувати змінну екземпляра замість "getters" та "setters", які справді не потрібні, запевняйте, знаючи, що якщо ви перейдете на властивість у майбутньому, код використовуйте ваш клас не потрібно міняти.

Це означає, що вам потрібно створити властивості лише в тому випадку, коли є складна поведінка, і для дуже поширеного простого випадку, коли, як описано в питанні, проста змінна екземпляр - це все, що потрібно, ви можете просто використовувати змінну екземпляра.


6

Ще одна приємна особливість властивостей, ніж використання сетерів і getters, що вони дозволяють продовжувати використовувати оператори OP = (наприклад, + =, - =, * = тощо) для своїх атрибутів, зберігаючи будь-яку перевірку, контроль доступу, кешування тощо, що сетери та геттери поставлять.

наприклад, якщо ви писали клас Personіз сеттером setage(newage)та getter getage(), то для збільшення віку вам доведеться писати:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

але якщо ви створили ageмайно, ви можете написати набагато чистіший:

bob.age += 1

5

Коротка відповідь на ваше запитання полягає в тому, що у вашому прикладі користі немає. Ймовірно, ви повинні використовувати форму, яка не містить властивостей.

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


4

Щось спочатку багато хто не помічає, це те, що ви можете створити власні підкласи власності. Це я знайшов дуже корисним для викриття лише атрибутів об’єкта чи атрибута для читання, які ви можете читати та писати, але не видаляти. Це також відмінний спосіб перетворити такі функції, як відстеження змін до об'єктних полів.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

Мені подобається це. Чи правильно я це читаю в тому, що зчитувач читається лише тоді, коли читач / запис доступу доступний без можливості видалення? Як би ви додали перевірку даних? Я досить новачок у Python, але я думаю, що, ймовірно, є спосіб додати зворотний виклик до attr = reader('_attr')рядка або якусь форму попередньої перевірки, як attr = if self.__isValid(value): reader('_attr'). Пропозиції?
Gabe Spradlin

Вибачте щойно зрозумів, що запитував про перевірку даних для змінної лише для читання. Але очевидно, це стосуватиметься лише частини встановлення класу accessor. Тож змініть attr = reader('_attr')на attr = accessor('_attr'). Спасибі
Габе Спрадлін

Ви маєте рацію, що якби ви хотіли перевірити, тоді ви додасте функцію для перевірки та підняття винятку, якщо недійсна (або будь-яка поведінка, яка вам сподобалась, включаючи нічого не робити) до init . Я змінив вищезазначене одним можливим шаблоном. Валідатор повинен повернути True | False, щоб визначити, чи відбувається набір чи ні.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.