Перейти к содержимому

Property python что это

  • автор:

Класс property() в Python, метод класса как свойство

Класс property() позволяет использовать методы в качестве вычисляемых свойств объектов (дескрипторов данных).

Если задана строка doc , то она будет строкой атрибута свойства. В противном случае строка документации будет скопирована из функции fget , если она существует.

Типичное использование класса property() — определить дескриптор (управляемый атрибут) x :

class C: def __init__(self): self._x = None def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") 

Если c является экземпляром C , то:

  • c.x вызовет метод getx ,
  • c.x = value вызовет setx ,
  • del c.x вызовет delx .

Используя класс property() в качестве декоратора @property можно легко создавать вычисляемые свойства только для чтения:

class Parrot: def __init__(self): self._voltage = 100000 @property def voltage(self): """Get the current voltage.""" return self._voltage p = Parrot() p.voltage = 220 # AttributeError del p.voltage # AttributeError 

Объект декоратора @property также имеет методы getter , setter и deleter , которые можно так же использовать в качестве декораторов. Эти декораторы удобно использовать с функциями, которые реализуют выше указанные свойства.

Это лучше всего объяснить на примере:

class C: def __init__(self): self._x = None @property def x(self): # В декораторах 'setter' и 'deleter' нужно указывать имя метода-свойства """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 

Этот код в точности соответствует первому примеру. Обязательно присваивайте дополнительным методам (под декораторами setter и deleter ) то же имя, что и у исходного метода-свойства.

Изменено в Python 3.5: Добавлена возможность установки строки документации для объекта-свойства

  • ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
  • Функция abs(), абсолютное значение числа
  • Функция all(), все элементы True
  • Функция any(), хотя бы один элемент True
  • Функция ascii(), преобразует строку в ASCII
  • Функция bin(), число в двоичную строку
  • Класс bool(), логическое значение объекта
  • Функция breakpoint(), отладчик кода
  • Класс bytearray(), преобразует в массив байтов
  • Класс bytes(), преобразует в строку байтов
  • Функция callable(), проверяет можно ли вызвать объект
  • Функция chr(), число в символ Юникода
  • Класс classmethod, делает функцию методом класса
  • Функция compile() компилирует блок кода Python
  • Класс complex(), преобразует в комплексное число
  • Функция delattr(), удаляет атрибут объекта
  • Класс dict() создает словарь
  • Функция dir(), все атрибуты объекта
  • Функция divmod(), делит числа с остатком
  • Функция enumerate(), счетчик элементов последовательности
  • Функция eval(), выполняет строку-выражение с кодом
  • Функция exec(), выполняет блок кода
  • Функция filter(), фильтрует список по условию
  • Класс float(), преобразует в вещественное число
  • Функция format(), форматирует значение переменной
  • Класс frozenset(), преобразует в неизменяемое множество
  • Функция getattr(), значение атрибута по имени
  • Функция globals(), переменные глобальной области
  • Функция hasattr(), наличие атрибута объекта
  • Функция hash(), хэш-значение объекта
  • Функция help(), справка по любому объекту
  • Функция hex(), число в шестнадцатеричную строку
  • Функция id(), идентификатор объекта
  • Функция input(), ввод данных с клавиатуры
  • Класс int(), преобразует в тип int
  • Функция isinstance(), принадлежность экземпляра к классу
  • Функция issubclass(), проверяет наследование класса
  • Функция iter(), создает итератор
  • Функция len(), количество элементов объекта
  • Класс list(), преобразовывает в список
  • Функция locals(), переменные локальной области
  • Функция map(), обработка последовательности без цикла
  • Функция max(), максимальное значение элемента
  • Класс memoryview(), ссылка на буфер обмена
  • Функция min(), минимальное значение элемента
  • Функция next(), следующий элемент итератора
  • Класс object(), возвращает безликий объект
  • Функция oct(), число в восьмеричную строку
  • Функция open(), открывает файл на чтение/запись
  • Функция ord(), число символа Unicode
  • Функция pow(), возводит число в степень
  • Функция print(), печатает объект
  • Класс property(), метод класса как свойство
  • Класс range(), генерирует арифметические последовательности
  • Функция repr(), описание объекта
  • Функция reversed(), разворачивает последовательность
  • Функция round(), округляет число
  • Класс set(), создает или преобразовывает в множество
  • Функция setattr(), создает атрибут объекта
  • Класс slice(), шаблон среза
  • Функция sorted(), выполняет сортировку
  • Декоратор staticmethod(), метод класса в статический метод
  • Класс str(), преобразует объект в строку
  • Функция sum(), сумма последовательности
  • Функция super(), доступ к унаследованным методам
  • Класс tuple(), создает или преобразует в кортеж
  • Класс type(), возвращает тип объекта
  • Функция vars(), словарь переменных объекта
  • Функция zip(), объединить элементы в список кортежей
  • Функция __import__(), находит и импортирует модуль
  • Функция aiter(), создает асинхронный итератор
  • Функция anext(), следующий элемент асинхронного итератора

Свойства property. Декоратор @property

На этом занятии вы узнаете о более удобном способе работы с приватными атрибутами через специальный объект property, который переводится как свойство. О чем здесь речь? Давайте представим, что мы разрабатываем класс для хранения и обработки данных о персонале:

class Person: def __init__(self, name, old): self.__name = name self.__old = old

И для простоты, в нем будут сохраняться имя и возраст сотрудника в виде приватных атрибутов __name и __old. Разумеется, чтобы обращаться к таким закрытым данным, необходимы сеттеры и геттеры. Пропишем их для возраста:

def get_old(self): return self.__old def set_old(self, old): self.__old = old

Я, надеюсь, вы помните, для чего делают реализации классов с приватными свойствами, а затем, добавляют еще методы для работы с ними? Мы об этом с вами уже говорили. Да, это необходимо, чтобы не нарушалась внутренняя логика работы алгоритма класса, а взаимодействие с классом и его объектами извне осуществлялась бы только через разрешенные (публичные) методы и свойства. Если вам это не понятно, то посмотрите внимательнее предыдущие занятия, а я продолжу.

Итак, теперь можно создать экземпляр этого класса:

p = Person('Сергей', 20)

и через геттер и сеттер считывать и менять возраст сотрудника:

p.set_old(35) print(p.get_old())

Это все мы с вами уже умеем и знаем. Но здесь есть одна маленькая проблема. Нам нужно прописывать разные сеттеры и геттеры для разных приватных атрибутов экземпляров класса. Например, добавить еще два для имени. В результате, пользователю этого класса (программисту) придется запоминать и держать в голове названия имен всех этих сеттеров и геттеров. Как можно было бы упростить работу с таким классом? Один из способов – воспользоваться объектом property. Давайте посмотрим на конкретном нашем примере, как это можно сделать.

В самом классе Person мы пропишем атрибут и придумаем ему имя, допустим, old. Этот атрибут класса будет ссылаться на объект property, которому мы передадим ссылку на геттер и сеттер:

old = property(get_old, set_old)

Что у нас тут с вами получилось? Смотрите. Из каждого экземпляра класса мы совершенно спокойно можем обращаться к атрибуту класса old. Этот атрибут является объектом property. Данный объект так устроен, что при считывании данных он вызывает первый метод get_old, этот метод возвращает значение приватного локального свойства __old экземпляра класса p и именно это значение дальше возвращается атрибутом old. Поэтому переменная a будет ссылаться на значение текущего возраста сотрудника.

Если же мы обращаемся к атрибуту класса old и присваиваем ему какое-то значение:

p.old = 35

то автоматически вызывается второй метод set_old и в локальное свойство __old заносится значение, указанное после оператора присваивания. В итоге, в текущем объекте p меняется локальное свойство __old на новое.

Здесь у вас может возникнуть резонный вопрос, почему строчка:

p.old = 35

не создает новое локальное свойство внутри объекта p, как это у нас было ранее в программах, а обращается именно к атрибуту класса Person? Все дело в приоритете. Если в классе задан атрибут как объект-свойство, то в первую очередь выбирается оно, даже если в экземпляре класса есть локальное свойство с таким же именем. В этом легко убедиться. Давайте создадим свойство с именем old прямо в объекте p через словарь __dict__:

p.__dict__['old'] = 'old in object p'

А, затем выведем всю информацию в консоль:

print(p.old, p.__dict__)

Отображается значение 35, а не строка, то есть, было обращение именно к объекту-свойству old класса Person. А если свойству old в классе присвоить, какое-либо числовое значение, например, то будет отображена строка из объекта p. Здесь уже срабатывают знакомые нам приоритеты: сначала локальная область видимости объекта, затем, класса. Вот этот момент нужно хорошо знать, при работе с объектами-свойствами.

Итак, теперь у нас есть класс и мы можем менять приватное свойство __old экземпляров этого класса через единый атрибут old (считывать информацию и записывать). Это гораздо удобнее использования сеттеров и геттеров. Здесь всего один атрибут и через него естественным образом происходит взаимодействие с закрытым свойством __old.

Декоратор @property

Я, думаю, из этого примера вы хорошо поняли, как создается объект property и для чего он нужен. Однако, в нашей реализации есть некое функциональное дублирование: мы можем работать с приватным свойством __old и через сеттер/геттер и через свойство класса old. Конечно, это не критичный момент и на него можно не обращать внимания. Но, на мой взгляд было бы лучше, если бы у нас был один интерфейс взаимодействия со свойством __old. Как это можно сделать?

Смотрите, вот этот класс property позволяет нам на уровне его объектов, использовать функции-декораторы. Если в консоли прописать:

a = property()
  • a.getter() – декоратор для сеттера;
  • a.setter() – декоратор для геттера;
  • a.deleter() – декоратор для делитера.
old = property(get_old, set_old)

можно переписать и так:

old = property() old = old.setter(set_old) old = old.getter(get_old)

Это будет одно и то же. При вызове метода setter осуществляется встраиванием метода set_old в алгоритм работы объекта property. И то же самое делает метод getter только для геттера. В обоих случаях они возвращают ссылку на объект property, который мы должны сохранять. Так вот, мы можем использовать эти декораторы, чтобы сразу нужный нам метод класса превратить в объект-свойство property. Делается это очень просто. Перед геттером (обратите внимание, именно перед геттером, а не сеттером или делитером) прописывается декоратор:

@property def get_old(self): return self.__old

По идее, он теперь представляет объект-свойство с именем get_old:

print(p.get_old)

Но пока присваивание не работает:

p.get_old = 35

так как мы не прописали декоратор для сеттера. Делается это просто. Метод set_old нужно переименовать в get_old, чтобы имена совпадали (это обязательное условие) и перед ним прописать декоратор:

@get_old.setter def get_old(self, old): self.__old = old

Все, мы сформировали новый объект-свойство с именем get_old. Давайте его переименуем просто в old, а строчки ниже удалим, получим следующий класс Person:

class Person: def __init__(self, name, old): self.__name = name self.__old = old @property def old(self): return self.__old @old.setter def old(self, old): self.__old = old

То, что мы сделали, эквивалентно предыдущему варианту с тем лишь отличием, что теперь напрямую вызывать сеттер или геттер для локального свойства __old не получится. У нас остался один интерфейс взаимодействия – объект-свойство old. Именно так чаще всего делают на практике. В заключение этого занятия добавлю еще один метод делитер, который вызывается при удалении свойства:

@old.deleter def old(self): del self.__old

Теперь, если выполнить команду:

del p.old print(p.__dict__)

то сработает делитер и мы увидим, что локальное свойство __old было удалено. Конечно, его легко снова создать, достаточно выполнить присвоение, то есть, вызвать сеттер:

p.old = 10

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

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

Руководство с примерами использования функции property в языке программирования Python.

Введение

Функция property — создает вычисляемое свойство, которое позволяет использовать методы класса в качестве свойств объектов. Свойство — это аттрибут класса, который может считывать или записывать информацию. Использование property создает дескриптор, который позволяет создавать свойства объекту.

Проблема

Давайте рассмотрим пример. Существует класс человек, возраст которого не может быть меньше 0 и больше 120. Типичная ООП программа будет выглядеть примерно так:

class Human: """Человек, возраст которого не может быть больше 120 и меньше 0""" def __init__(self, age=0): self.set_age(age) def get_age(self): return self.age def set_age(self, age): if age < 120 and age >= 0: self.age = age else: self.age = 0

Как мы видим в этой реализации присутствуют некоторые проблемы и давайте их рассмотрим внимательно.

Создадим экземпляр 30 летнего человека, и проверим корректность введенных данных.

h = Human(age=30) print(h.get_age()) 30

Число 30 входит в рамки человеческой жизни и проблем у нас не возникло.

Попробуем ввести возраст вне рамок человеческой жизни

h.set_age(150) print(h.get_age()) 0

В данном примере мы обратились к методу класса, который проверил входные данные, из-за чего нам не удалось выставить некорректный возраст.

Но мы можем напрямую обратиться к аттрибуту self.age и указать абсолютно любой возраст.

h.age = 150 print(h.age) 150

Таким способом мы нарушаем алгоритм работы нашего класса, что является серьезной проблемой.

Решение

Для решения этой проблемы существуют два способа. Первый способ подразумевает использование декоратора функции, а второй способ указывает в функции property getter, setter и deleter.

Использование декоратора

@property — это декоратор, который обрабатывает получение, установку и удаление переменных класса так, как это было задумано в Python. Код для вышеописанного сценария будет выглядеть теперь так:

class Human: """Человек, возраст которого не может быть больше 120 и меньше 0""" def __init__(self, age=0): self.age = age @property def age(self): return self.__age @age.setter def age(self, age): if age < 120 and age >= 0: self.__age = age else: self.__age = 0

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

  • Мы использовали декоратор @property, который создает для декорируемой функции геттер.
  • Затем мы использовали декоратор @age.setter, который создает для декорируемой функции сеттер. Сеттер определяет, что делать при установке значения переменной.

Обратите внимание, что обе функции имеют одинаковое имя. Это необходимо для работы программы.

Попробуем установить некорректное значение и посмотрим что получится:

h = Human(age=30) h.age = 150 print(h.age) 0

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

Прямое указание функций

Во втором варианте использование property мы описываем функцию для установки значения и получения значения.

class Human: """Человек, возраст которого не может быть больше 120 и меньше 0""" def __init__(self, age=0): self.set_age(age) def get_age(self): return self.__age def set_age(self, age): if age < 120 and age >= 0: self.__age = age else: self.__age = 0 age = property(get_age, set_age)

Как видите, нам просто нужно было изменить переменные и добавить специальную строку age = property(get_age, set_age), которая залатала дыры в нашем исходном коде и сэкономила нам кучу времени!

h = Human(age=30) h.age = 150 print(h.age) 0

Все работает, как и ожидалось.

Вывод

В данном руководстве мы рассмотрели, как и для чего использовать property, воспользовались двумя вариантами реализации и проверили оба. Я не упомянул в статье о так называемых deleter, предлагаю вам сделать это самостоятельно.

Декоратор property#

Python позволяет создавать и изменять переменные экземпляров:

In [1]: class Robot: . : def __init__(self, name): . : self.name = name . : In [2]: bb8 = Robot('BB-8') In [3]: bb8.name Out[3]: 'BB-8' In [4]: bb8.name = 'R2D2' In [5]: bb8.name Out[5]: 'R2D2' 

Однако иногда нужно сделать так чтобы при изменении/установке значения переменной, проверялся ее тип или диапазон значений, также иногда необходимо сделать переменную неизменяемой и сделать ее доступной только для чтения. В некоторых языках программирования для этого используются методы get и set, например:

In [9]: class IPAddress: . : def __init__(self, address, mask): . : self._address = address . : self._mask = int(mask) . : . : def set_mask(self, mask): . : if not isinstance(mask, int): . : raise TypeError("Маска должна быть числом") . : if not mask in range(8, 32): . : raise ValueError("Маска должна быть в диапазоне от 8 до 32") . : self._mask = mask . : . : def get_mask(self): . : return self._mask . : In [10]: ip1 = IPAddress('10.1.1.1', 24) In [12]: ip1.set_mask(23) In [13]: ip1.get_mask() Out[13]: 23 

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

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

Пример создания property:

In [14]: class IPAddress: . : def __init__(self, address, mask): . : self._address = address . : self._mask = int(mask) . : . : @property . : def mask(self): . : return self._mask . : 

Теперь можно обращаться к mask как к обычной переменной:

In [15]: ip1 = IPAddress('10.1.1.1', 24) In [16]: ip1.mask Out[16]: 24 

Один из плюсов property — переменная становится доступной только для чтения:

In [17]: ip1.mask = 30 --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) ipython-input-17-e153170a5893> in module> ----> 1 ip1.mask = 30 AttributeError: can't set attribute' 

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

In [19]: class IPAddress: . : def __init__(self, address, mask): . : self._address = address . : self._mask = int(mask) . : . : @property . : def mask(self): . : return self._mask . : . : @mask.setter . : def mask(self, mask): . : if not isinstance(mask, int): . : raise TypeError("Маска должна быть числом") . : if not mask in range(8, 32): . : raise ValueError("Маска должна быть в диапазоне от 8 до 32") . : self._mask = mask . : In [20]: ip1 = IPAddress('10.1.1.1', 24) In [21]: ip1.mask Out[21]: 24 In [23]: ip1.mask = 30 In [24]: ip1.mask = 320 --------------------------------------------------------------------------- ValueError Traceback (most recent call last) ipython-input-24-8573933afac9> in module> ----> 1 ip1.mask = 320 ipython-input-19-d0e571cd5e2b> in mask(self, mask) 13 raise TypeError("Маска должна быть числом") 14 if not mask in range(8, 32): ---> 15 raise ValueError("Маска должна быть в диапазоне от 8 до 32") 16 self._mask = mask 17 ValueError: Маска должна быть в диапазоне от 8 до 32 

Пример использования property для динамического получения значения:

from base_ssh import BaseSSH import time class CiscoSSH(BaseSSH): def __init__(self, ip, username, password, enable_password, disable_paging=True): super().__init__(ip, username, password) self._ssh.send('enable\n') self._ssh.send(enable_password + '\n') if disable_paging: self._ssh.send('terminal length 0\n') time.sleep(1) self._ssh.recv(self._MAX_READ) self._cfg = None @property def cfg(self): if not self._cfg: self._cfg = self.send_show_command('sh run') return self._cfg 

При обращении к переменной cfg первый раз, на оборудовании выполняется команда sh run и записывается в переменную self._cfg, второй раз значение просто берется из переменной:

In [6]: r1 = CiscoSSH('192.168.100.1', 'cisco', 'cisco', 'cisco') In [7]: r1.cfg # тут возникает пауза Out[7]: 'sh run\r\nBuilding configuration. \r\n\r\nCurrent configuration : 2286 bytes\r\n!\r\nversion 15.2\r\n. ' In [8]: r1.cfg Out[8]: 'sh run\r\nBuilding configuration. \r\n\r\nCurrent configuration : 2286 bytes\r\n!\r\nversion 15.2\r\n. ' 

В этом примере property используется для создания переменной, которая отвечает за чтение/изменение основного IP-адреса:

import re import time from base_ssh import BaseSSH class CiscoSSH(BaseSSH): def __init__(self, ip, username, password, enable_password, disable_paging=True): super().__init__(ip, username, password) self._ssh.send('enable\n') self._ssh.send(enable_password + '\n') if disable_paging: self._ssh.send('terminal length 0\n') time.sleep(1) self._ssh.recv(self._MAX_READ) self._mgmt_ip = None def config_mode(self): self._ssh.send('conf t\n') time.sleep(0.5) result = self._ssh.recv(self._MAX_READ).decode('ascii') return result def exit_config_mode(self): self._ssh.send('end\n') time.sleep(0.5) result = self._ssh.recv(self._MAX_READ).decode('ascii') return result def send_config_commands(self, commands): result = self.config_mode() result += super().send_config_commands(commands) result += self.exit_config_mode() return result @property def mgmt_ip(self): if not self._mgmt_ip: loopback0 = self.send_show_command('sh run interface lo0') self._mgmt_ip = re.search('ip address (\S+) ', loopback0).group(1) return self._mgmt_ip @mgmt_ip.setter def mgmt_ip(self, new_ip): if self.mgmt_ip != new_ip: self.send_config_commands([f'interface lo0', f'ip address new_ip> 255.255.255.255']) self._mgmt_ip = new_ip 

Теперь при чтении переменной mgmt_ip считывается конфиг или читается переменная _mgmt_ip, а при записи адрес перенастраивается на оборудовании:

In [19]: r1 = CiscoSSH('192.168.100.1', 'cisco', 'cisco', 'cisco') In [22]: r1.mgmt_ip Out[22]: '4.4.4.4' In [23]: r1.mgmt_ip = '10.4.4.4' In [24]: r1.mgmt_ip Out[24]: '10.4.4.4' In [27]: print(r1.send_show_command('sh run interface lo0')) sh run interface lo0 Building configuration. Current configuration : 64 bytes ! interface Loopback0 ip address 10.4.4.4 255.255.255.255 end R1#

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *