Разное

Python 3 property: Python. Функция property() | Way23

Содержание

Python. Функция property() | Way23

Перевод статьи Python | property() function.

Функция property() создаёт новое свойство. Свойство — это атрибут класса с которым связаны функции чтения и записи.

property(fget, fset, fdel, doc)



property(fget, fset, fdel, doc)

Параметры:

  • fget() – используется для получения значения атрибута
  • fset() – используется для установки значения атрибута
  • fdel() – используется для удаления атрибута
  • doc() – строка с документацией (docstring) для атрибута

Функция property() возвращает свойство с данными геттером, сеттером и deleter.

Вызванная без аргументов функция property() возвращает свойство без геттера, сеттера и deleter.

Если не задан doc то property() берёт docstring из геттера.

# Пример использования функции property()

class Alphabet:
def __init__(self, value):
self._value = value

def getValue(self):
print(‘Getting value’)
return self._value

def setValue(self, value):
print(‘Setting value to ‘ + value)
self._value = value

def delValue(self):
print(‘Deleting value’)
del self._value

value = property(getValue, setValue, delValue, )

# passing the value
x = Alphabet(‘GeeksforGeeks’)
print(x.value)

x.value = ‘GfG’

del x.value


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

# Пример использования функции property()

 

class Alphabet:

    def __init__(self, value):

        self._value = value

 

    def getValue(self):

        print(‘Getting value’)

        return self._value

 

    def setValue(self, value):

        print(‘Setting value to ‘ + value)

        self._value = value

 

    def delValue(self):

        print(‘Deleting value’)

        del self._value

 

    value = property(getValue, setValue, delValue, )

 

# passing the value

x = Alphabet(‘GeeksforGeeks’)

print(x.value)

 

x.value = ‘GfG’

 

del x.value

Output:

Getting value
GeeksforGeeks
Setting value to GfG
Deleting value



Output:

 

Getting value

GeeksforGeeks

Setting value to GfG

Deleting value

Декоратор

При использовании декоратора сперва укажите что метод value() является свойством класса Alphabet, затем используя имя свойства (value) задайте сеттер, геттер и deleter. Результат работы декоратора @property аналогичен функции property():

# Пример использования декоратора @property

class Alphabet:
def __init__(self, value):
self._value = value

@property
def value(self):
print(‘Getting value’)
return self._value

@value.setter
def value(self, value):
print(‘Setting value to ‘ + value)
self._value = value

@value.deleter
def value(self):
print(‘Deleting value’)
del self._value

# passing the value
x = Alphabet(‘Peter’)
print(x.value)

x.value = ‘Diesel’

del x.value


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

# Пример использования декоратора @property

 

class Alphabet:

    def __init__(self, value):

        self._value = value

 

    @property

    def value(self):

        print(‘Getting value’)

        return self._value

 

    @value.setter

    def value(self, value):

        print(‘Setting value to ‘ + value)

        self._value = value

 

    @value.deleter

    def value(self):

        print(‘Deleting value’)

        del self._value

 

# passing the value

x = Alphabet(‘Peter’)

print(x.value)

 

x.value = ‘Diesel’

 

del x.value

Output:

Getting value
Peter
Setting value to Diesel
Deleting value



Output:

 

Getting value

Peter

Setting value to Diesel

Deleting value

Декораторы часто применяются чтобы добавить новые функции в уже существующий код. Это называется метапрограммирование: одна часть программы изменяют другую часть программы во время компиляции. Функцию property() (и декоратор) позволяет изменить наш класс (например, добавить ограничения значений свойства) без необходимости менять код использующий наш класс.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Про Python — Справочник — property (свойство)

«Вычисляемое» свойство.

Позволяет использовать методы в качестве свойств объектов — порождает дескриптор, позволяющий создавать «вычисляемые» свойства (тип property).

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

    class Mine(object):

def __init__(self):
self._x = None

def get_x(self):
return self._x

def set_x(self, value):
self._x = value

def del_x(self):
self._x = 'No more'

x = property(get_x, set_x, del_x, 'Это свойство x.')

type(Mine.x) # property
mine = Mine()
mine.x # None
mine.x = 3
mine.x # 3
del mine.x
mine.x # No more

Используя функцию в качестве декоратора можно легко создавать вычисляемые свойства только для чтения:

    class Mine(object):

def __init__(self):
self._x = 'some value'

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

mine = Mine()
mine.prop # some value
mine.prop = 'other value' # AttributeError
del mine.prop # AttributeError

+py2.6 Объект свойства также предоставляет методы getter, setter, deleter, которые можно использовать в качестве декораторов для указания функций реализующих получение, установку и удаление свойства соответственно. Следующий код эквивалентен коду из первого примера:

    class Mine(object):

def __init__(self):
self._x = None

x = property()

@x.getter
def x(self):
"""Это свойство x."""
return self._x

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

@x.deleter
def x(self):
self._x = 'No more'

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

+py3.5 Добавлена возможность установки строки документации для объекта-свойства.

Синонимы поиска: property (свойство), свойства, свойство, @property, properties

ООП Python 3: объекты свойства (property)

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

coordX

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

pt.coordX = 100 # запись значения
x = pt.coordX   # чтение значения

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

def __getCoordX(self):
    print("вызов __getCoordX")
    return self.__x
 
def __setCoordX(self, x):
    print("вызов __setCoordX")
    self.__x = x

То есть, при
вызове геттера мы возвращаем значение приватного свойства__x, а при вызове
сеттера – заносим новое значение в этот атрибут. На основе этих приватных
методов создаем свойство через специальный класс property:

coordX = property(__getCoordX, __setCoordX)

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

pt = Point()
print( pt.coordX )
pt.coordX = 100
print( pt.coordX )

Видите, это
намного удобнее, чем вызывать отдельно сеттеры и геттеры.

Теперь уберем из
геттера и сеттера функцию print и добавим
проверку на корректность передаваемых данных:

def __getCoordX(self):
    return self.__x
 
def __setCoordX(self, x):
    if Point.__checkValue(x):
        self.__x = x
    else:
        raise ValueError("Неверный формат данных")

Теперь, если
попытаться передать не числовое значение:

то возникнет
исключение ValueError.

У свойства может
быть еще один метод, вызываемый при его удалении:

def __delCoordX(self):
    print("Удаление свойства")
    del self.__x

Он указывается
третьим параметром при вызове класса property, а четвертым
можно указать описание свойства:

coordX = property(__getCoordX, __setCoordX, __delCoordX, "Работа с X")

Если теперь
выполнить удаление свойства:

то увидим
сообщение «Удаление свойства» и дальнейшая попытка к его обращению:

приведет к
ошибке, т.к. приватного свойства __x уже не существует. Вот так
работают и создаются свойства в Python. И давайте здесь же я вам покажу
еще один способ объявления свойств через декораторы (если вы не знаете что
такое декоратор, то смотрите урок по этой теме – ссылка
под этим видео).

Перед геттером
мы укажем декоратор

И название
метода должно совпадать с названием свойства:

Далее, у сеттера
указываем декоратор с то же имя метода:

@coordX.setter
def coordX(self, x):

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

@coordX.deleter
def coordX(self):

Все, теперь мы
абсолютно также можем работать со свойством coordX:

pt = Point()
print( pt.coordX )
pt.coordX = 100
print( pt.coordX )
del pt.coordX

Хорошо, свойство
coordX у нас есть. Но
нам нужно создать еще одно – coordY. Как вы понимаете, это,
фактически, приведет к дублированию кода для coordX, что не есть
хорошо, так как нарушает принцип программирования

DRY
(Don’t Repeat Yourself) – не повторяйся!

И здесь нам на
помощь приходит другой механизм Python – дескрипторы, о котором речь пойдет на следующем занятии.

Python. Свойства | Way23

Перевод статьи Python @property.

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

Начальный пример

Представим что вы решили сделать класс хранящий температуру в градусах Цельсия. Он также должен реализовывать метод для конвертации температуры в градусы Фаренгейта. Реализуем класс так:

class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature

def to_fahrenheit(self):
return (self.temperature * 1.8) + 32



class Celsius:

    def __init__(self, temperature = 0):

        self.temperature = temperature

 

    def to_fahrenheit(self):

        return (self.temperature * 1.8) + 32

Затем создаём объект этого класса и меняем значение температуры как пожелаем:

>>> # создание нового объекта
>>> man = Celsius()
>>> # установка температуры
>>> man.temperature = 37
>>> # получение значения температуры
>>> man.temperature
37
>>> # конвертация в градусы Фаренгейта
>>> man.to_fahrenheit()
98.60000000000001



>>> # создание нового объекта

>>> man = Celsius()

>>> # установка температуры

>>> man.temperature = 37

>>> # получение значения температуры

>>> man.temperature

37

>>> # конвертация в градусы Фаренгейта

>>> man.to_fahrenheit()

98.60000000000001

Дополнительные десятичные разряды при конвертации в градусы Фаренгейта происходят из-за арифметической ошибки с плавающей запятой (попробуйте сложить 1.1 + 2.2 в интерпретаторе).

Когда мы присваиваем или извлекаем атрибут объекта, такой как temperature, Python ищет его в словаре объекта __dict__.

>>> man.__dict__
{‘temperature’: 37}



>>> man.__dict__

{‘temperature’: 37}

Внутри интерпретатора man.temperature становится man.__dict__['temperature'].

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

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

Очевидным решением будет скрыть атрибут temperature (сделать его приватным) и определить интерфейс в виде геттера и сеттера для управления полем.

class Celsius:
def __init__(self, temperature = 0):
self.set_temperature(temperature)

def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32

# new update
def get_temperature(self):
return self._temperature

def set_temperature(self, value):
if value < -273:
raise ValueError(«Temperature below -273 is not possible»)
self._temperature = value



class Celsius:

    def __init__(self, temperature = 0):

        self.set_temperature(temperature)

 

    def to_fahrenheit(self):

        return (self.get_temperature() * 1.8) + 32

 

    # new update

    def get_temperature(self):

        return self._temperature

 

    def set_temperature(self, value):

        if value < -273:

            raise ValueError(«Temperature below -273 is not possible»)

        self._temperature = value

Добавлены методы get_temperature() и set_temperature(), имя поле temperature заменено на _temperature. Подчёркивание в начале имени используется для обозначения приватных переменных в Python.

>>> c = Celsius(-277)
Traceback (most recent call last):

ValueError: Temperature below -273 is not possible
>>> c = Celsius(37)
>>> c.get_temperature()
37
>>> c.set_temperature(10)
>>> c.set_temperature(-300)
Traceback (most recent call last):

ValueError: Temperature below -273 is not possible



>>> c = Celsius(-277)

Traceback (most recent call last):

ValueError: Temperature below -273 is not possible

>>> c = Celsius(37)

>>> c.get_temperature()

37

>>> c.set_temperature(10)

>>> c.set_temperature(-300)

Traceback (most recent call last):

ValueError: Temperature below -273 is not possible

Изменения успешно реализуют новые ограничения. Мы больше не можем установить температуру ниже -273.

В языке Python нет приватных переменных. Существуют нормы которым придерживаются разработчики, но язык сам не применяет ограничения.

>>> c._temperature = -300
>>> c.get_temperature()
-300



>>> c._temperature = -300

>>> c.get_temperature()

-300

Но это не является большой проблемой. Гораздо хуже то что все клиенты использующие предыдущую версию класса должны изменить свой код с obj.temperature на obj.get_temperature() и все присвоения obj.temperature = val на obj.set_temperature(val). Наше обновление не поддерживает обратную совместимость. Свойства помогают решить эту проблему.

Свойства

Добавление ограничений в класс в стиле Python:

class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature

def to_fahrenheit(self):
return (self.temperature * 1.8) + 32

def get_temperature(self):
print(«Getting value»)
return self._temperature

def set_temperature(self, value):
if value < -273:
raise ValueError(«Temperature below -273 is not possible»)
print(«Setting value»)
self._temperature = value

temperature = property(get_temperature,set_temperature)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

class Celsius:

    def __init__(self, temperature = 0):

        self.temperature = temperature

 

    def to_fahrenheit(self):

        return (self.temperature * 1.8) + 32

 

    def get_temperature(self):

        print(«Getting value»)

        return self._temperature

 

    def set_temperature(self, value):

        if value < -273:

            raise ValueError(«Temperature below -273 is not possible»)

        print(«Setting value»)

        self._temperature = value

 

    temperature = property(get_temperature,set_temperature)

Мы добавили функции print() внутрь get_temperature() и set_temperature() чтобы ясно видеть когда они запускаются.

Последняя строка кода создаёт объект свойства temperature. Свойство присоединяет некоторый код (get_temperature и set_temperature) к атрибутту класса (temperature).

Код извлекающий значение из temperature автоматически вызывает get_temperature() вместо поиска по словарю (__dict__). Таким же образом, код присваивающий значение temperature автоматически вызовет set_temperature().

>>> c = Celsius()
Setting value



>>> c = Celsius()

Setting value

После создания объекта появляется сообщение из метода set_temperature(). Это происходит потому что при создании объекта вызывается метод __init__(), а в нём приходит присвоение self.temperature = temperature. Присвоение автоматически вызывает set_temperature().

Пользовательские атрибуты в Python / Хабр

Вы когда нибудь задумывались о том, что происходит, когда вы ставите точку в python? Что скрывает за собой символ str(“\u002E”)? Какие тайны он хранит? Если без мистики, вы знаете как происходит поиск и установка значений пользовательских атрибутов в python? Хотели бы узнать? Тогда… добро пожаловать!
Чтобы время, проведённое за чтением прошло легко, приятно и с пользой, было бы неплохо знать несколько базовых понятий языка. В частности, понимание type и object будут исключительно полезны, так же как знание нескольких примеров обеих сущностей. Почитать о них можно, в том числе, здесь.
Немного о терминологии, которую я использую, прежде чем мы приступим к тому, ради чего собрались:

  • Объект есть любая сущность в python (функция, число, строка… словом, всё).
  • Класс это объект, чьим типом является type (тип можно подсмотреть в атрибуте __class__).
  • Экземпляр некоторого класса A — это объект, у которого в атрибуте __class__ есть ссылка на класс A.

Ах, да, все примеры в статье написаны на python3! Это определённо следует учесть.
Если ничто из вышесказанного не смогло умерить ваше желание узнать, что там будет дальше, приступим!

__dict__

Атрибуты объекта можно условно разделить две группы: определённые python-ом (такие как __class__, __bases__) и определённые пользователем, о них я как раз собираюсь рассказать. __dict__ согласно этой классификации, относится к “системным” (определённым python-ом) атрибутам. Его задача — хранить пользовательские атрибуты. Он представляет собой dictionary, в котором ключом является имя_атрибута, значением, соответственно, значение_атрибута.
Чтобы найти атрибут объекта o, python обыскивает:

  1. Сам объект (o.__dict__ и его системные атрибуты).
  2. Класс объекта (o.__class__.__dict__). Только __dict__ класса, не системные атрибуты.
  3. Классы, от которых унасаледован класс объекта (o.__class__.__bases__.__dict__).

Таким образом, с помощью __dict__ атрибут может быть определён как для конкретного экземпляра, так и для класса (то есть для всех объектов, которые являются экземплярами данного класса).

class StuffHolder:
    stuff = "class stuff"

a = StuffHolder()
b = StuffHolder()
a.stuff     # "class stuff"
b.stuff     # "class stuff"

b.b_stuff = "b stuff"
b.b_stuff   # "b stuff"
a.b_stuff   # AttributeError

В примере описан класс StuffHolder с одним атрибутом stuff, который, наследуют оба его экземпляра. Добавление объекту b атрибута b_stuff, никак не отражается на a.
Посмотрим на __dict__ всех действующих лиц:

StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}
a.__dict__              # {}
b.__dict__              # {'b_stuff': 'b stuff'}

a.__class__             # <class '__main__.StuffHolder'>
b.__class__             # <class '__main__.StuffHolder'>

(У класса StuffHolder в __dict__ хранится объект класса dict_proxy с кучей разного барахла, на которое пока не нужно обращать внимание).

Ни у a ни у b в __dict__ нет атрибута stuff, не найдя его там, механизм поиска ищет его в __dict__ класса (StuffHolder), успешно находит и возвращает значение, присвоенное ему в классе. Ссылка на класс хранится в атрибуте __class__ объекта.

Поиск атрибута происходит во время выполнения, так что даже после создания экземпляров, все изменения в __dict__ класса отразятся в них:

a.new_stuff                 # AttributeError
b.new_stuff                 # AttributeError

StuffHolder.new_stuff = "new"
StuffHolder.__dict__        # {... 'stuff': 'class stuff', 'new_stuff': 'new'...}
a.new_stuff                 # "new"
b.new_stuff                 # "new"

В случае присваивания значения атрибуту экземпляра, изменяется только __dict__ экземпляра, то есть значение в __dict__ класса остаётся неизменным (в случае, если значением атрибута класса не является data descriptor):

StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}
c = StuffHolder()
c.__dict__              # {}

c.stuff = "more c stuff"
c.__dict__              # {'stuff': 'more c stuff'}
StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}

Если имена атрибутов в классе и экземпляре совпадают, интерпретатор при поиске значения выдаст значение экземпляра (в случае, если значением атрибута класса не является data descriptor):

StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}
d = StuffHolder()
d.stuff                 # "class stuff"

d.stuff = "d stuff"
d.stuff                 # "d  stuff"

По большому счёту это всё, что можно сказать про __dict__. Это хранилище атрибутов, определённых пользователем. Поиск в нём производится во время выполнения и при поиске учитывается __dict__ класса объекта и базовых классов. Также важно знать, что есть несколько способов переопределить это поведение. Одним из них является великий и могучий Дескриптор!

Дескрипторы

С простыми типами в качестве значений атрибутов пока всё ясно. Посмотрим, как ведёт себя функция в тех же условиях:

class FuncHolder:
    def func(self):
        pass
fh = FuncHolder()

FuncHolder.func     # <function func at 0x8f806ac>
FuncHolder.__dict__ # {...'func': <function func at 0x8f806ac>...}
fh.func             # <bound method FuncHolder.func of <__main__.FuncHolder object at 0x900f08c>>

WTF!? Спросите вы… возможно. Я бы спросил. Чем функция в этом случае отличается от того, что мы уже видели? Ответ прост: методом __get__.

FuncHolder.func.__class__.__get__   # <slot wrapper '__get__' of 'function' objects>

Этот метод переопределяет механизм получения значения атрибута func экземпляра fh, а объект, который реализует этот метод непереводимо называется non-data descriptor.

Из howto:

Дескриптор — это объект, доступ к которому через атрибут переопределён методами в дескриптор протоколе:

descr.__get__(self, obj, type=None) --> value   (переопределяет способ получения значения атрибута)
descr.__set__(self, obj, value) --> None        (переопределяет способ присваивания значения атрибуту)
descr.__delete__(self, obj) --> None            (переопределяет способ удаления атрибута)

Дескрипторы бывают двух видов:

  1. Data Descriptor (дескриптор данных) — объект, который реализует метод __get__() и __set__()
  2. Non-data Descriptor (дескриптор не данных?) — объект, который реализует метод __get__()

Отличаются они своим поведением по отношению к записям в __dict__ экземпляра. Если в __dict__ есть запись с тем же именем, что у дескриптора данных, у дескриптора преимущество. Если имя записи совпадает с именем “дескриптора не данных”, приоритет записи в __dict__ выше.

Дескрипторы данных

Рассмотрим повнимательней дескриптор данных:

class DataDesc:

    def __get__(self, obj, cls):
        print("Trying to access from {0} class {1}".format(obj, cls))

    def __set__(self, obj, val):
        print("Trying to set {0} for {1}".format(val, obj))

    def __delete__(self, obj):
        print("Trying to delete from {0}".format(obj))

class DataHolder:
        data = DataDesc()
d = DataHolder()

DataHolder.data # Trying to access from None class <class '__main__.DataHolder'>
d.data          # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'>
d.data = 1      # Trying to set 1 for <__main__.DataHolder object at ...>
del(d.data)     # Trying to delete from <__main__.DataHolder object at ...>

Стоит обратить внимание, что вызов DataHolder.data передаёт в метод __get__ None вместо экземпляра класса.
Проверим утверждение о том, что у дата дескрипторов преимущество перед записями в __dict__ экземпляра:

d.__dict__["data"] = "override!"
d.__dict__  # {'data': 'override!'}
d.data      # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'>

Так и есть, запись в __dict__ экземпляра игнорируется, если в __dict__ класса экземпляра (или его базового класса) существует запись с тем же именем и значением — дескриптором данных.

Ещё один важный момент. Если изменить значение атрибута с дескриптором через класс, никаких методов дескриптора вызвано не будет, значение изменится в __dict__ класса как если бы это был обычный атрибут:

DataHolder.__dict__ # {...'data': <__main__.DataDesc object at ...>...}
DataHolder.data = "kick descriptor out"
DataHolder.__dict__ # {...'data': 'kick descriptor out'...}
DataHolder.data     # "kick descriptor out"
Дескрипторы не данных

Пример дескриптора не данных:

class NonDataDesc:

    def __get__(self, obj, cls):
        print("Trying to access from {0} class {1}".format(obj, cls))

class NonDataHolder:
    non_data = NonDataDesc()
n = NonDataHolder()

NonDataHolder.non_data  # Trying to access from None class <class '__main__.NonDataHolder'>
n.non_data              # Trying to access from <__main__.NonDataHolder object at ...> class <class '__main__.NonDataHolder'>
n.non_data = 1
n.non_data              # 1
n.__dict__              # {'non_data': 1}

Его поведение слегка отличается от того, что вытворял дата-дескриптор. При попытке присвоить значение атрибуту non_data, оно записалось в __dict__ экземпляра, скрыв таким образом дескриптор, который хранится в __dict__ класса.

Примеры использования

Дескрипторы это мощный инструмент, позволяющий контролировать доступ к атрибутам экземпляра класса. Один из примеров их использования — функции, при вызове через экземпляр они становятся методами (см. пример выше). Также распространённый способ применения дескрипторов — создание свойства (property). Под свойством я подразумеваю некое значение, характеризующее состояние объекта, доступ к которому управляется с помощью специальных методов (геттеров, сеттеров). Создать свойство просто с помощью дескриптора:

class Descriptor:
    def __get__(self, obj, type):
        print("getter used")
    def __set__(self, obj, val):
        print("setter used")
    def __delete__(self, obj):
        print("deleter used")

class MyClass:
    prop = Descriptor()

Или можно воспользоваться встроенным классом property, он представляет собой дескриптор данных. Код, представленный выше можно переписать следующим образом:

class MyClass:

    def _getter(self):
        print("getter used")
    def _setter(self, val):
        print("setter used")
    def _deleter(self):
        print("deleter used")

    prop = property(_getter, _setter, _deleter, "doc string")

В обоих случаях мы получим одинаковое поведение:

m = MyClass()
m.prop          # getter used
m.prop = 1      # setter used
del(m.prop)     # deleter used

Важно знать, что property всегда является дескриптором данных. Если в его конструктор не передать какую либо из функций (геттер, сеттер или делитер), при попытке выполнить над атрибутом соответствующее действие — выкинется AttributeError.

class MySecondClass:
    prop = property()

m2 = MySecondClass()
m2.prop     # AttributeError: unreadable attribute
m2.prop = 1 # AttributeError: can't set attribute
del(m2)     # AttributeError: can't delete attribute

К встроенным дескрипторам также относятся:

  • staticmethod — то же, что функция вне класса, в неё не передаётся экземпляр в качестве первого аргумента.
  • classmethod — то же, что метод класса, только в качестве первого аргумента передаётся класс экземпляра.
class StaticAndClassMethodHolder:

    def _method(*args):
        print("_method called with ", args)
    static = staticmethod(_method)
    cls = classmethod(_method)

s = StaticAndClassMethodHolder()
s._method()     # _method called with (<__main__.StaticAndClassMethodHolder object at ...>,)
s.static()      # _method called with ()
s.cls()         # _method called with (<class '__main__.StaticAndClassMethodHolder'>,)
__getattr__(), __setattr__(), __delattr__() и __getattribute__()

Если нужно определить поведение какого-либо объекта как атрибута, следует использовать дескрипторы (например property). Тоже справедливо для семейства объектов (например функций). Ещё один способ повлиять на доступ к атрибутам: методы __getattr__(), __setattr__(), __delattr__() и __getattribute__(). В отличие от дескрипторов их следует определять для объекта, содержащего атрибуты и вызываются они при доступе к любому атрибуту этого объекта.

__getattr__(self, name) будет вызван в случае, если запрашиваемый атрибут не найден обычным механизмом (в __dict__ экземпляра, класса и т.д.):

class SmartyPants:
    def __getattr__(self, attr):
        print("Yep, I know", attr)
    tellme = "It's a secret"

smarty = SmartyPants()
smarty.name = "Smartinius Smart"

smarty.quicksort    # Yep, I know quicksort
smarty.python       # Yep, I know python
smarty.tellme       # "It's a secret"
smarty.name         # "Smartinius Smart"

__getattribute__(self, name) будет вызван при попытке получить значение атрибута. Если этот метод переопределён, стандартный механизм поиска значения атрибута не будет задействован. Следует иметь ввиду, что вызов специальных методов (например __len__(), __str__()) через встроенные функции или неявный вызов через синтаксис языка осуществляется в обход __getattribute__().

class Optimist:
    attr = "class attribute"

    def __getattribute__(self, name):
        print("{0} is great!".format(name))

    def __len__(self):
        print("__len__ is special")
        return 0

o = Optimist()
o.instance_attr = "instance"

o.attr          # attr is great!
o.dark_beer     # dark_beer is great!
o.instance_attr # instance_attr is great!
o.__len__       # __len__ is great!
len(o)          # __len__ is special\n 0

__setattr__(self, name, value) будет вызван при попытке установить значение атрибута экземпляра. Аналогично __getattribute__(), если этот метод переопределён, стандартный механизм установки значения не будет задействован:

class NoSetters:
    attr = "class attribute"
    def __setattr__(self, name, val):
        print("not setting {0}={1}".format(name,val))

no_setters = NoSetters()
no_setters.a = 1            # not setting a=1
no_setters.attr = 1         # not setting attr=1
no_setters.__dict__         # {}
no_setters.attr             # "class attribute"
no_setters.a                # AttributeError

__delattr__(self, name) — аналогичен __setattr__(), но используется при удалении атрибута.

При переопределении __getattribute__(), __setattr__() и __delattr__() следует иметь ввиду, что стандартный способ получения доступа к атрибутам можно вызвать через object:

class GentleGuy:
    def __getattribute__(self, name):
        if name.endswith("_please"):
            return object.__getattribute__(self, name.replace("_please", ""))
        raise AttributeError("And the magic word!?")

gentle = GentleGuy()

gentle.coffee = "some coffee"
gentle.coffee           # AttributeError
gentle.coffee_please    # "some coffee"
Соль

Итак, чтобы получить значение атрибута attrname экземпляра a в python:

  1. Если определён метод a.__class__.__getattribute__(), то вызывается он и возвращается полученное значение.
  2. Если attrname это специальный (определённый python-ом) атрибут, такой как __class__ или __doc__, возвращается его значение.
  3. Проверяется a.__class__.__dict__ на наличие записи с attrname. Если она существует и значением является дескриптор данных, возвращается результат вызова метода __get__() дескриптора. Также проверяются все базовые классы.
  4. Если в a.__dict__ существует запись с именем attrname, возвращается значение этой записи. Если a — это класс, то атрибут ищется и среди его базовых классов и, если там или в __dict__ a дескриптор данных — возвращается результат __get__() дескриптора.
  5. Проверяется a.__class__.__dict__, если в нём существует запись с attrname и это “дескриптор не данных”, возвращается результат __get__() дескриптора, если запись существует и там не дескриптор, возвращается значение записи. Также обыскиваются базовые классы.
  6. Если существует метод a.__class__.__getattr__(), он вызывается и возвращается его результат. Если такого метода нет — выкидывается AttributeError.

Чтобы установить значение value атрибута attrname экземпляра a:

  1. Если существует метод a.__class__.__setattr__(), он вызывается.
  2. Проверяется a.__class__.__dict__, если в нём есть запись с attrname и это дескриптор данных — вызывается метод __set__() дескриптора. Также проверяются базовые классы.
  3. В a.__dict__ добавляется запись value с ключом attrname.
__slots__

Как пишет Guido в своей истории python о том, как изобретались new-style classes:

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

На случай, если пользователи разочаруются ухудшением производительности, заботливые разработчики python придумали __slots__.
Наличие __slots__ ограничивает возможные имена атрибутов объекта теми, которые там указаны. Также, так как все имена атрибутов теперь заранее известны, снимает необходимость создавать __dict__ экземпляра.

class Slotter:
    __slots__ = ["a", "b"]

s = Slotter()
s.__dict__      # AttributeError
s.c = 1         # AttributeError
s.a = 1
s.a             # 1
s.b = 1
s.b             # 1
dir(s)          # [ ... 'a', 'b' ... ]

Оказалось, что опасения Guido не оправдались, но к тому времени, как это стало ясно, было уже слишком поздно. К тому же, использование __slots__ действительно может увеличить производительность, особенно уменьшив количество используемой памяти при создании множества небольших объектов.

Заключение

Доступ к атрибутом в python можно контролировать огромным количеством способов. Каждый из них решает свою задачу, а вместе они подходят практически под любой мыслимый сценарий использования объекта. Эти механизмы — основа гибкости языка, наряду с множественным наследованием, метаклассами и прочими вкусностями. У меня ушло некоторое время на то, чтобы разобраться, понять и, главное, принять это множество вариантов работы атрибутов. На первый взгляд оно показалось слегка избыточным и не особенно логичным, но, учитывая, что в ежедневном программировании это редко пригодиться, приятно иметь в своём арсенале такие мощные инструменты.
Надеюсь, и вам эта статья прояснила парочку моментов, до которых руки не доходили разобраться. И теперь, с огнём в глазах и уверенностью в Точке, вы напишите огромное количество наичистейшего, читаемого и устойчивого к изменениям требований кода! Ну или комментарий.

Спасибо за ваше время.

Ссылки
  1. Shalabh Chaturvedi. Python Attributes and Methods
  2. Guido Van Rossum. The Inside Story on New-Style Classes
  3. Python documentation

UPD: Полезный линк от пользователя leron: Python Data Model

Объектно-ориентированное Программирование в Python

Объектно-ориентированное программирование (ООП) — это парадигма программирования, где различные компоненты компьютерной программы моделируются на основе реальных объектов. Объект — это что-либо, у чего есть какие-либо характеристики и то, что может выполнить какую-либо функцию.

Содержание

Представьте сценарий, где вам нужно разработать болид Формулы-1 используя подход объектно-ориентированного программирования. Первое, что вам нужно сделать — это определить реальные объекты в настоящей гонке Формула-1. Какие аспекты в Формуле-1 обладают определенными характеристиками и могут выполнять ту или иную функцию?

Есть вопросы по Python?

На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!

Telegram Чат & Канал

Вступите в наш дружный чат по Python и начните общение с единомышленниками! Станьте частью большого сообщества!

Паблик VK

Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!


Один из очевидных ответов на этот вопрос — гоночный болид. Условный болид может обладать такими характеристиками как:

  • мощность двигателя;
  • марка;
  • модель;
  • производитель, и т. д.

Соответственно, болид можно запустить, остановить, ускорить, и так далее. Гонщик может быть еще одним объектом в Формуле-1. Гонщик имеет национальность, возраст, пол, и так далее, кроме этого, он обладает таким функционалом, как управление болидом, рулевое управление, переключение передач.

Как и в этом примере, в объектно-ориентированном программировании мы создадим объекты, которые будут соответствовать реальным аспектам.

Стоит обратить внимание на то, что объектно-ориентированное программирование — не зависящая от языка программирования концепция. Это общая концепция программирования и большинство современных языков, такие как Java, C#, C++ и Python поддерживают объектно-ориентированное программирование.

В этой статье мы разберем подробную инструкцию объектно-ориентированного программирования в Python, но перед этим, рассмотрим некоторые преимущества и недостатки объектно-ориентированного программирования.

Преимущества и недостатки ООП Python

Рассмотрим несколько основных преимуществ объектно-ориентированного программирования:

  1. Объектно-ориентированное программирование подразумевает повторное использование. Компьютерная программа написанная в форме объектов и классов может быть использована снова в других проектах без повторения кода;
  2. Использование модулярного подхода в объектно-ориентированном программировании позволяет получить читаемый и гибкий код;
  3. В объектно-ориентированном программировании каждый класс имеет определенную задачу. Если ошибка возникнет в одной части кода, вы можете исправить ее локально, без необходимости вмешиваться в другие части кода;
  4. Инкапсуляция данных (которую мы рассмотрим дальше в статье) вносит дополнительный уровень безопасности в разрабатываемую программу с использованием объектно-ориентированного подхода;

Хотя объектно-ориентированное программирование обладает рядом преимуществ, оно также содержит определенные недостатки, некоторые из них находятся в списке ниже:

  1. Для создания объектов необходимо иметь подробное представление о разрабатываемом программном обеспечении;
  2. Не каждый аспект программного обеспечения является лучшим решением для реализации в качестве объекта. Для новичков может быть тяжело прочертить линию в золотой середине;
  3. С тем, как вы вносите все новые и новые классы в код, размер и сложность программы растет в геометрической прогрессии;

В следующем разделе мы рассмотрим ряд самых важных концепций объектно-ориентированного программирования.

Как и следует из названия, объектно-ориентированное программирование — это речь об объектах. Однако, перед тем как создать объект, нам нужно определить его класс.

Класс

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

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

Отношение между классом и объектом можно представить более наглядно, взглянув на отношение между машиной и Audi. Да, Audi – это машина. Однако, нет такой вещи, как просто машина. Машина — это абстрактная концепция, которую также реализуют в Toyota, Honda, Ferrari, и других компаниях.

Ключевое слово class используется для создания класса в Python. Название класса следует за ключом class, за которым следует двоеточие. Тело класса начинается с новой строки, с отступом на одну вкладку влево.

Давайте рассмотрим, как мы можем создать самый простой класс в Python. Взглянем на следующий код:

# Создаем класс Car
class Car:

# создаем атрибуты класса
name = «c200″
make = «mercedez»
model = 2008

# создаем методы класса
def start(self):
print («Заводим двигатель»)

def stop(self):
print («Отключаем двигатель»)



# Создаем класс Car

class Car:

 

    # создаем атрибуты класса

    name = «c200»

    make = «mercedez»

    model = 2008

 

    # создаем методы класса

    def start(self):

        print («Заводим двигатель»)

 

    def stop(self):

        print («Отключаем двигатель»)

В примере выше мы создали класс под названием Car с тремя атрибутами: имя name, марка make и модель model. Наш класс также содержит два метода: start() и stop().

Объекты

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

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

Объект также называется экземпляром. Тем не менее, процесс создания объекта класса называется инициализация. В Python, чтобы создать объект класса, нам просто нужно вписать название класса, с последующими открывающимися и закрывающимися скобками.

Давайте создадим объект класса Car, который мы создали в предыдущем разделе.

# Создаем объект класса Car под названием car_a
car_a = Car()

# Создаем объект класса Car под названием car_b
car_b = Car()



# Создаем объект класса Car под названием car_a

car_a = Car()

 

# Создаем объект класса Car под названием car_b

car_b = Car()

В этом скрипте мы создали два объекта класса Car: car_a и car_b. Чтобы узнать тип созданных нами объектов, мы можем использовать метод type и передать ему названия наших объектов. Выполните следующий код:

В выдаче вы увидите:

Это говорит нам о том, что тип объекта car_b – класс Car.

На данный момент мы создали наш класс и соответствующие ему объекты. Теперь настало время получить доступ к атрибутам класса и вызвать метод класса при помощи объекта класса. Чтобы сделать это, вам нужно только вписать имя объекта, за которым следует оператор точка . и название атрибута или метода, к которому вы хотите получить доступ или вызов, соответственно. Давайте рассмотрим следующий пример:

В этом скрипте мы вызываем метод start() через объект car_b. Выдача будет выглядеть следующим образом:

Заводим двигатель



Заводим двигатель

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

В выдаче вы увидите значение атрибута модели, как показано ниже:

Атрибуты класса

В предыдущей секции мы разобрались, как создавать объекты класса и как мы можем использовать эти объекты для получения доступа к атрибутам класса

В Python, каждый объект содержит определенные атрибуты по умолчанию и методы в дополнение к определенным пользователем атрибутами. Чтобы посмотреть на все атрибуты и методы объекта, используйте встроенную функцию под названием dir(). Попробуем взглянуть на все атрибуты объекта car_b, который мы создали в предыдущем разделе. Выполните следующий скрипт:

В выдаче вы увидите следующие атрибуты:

[‘__class__’,
‘__delattr__’,
‘__dict__’,
‘__dir__’,
‘__doc__’,
‘__eq__’,
‘__format__’,
‘__ge__’,
‘__getattribute__’,
‘__gt__’,
‘__hash__’,
‘__init__’,
‘__init_subclass__’,
‘__le__’,
‘__lt__’,
‘__module__’,
‘__ne__’,
‘__new__’,
‘__reduce__’,
‘__reduce_ex__’,
‘__repr__’,
‘__setattr__’,
‘__sizeof__’,
‘__str__’,
‘__subclasshook__’,
‘__weakref__’,
‘make’,
‘model’,
‘name’,
‘start’,
‘stop’]


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

[‘__class__’,

‘__delattr__’,

‘__dict__’,

‘__dir__’,

‘__doc__’,

‘__eq__’,

‘__format__’,

‘__ge__’,

‘__getattribute__’,

‘__gt__’,

‘__hash__’,

‘__init__’,

‘__init_subclass__’,

‘__le__’,

‘__lt__’,

‘__module__’,

‘__ne__’,

‘__new__’,

‘__reduce__’,

‘__reduce_ex__’,

‘__repr__’,

‘__setattr__’,

‘__sizeof__’,

‘__str__’,

‘__subclasshook__’,

‘__weakref__’,

‘make’,

‘model’,

‘name’,

‘start’,

‘stop’]

Эта встроенная функция очень полезна при изучении атрибутов и функций объекта, особенно при использовании через REPL.

Атрибуты класса против атрибутов экземпляров

Атрибуты могут быть наглядно отнесены к двум типам:

  • атрибуты класса
  • атрибуты экземпляров

Атрибуты класса делятся среди всех объектов класса, в то время как атрибуты экземпляров являются собственностью экземпляра.
Помните, что экземпляр — это просто альтернативное название объекта.

Атрибуты экземпляра объявляются внутри любого метода, в то время как атрибуты класса объявляются вне любого метода.

Следующий пример прояснит эту разницу:

class Car:

# создаем атрибуты класса
car_count = 0

# создаем методы класса
def start(self, name, make, model):
print(«Двигатель заведен»)
self.name = name
self.make = make
self.model = model
Car.car_count += 1



class Car:

 

    # создаем атрибуты класса

    car_count = 0

 

    # создаем методы класса

    def start(self, name, make, model):

        print(«Двигатель заведен»)

        self.name = name

        self.make = make

        self.model = model

        Car.car_count += 1

В указанном выше скрипте мы создаем класс Car с одним атрибутом класса под названием car_count и три атрибута экземпляра под названием name, make и model. Класс содержит один метод start(), который содержит наши три атрибута экземпляров. Значения атрибутов экземпляров переданы в качестве аргументов методу start(). Внутри метода start, атрибут car_count увеличен на один.

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

Давайте создадим объект класса Car и вызовем метод start().

car_a = Car()
car_a.start(«Corrola», «Toyota», 2015)
print(car_a.name)
print(car_a.car_count)



car_a = Car()  

car_a.start(«Corrola», «Toyota», 2015)  

print(car_a.name)  

print(car_a.car_count)

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

Двигатель заведен
Corrola
1



Двигатель заведен

Corrola  

1

Теперь создадим еще один объект класса Car и вызываем метод start().

car_b = Car()
car_b.start(«City», «Honda», 2013)
print(car_b.name)
print(car_b.car_count)



car_b = Car()  

car_b.start(«City», «Honda», 2013)  

print(car_b.name)  

print(car_b.car_count)

Сейчас если вы выведите значение атрибута car_count, вы увидите 2 в выдаче. Это связано с тем, что атрибут car_count является атрибутом класса и таким образом он разделяется между экземплярами. Объект car_a увеличил свое значение до 1, в то время как car_b увеличил свое значение еще раз, так что итоговое значение равняется 2. Выдача выглядит следующим образом:

Двигатель заведен
City
2



Двигатель заведен

City  

2

Методы

Как мы выяснили ранее, в объектно-ориентированном программировании, методы используются для реализации функционалов объекта. В предыдущем разделе мы создали методы start() и stop() для класса Car. До этих пор, мы использовали объекты класса для вызова методов. Однако, есть тип методов, который может быть вызван напрямую при помощи имени класса. Такой метод называется статичным методом.

Статичные методы

Для объявления статического метода, вам нужно указать дескриптор @staticmethod перед названием метода, как показано ниже:

class Car:

@staticmethod
def get_class_details():
print («Это класс Car»)

Car.get_class_details()



class Car:

 

    @staticmethod

    def get_class_details():

        print («Это класс Car»)

 

Car.get_class_details()

В коде выше мы создали класс Car с одним статичным методом get_class_details(). Давайте вызовем этот метод, используя название класса.

Вы можете видеть что нам не нужно создавать экземпляр класса Car для вызова метода get_class_details(), вместо этого мы просто использовали название класса. Стоит упомянуть, что статические методы могут иметь доступ только к атрибутам класса в Python, вы не сможете обратиться к методам через self.

Возврат множественных значений из метода

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

class Square:

@staticmethod
def get_squares(a, b):
return a*a, b*b

print(Square.get_squares(3, 5))

Понимание дескрипторов get и set и Python

я пытаюсь понять, что такое дескрипторы Python и для чего они могут быть полезны.

дескрипторы-это атрибуты класса (например, свойства или методы) с любым из следующих специальных методов:

  • __get__ (метод без дескриптора данных, например, для метода/функции)
  • __set__ (метод дескриптора данных, например, на экземпляре свойства)
  • __delete__ (дескриптор данных метод)

эти объекты дескриптора могут использоваться в качестве атрибутов в других определениях классов объектов. (То есть, они живут в __dict__ объекта класса.)

объекты дескриптора могут использоваться для программного управления результатами точечного поиска (например,foo.descriptor) в обычном выражении, назначении и даже удалении.

функции / методы, связанные методы,property, classmethod и staticmethod все используют эти специальные методы для управления как они доступны через пунктирный поиск.

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

другой дескриптор данных, a member_descriptor, создано __slots__, позволяет экономить память, позволяя классу хранить данные в изменяемой кортеж-подобной структуре данных вместо более гибкий, но занимающий много места __dict__.

не-дескрипторы данных, обычно экземпляр, класс и статические методы, получают свои неявные первые аргументы (обычно называемые cls и self, соответственно) из их метода дескриптора без данных,__get__.

большинство пользователей Python должны изучить только простое использование, и нет необходимости изучать или понимать реализацию дескрипторов дальше.

В Глубине: Что Дескрипторы?

дескриптор-это объект с любым из следующих методов (__get__, __set__ или __delete__), предназначенный для использования через точечный поиск, как если бы это был типичный атрибут экземпляра. Для владельца-объекта, obj_instance С , property является дескриптором данных:

>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])

Пунктирный Порядок Поиска

это важно:различия, поскольку они влияют на порядок поиска для точечного поиска.

obj_instance.attribute
  1. сначала выше выглядит, чтобы увидеть, является ли атрибут дескриптором данных в классе экземпляра,
  2. если нет, похоже, что атрибут находится в obj_instance ‘ s __dict__, тогда
  3. он, наконец, возвращается к Не-данных-дескриптором.

следствием этого порядка поиска является то, что не-дескрипторы данных, такие как функции/методы, могут быть переопределяется экземпляров.

резюме и следующие шаги

мы узнали, что дескрипторы являются объектами с любым из __get__, __set__ или __delete__. Эти объекты дескриптора могут использоваться в качестве атрибутов в других определениях классов объектов. Теперь мы рассмотрим как они используются, используя ваш код в качестве примера.


анализ кода из вопроса

вот ваш код, а затем ваши вопросы и ответы на каждый:

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Temperature(object):
    celsius = Celsius()
  1. зачем мне нужен класс дескриптора? Пожалуйста, объясните, используя этот пример или тот, который вы считаете лучшим.

ваш дескриптор гарантирует, что у вас всегда есть float для этого атрибута класса Temperature, а что вы не могу использовать del чтобы удалить атрибут:

>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

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

class Temperature(object):
    celsius = 0.0

это точно такое же поведение, как в вашем примере (см. ответ на вопрос 3 ниже), но использует встроенный Pythons (property), и будет считаться более идиоматичным:

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)
  1. что это instance и owner здесь? (in __get__). Итак, мой вопрос: какова цель третьего параметра здесь?

instance является экземпляром владельца, вызывающего дескриптор. Владелец-это класс, в котором объект дескриптора используется для управления доступом к данным точка. См. описания специальных методов, которые определяют дескрипторы рядом с первым пунктом этого ответа для более описательные имена переменных.

  1. как бы я назвал / использовал этот пример?

вот пример:

>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>> 
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0

вы не можете удалить атрибут:

>>> del t2.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

и вы не можете назначить переменную, которая не может быть преобразована в поплавок:

>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02

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

ожидаемый способ, которым большинство опытных программистов Python выполнят этот результат, будет использовать property декоратор, который использует те же дескрипторы под капотом, но вносит поведение в реализацию класса owner (опять же, как определено выше):

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)

, который имеет точно такое же ожидаемое поведение исходного фрагмента кода:

>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02

вывод

мы рассмотрели атрибуты, определяющие дескрипторы, разницу между дескрипторами данных и не — данных, встроенные объекты, которые их используют, и конкретные вопросы об использовании.

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

Python @property: как его использовать и зачем?

Программирование на Python

предоставляет нам встроенный декоратор @property , который значительно упрощает использование геттеров и сеттеров в объектно-ориентированном программировании.

Прежде чем вдаваться в подробности, что такое декоратор @property , давайте сначала разберемся, зачем он вообще может понадобиться.


Класс без геттеров и сеттеров

Предположим, что мы решили создать класс, который хранит температуру в градусах Цельсия.Также будет реализован метод преобразования температуры в градусы Фаренгейта. Один из способов сделать это:

  класс Цельсия:
    def __init __ (self, temperature = 0):
        self.temperature = температура

    def to_fahrenheit (сам):
        обратка (собственная температура * 1,8) + 32  

Мы можем создавать объекты из этого класса и манипулировать атрибутом temperature по своему желанию:

  # Базовый метод установки и получения атрибутов в Python
класс Цельсия:
    def __init __ (self, temperature = 0):
        я.температура = температура

    def to_fahrenheit (сам):
        обратка (собственная температура * 1,8) + 32


# Создаем новый объект
человек = Цельсий ()

# Установить температуру
человеческая температура = 37

# Получить атрибут температуры
печать (человеческая температура)

# Получение метода to_fahrenheit
печать (human.to_fahrenheit ())  

Выход

  37
98.60000000000001  

Дополнительные десятичные разряды при преобразовании в градусы Фаренгейта возникают из-за арифметической ошибки с плавающей запятой.Чтобы узнать больше, посетите Python Ошибка арифметики с плавающей запятой.

Каждый раз, когда мы назначаем или извлекаем какой-либо атрибут объекта, например temperature , как показано выше, Python ищет его во встроенном атрибуте словаря __dict__ объекта.

  >>> человек .__ dict__
{'температура': 37}  

Следовательно, ман. Температура внутренне становится ман .__ dict __ ['температура'] .


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

Предположим, мы хотим расширить возможности использования класса Celsius , определенного выше.Мы знаем, что температура любого объекта не может опускаться ниже -273,15 градусов по Цельсию (Абсолютный ноль в термодинамике)

Давайте обновим наш код, чтобы реализовать это ограничение значения.

Очевидным решением вышеуказанного ограничения будет скрытие атрибута temperature (сделать его закрытым) и определение новых методов получения и установки для управления им. Это можно сделать так:

  # Создание методов получения и установки
класс Цельсия:
    def __init __ (self, temperature = 0):
        я.заданная_температура (температура)

    def to_fahrenheit (сам):
        return (self.get_tempera () * 1.8) + 32

    # метод получения
    def get_tempera (self):
        вернуть self._temperature

    # метод установки
    def set_temperature (self, value):
        если значение <-273,15:
            поднять ValueError («Температура ниже -273,15 невозможна.»)
        self._temperature = значение  

Как мы видим, вышеупомянутый метод вводит два новых метода get_tempera () и set_temperature () .

Кроме того, температура была заменена на _ температура . Нижнее подчеркивание _ в начале используется для обозначения частных переменных в Python.


Теперь воспользуемся этой реализацией:

  # Создание методов получения и установки
класс Цельсия:
    def __init __ (self, temperature = 0):
        self.set_temperature (температура)

    def to_fahrenheit (сам):
        return (self.get_tempera () * 1.8) + 32

    # метод получения
    def get_tempera (self):
        вернуть себя._temperature

    # метод установки
    def set_temperature (self, value):
        если значение <-273,15:
            поднять ValueError («Температура ниже -273,15 невозможна.»)
        self._temperature = значение


# Создаем новый объект set_tempera (), вызываемый изнутри __init__
человек = Цельсий (37)

# Получить атрибут температуры через геттер
печать (human.get_tempera ())

# Получить метод to_fahrenheit, get_tempera (), вызванный самим методом
печать (human.to_fahrenheit ())

# новая реализация ограничения
человек.заданная температура (-300)

# Получение метода to_fahreheit
печать (human.to_fahrenheit ())  

Выход

  37
98.60000000000001
Отслеживание (последний вызов последний):
  Файл «<строка>», строка 30, в <модуле>
  Файл "<строка>", строка 16, в set_temperature
ValueError: Температура ниже -273,15 невозможна.  

Это обновление успешно реализовало новое ограничение. Нам больше не разрешается устанавливать температуру ниже -273.15 градусов по Цельсию.

Примечание : частных переменных в Python фактически не существует. Есть просто нормы, которым нужно следовать. Сам язык не накладывает никаких ограничений.

  >>> человеческая_температура = -300
>>> human.get_temperature ()
-300  

Однако более серьезная проблема с вышеуказанным обновлением заключается в том, что все программы, реализовавшие наш предыдущий класс, должны изменить свой код с obj.temperature на obj.get_tempera () и все выражения вроде obj.temperature = val от до obj.set_temperature (val) .

Этот рефакторинг может вызвать проблемы при работе с сотнями тысяч строк кода.

В общем, наше новое обновление не имело обратной совместимости. Здесь на помощь приходит @property .


Класс собственности

Питонический способ справиться с вышеуказанной проблемой - использовать свойство класса .Вот как мы можем обновить наш код:

  # using класс свойств
класс Цельсия:
    def __init __ (self, temperature = 0):
        self.temperature = температура

    def to_fahrenheit (сам):
        обратка (собственная температура * 1,8) + 32

    # получатель
    def get_tempera (self):
        print ("Получение значения ...")
        вернуть self._temperature

    # сеттер
    def set_temperature (self, value):
        print ("Значение настройки ...")
        если значение <-273,15:
            поднять ValueError ("Температура ниже -273.15 невозможно »)
        self._temperature = значение

    # создание объекта свойства
    температура = свойство (get_tempera, set_temperature)  

Мы добавили функцию print () внутри get_temperature () и set_temperature () , чтобы четко видеть, что они выполняются.

Последняя строка кода превращает объект свойства в температуру . Проще говоря, свойство прикрепляет некоторый код ( get_temperature и set_temperature ) к доступам к атрибуту элемента ( temperature ).

Давайте использовать этот код обновления:

  # using класс свойств
класс Цельсия:
    def __init __ (self, temperature = 0):
        self.temperature = температура

    def to_fahrenheit (сам):
        обратка (собственная температура * 1,8) + 32

    # получатель
    def get_tempera (self):
        print ("Получение значения ...")
        вернуть self._temperature

    # сеттер
    def set_temperature (self, value):
        print ("Значение настройки ...")
        если значение <-273,15:
            поднять ValueError ("Температура ниже -273.15 невозможно »)
        self._temperature = значение

    # создание объекта свойства
    температура = свойство (get_tempera, set_temperature)


человек = Цельсий (37)

печать (человеческая температура)

печать (human.to_fahrenheit ())

человеческая температура = -300  

Выход

  Значение настройки ...
Получение ценности ...
37
Получение ценности ...
98.60000000000001
Значение настройки ...
Отслеживание (последний вызов последний):
  Файл «<строка>», строка 31, в <модуле>
  Файл «<строка>», строка 18, в set_temperature
ValueError: Температура ниже -273 невозможна  

Как мы видим, любой код, который получает значение temperature , автоматически вызывает get_tempera () вместо поиска по словарю (__dict__).Точно так же любой код, который присваивает значение температуре , автоматически вызывает set_temperature () .

Мы даже можем видеть выше, что set_temperature () вызывалась даже тогда, когда мы создавали объект.

  >>> человек = Цельсий (37)
Значение настройки ...  

Угадайте, почему?

Причина в том, что при создании объекта вызывается метод __init __ () . Этот метод имеет строку self.температура = температура . Это выражение автоматически вызывает set_temperature () .

Аналогично, любой доступ, такой как c.temperature , автоматически вызывает get_tempera () . Вот что делает собственность. Вот еще несколько примеров.

  >>> человеческая температура
Получение ценности
37
>>> человеческая температура = 37
Значение настройки

>>> c.to_fahrenheit ()
Получение ценности
98.60000000000001  

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

Примечание : Фактическое значение температуры сохраняется в частной переменной _temperature . Атрибут temperature - это объект свойства, который предоставляет интерфейс для этой частной переменной.


Декоратор @property

В Python property () - это встроенная функция, которая создает и возвращает объект property . Синтаксис этой функции:

  свойство (fget = None, fset = None, fdel = None, doc = None)  

где,

  • fget - функция для получения значения атрибута
  • fset - функция для установки значения атрибута
  • fdel - функция для удаления атрибута
  • doc - строка (как комментарий)

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

  >>> свойство ()
<объект недвижимости по адресу 0x0000000003239B38>  

Объект свойства имеет три метода: getter () , setter () и deleter () , чтобы в дальнейшем указать fget , fset и fdel . Это означает, что строка:

  температура = свойство (get_tempera, set_temperature)  

можно разбить как:

  # сделать пустое свойство
температура = свойство ()
# назначить fget
температура = температура.геттер (get_tempera)
# назначить fset
temperature = temperature.setter (set_temperature)  

Эти две части кода эквивалентны.

Программисты, знакомые с декораторами Python, могут понять, что указанная выше конструкция может быть реализована как декораторы.

Мы даже не можем определить имена get_tempera и set_tempera , поскольку они не нужны и загрязняют пространство имен классов.

Для этого мы повторно используем имя temperature при определении наших функций получения и установки.Давайте посмотрим, как это реализовать в качестве декоратора:

  # Использование декоратора @property
класс Цельсия:
    def __init __ (self, temperature = 0):
        self.temperature = температура

    def to_fahrenheit (сам):
        обратка (собственная температура * 1,8) + 32

    @свойство
    def температура (self):
        print ("Получение значения ...")
        вернуть self._temperature

    @ temperature.setter
    def температура (self, value):
        print ("Значение настройки ...")
        если значение <-273.15:
            поднять ValueError («Температура ниже -273 невозможна»)
        self._temperature = значение


# создать объект
человек = Цельсий (37)

печать (человеческая температура)

печать (human.to_fahrenheit ())

coldest_thing = Цельсия (-300)  

Выход

  Значение настройки ...
Получение ценности ...
37
Получение ценности ...
98.60000000000001
Значение настройки ...
Отслеживание (последний вызов последний):
  Файл «<строка>», строка 29, в <модуле>
  Файл «<строка>», строка 4, в __init__
  Файл "", строка 18, по температуре
ValueError: Температура ниже -273 невозможна  

Вышеупомянутая реализация проста и эффективна.Это рекомендуемый способ использования , свойство .

.Свойства

в Python - изучение на примере

Некоторые объектно-ориентированные языки, такие как Java и C #, поддерживают атрибуты частных объектов; к которым нельзя получить прямой доступ извне. Программистам часто приходится писать методы получения и установки для доступа к таким частным атрибутам.

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

Если вы хотите предотвратить прямой доступ к атрибуту, вы должны определить его как свойство .

Это простой способ настроить доступ к атрибуту.

Определение свойства

Давайте посмотрим, как можно определить свойства в Python. Вот простой пример, в котором определен класс Person.

Этот класс имеет единственный атрибут с именем hidden_name , к которому мы не хотим, чтобы люди имели прямой доступ. Следовательно, в классе определены два метода: метод получения get_name () и метод установки set_name () .

  класс Лицо ():
    def __init __ (self, value):
        я.hidden_name = значение

    # функция получения
    def get_name (self):
        print ('Получение имени:')
        вернуть self.hidden_name
    
    # функция установки
    def set_name (self, value):
        print ('Установка имени в', значение)
        self.hidden_name = значение
    
    # создать недвижимость
    name = property (get_name, set_name)  

Методы get_name () и set_name () действуют как обычные геттеры и сеттеры до этой строки.

  name = property (get_name, set_name)  

Он создает новый атрибут класса с именем «name» и определяет два метода как свойства.

Теперь, когда вы ссылаетесь на атрибут name любого объекта Person, Python фактически вызывает метод get_name () .

  p = Человек («Боб»)
печать (p.name)
# Выводит Получение имени: Bob  

Когда вы присваиваете значение атрибуту name, вызывается метод set_name () .

  p.name = "Сэм"
# Печать Установка имени для Сэма

печать (p.name)
# Печатает Получение имени: Sam  

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

property () Функция

Вы генерируете свойство, вызывая встроенную функцию property (), передавая ей три метода (getter, setter и deleter), а также строку документации для свойства.

Функция property () имеет следующий синтаксис:

attrib = property (fget, fset, fdel, doc)

Это создает атрибут класса с именем attrib и определяет три метода как свойства.

Теперь, когда вы ссылаетесь на x.attrib , Python вызывает метод fget.

Когда вы назначаете x.attrib = value , Python вызывает метод fset и передает значение в качестве аргумента.

Когда вы выполняете del x.attrib , Python вызывает метод fdel.

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

Перезапись Person Class

Давайте перепишем наш предыдущий пример, включая метод удаления и строку документации.

  класс Лицо ():
    def __init __ (self, value):
        я.hidden_name = значение
    
    # функция получения
    def get_name (self):
        print ('Получение имени:')
        вернуть self.hidden_name

    # функция установки
    def set_name (self, value):
        print ('Установка имени в', значение)
        self.hidden_name = значение
        
    # функция удаления
    def del_name (сам):
        print ('Удаление имени')
        del self.hidden_name

    # создать недвижимость
    name = property (get_name, set_name, del_name, doc = 'name of the person')  

Вот пример нового используемого класса:

  p = Person ('Bob')

# вызывает геттер
печать (стр.имя)
# Печать Получение имени: Боб

# вызывает сеттер
p.name = 'Сэм'
# Печать Установка имени для Сэма

# docstring
print ('Строка документа:', Имя.__ doc__)
# Печатает Docstring: имя человека

# вызывает удаление
del p.name
# Печатает Удаление имени  

@property - Свойство как декоратор

Более элегантный синтаксис для определения свойств в классе заключается в использовании свойства как декоратора

В следующем примере мы определим три разных метода, каждый из которых называется name () , но перед ним стоят разные декораторы:

  • @property декоратор идет перед методом получения
  • @name.декоратор setter идет до метода setter
  • @ name.deleter Декоратор идет до метода удаления

Вот как они фактически выглядят в коде:

  class Person ():
    def __init __ (self, value):
        self.hidden_name = значение
    
    @свойство
    def имя (сам):
        print ('Получение имени:')
        вернуть self.hidden_name

    @ name.setter
    def имя (себя, значение):
        print ('Установка имени в', значение)
        self.hidden_name = значение
        
    @имя.удалитель
    def имя (сам):
        print ('Удаление имени')
        del self.hidden_name  

Здесь первый метод является геттером и устанавливает имя как свойство. Два других метода присоединяют сеттер и средство удаления к свойству name.

Вы по-прежнему можете обращаться к имени как к атрибуту:

  p = Person ('Bob')

# вызывает геттер
печать (p.name)
# Печать Получение имени: Боб

# вызывает сеттер
p.name = 'Сэм'
# Печать Установка имени для Сэма

# вызывает удаление
дель п.имя
# Печатает Удаление имени  

Обратите внимание, что вы не можете определить декораторы @ name.setter и @ name.deleter, если вы еще не установили name как свойство с помощью декоратора @property.

Пример реального мира

Свойства обычно используются в случаях, когда вы хотите добавить дополнительную обработку (например, проверку типа или проверку) к получению или настройке атрибута экземпляра.

Например, приведенный ниже код определяет свойство, которое добавляет к атрибуту простую проверку типа:

  class Person:
    def __init __ (self, value):
        я.имя = значение

    @свойство
    def имя (сам):
        вернуть self._name

    @ name.setter
    def имя (себя, значение):
        если не isinstance (значение, str):
            поднять TypeError ('Ожидается строка')
        self._name = значение

    @ name.deleter
    def имя (сам):
        поднять AttributeError («Невозможно удалить атрибут»)

p = Person (42) # Триггеры TypeError: Ожидается строка

p = Человек ('Боб')

print (p.name) # Распечатывает Боба

p.name = 42 # Triggers TypeError: Ожидается строка

дель п.name # Triggers AttributeError: невозможно удалить атрибут  

В приведенном выше примере

1. Проверка типа выполняется в функции установщика. Он проверяет, является ли тип присвоенного значения строкой. Если это не строка, возникает исключение TypeError .

  p.name = 42 # Triggers TypeError: Ожидается строка  

2. AttributeError Исключение возникает, когда пользователь пытается удалить атрибут.

  del p.name # Триггеры AttributeError: невозможно удалить атрибут  

3. Проверка типа также выполняется во время инициализации.

  p = Person (42) # Triggers TypeError: Ожидается строка  

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

Вот почему метод __init __ () устанавливает self.name вместо self._name . Установив собственное имя, метод __init __ () автоматически вызывает метод установки.

Вычисляемые атрибуты

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

Давайте определим класс Rectangle, который имеет два обычных атрибута (ширина и высота) и один вычисляемый атрибут (область).

  класс Rectangle (объект):
    def __init __ (self, width, height):
        я.ширина = ширина
        self.height = высота

    @свойство
    область определения (self):
        return self.width * self.height  

Давайте создадим объект Rectangle с начальным значением его ширины и высоты.

Теперь вы можете вызвать область как атрибут:

  print (r.area)
# Prints 10  

А вот самое интересное: вы можете изменить ширину и высоту прямоугольника в любое время, и свойство area будет вычислено соответственно:

  r.ширина = 3
r.height = 6

печать (r.area)
# Prints 18  

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

  r.area = 18 # Триггеры AttributeError: невозможно установить атрибут  

Расширение свойства в подклассе

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

Вам нужно выяснить, будете ли вы переопределять все методы вместе или только один из методов.

Вот пример класса, который наследуется от Person и расширяет свойство name с помощью новых функций:

  class Person ():
    def __init __ (self, value):
        self.hidden_name = значение
    
    @свойство
    def имя (сам):
        print ('Получение имени:')
        вернуть self.hidden_name

    @ name.setter
    def имя (себя, значение):
        print ('Установка имени в', значение)
        я.hidden_name = значение
        
    @ name.deleter
    def имя (сам):
        print ('Удаление имени')
        del self.hidden_name

класс SubPerson (Человек):
    @свойство
    def имя (сам):
        print ('Внутренний получатель подличного лица')
        вернуть super (). имя

    @ name.setter
    def имя (себя, значение):
        print ('Внутренний установщик подличного лица')
        super (SubPerson, SubPerson) .name .__ set __ (self, value)

    @ name.deleter
    def имя (сам):
        print ('Внутренний удалитель подчиненного лица')
        super (SubPerson, SubPerson).name .__ delete __ (self)  

Вот пример нового используемого класса:

  s = SubPerson ('Bob')

# вызывает геттер
печать (имя)
# Печатает внутри получателя подчиненного лица
# Печать Получение имени: Боб

# вызывает сеттер
s.name = 'Сэм'
# Печатает внутри установщика субличностей
# Печать Установка имени для Сэма

# вызывает удаление
del s.name
# Печатает внутри субличного удаления
# Печатает Удаление имени  

В приведенном выше примере все методы свойств переопределяются вместе. В каждом методе функция super () используется для вызова реализации суперкласса.

Если вы хотите переопределить только один из методов, недостаточно использовать @property отдельно, используйте следующий код:

  class SubPerson (Person):
    @ Person.name.getter
    def имя (сам):
        print ('Внутренний получатель подличного лица')
        return super (). name  

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

Если вы просто хотите переопределить сеттер, используйте этот код:

  class SubPerson (Person):
    @Человек.name.setter
    def имя (себя, значение):
        print ('Внутренний установщик подличного лица')
        super (SubPerson, SubPerson) .name .__ set __ (self, value)  

.Практическое руководство по дескриптору

- документация Python 3.8.6

Автор

Раймонд Хеттингер

Контакт

Определяет дескрипторы, резюмирует протокол и показывает, как дескрипторы
называется. Исследует пользовательский дескриптор и несколько встроенных дескрипторов Python
включая функции, свойства, статические методы и методы классов. Покажи покажи
каждый работает, предоставляя чистый эквивалент Python и образец приложения.

Изучение дескрипторов не только обеспечивает доступ к большему набору инструментов, но и
дает более глубокое понимание того, как работает Python, и признательность за
элегантность его дизайна.

В общем, дескриптор - это атрибут объекта с «поведением привязки», один
доступ к атрибуту которого был переопределен методами в дескрипторе
протокол. Эти методы: __get __ () , __set __ () и
__ удалить __ () . Если какой-либо из этих методов определен для объекта, он
называется дескриптором.

Поведение по умолчанию для доступа к атрибутам - получить, установить или удалить
атрибут из словаря объекта. Например, a.x имеет поисковую цепочку
начиная с a .__ dict __ ['x'] , затем type (a) .__ dict __ ['x'] и
продолжая через базовые классы типа (a) , исключая метаклассы. Если
искомое значение - это объект, определяющий один из методов дескриптора, затем Python
может переопределить поведение по умолчанию и вместо этого вызвать метод дескриптора.То, где это происходит в цепочке приоритетов, зависит от того, какие методы дескриптора
были определены.

Дескрипторы - это мощный протокол общего назначения. Они механизм
за свойствами, методами, статическими методами, методами класса и super () .
Они используются в самом Python для реализации новых классов стилей.
введено в версии 2.2. Дескрипторы упрощают базовый C-код и предлагают
гибкий набор новых инструментов для повседневных программ на Python.

описание__get __ (self, obj, type = None) -> значение

descr .__ set __ (self, obj, value) -> Нет

descr .__ delete __ (self, obj) -> Нет

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

Если объект определяет __set __ () или __delete __ () , он считается
дескриптор данных. Вызываются дескрипторы, которые определяют только __get __ () .
дескрипторы, не относящиеся к данным (они обычно используются для методов, но другие применения
возможное).

Дескрипторы данных и не-данные различаются тем, как вычисляются переопределения с помощью
по отношению к записям в словаре экземпляра. Если словарь экземпляра
имеет запись с тем же именем, что и дескриптор данных, дескриптор данных
имеет приоритет. Если в словаре экземпляра есть запись с таким же
name как дескриптор, не связанный с данными, словарная запись имеет приоритет.

Чтобы создать дескриптор данных только для чтения, определите как __get __ (), и

.

Свойство Python - Проблема и решение

1. Свойство Python

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

Итак, приступим к Руководству по свойствам Python.

Свойство Python

2. Свойство Python - проблема

Давайте сначала посмотрим на песню класса Python.

 >>> класс Песня:
def __init __ (я, заголовок):
self.title = title
def show_title (сам):
print (f "Я слушаю {self.title}") 

Затем мы создаем объект Teddy_Bear и вызываем для него show_title ().

 >>> Teddy_Bear = Song ('Мишка Тедди')
>>> Teddy_Bear.show_title () 

Я слушаю Teddy Bear
Здесь мы можем получить и установить атрибут экземпляра заголовка следующим образом:

 >>> Teddy_Bear.title # получение титула 

«Мишка Тедди»

 >>> Тедди_Медведь.title = 'TEDDY BEAR' # установка названия
>>> Teddy_Bear.title 

‘TEDDY BEAR’
Проверяя словарь объекта, мы находим следующее:

 >>> Teddy_Bear .__ dict__ 

{‘title’: ‘TEDDY BEAR’}
Проблема: что, если клиенты начнут получать доступ к переменной ‘title’ и изменять ее? Это могло вызвать разного рода хаос.

3. Свойство Python - возможное решение

Давайте сделаем две вещи:

  • Определите функции установки и получения.Чтобы установить и получить значение, мы определяем следующие функции:
 >>> def get_title (self):
вернуть self._title
>>> def set_title (self, title):
self._title = title.upper () 

Здесь get_title возвращает название книги, а set_title преобразует его во все заглавные (мы видели это в

Встроенные функции Python )

  • Сделайте переменную "title" частной. Для этого мы используем нижнее подчеркивание. После всех этих изменений наш класс выглядит так:
 >>> класс Песня:
def __init __ (я, заголовок):
я.title = название
def show_title (сам):
print (f "Я слушаю {self.title}")
def get_title (сам):
вернуть self._title
def set_title (я, заголовок):
self._title = title.upper () 

Ну, подчеркивание на самом деле не делает переменную частной, потому что Python не реализует никаких подобных ограничений на своем уровне. Но это нормы, которым мы должны следовать.
Но проблема этого решения в том, что нам нужно будет обновить «Teddy_Bear.title» до «Teddy_Bear.get_title () »и« Teddy_Bear.title = «TEDDY BEAR» »на« Teddy_Bear.set_title () »в каждом случае. Реальный код, клиенты могут иметь тысячи строк, в которых они реализовали наш класс. В этом случае рефакторинг каждого случая становится утомительным. Это делает его несовместимым с предыдущими версиями. Итак, давайте теперь рассмотрим лучший вариант. Фактически, мы просто добавим строчку к этому подходу, и он будет работать как по волшебству.

4. Свойство Python - решение

Чтобы решить эту проблему, Python предоставляет нам функцию property ().Вот как выглядит это свойство Python:

свойство (fget, fset, fdel, doc)

Здесь fget принимает геттер, fset принимает сеттер, fdel принимает функцию для удаления атрибута, а doc - это строка. Однако эти аргументы необязательны. Вот вызов property () без аргументов:

 >>> недвижимость () 

<объект свойства в 0x062CDDB0>

Для fget, fset и fdel объект свойства имеет следующие методы: getter (), setter () и delete ().Итак, это эквивалентно этому:

 >>> title = property () # создать пустое свойство
>>> title = title.getter (get_title) #assign fget
>>> title = title.setter (set_title) #assign fset 

Для нашего класса Song мы добавляем следующую строку:

 >>> title = property (get_title, set_title) 

Класс выглядит так:

 >>> класс Песня:
  def __init __ (я, заголовок):
    self.title = title
  def show_title (сам):
    print (f "Я слушаю {self.заглавие}")
  def get_title (сам):
    вернуть self._title
  def set_title (я, заголовок):
    self._title = title.upper ()
  title = свойство (get_title, set_title) 

Теперь попробуем получить доступ к названию объекта Teddy_Bear.

 >>> Teddy_Bear = Song ('Мишка Тедди')
>>> Teddy_Bear.title 

«МЕДВЕДЬ»

 >>> Teddy_Bear.show_title () 

Я слушаю МЕДВЕДЬ

Эта реализация обратно совместима.Это избавляет нас от работы по рефакторингу. Также обратите внимание, что именно _title содержит фактическое значение названия книги. «Title» - это просто объект свойства, благодаря которому это происходит.

5. Немного синтаксического сахара

Мы видели @ -синтаксис в нашем уроке по Python Property Decorators . Здесь мы делаем это:

 >>> класс Песня:
def __init __ (я, заголовок):
self.title = title
def show_title (сам):
print (f "Я слушаю {self.title}")
@свойство
def title (self):
вернуть себя._заглавие
@ title.setter
def title (self, title):
self._title = title.upper () 

Во-первых, обратите внимание, что мы удалили вызов свойства. Затем мы отказались от имен «get_title» и «set_title» и использовали вместо них «title». Наконец, мы использовали @property перед геттером и @ title.setter перед сеттером.
Давайте посмотрим на это сейчас.

 >>> Teddy_Bear = Song ('Мишка Тедди')
>>> Teddy_Bear.title 

«МЕДВЕДЬ»

 >>> Тедди_Медведь.show_title () 

Я слушаю МЕДВЕДЬ

Итак, это все о Python Property Tutorial. Надеюсь, вам понравится ваше объяснение.

6. Заключение

В этом руководстве по Python Property мы узнали о различных проблемах и решениях этих проблем. Прокомментируйте сомнение или оставьте предложение. Мы обязательно к вам вернемся!

Для справки

.

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

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