Пример реального мира о том, как использовать функцию свойства в Python?

145

Меня интересует, как использовать @propertyв Python. Я читал документы 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

Думаю, в некоторых ситуациях функция свойств может пригодиться. Но когда? Не мог бы кто-нибудь дать мне несколько реальных примеров?

Спасибо.

сяо 啸
источник
11
Это лучшее и самое ясное объяснение, которое я нашел о декораторе свойств. [Щелкните здесь ]
Sumudu
2
@Anubis в последнем примере в предоставленной вами ссылке, установка c = Celsius (-500) не вызвала никакой ValueError, что, как мне кажется, не приводит к желаемому результату.
Sajuuk
Согласитесь с @Anubis. Здесь он реализован правильно: python-course.eu/python3_properties.php
anon01

Ответы:

93

Другими примерами могут быть проверка / фильтрация установленных атрибутов (принуждение их к ограничению или приемлемости) и ленивая оценка сложных или быстро меняющихся терминов.

Сложный расчет, скрытый за атрибутом:

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
Benosteen
источник
1
Мне нравится пример PDB_Calculator - сложные вещи абстрагируются, все работает, и пользователь может наслаждаться простотой!
Adam Kurkiewicz 05
2
возможно, с профессиональной точки зрения это очень хорошие примеры. Но, как новичок, я считаю эти примеры совершенно неэффективными. мой плохой ... :(
kmonsoor
79

Одним из простых вариантов использования будет установка атрибута экземпляра только для чтения, поскольку вы знаете, что начало имени переменной с одним подчеркиванием _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 вы обычно можете отказаться от явного метода получения / установки, поскольку, если они вам понадобятся на каком-то этапе, вы можете использовать их 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
adarsh
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'.

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

Это означает, что ваш первый пример может действительно использовать переменную экземпляра. Если что-то изменилось, а затем вы решите изменить свою реализацию и свойство окажется полезным, интерфейс к свойству останется таким же, как из кода вне класса. Переход от переменной экземпляра к свойству не влияет на код вне класса.

Многие другие языки и курсы программирования будут инструктировать, что программист никогда не должен раскрывать переменные экземпляра, а вместо этого использовать «геттеры» и «сеттеры» для любого значения, к которому будет осуществляться доступ извне класса, даже в простом случае, указанном в вопросе.

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

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

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

И при реализации класса есть много «геттеров» и «сеттеров», которые действуют точно так же, как ваш первый пример: реплицируют просто переменную экземпляра. Эти геттеры и сеттеры необходимы, потому что, если реализация класса изменится, весь код вне класса должен будет измениться. Но свойства python позволяют коду вне класса быть таким же, как с переменными экземпляра. Таким образом, код вне класса не нужно изменять, если вы добавляете свойство или имеете простую переменную экземпляра. Итак, в отличие от большинства объектно-ориентированных языков, для вашего простого примера вы можете использовать переменную экземпляра вместо «геттеров» и «сеттеров», которые на самом деле не нужны, будучи уверенными в том, что если вы измените свойство в будущем, код, использующий ваш класс не нужно менять.

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

инновации8
источник
6

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

например, если вы написали класс Personс сеттером setage(newage)и геттером getage(), то для увеличения возраста вам нужно будет написать:

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

но если вы создали ageсвойство, вы могли бы написать намного чище:

bob.age += 1
викторина
источник
5

Короткий ответ на ваш вопрос заключается в том, что в вашем примере нет никакой выгоды. Вероятно, вам следует использовать форму, в которой нет свойств.

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

SpoonMeiser
источник
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'). Предложения?
Гейб Спрэдлин,
Извините, я только что понял, что спрашивал о проверке данных для переменной только для чтения. Но очевидно, что это применимо только к установочной части класса средств доступа. Так что измените attr = reader('_attr')на attr = accessor('_attr'). Спасибо
Гейб Спрэдлин
Вы правы в том, что если вам нужна валидация, вы бы добавили функцию для проверки и создания исключения в случае недействительности (или любого другого поведения, которое вам нравится, включая бездействие) в init . Я изменил приведенное выше с помощью одного возможного шаблона. Валидатор должен вернуть True | False, чтобы определить, произойдет ли установка или нет.