Введение в python dataclasses
Содержание:
- 9.6. Private Variables¶
- Conclusion & Further Reading
- Методы[править]
- Alternatives to Data Classes
- Класс
- 9.5. Inheritance¶
- Функциональный стиль в Python
- Что такое питон простыми словами
- Инкапсуляция в Python
- Сравнение данных
- Конструктор классов
- 9.8. Iterators¶
- Обработка после инициализации
- Различия между списком и генератором
- post-init parameters
- Свойства Python (@property)
- Python Objects and Classes
- Протоколы
9.6. Private Variables¶
“Private” instance variables that cannot be accessed except from inside an
object don’t exist in Python. However, there is a convention that is followed
by most Python code: a name prefixed with an underscore (e.g. ) should
be treated as a non-public part of the API (whether it is a function, a method
or a data member). It should be considered an implementation detail and subject
to change without notice.
Since there is a valid use-case for class-private members (namely to avoid name
clashes of names with names defined by subclasses), there is limited support for
such a mechanism, called name mangling. Any identifier of the form
(at least two leading underscores, at most one trailing underscore)
is textually replaced with , where is the
current class name with leading underscore(s) stripped. This mangling is done
without regard to the syntactic position of the identifier, as long as it
occurs within the definition of a class.
Name mangling is helpful for letting subclasses override methods without
breaking intraclass method calls. For example:
class Mapping def __init__(self, iterable): self.items_list = [] self.__update(iterable) def update(self, iterable): for item in iterable self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item)
The above example would work even if were to introduce a
identifier since it is replaced with in the
class and in the
class respectively.
Note that the mangling rules are designed mostly to avoid accidents; it still is
possible to access or modify a variable that is considered private. This can
even be useful in special circumstances, such as in the debugger.
Conclusion & Further Reading
Data classes are one of the new features of Python 3.7. With data classes, you do not have to write boilerplate code to get proper initialization, representation, and comparisons for your objects.
You have seen how to define your own data classes, as well as:
- How to add default values to the fields in your data class
- How to customize the ordering of data class objects
- How to work with immutable data classes
- How inheritance works for data classes
If you want to dive into all the details of data classes, have a look at PEP 557 as well as the discussions in the original GitHub repo.
In addition, Raymond Hettinger’s PyCon 2018 talk Dataclasses: The code generator to end all code generators is well worth watching.
Методы[править]
Методправить
Синтаксис описания метода ничем не отличается от описания функции, разве что его положением внутри класса и характерным первым формальным параметром , с помощью которого внутри метода можно ссылаться на сам экземпляр класса (название self является соглашением, которого придерживаются программисты на Python):
class MyClass(object): def mymethod(self, x): return x == self._x
Статический методправить
Статические методы в Python являются синтаксическими аналогами статических функций
в основных языках программирования. Они не получают ни экземпляр (),
ни класс () первым параметром.
Для создания статического метода (только классы могут иметь статические методы) используется
>>> class D(object): @staticmethod def test(x): return x == ... >>> D.test(1) # доступ к статическому методу можно получать и через класс False >>> f = D() >>> f.test() # и через экземпляр класса True
Статические методы реализованы с помощью
.
Метод классаправить
Классовые методы в Python занимают промежуточное положение между
статическими и обычными. В то время как обычные методы получают первым
параметром экземпляр класса, а статические не получают ничего, в классовые
методы передается класс. Возможность создания классовых методов является
одним из следствий того, что в Python классы также являются объектами.
Для создания классового (только классы могут иметь классовые методы) метода можно использовать
декоратор
>>> class A(object): def __init__(self, int_val): self.val = int_val + 1 @classmethod def fromString(cls, val): # вместо self принято использовать cls return cls(int(val)) ... >>> class B(A):pass ... >>> x = A.fromString("1") >>> print x.__class__.__name__ A >>> x = B.fromString("1") >>> print x.__class__.__name__ B
Классовые методы достаточно часто используются для перегрузки конструктора.
Классовые методы, как и статические, реализуются через
.
Мультиметодыправить
Примером для иллюстрации сути мультиметода может служить функция из модуля :
>>> import operator as op >>> print op.add(2, 2), op.add(2.0, 2), op.add(2, 2.0), op.add(2j, 2) 4 4.0 4.0 (2+2j)
from multimethods import Dispatch class Asteroid(object): pass class Spaceship(object): pass def asteroid_with_spaceship(a1, s1): print "A-><-S" def asteroid_with_asteroid(a1, a2): print "A-><-A" def spaceship_with_spaceship(s1, s2): print "S-><-S" collide = Dispatch() collide.add_rule((Asteroid, Spaceship), asteroid_with_spaceship) collide.add_rule((Asteroid, Asteroid), asteroid_with_asteroid) collide.add_rule((Spaceship, Spaceship), spaceship_with_spaceship) collide.add_rule((Spaceship, Asteroid), lambda x,y asteroid_with_spaceship(y,x)) a, s1, s2 = Asteroid(), Spaceship(), Spaceship() collision1 = collide(a, s1)[ collision2 = collide(s1, s2)[
Alternatives to Data Classes
For simple data structures, you have probably already used a or a . You could represent the queen of hearts card in either of the following ways:
>>>
It works. However, it puts a lot of responsibility on you as a programmer:
- You need to remember that the variable represents a card.
- For the version, you need to remember the order of the attributes. Writing will mess up your program but probably not give you an easily understandable error message.
- If you use the version, you must make sure the names of the attributes are consistent. For instance will not work as expected.
Furthermore, using these structures is not ideal:
>>>
A better alternative is the . It has long been used to create readable small data structures. We can in fact recreate the data class example above using a like this:
This definition of will give the exact same output as our example did:
>>>
So why even bother with data classes? First of all, data classes come with many more features than you have seen so far. At the same time, the has some other features that are not necessarily desirable. By design, a is a regular tuple. This can be seen in comparisons, for instance:
>>>
While this might seem like a good thing, this lack of awareness about its own type can lead to subtle and hard-to-find bugs, especially since it will also happily compare two different classes:
>>>
The also comes with some restrictions. For instance, it is hard to add default values to some of the fields in a . A is also by nature immutable. That is, the value of a can never change. In some applications, this is an awesome feature, but in other settings, it would be nice to have more flexibility:
>>>
Data classes will not replace all uses of . For instance, if you need your data structure to behave like a tuple, then a named tuple is a great alternative!
Another alternative, and one of the inspirations for data classes, is the project. With installed (), you can write a card class as follows:
This can be used in exactly the same way as the and examples earlier. The project is great and does support some features that data classes do not, including converters and validators. Furthermore, has been around for a while and is supported in Python 2.7 as well as Python 3.4 and up. However, as is not a part of the standard library, it does add an external dependency to your projects. Through data classes, similar functionality will be available everywhere.
In addition to , , , and , there are , including , , , , and . While data classes are a great new alternative, there are still use cases where one of the older variants fits better. For instance, if you need compatibility with a specific API expecting tuples or need functionality not supported in data classes.
Класс
Классы, в некотором смысле, подобны чертежам: это не объекты сами по себе, а их схемы. Класс «банковских счетов» имеет строго определенные и одинаковые для всех атрибуты, но объекты в нём – сами счета – уникальны.
Как в Python создать класс
В Python классы и объекты по смыслу не отличаются от других языков. Нюансы в реализации. Для создания класса в Питоне необходимо написать инструкцию , а затем выбрать имя. В простейшем случае, класс выглядит так:
Для именования классов в Python обычно используют стиль «camel case», где первая буква – заглавная.
Конструктор
Метод, который вызывается при создании объектов, в ООП зовётся конструктором. Он нужен для объектов, которые изначально должны иметь какие-то значение. Например, пустые экземпляры класса «Студент» бессмысленны, и желательно иметь хотя бы минимальный обозначенный набор вроде имени, фамилии и группы.
В качестве Питоновского конструктора выступает метод
Атрибуты класса
Вы уже поняли, что у каждого класса есть собственный набор характеристик, который помогает описывать его сущность. Эти свойства еще называются полями или атрибутами.
Поля могут быть статическими и динамическими:
- Статические поля (поля класса) можно использовать без создания объекта. А значит, конструктор вам не нужен.
- Динамические поля (поля объекта) задаются с помощью конструктора, и тут уже, как вы видели, экземпляр нужно создать, а полям присвоить значения.
️ Обратите внимание – статический и динамический атрибут может иметь одно и то же имя:
Методы класса
Метод – это функция класса.
Например, у всех научно-фантастических космических кораблей есть бортовое оружие. И оно может стрелять.
Что такое self?
Аналог этого ключевого слова в других языках – слово . – это всего лишь ссылка на текущий экземпляр класса.
Отличный пример с котофеями:
- Все котики умеют мурлыкать;
- Эта способность реализована в классе , как метод ;
- Вы хотите, чтобы ваш кот по имени Пушок помурчал;
- Если сделать так: , то мурлыкать начнут все коты во Вселенной;
- Но так как вам нужен один конкретный кот, то нужно вызвать метод иначе: ;
- Сделано. Пушок мурлыкает.
Уровни доступа атрибутов и методов
В Питоне не существует квалификаторов доступа к полям класса. Отсутствие аналогов связки public/private/protected можно рассматривать как упущение со стороны принципа инкапсуляции.
Декораторы
Декоратор – это функция-обёртка. В неё можно завернуть другой метод, и, тем самым, изменить его функциональность, не меняя код.
9.5. Inheritance¶
Of course, a language feature would not be worthy of the name “class” without
supporting inheritance. The syntax for a derived class definition looks like
this:
class DerivedClassName(BaseClassName): <statement-1> . . . <statement-N>
The name must be defined in a scope containing the
derived class definition. In place of a base class name, other arbitrary
expressions are also allowed. This can be useful, for example, when the base
class is defined in another module:
class DerivedClassName(modname.BaseClassName):
Execution of a derived class definition proceeds the same as for a base class.
When the class object is constructed, the base class is remembered. This is
used for resolving attribute references: if a requested attribute is not found
in the class, the search proceeds to look in the base class. This rule is
applied recursively if the base class itself is derived from some other class.
There’s nothing special about instantiation of derived classes:
creates a new instance of the class. Method references
are resolved as follows: the corresponding class attribute is searched,
descending down the chain of base classes if necessary, and the method reference
is valid if this yields a function object.
Derived classes may override methods of their base classes. Because methods
have no special privileges when calling other methods of the same object, a
method of a base class that calls another method defined in the same base class
may end up calling a method of a derived class that overrides it. (For C++
programmers: all methods in Python are effectively .)
An overriding method in a derived class may in fact want to extend rather than
simply replace the base class method of the same name. There is a simple way to
call the base class method directly: just call . This is occasionally useful to clients as well. (Note that this
only works if the base class is accessible as in the global
scope.)
Python has two built-in functions that work with inheritance:
-
Use to check an instance’s type:
will be only if is or some class
derived from . -
Use to check class inheritance:
is since is a subclass of . However,
is since is not a
subclass of .
Функциональный стиль в Python
В функциональном программировании вычисления выполняются путем объединения функций, которые принимают аргументы и возвращают конкретное значение (или значения). Эти функции не изменяют свои входные аргументы и не изменяют состояние программы. Они просто предоставляют результат данного вычисления. Такие функции обычно называются чистыми функциями (pure functions).
Теоретически программы, построенные с использованием функционального стиля, проще:
- Разрабатывать, потому что вы можете кодировать и использовать каждую функцию изолированно
- Отлаживать и тестировать, потому что вы можете тестировать и отлаживать отдельные функции, не глядя на остальную часть программы
- Понимать, потому что вам не нужно иметь дело с изменениями состояния на протяжении всей программы
Функциональное программирование обычно использует списки, массивы и другие итерационные объекты для представления данных вместе с набором функций, которые работают с этими данными и преобразовывают их. Когда дело доходит до обработки данных в функциональном стиле, обычно используются как минимум три метода:
- Сопоставление (Mapping) заключается в применении функции преобразования к итерируемому объекту для создания нового объекта. Элементы в новой итерации создаются путем вызова функции преобразования для каждого элемента в исходной итерации.
- Фильтрация (Filtering) состоит из применения предиката или булевозначной функции (predicate or Boolean-valued function) к итерируемому объекту для создания нового итерируемого объекта. Элементы в новой итерации создаются путем фильтрации любых элементов в исходной итерации, которые заставляют функцию предиката возвращать false.
- Сокращение (Reducing) состоит из применения функции reduce к итерируемому объекту для получения единственного накопленного значения.
По словам Гвидо ван Россума, на Python в большей степени влияют императивные языки программирования, чем функциональные языки:
Однако еще в 1993 году сообщество Python требовало некоторых функций функционального программирования. Они просили:
- Анонимные функции
- Функцию
- Функцию
- Функцию
Эти функциональные возможности были добавлены в язык благодаря участию многих членов сообщества. В настоящее время , и являются фундаментальными компонентами стиля функционального программирования в Python.
В этом руководстве мы рассмотрим одну из этих функциональных возможностей — встроенную карту функций map(). Вы также узнаете, как использовать составные части списковых включений (comprehensions) и выражения генератора (generator expressions), чтобы получить ту же функциональность, что и map(), в питоническом и удобочитаемом виде.
Что такое питон простыми словами
Для того чтобы понять, что такое Python нужно разобраться зачем вообще нужны языки программирования. Они нужны для взаимодействия человека и компьютера. Между собой люди общаются на естественном языке.
Язык программирования Python
Питон является относительно простым высокоуровневым скриптовым языком, который служит для создания различных сценариев. Это означает, что для программирования на Python, не требуется знания машинных кодов — команд для компьютера.
Программирование при помощи машинных кодов ускоряет программу, но очень трудоемко и требует хорошего знания архитектуры компьютера. Одним из таких низкоуровневых языков является ассемблер. Чаще всего низкоуровневое программирование используется в промышленности для специализированных контроллеров, где нет большого объема памяти.
Python может использоваться для программирования контроллеров в станках ЧПУ и в робототехнике. Популярный во всем мире одноплатный микрокомпьютер Raspberry Pi также программируется на питоне. С помощью «малинки» можно программировать роботов, умные дома, квадрокоптеры и множество других вещей. Pi в названии миникомпьютера обозначает, что программирование происходит на Python.
одноплатный микрокомпьютер Raspberry Pi
На языке высокого уровня нужно уметь программировать при помощи понятий, обозначаемых словами. Чаще всего это английский язык, но есть и примеры кодирования на других языках. Например, программу «Бухгалтерия 1С» можно редактировать и изменять на русском.
Питон поддерживает объектно-ориентированное программирование и является интерпретируемым языком. Это означает, что программа выполняется построчно и для ее выполнения на компьютере должен быть установлен интерпретатор. Самым распространенным и популярным интерпретатором является CPython.
Инкапсуляция в Python
Это концепция упаковки данных так, что внешний мир имеет доступ только к открытым свойствам. Некоторые свойства могут быть скрыты, чтобы уменьшить уязвимость. Это так называемая реализация сокрытия данных. Например, вы хотите купить брюки с интернет-сайта. Данные, которые вам нужны, это их стоимость и доступность. Количество предметов и их расположение — это информация, которая вас не беспокоит. Следовательно, эта информация скрыта.
В Python это реализуется путем создания private, protected и public переменных и методов экземпляра.
Private свойства имеют двойное подчеркивание (__) в начале, в то время как protected имеют одиночное подчеркивание (_). По умолчанию, все остальные переменные и методы являются public.
Private атрибуты доступны только внутри класса и недоступны для дочернего класса (если он унаследован). Protected доступны внутри класса, но доступны и дочернему классу. Все эти ограничения сняты для public атрибутов.
Следующие фрагменты кода являются примером этой концепции:
Сравнение данных
Как правило, объекты данных необходимо сравнивать друг с другом.
Сравнение между двумя объектами и обычно состоит из следующих операций:
- a < b
- a > b
- a == b
- a >= b
- a <= b
В python можно определить в классах, которые могут выполнять вышеуказанные операции. Для простоты, я продемонстрирую лишь реализацию == и <.
Обычный класс
class Number: def __init__( self, val = 0): self.val = val def __eq__(self, other): return self.val == other.val def __lt__(self, other): return self.val < other.val
@dataclass(order = True) class Number: val: int = 0
Да, вот и все.
Нам не нужно определять методы __eq__ и __lt__, потому что декоратор dataclass автоматически добавляет их в определение класса при вызове с order = True
Ну, как это реализуется?
Когда вы используете dataclass, он добавляет функции __eq__ и __lt__ в определение класса. Мы уже знаем это. Как эти функции знают, что нужно проверить равенство или сделать сравнение?
Сгенерированная функцией __eq__ будет сравнивать кортеж своих атрибутов с кортежем атрибутов другого экземпляра того же класса. В нашем случае вот что эквивалентно автоматически сгенерированной функции __eq__:
def __eq__(self, other): return (self.val,) == (other.val,)
Давайте посмотрим на более сложный пример:
Мы напишем класс данных Person, которое будет содержать имя (name) и возраст (age).
@dataclass(order = True) class Person: name: str age:int = 0
Автоматически сгенерированный метод __eq__ будет эквивалентен:
def __eq__(self, other): return (self.name, self.age) == ( other.name, other.age)
Обратите внимание на порядок атрибутов. Они всегда будут генерироваться в порядке, который вы определили в определении класса данных
Аналогично, эквивалентная функция __le__ будет похожа на:
def __le__(self, other): return (self.name, self.age) <= (other.name, other.age)
Необходимость определения функции, подобной __le__, обычно возникает, когда вам нужно отсортировать список ваших объектов данных. Встроенная функция сортировки Python основана на сравнении двух объектов.
>>> import random >>> a = #generate list of random numbers >>> a >>> >>> sorted_a = sorted(a) #Sort Numbers in ascending order >>> >>> reverse_sorted_a = sorted(a, reverse = True) #Sort Numbers in descending order >>> reverse_sorted_a >>>
Конструктор классов
#defining constructor def __init__(self, personName, personAge): self.name = personName self.age = personAge
Конструктор класса в Python – это первый фрагмент кода, который должен выполняться при создании нового объекта класса.
Прежде всего, конструктор можно использовать для помещения значений в переменные-члены. Вы также можете распечатать сообщения в конструкторе, чтобы убедиться, что объект был создан.
Метод конструктора начинается с def __init__. После этого первым параметром должно быть значение «self», так как он передает ссылку на экземпляр самого класса. Вы также можете добавить дополнительные параметры, как показано в примере. «personName» и «personAge» – это два параметра, которые необходимо отправить, когда должен быть создан новый объект.
9.8. Iterators¶
By now you have probably noticed that most container objects can be looped over
using a statement:
for element in 1, 2, 3]: print(element) for element in (1, 2, 3): print(element) for key in {'one'1, 'two'2}: print(key) for char in "123" print(char) for line in open("myfile.txt"): print(line, end='')
This style of access is clear, concise, and convenient. The use of iterators
pervades and unifies Python. Behind the scenes, the statement
calls on the container object. The function returns an iterator
object that defines the method which accesses
elements in the container one at a time. When there are no more elements,
raises a exception which tells the
loop to terminate. You can call the method
using the built-in function; this example shows how it all works:
>>> s = 'abc' >>> it = iter(s) >>> it <iterator object at 0x00A1DB50> >>> next(it) 'a' >>> next(it) 'b' >>> next(it) 'c' >>> next(it) Traceback (most recent call last): File "<stdin>", line 1, in <module> next(it) StopIteration
Having seen the mechanics behind the iterator protocol, it is easy to add
iterator behavior to your classes. Define an method which
returns an object with a method. If the class
defines , then can just return :
class Reverse """Iterator for looping over a sequence backwards.""" def __init__(self, data): self.data = data self.index = len(data) def __iter__(self): return self def __next__(self): if self.index == raise StopIteration self.index = self.index - 1 return self.dataself.index
Обработка после инициализации
С помощью Dataclasses мы может отказаться от использования метода __init__ для присваивания переменных self. Но теперь мы теряем гибкость выполнения вызовов функций, которые могут потребоваться сразу после назначения переменных.
Давайте обсудим пример, в котором мы определяем класс для хранения чисел с плавающей точкой, и сразу вычисляем целую и десятичную части, после инициализации.
Обычный класс
import math class Float: def __init__(self, val = 0): self.val = val self.process() def process(self): self.decimal, self.integer = math.modf(self.val) >>> a = Float( 2.2) >>> a.decimal >>> 0.2000 >>> a.integer >>> 2.0
К счастью, в классах данных обработка после инициализации выполняется с помощью метода .
Сгенерированный метод __init__ вызывает метод __post_init__ . Таким образом, любая обработка может быть выполнена в этом методе.
import math @dataclass class FloatNumber: val: float = 0.0 def __post_init__(self): self.decimal, self.integer = math.modf(self.val) >>> a = Number(2.2) >>> a.val >>> 2.2 >>> a.integer >>> 2.0 >>> a.decimal >>> 0.2
Изящно, не правда ли!
Различия между списком и генератором
В следующих примерах будет создан список и генератор, чтобы увидеть их отличия. Сначала создадим простой список и проверим его тип:
При запуске этого кода, программа вернёт результат «list».
Теперь давайте переберём все элементы в списке squared_list.
Приведенный выше сценарий вернёт следующие результаты:
Теперь создадим генератор и выполнить ту же задачу:
Генератор создаётся подобно коллекции списка, но вместо квадратных скобок нужно использовать круглые скобки. Приведенный выше сценарий вернёт значение «generator» как тип переменной squared_gen. Теперь давайте переберём элементы генератора с помощью цикла for.
Выход будет:
Выходные данные такие же, как у списка. Так в чем же разница? Одно из главных отличий заключается в том, как в список и генератор хранят элементы в памяти. Списки хранят все элементы в памяти сразу, тогда как генераторы «создают» каждый элемент на лету, отображая их, а затем перемещаются к следующему элементу, удаляя предыдущий элемент из памяти.
Один из способов проверить это, узнать длину как списка, так и генератора, который только что создали. Функции len(squared_list) вернет 5, а len(squared_gen) выдаст ошибку отсутствия длины у генератора. Кроме того, список можно перебирать столько раз, сколько захотите, но генератор можно перебирать только один раз. Для повторной итерации необходимо создать генератор снова.
post-init parameters
In an earlier version of this PEP before InitVar was added, the
post-init function __post_init__ never took any parameters.
The normal way of doing parameterized initialization (and not just
with Data Classes) is to provide an alternate classmethod constructor.
For example:
@dataclass class C: x: int @classmethod def from_file(cls, filename): with open(filename) as fl: file_value = int(fl.read()) return C(file_value) c = C.from_file('file.txt')
Because the __post_init__ function is the last thing called in the
generated __init__, having a classmethod constructor (which can
also execute code immediately after constructing the object) is
functionally equivalent to being able to pass parameters to a
__post_init__ function.
With InitVars, __post_init__ functions can now take
parameters. They are passed first to __init__ which passes them
to __post_init__ where user code can use them as needed.
The only real difference between alternate classmethod constructors
and InitVar pseudo-fields is in regards to required non-field
parameters during object creation. With InitVars, using
__init__ and the module-level replace() function InitVars
must always be specified. Consider the case where a context
object is needed to create an instance, but isn’t stored as a field.
With alternate classmethod constructors the context parameter is
always optional, because you could still create the object by going
through __init__ (unless you suppress its creation). Which
approach is more appropriate will be application-specific, but both
approaches are supported.
Свойства Python (@property)
Python содержит очень удобный небольшой концепт, под названием property, который выполняет несколько полезных задач. Мы рассмотрим, как делать следующее:
- Конвертация метода класс в атрибуты только для чтения;
- Как реализовать сеттеры и геттеры в атрибут
Один из самых простых способов использования property, это использовать его в качестве декоратора метода. Это позволит вам превратить метод класса в атрибут класса. Для меня это было очень полезно, когда мне нужно сделать какую-нибудь комбинацию значений.
Для других это было очень кстати при написании методов конверсии, которые нужно было принять в качестве методов. Давайте взглянем на простой пример:
Python
# -*- coding: utf-8 -*-
class Person(object):
«»»»»»
def __init__(self, first_name, last_name):
«»»Конструктор»»»
self.first_name = first_name
self.last_name = last_name
@property
def full_name(self):
«»»
Возвращаем полное имя
«»»
return «%s %s» % (self.first_name, self.last_name)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# -*- coding: utf-8 -*- classPerson(object) «»»»»» def__init__(self,first_name,last_name) «»»Конструктор»»» self.first_name=first_name self.last_name=last_name @property deffull_name(self) «»» Возвращаем полное имя return»%s %s»%(self.first_name,self.last_name) |
В данном коде мы создали два класса атрибута, или свойств: self.first_name и self.last_name.Далее мы создали метод full_name, который содержит декоратор <*@property>*. Это позволяет нам использовать следующий код в сессии интерпретатора:
Python
person = Person(«Mike», «Driscoll»)
print(person.full_name) # Mike Driscoll
print(person.first_name) # Mike
person.full_name = «Jackalope»
Traceback (most recent call last):
File «<string>», line 1, in <fragment>
AttributeError: can’t set attribute
1 2 3 4 5 6 7 8 9 10 |
person=Person(«Mike»,»Driscoll») print(person.full_name)# Mike Driscoll print(person.first_name)# Mike person.full_name=»Jackalope» Traceback(most recent call last) File»<string>»,line1,in<fragment> AttributeErrorcan’tsetattribute |
Как вы видите, в результате превращение метода в свойство, мы можем получить к нему доступ при помощи обычной точечной нотации. Однако, если мы попытаемся настроить свойство на что-то другое, мы получим ошибку AttributeError. Единственный способ изменить свойство full_name, это сделать это косвенно:
Python
person.first_name = «Dan»
print(person.full_name) # Dan Driscoll
1 2 |
person.first_name=»Dan» print(person.full_name)# Dan Driscoll |
Это своего рода ограничение, так что взглянем на другой пример, где мы можем создать свойство, которое позволяет нам делать настройки.
Python Objects and Classes
Python is an object oriented programming language. Unlike procedure oriented programming, where the main emphasis is on functions, object oriented programming stresses on objects.
An object is simply a collection of data (variables) and methods (functions) that act on those data. Similarly, a class is a blueprint for that object.
We can think of class as a sketch (prototype) of a house. It contains all the details about the floors, doors, windows etc. Based on these descriptions we build the house. House is the object.
As many houses can be made from a house’s blueprint, we can create many objects from a class. An object is also called an instance of a class and the process of creating this object is called instantiation.
Протоколы
Такие термины как «протокол итератора» или «протокол дескрипторов» уже привычны и используются давно.
Теперь можно описывать протоколы в виде кода и проверять их соответствие на этапе статического анализа.
Стоит отметить, что начиная с Python 3.6 в модуль typing уже входят несколько стандартных протоколов.
Например, (требующего наличие метода ), (требует ) и некоторых других.
Описание протокола
Протокол описывается как обычный класс, наследующийся от Protocol. Он может иметь методы (в том числе с реализацией) и поля.
Реальные классы, реализующие протокол могут наследоваться от него, но это не обязательно.
Мы можете комбинировать протоколы с помощью наследования, создавая новые.
Однако в этом случае вы так же должны явно указать Protocol как родительский класс
Дженерики, self-typed, callable
Протоколы как и обычные классы могут быть Дженериками. Вместо указания в качестве родителей и можно просто указать
Кроме того, протоколы могут использоваться в тех случаях, когда синтаксиса аннотации недостаточно.
Просто опишите протокол с методом нужной сигнатуры
Проверки в рантайме
Хотя протоколы и рассчитаны в первую очередь на использование статическими анализаторами, иногда бывает нужно проверить принадлежность класса нужному протоколу.
Чтобы это было возможно, примените к протоколу декоратор и / проверки начнут проверять соответствие протоколу
Однако такая возможность имеет ряд ограничений на использование. В частности, не поддерживаются дженерики