Python: большая книга примеров [Антон Леонардович Марченко] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

А. Л. М арченко

Python
ш

БОЛЬШАЯ КНИГА ПРИМЕРОВ

И здательство Московского университета
2023

УДК 004.438
ББК 32.973.2
М30

М30

Марченко, А. Л.
Python: большая книга примеров / А. Л. Марченко. — Москва :
Издательство Московского университета, 2023. — 361, [1] с. —
(Электронное издание сетевого распространения).
ISBN 978-5-19-011853-7 (e-book)
Большая книга примеров основывается на описании языка програм­
мирования Python (https://docs.python.org/) и множества материалов
из различных интернет-источников. Основное предназначение книги —
формирование представления о языке на основе его описания и приме­
ров его применения.
Книга может быть использована в качестве учебного пособия.
УДК 004.438
ББК 32.973.2

ISBN 978-5-19-011853-7 (e-book)
© Марченко А. Л., 2023
© Издательство Московского университета, 2023

Языки. Общие представления

https://t.me/it_boooks/2

Язык - это инструмент для представления (кодирования) ИНФОРМАЦИИ. Информация первична:
сам по себе как инструмент язык представляет ограниченный в деле создания, описания и
сравнения возможностей языков. Языков множество: одна и та же информация одновременно
может быть представлена многими различными разговорными языками естественного
происхождения, языками жестов, языками отдельных областей знаний (язык математики и
логики), языками программирования. В зависимости от языка передаваемая информация может
быть закодирована и передана лицам, соответственно владеющим английским, французским и
другими языками. Существуют подмножества языков (языков жестов), предназначенных для
лётчиков и палубной команды на борту авианосца (aircraft handling officers) в составе (список
прилагается):


Команда катапульты и сдерживающих сетей. (Catapult and arresting gear crews).

■ Авиатехники. (Air wing maintenance personnel).


Команда осуществляющая чек-проверку самолетов (Air wing quality control personnel).



Погрузчики (Cargo-handling personnel).



Команда оборудования
troubleshooters).



Команда отвечающая за улавливающий трос (Hook runners).

наземной

поддержки.

(Ground

support

equipment

(GSE)

■ Фотографы и операторы. (Photographer's mates).


Команда обслуживающая посадку вертолетов. (Helicopter landing signal enlisted personnel
(LSE).



Инспекция по качеству. (Quality Assurance (QA))



Инспектор самолетов эскадрильи. (Squadron plane inspectors)

■ Служба наземных сигналов. (Landing signal officer (LSO))
■ Офицеры по воздушному транспорту. (Air transfer officers (ATO))


Команда работающая с сжиженным кислородом. (Liquid oxygen (LOX) crews)

■ Смотрящий за безопасностью на палубе. (Safety observers)


Медики. (Medical personnel)

■ Оружейники. (Ordnancemen)
■ Спасатели. (Crash and salvage crews)


Палубные взрывотехники. (Explosive ordnance disposal (EOD))



Пожарные. (Firefighter)

■ Авиа регулировщики- стажеры. (Plane handlers (Trainees))
■ Смотрители такелажа/ чернорабочие. (Chocks and chains - entry-level flight-deck workers under
the yellowshirts)
■ Авиалифтеры. (Aircraft elevator operators)


Палубные водители. (Tractor drivers)

2

■ Связисты. (Messengers and phone talkers)
■ Заправщики. (Aviation fuel handlers)


Механики эскадрильи. (Air wing plane captains: squadron personnel that prepare aircraft for flight)



Инспектор финальной проверки. (Final checker (inspector)).



Палубные регулировщики. (Traffic controller)

Например, инспектор финальной проверки (Final checker inspector) жестами сообщает лётчику о
состоянии самолёта при его подготовке к полёту, о ситуации на взлётной палубе, даёт "добро" на
взлет самолета, жестами непосредственно руководит взлётом. Лётчик жестами подтверждает, что
он понимает сигналы инспектора и старается выполнять его команды. О возможно возникающих
проблемах на борту самолёта лётчик жестами (тот же язык но со стороны лётчика) сообщает об
этом палубной команде. Также лётчик сообщает о готовности к полёту, и непосредственно перед
взлётом благодарит персонал палубной команды. Соблюдение правил языка (грамотное
общение) позволяет поддерживать порядок на ограниченной по размерам палубе авианосца
большому количеству самолётов, находящихся в разном состоянии и степени готовности к полёту.
Также существуют языки (языки программирования), которые позволяют закодировать и донести
информацию от программиста до компьютера. В этом случае цель программиста — «внятно и
понятно» изъясняться с компьютером через специальные выполняемые компьтером программы
(интерпретаторы и компиляторы) так, чтобы компьютер "понимал" программиста, а программисту
в ходе общения (решения задачи) были бы понятны сообщения компьютера для данной задачи,
платформы и операционной системы. Для многих конкретных случаев (задач и систем) хороши
определённые языки, а всего их более 1 000. Языков программирования так много, потому что
программисты постоянно находятся в поисках новых инструментов и возможностей, чтобы
упростить и сделать более эффективным процесс разработки программного обеспечения.
Поэтому создаётся много узкоспециализированных языков, написанных специально под
определённую задачу и область знания, а широко известные универсальные языки
программирования совершенствуются и обновляются.Изучить их все невозможно, однако можно
успешно работать с несколькими языками или даже с одним из них.
Примеры трёх программ (скриптов), «понятных» (хотелось, чтобы это было именно так)
программисту
при
описании задачи
и компьютеру
в результате
определённых
регламентированных работ по созданию и выполнению кода. На различных языках
программирования выводятся на экран надписи «Hello, world!»

Язык python
#print("Hello, world!")Pythonusing System;
# This is a sample Python script.
# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows,
# actions, and settings.
# def print hi(name):
def print hi(WRLD):
# Use a breakpoint in the code line below to debug your script.
#print(f'Hi, {name}')
# Press Ctrl+F8 to toggle the breakpoint.
print(f'Hi... {WRLD}')
# Press Ctrl+F8 to toggle the breakpoint.
# Press the green button in the gutter to run the script.
if
name
== ' main ':
#print hi('PyCharm')
print hi('Hello, world')

3

Язык C#
package demo
fun main(args : Array)
{
println(”Hello, world!”)
}

Язык C++
#include
using namspace std;
// class definition
class Message
{
public:
void display()
{
cout 0.8999999999999999
print(0.3 * 3 == 0.9)
> False
Записи float не отличаются от записи int:
# примеры вещественных чисел
zero = 0.0
pi = 3.14
e = 2.71
Область применения float — математические вычисления.
■ complex (комплексное число)
Вещественный ряд расширяет множество рациональных чисел. Ряд комплексных чисел расширяет
множество вещественных. Принципиальной особенностью комплексного ряда является
возможность извлечения корня из отрицательных чисел.
В python комплексные числа задаются с помощью функции complex():
# пример комплексного числа
z = complex(1, 2)
print(z)
> (1+2j)
# вещественная часть
print(z.real)
> 1.0
# мнимая часть
print(z.imag)
> 2.0
# сопряженное комплексное число
print(z.conjugate())
>
ВАЖНО! Для комплексных чисел операция сравнения не определена:
z1 = complex(4, 5)
z2 = complex(100, 200)
print(z1 > z2)
>
Traceback (most recent call last):
print(z1> z2)
TypeError: '>' not supported between instances of 'complex' and 'complex'

16

Комплексные
уравнений.

числа

широко

применяются,

например,

для

решения

дифференциальных

■ bool (логический тип данных)
Самый простой и понятный из всех типов данных python. У bool есть всего два значения:


Истина (True);

■ Ложь (False).
Для булевой алгебры этих значений достаточно.

# пример bool
pravda = True
lozh = False
Переменные логического типа нужны для реализации ветвлений, они применяются для установки
флажков, фиксирующих состояния программы, а также используются в качестве возвращаемых
значений для функций, названия которых, зачастую, начинаются на "is" (isPrime, isEqual, isDigit).
То есть тех, которые, на человеческом языке, отвечали бы на вопрос одним словом "Да" или
"Нет".
■ П оследовательности
Ещё одно понятие из математики, где последовательность — есть нумерованный набор
элементов, в котором возможны их повторения, а порядок имеет значение.
Последовательность в python : упорядоченная коллекция объектов.

■ str (строка)
Строки, - единственный тип, который по частоте применения может сравнитьсяс числовым типом
данных. Определение, справедливое для python звучит так:

строка — это последовательность односимвольных строк.

s = 'Hello, friend. You are my world'
print(type(s))
>
Важность строк велика в первую очередь для людей: что вся письменная речь может
рассматриваться, как множество строк. А так как человеку свойственно обмениваться
информацией именно в виде набора слов, то можно говорить о неограниченном количестве
областей применения строкового типа данных.

■ list (список)
Список — это ещё один вид последовательностей... Здесь стоит остановиться и отметить, что
последовательности в Python бывают изменяемыми и неизменяемыми. Список — изменяемая
17

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

# пример списка
list of lists = [['code alpha', 'code beta'],
list of lists[0][1] = 'code omega'
print(list of lists)
> [['code alpha', 'code omega'],

[553, 434]]

[553, 434]]

Список это объект для хранения наборов данных.
■ tuple (корт еж)
Кортежи в языке python можно рассматривать, как неизменяемые списки со всеми вытекающими
последствиями:

# пример кортежа
tup = ('i', 'j')
# мы можем получить первый элемент
print(tup[0])
> i
# но изменить его не получится
tup[0] = 'k'
>
Traceback (most recent call last):
tup[0] = 'k'
TypeError: 'tuple' object does not support item assignment
Использование кортежей оправдано, когда разработчику
неизменяемость элементов последовательности.

важна

скорость

работы

или

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

# пример простого словаря
dictionary = {'Огонёк': 'уменьш. от слова 'Огонь'}
Применяются они, когда для работы требуется тип данных концептуально схожий с обычным
телефонным справочником, где каждая запись — есть пара сопоставленных друг с другом
значений, по одному из которых (уникальному ключу) можно получить второе (собственно,
значение).

■ set (множество)
Ещё один "набор, но не последовательность".

# пример множества
integer num set = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}

18

Если не важен порядок элементов, но важна их уникальность, то подходит множество.
# свойство уникальности
unique set = {6, 6, 6, 5}
print(unique set)
> {5, 6}

■ Ф айл
Работа с файлами, хранящимися где-то на внешнем носителе, в Python реализована в виде
объектов-файлов. Они относятся к объектам базового типа, но обладают весьма характерной
чертой: нельзя создать экземпляр объекта-файла при помощи литералов.
Чтобы начать работу с файлами, нужно вызвать функцию open() и передать ей в качестве
аргументов имя файла из внешнего источника и строку, описывающую режим работы функции:

f = open('filename.txt',

'w')

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


r — выбирается по умолчанию, означает открытие файла для чтения;

■ w — файл открывается для записи (если не существует, то создаётся новый);
■ x — файл открывается для записи (если не существует, то генерируется исключение);
■ a — режим записи, при котором информация добавляется в конец файла, а не затирает
уже имеющуюся;


b — открытие файла в двоичном режиме;

■ t — ещё одно значение по умолчанию, означающее открытие файла в текстовом режиме;
■ + — чтение и запись;


range object (a type of iterable).

В языке python реализована функция range(), которая создаёт непрерывную последовательность
целых чисел:
r = range(10)
print(r)
> range(0, 10)
for i in r:
print(i, end=' ')
> 0 1 2 3 4 5 6 7 8 9
Она очень удобна при создании циклов.
■ None
None — специальный тип языка python. Объект этого типа означает пустоту, всегда имеет
значение False и может рассматриваться в качестве аналога NULL для языка ^С++. Объект None
возвращается функциями по умолчанию.

null = None

19

print(type(null))
>
На практике этот тип данных может быть полезен, когда надо заполнить список отсутствующими
значениями, чтобы он увеличился, и можно было обращаться к старшим элементам по индексу.
■ Работа с типам и в python
Далее описываются некоторые простые (???) приёмы работы с данными разных типов в python.
■ Определение типа данных
Узнать тип данных объекта в python можно следующим способом:

# Нужно воспользоваться встроенной функцией type()
my list = {'Python': 'The best!'} # объявление объекта - словаря
print(type(my list))
>
■ Изменение типа данных (приведение типов)
Встроенные функции для приведения типов — int(), list(), set(), tuple(), str(), bin().
Важно! встроенная функция для приведения типа не модифицирует переданное значение, а
возвращает новое значение другого типа.

# int() - преобразует числовую строку в целое число
seven = '7'
new seven = int(seven)
print(new seven, type(new seven))
> 7
# list() - приведение итерируемого объекта к списку
nums = (11, 12)
new nums = list(nums)
print(new nums, type(new nums))
> [11, 12]
# set() - трансформация итерируемого объекта во множество
vegetables = ['carrot', 'tomato', 'cucumber']
new vegetables = set(vegetables)
print(new vegetables, type(new vegetables))
> {'cucumber', 'carrot', 'tomato'}
# tuple() - аналогично, но в кортеж
python = 'Python'
new python = tuple(python)
print(new python, type(new python))
> ('P', 'y', 't', 'h', 'o', 'n')
# str() - приведение к строковому типу
ex dict = {'qiwi': 1453}
new ex dict = str(ex dict)
print(new ex dict, type(new ex dict))
> {'qiwi': 1453}
# bin() - преобразует десятичное число в двоичный формат

20

dec = 10
new dec = bin(dec)
print(new dec)
> 0b1010
■ Отличие type() от isinstanceQ
В отличие от type(), функция isinstance() возвращает не тип данных аргумента, а булево значение,
говорящее о том, принадлежит объект к определенному классу или нет:
num = 4.44
print(isinstance(num, float))
> True
isinstance() также умеет проверять принадлежность объекта хотя к одному типу из кортежа,
переданного в качестве второго аргумента:
name = 'Ash'
print(isinstance(name,
> False
print(isinstance(name,
> True

(float, int, complex)))

(float, int, complex, str)))

Важным отличием также является то, что isinstance() "знает" о наследовании. Функция
воспринимает объект производного класса, как объект базового.
class BaseExample:
pass
class DerivedExample(BaseExample):
pass
test = DerivedExample()
print(isinstance(test, BaseExample))
> True
А вот вывод результата работы функции type():
print(type(test))
>

Здесь видно, что для type() объект test является объектом-представителем класса DerivedExample.

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


Методы — действия, которые они могут совершать.

21

■ Принципы ООП
Абстрагирование, полиморфизм, наследование, инкапсуляция... Классы подобны чертежам: это
не объекты, а их схемы. За счёт принципов ООП класс "банковских счетов" имеет строго
определенные и одинаковые для всех атрибуты, но его объекты (сами счета) уникальны.
Далее обсуждаются вопросы, связанные с принципами ООП, объявлением классов, методов и
созданием объектов, представляющих эти классы.

■ Абстракция
Абстракция — это выделение
игнорирование второстепенных.

основных,

наиболее

значимых характеристик

объекта

и

Любой "составной" объект реального мира — это абстракция. Говоря "ноутбук", не требуется
дальнейших пояснений, вроде того, что это организованный набор пластика, металла,
жидкокристаллического дисплея и микросхем.
Абстракция позволяет игнорировать нерелевантные детали, поэтому для сознания человека это
один из способов справляться со сложностью реального мира. Если бы, подходя к холодильнику,
нужно было бы "иметь дело" с отдельно металлом корпуса, пластиковыми фрагментами,
лакокрасочным слоем и мотором, достать из морозилки замороженную клубнику было бы
намного сложнее.

■ Полиморфизм
Полиморфизм подразумевает возможность нескольких реализаций одной идеи. Простой пример:
есть класс "Персонаж", а у него есть метод "Атаковать". Для воина это будет означать удар мечом,
для рейнджера — выстрел из лука, а для волшебника — чтение заклинания "Огненный Шар". В
сущности, все эти три действия — атака, но в программном коде они будут реализованы
совершенно по-разному.

■ Наследование
Это способность одного класса расширять понятие другого, и главный механизм повторного
использования кода в ООП. На уровне абстракции "Автотранспорт" можно не учитывать
особенности каждого конкретного вида транспортного средства, а рассматривать их "в целом".
Если же более детализировано приглядеться, например, к грузовикам, то окажется, что у них есть
такие свойства и возможности, которых нет ни у легковых, ни у пассажирских машин. Но, при
этом, они всё ещё обладают всеми другими характеристиками, присущими автотранспорту.

Можно было бы объявить отдельный класс "Грузовик", который является наследником базового
класса "Автотранспорт". Объекты этого класса могли бы определять все прошлые атрибуты (цвет,
год выпуска), но и получить новые. Для грузовиков это могли быть грузоподъёмность,
снаряженная масса и наличие жилого отсека в кабине. А методом, который есть только у
грузовиков, могла быть функция сцепления и отцепления прицепа.

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

22

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

■ Методы класса
Метод — это функция, объявляемая в классе.
Например, у боевого корабля (броненосец, линкор) есть бортовое оружие. И оно может стрелять.

class WarShip:
def atack(self):
print('Ahh!')
destroyer = WarShip()
destroyer.atack()
> Ahh!

■ Атрибуты класса
У класса имеется собственный набор характеристик, который позволяет описать его сущность. Эти
свойства задаются на этапе объявления класса и называются полями или атрибутами класса.
Поля в классе могут быть объявлены статическими или динамическими.
Статические поля (поля класса) можно использовать без создания объекта. Значения, которые
записываются в эти поля (статические переменные) актуальны для всего множества объектов
(которые объявлены и которые могут быть объявлены в ходе выполнения программного кода).
Статические поля объявляются непосредственно в классе.
Динамические поля (поля объекта) задаются при создании конкретных объектов с помощью
специального метода-конструктора. Информация, которая записывается в поля объекта,
актуальна только для конкретного объекта. Экземпляр нужно создать, а полям присвоить
значения. Динамические поля объявляются в теле метода-конструктора__ init__ .
Объявление класса с именем firstClass и статическим атрибутом (свойством) x:

class firstClass:
x = 5
print(firstClass.x)
Объявление класса со статическим атрибутом и динамическим атрибутом, а также методомконструктором __ init__ .

class MightiestWeapon:
# статический атрибут
name = "Default name"
def

init
(self, weapon type):
# динамический атрибут
self.weapon type = weapon type

23

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

class MightiestWeapon:
# статический атрибут
name = "Default name"
def

init
(self, name):
# динамический атрибут
self.name = name

weapon = MightiestWeapon("sword")
print(MightiestWeapon.name)
print(weapon.name)

В том, что в классе объявлены атрибуты с одним и тем же именем, нет никаких проблем. Это всё
равно разные идентификаторы. Полное имя статического атрибута MightiestWIeapon.name
(имя_класса.имя_атрибута),
полное
имя
динамического
атрибута
weapon, name
(имя_объекта. имя_атр ибута).
На основе класса создаётся объект-представитель класса firstClass под названием p1. Функция
print выводит значение поля x для объекта p1:

p1 = MyClass()
print (p1.x)

В отличие от многих объектно-ориентированных языков, в python не определены спецификаторы
доступа к полям и методам класса. Отсутствие аналогов спецификаторов уровня доступа
public/private/protected (все значения атрибутов и методы одинаково доступны) можно
рассматривать как "упущение" со стороны принципа инкапсуляции.
■ Кон стр уктор
Метод, который вызывается при создании объектов, называется конструктором. Он нужен для
объектов, которые изначально должны иметь какие-то значение. Например, пустые экземпляры
класса "Student" (Студент) бессмысленны, и желательно иметь хотя бы минимальный
обозначенный набор значений вроде имени, фамилии и группы.
В качестве конструктора python обычно выступает метод__ init__ ():

class Student:
def
init
(self, name, surname, group):
self.name = name
self.surname = surname
self.group = group
alex = Student("Alex", "Ivanov", "admin")
Таким образом, объявляемая по умолчанию функция инициализации __ init__ (...) . При
объявлении класса встроенная функция __ init__ может быть переопределена в соответствии с
особенностями объявляемого класса.

24

Эта функция выполняется при создании объекта-представителя данного класса и обычно
используется для присвоения значений свойствам (полям) объекта или выполнения других
операций, которые выполняются при создании объекта. Например, при объявлении класса под
названием Person и предназначенного для сохранения персональной информации, функция
__ init__ () при создании объекта-представителя класса Person применяется для присвоения
значений имени и возраста:

class Person:
def
init
(self, name, age):
self.name = name
self.age = age
pi = Person("Boris",
print(pl.name)
print(pi.age)

36)

Функция __ init__ () автоматически вызывается каждый раз при создании нового объектапредставителя данного класса.
В классе объявляются разнообразные методы. Методы — это функции, которые объявляются в
классе и вызываются 'от имени' объектов-представителей данного класса.
У методов по умолчанию первым параметром является ссылка на объект. Он принимает в
качестве значения ссылку на объект, от имени которого вызывается данный метод. Обычно этот
параметр называется self.
Например, в классе Person, метод hellofunc(self) по значению ссылки self обеспечивает в форме
приветствия вывод информации от имени данного объекта:

class Person:
def
init
(self, name, age):
self.name = name
self.age = age
def

hellofunc(self):
print("Hello, это "

pi = Person("Boris",
print(pi.name)
print(pi.age)
pi.hellofunc()

+ self.name)

36)

Таким образом, параметр self является ссылкой на объект, представляющий класс и используется
для доступа к полям и методам объекта, объявленным в классе. Его не обязательно называть self,
он может называться как угодно, но параметр-ссылка на объект должен быть первым параметром
метода в классе, если этот метод вызывается от имени объекта-представителя класса
(нестатический метод).
Принципы ООП позволяют легко изменять свойства ранее созданных объектов. Например,
изменение возраста объекта p1 (значения поля age) с 36 на 40:
class Person:
def

init
(self, name, age):
self.name = name
self.age = age

25

def

hellofunc(self):
print("Hello, это

pi = Person("Boris",
print(pl.name)
print(pi.age)
pi.hellofunc()

+ self.name)

36)

pi.age = 40
# изменение возраста с 36 на 40
print(pi.age)
p1.hellofunc()

Ранее созданные объекты удаляются с использованием ключевого слова del.

■ Наследование
При написании кода может оказаться, что некоторые объекты-представители разных классов
аналогичны аналогичны за исключением нескольких различий. Определение сходств и различий
между такими объектами реализуется за счёт принципа ООП "наследованием". Несколько классов
наследуют общему классу (классу-родителю).
# класс "Животное". Это достаточно абстрактный класс всего с одним методом
"Издать звук".
class Animal:
def make a sound(self):
print("Издаёт животный звук")

Известно что кошки лазают по деревьям, а собаки грызут всякие предметы. Далее объявляется
пара соответствующих классов-наследников. Факт наследования в python указывается при
объявлении класса-наследника. После имени класса в скобках указывается имя класса-родителя:

class Cat(Animal):
def go on tree(self):
print('Классная ветка! На ней может быть гнездо...')

class Dog(Animal):
def gnaw objects(self):
print('Однажды я доберусь до хозяйской мобилы!')
Теперь объекты этих двух классов могут не только издавать животные звуки, но и выполнять
собственные уникальные действия:
Tom = Cat()
Tom.make a sound()
> Издаёт животный звук
Tom.go on tree()
> Классная ветка! На ней может быть гнездо...!

26

■ Переопределение
Как кошка, так и собака просто "издают животные звуки", а хотелось бы, чтобы звуки были
свойственные именно этим животным. Для этого существует механика переопределения,
основанная на принципе полиморфизма. При этом в классе-наследнике объявляется метод с тем
же названием, что и в базовом классе:
class Dog(Animal):
def gnaw objects(self):
р^пМ'Однажды я доберусь до хозяйской мобилы!')
# далее для объектов класса "Собака" будет выполняться
# именно эта реализация метода
def make a sound(self):
print(,Гав-гав!')
Balto = Dog()
Balto.make a sound()
> Гав-гав!

■ Объявление аргументов в функциях и методах классов
При вызове ранее объявленных функций или методов классов, особенно в случае наследования,
параметры в функции и методы часто передаются в виде последовательностей, представляющих
собой кортежи или словари. Для обозначения кортежей и словарей в объявлении аргументов
функций и методов применяются специальные символы - приставки: "*" или "**" соответственно.
Это особенно полезно в случаях, когда функция / метод принимает переменное количество
параметров.
В python принято, что в программе для передачи кортежа аргументов используется переменная
args, а в случае со словарём — переменная kwargs. Если перед переменной args указан символ
"*", то все дополнительные аргументы, переданные функции / методу, сохранятся в args в виде
кортежа. Если перед kwargs указан символ "**", то все дополнительные параметры будут
рассматриваться как пары "ключ - значение" в аргументе, представленном в виде словаря.
В функциях / методах, задающих свойства таких графических объектов как линия, текст,
прямоугольник, параметры часто объединяют в виде последовательностей * args, либо словарей
** kwargs.
Считается, что это облегчает объявление классов, их свойств методов.

27

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

■ Docstring
Docstring — это строковый литерал, который располагается сразу за объявлением модуля,
функции, класса или метода. О том, какие существуют соглашения в документировании Python
кода описано в документации PEP257.

■ Документация для классов
Документация класса создается для самого класса, а также для его методов.
class Speaker:
"""Это docstring класса Speaker"""
def say something(self):
.. Это docstring метода..
print("something")
Правила документирования: после строки документации нужно оставлять пустую строку.
■ Д окум ентация класса
Документация для класса может содержать следующую информацию:


краткое описание класса (+ его поведение);

■ описание атрибутов класса;
■ описание публичных методов;


все, что связано с интерфейсом для подклассов.

■ Д окум ентация методов класса
Для методов класса документация может содержать:


краткое описание метода (+ его поведение);

■ описание аргументов метода;


побочные эффекты (если таковые возникают при выполнении метода);



исключения.

Далее — пример с более подробной документацией класса:
class TextSplitter:
"""Класс TextSplitter используется для разбивки текста на слова
Основное применение - парсинг логов на отдельные элементы

28

по указанному разделителю.
Note:
Возможны проблемы с кодировкой в Windows
Attributes
file path : str
полный путь до текстового файла
lines : list
список строк исходного файла
Methods
load()
Читает файл и сохраняет его в виде списка строк в lines
get splitted(split symbol=" ")
Разделяет строки списка по указанному разделителю
и возвращает результат в виде списка
I I I I II

def

init
(self, file path: str):
self.file path = file path.strip()
self.lines = []

def load(self) -> None:
"""Метод для загрузки файла в список строк lines
Raises
Exception
Если файл пустой вызовется исключение
I I I I II

with open(self.file path, encoding="utf-8") as f:
for line in f:
self.lines.append(line.rstrip('\n'))
if len(self.lines) == 0:
raise Exception(f"file {self.file path} is empty")
def get splitted(self, split symbol: str = " ") -> list:
"""Разбивает текстовые строки lines, преобразуя строку в
список слов по разделителю
Если аргумент split symbol не задан, в качестве разделителя
используется пробел
Parameters
split symbol : str, optional
разделитель
I I I I II

split list = []
for str line in self.lines:
split list.append(str line.split(split symbol))
return split list

29

■ Д окум ентация д л я пакетов
Документация пакета размещается в файле __ init__ .py в верхней части файла (начиная с 1-й
строки). В ней может быть указано:
■ описание пакета;
■ список модулей и пакетов, экспортируемых этим модулем;
■ автор;


контактные данные;

■ лицензия.

II м II

Пакет Mos помогает создать полноэкранный текстовый интерфейс в консоли.
Alex Ivanov [https://alex.ivanov.ru/]
alex.ivanov@gmail.com
# License: BSD
II II II

author

= 'Alex Ivanov'

try:
from .version import version
except ImportError:
version = "0.0.0"
version

= version

■ Д окум ентация д л я модулей
Документация модулей аналогична документации классов. Вместо класса и методов в данном
случае документируется модуль со всеми его функциями. Размещается в верхней части файла
(начиная с 1-й строки).

■ Ф орм аты Docstring
Строки документации могут иметь различное форматирование. В примере выше использовался
стиль NumPy. Существуют и другие форматы:
■ Google styleguide -> Comments and Docstrings


Numpydoc docstring guide



Epydoc



reStructuredText (reST)

__ doc__ и help(): вывод документации на экран
Строки документации доступны:


из атрибута__ doc__ для любого объекта;

■ с помощью встроенной функции help().
Вывод документации с помощью функции help()
>>> import my module
>>> help(my module)
Help on module test:

30

NAME
test - Это docstring модуля, он однострочный.
FILE
/var/www/test.py
CLASSES
MyClass
class MyClass
| Это docstring класса.
|
| Methods defined here:
my method(self)
Это docstring метода
FUNCTIONS
my function(a)
Это многострочный docstring для функции my function.
В многострочном docstring первое предложение
кратко описывает работу функции.

Также можно выводить документацию отдельного объекта:
>>>
>>>
>>>
>>>
>>>

import my module
my module. doc
my module.my function. doc
my module.MyClass. doc
my module.MyClass.my method.

doc

■ pydoc
Для более удобной работы с документацией, в python существует встроенная библиотека pydoc.
На основе python модулейpydoc автоматически генерирует документацию. Информацию по
доступным командам модуля pydoc можно получить набрав в терминале:
python -m pydoc
Далее приводится описание функционала pydoc

■ Вывод текста документации
pydoc —
покажет текст документации указанного модуля, пакета, функции, класса и т.д. Если
содержит "\", Python будет искать документацию по указанному пути.
Например, документация встроенного модуля math:

python -m pydoc math
Help on built-in module math:
NAME
math

31

DESCRIPTION
This module provides access to the mathematical functions
defined by the C standard.
FUNCTIONS
acos(x, /)
Return the arc cosine (measured in radians) of x.
acosh(x, /)
Return the inverse hyperbolic cosine of x.

В консоль выведется название модуля, его описание и описание всех функций в модуле.

■ Поиск по документации
pydoc -k —
найдет ключевое слово в документации всех доступных модулей.
Пусть требуется распаковать gzip файл. Поиск слова "gzip":
python -m pydoc -k gzip
compression - Internal classes used by the gzip, lzma and bz2 modules
gzip - Functions that read and write gzipped files.
test.test gzip - Test script for the gzip module.
В списке виден модуль gzip. Теперь можно посмотреть его документацию:
python -m pydoc gzip
Help on module gzip:
NAME
gzip - Functions that read and write gzipped files.
DESCRIPTION
The user of the file doesn't have to worry about the compression,
but random access is not allowed.
По описанию, данный модуль может решить эту задачу.

■ H T T P сервер с докум ентацией
Для удобства просмотра документации, pydoc позволяет одной командой создать HTTP-сервер:

python -m pydoc -p 331
Server ready at http://localhost:331/
Server commands: [b]rowser, [q]uit
server>
Теперь можно перейти в браузер и зайти на http://localhost:331/
Для остановки сервера ввести "q" и нажать "Enter":
server> q
Server stopped
Также HTTP-сервер доступен через

32

python -m pydoc -b
эта команда создаст сервер на свободном порту, откроет браузер и перейдет на нужную страницу.

■ Запись до кум ентации в файл
python -m pydoc -w sqlite3 —
запись файла с документацией по модулю sqlite3 в html файл.

■ Автодокум ентирование кода
Для того чтобы облегчить написание документации и улучшить ее в целом, существуют различные
python-пакеты. Один из них — pyment.
pyment работает следующим образом:
■ Анализирует один или несколько скриптов.


Получает существующие строки документации.



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

■ Далее можно применить сгенерированные строки к своим файлам.
Этот инструмент особенно полезен когда код плохо задокументирован, или когда документация
вовсе отсутствует. Также pyment будет полезен в команде разработчиков для форматирования
документации в едином стиле.

■ Установка:
pip install pyment
■ Использование:
pyment myfile.py
# для файла
pyment -w myfile.py # для файла + запись в файл
pyment my/folder/ # для всех файлов в папке
Для большинства IDE также существуют плагины, помогающие документировать код:
■ AutoDocstring - для VS Code.
■ AutoDocstring - для SublimeText.


Python DocBlock Package - для Atom.

■ Autodoc - для PyCharm.
В PyCharm существует встроенный функционал добавления документации к коду. Для этого
нужно:


Переместить курсор под объявление функции.



Написать тройные кавычки.....и нажать "Enter".

■ Сведения об операторах python
Операторы python применяются для формирования выражений из операндов
Большие выражения (с большим количеством операндов) можно форматировать. Д ля этого
используется 'обратный слеш' (\).

33

Благодаря структуре 'лесенкой' язык позволяет проводить цепочечные сравнения.
Логические операторы просты с точки зрения синтаксиса и считаются 'ленивыми'.
Реализовано управление списками и прочими последовательностями. Есть индексация элемента
в списке.
Строки выражений состоят из простых операторов, большинство из которых интуитивно понятны.
При этом выражения формируются без лишних служебных скобок и других операторов типа
'начало' и 'конец'.

■ Операторы управления потоком выполнения
Выражения строятся из операндов с применением операторов. При разработке программного
обеспечения возможно создание 'идеального' кода, который выполняется независимо от
'внешних' условий. На практике приходится создавать 'идеальный' код, который оказывается
намного проще описываемых в реальном мире событий и работает совсем не так как это
предполагалось. Например, нужно выполнить ряд выражений только в том случае, если
соблюдаются определенные условия. Для обработки таких ситуаций в языках программирования
(по аналогии с естественными языками) применяются операторы управления потоком
выполнения. В зависимости от состояния выполняемых выражений (когда какое-то условие в
программе истинно) операторы обеспечивают выполнение циклов или пропуск инструкций.
Оператор-выражение if.
Оператор-выражение if-else.
Оператор цикла while.
Оператор цикла for.
Оператор break.
Оператор continue.

■ if
Оператор if используется для проверки условия. Если условие истинно, запускается блок
операторов (называемый блоком if), в противном случае обрабатывается другой блок операторов
(называемый блоком else). Предложение else не является обязательным.
Синтаксис оператора if:
if condition:



Первая строчка оператора if condition: — это условие if и логическое выражение condition, которое
возвращает True или False. Далее располагается блок инструкций (блоком if). Он представляет
собой одну или больше инструкций.
У каждой инструкции в блоке if одинаковый отступ от слова if.
Во многих языках ( C, C++, Java, PHP,) для определения начала и конца блока используются
фигурные скобки ({}). Для этого в Python используются отступы.

34

Каждая инструкция блока if должна содержать одинаковое количество пробелов. В противном
случае интерпретатор Python возращает синтаксическую ошибку. В документации Python
рекомендуется делать отступы на 4 пробела.
При выполнении инструкции if проверяется условие. Если условие истинно, тогда все инструкции в
блоке if выполняются. Но если условие оказывается неверным, тогда все инструкции внутри этого
блока пропускаются.
Инструкции следом за условием if, у которых нет отступов, к блоку if не относятся. Например,
(см. определение оператора выше) — это не часть блока if, поэтому она
будет выполнена в любом случае.
Пример:

number = ^ М ^ р и М " В в е д и т е число: "))
if number > 10:
р ^ п М " Ч и с л о больше 10")
Следующий код в качестве демонстрации более тонкого применения оператора if:

# This is a sample Python script.
def print str(number):
print(f'number, {number}')
if number > 10:
print("первое уведомление")
print("второе уведомление")
print("третье уведомление")

# 3
# 4
# 5

print("Этот код выполняется каждый раз при запуске программы")
print("Конец")
# Press the green button in the gutter to run the script.
if
name
== ' main ':
number = int(input("Ввести число: "))
print str(number)
Первый вывод:

Введите число: 45
первое уведомление
второе уведомление
третье уведомление
Этот код выполняется каждый раз при запуске программы
Конец

Второй вывод:

35

# 7
# 8

Введите число: 4
Этот код выполняется каждый раз при запуске программы
Конец
Важно, что в приведённом примере к блоку if относятся только выражения на строках 3, 4 и 5.
И они будут выполнены в том случае, когда условие if будет истинно. Инструкции на строках 7 и 8
выполняются в любом случае.
При работе в консоли Python для разбития выражения на несколько строк используется оператор
продолжение (\). Но в случае с операторами управления интерпретатор Python автоматически
активирует мультистрочный режим, если нажать Enter после условия if.
При применении операторов управления непосредственно в консоли Python, её реакция будет
другой:

>>>
>>> n = 100
>>> if n > 10:
Для многострочных инструкций консоль Python после нажатия Enter на строке с условием if
преобразует командную строку с '>>>' на '...'. Таким образом она показывает, что начатая
инструкция все еще не закончена.

Чтобы завершить инструкцию if, в блок if нужно добавить пустую инструкцию:

>>>
>>> n = 100
>>> if n > 10:
...
print(”n v 10”)
>>>
Python автоматически отступов не добавляет. Программист это делает самостоятельно., Чтобы
после ввода инструкции на консоли сразу выполнить эту инструкцию, инструкцию, нужно дважды
нажать Enter. После этого консоль возвращается к изначальному состоянию.

>>>
>>> n = 100
>>> if n > 10:
...
print(”n больше чем 10”)
n больше чем 10
>>>

Все эти программы заканчиваются внезапно, ничего не показывая, если условие не истинно. Но в
большинстве случаев пользователю нужно показать хотя бы что-нибудь. Для этого используется
оператор-выражение if-else.

36

■ if - else
if-else исполняет одну порцию инструкций, если условие истинно и другую — если нет. Таким
образом, оператор предлагает два направления действий. Синтаксис оператора if-else
следующий:

if

condition:
# блок if
statement 1
statement 2
and so on
else:
# блок else
statement 3
statement 4
and so on:
Описание работы:
При выполнении оператора if-else, условие проверяется, и если оно возвращает True, когда
инструкции в блоке if исполняются. Если возвращается False, исполняются инструкции из блока
else.
Пример 1: программа для расчета площади и длины окружности круга.

radius = int(input("Введите радиус: "))
if radius >= 0:
print("Длина окружности = ", 2 * 3.14 * radius)
print("Площадь = ", 3.14 * radius ** 2)
else:
print("Требуется ввести положительное число")
Первый вывод:

Введите радиус: 4
Длина окружности = 25.12
Площадь = 50.24
Второй вывод:

Введите радиус: -12
Требуется ввести положительное число
Теперь программа показывает корректный ответ пользователю, даже если условие if не является
истинным.

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

37

radius = ^ М ^ р и М " В в е д и т е радиус: "))

if radius >= 0:
print("Длина окружности = ", 2 * 3.14
р^пМ"Плош;адь = ", 3.14 * radius ** 2)

*

radius)

else:
р^пМ"Введите положительное число")
При запуске этой программы, появляется сообщение об ошибке:

$ python3 if and else not aligned.py
File "if and else not aligned.py", line 6
else:
A

SyntaxError: invalid syntax
$
Для исправления проблемы нужно вертикально выровнять if и else

Другой пример:

Пример 2: код для проверки пароля, введенного пользователем.

password = input("Введите пароль: ")
if password == "sshh":
Р ^ п М " Д о 6р о пожаловать")
else:
Р ^ п М " Д о с т у п запрещен")
Первый вывод:

Введите пароль: sshh
Добро пожаловать
Второй вывод:

Введите пароль: abc
Доступ запрещен

■ Оператор if внутр и другого if -оператора
Операторы if-else можно использовать внутри других инструкций if или if-else (вложенные
операторы). Пример 1:
#программа, проверяющая, имеется ли право на кредит.
38

def def score(gre score, per grad):
if per grad > 70:
# внешний блок if
if gre score > 150:
# внутренний блок if
print("вам выдан кредит")
else:
print^^e: не имеете права на кредит")

if

name
== ' main ':
gre score = ^ М ^ р и М " В в е д и т е текущий лимит: "))
per grad = ^ М ^ р и М " В в е д и т е кредитный рейтинг: "))
def score(gre score, per grad)

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

Описание работы:

Сначала оценивается внешнее условие if, то есть per_grad > 70.
Если оно возвращает True, тогда управление программой происходит внутри внешнего блока if.
Там же проверяется условие gre_score > 150.
Если оно возвращает True, тогда в консоль выводится "you are eligible for loan".
Если False, тогда программа выходит изинструкции if-else, чтобы исполнить следующие операции.
Ничего при этом не выводится в консоль.

При этом если внешнее условие возвращает False, тогда выполнение инструкций внутри блока if
пропускается, и контроль переходит к блоку else (9 строчка).

Первый вывод:

Введите текущий лимит: 160
Введите кредитный рейтинг: 75
вам выдан кредит
Второй вывод:

Введите текущий лимит: 160
Введите кредитный рейтинг: 60
вы не имеете права на кредит

39

У этой программы есть одна проблема при её повторном запуске и вводе gre_score меньше чем
150, а per_grade — больше 70:

Вывод:

Введите текущий лимит: 140
Введите кредитный рейтинг: 80
Программа ничего не выводит. Причина в том, что у вложенного оператора if нет условия else.
Оно добавляется в следующем примере.

Пример 2: инструкция if-else внутри другого оператора if.

#программа, проверяющая, имеется ли право на кредит.
def def score(gre score, per grad):
if per grad > 70:
# внешний блок if
if gre score > 150:
# внутренний блок if
print("вам выдан кредит")
else
print("y вас низкий кредитный лимит")
else:
print^^e: не имеете права на кредит")

if

name
== ' main ':
gre score = int(input("Введите текущий лимит: "))
per grad = int(input("Введите кредитный рейтинг: "))
def score(gre score, per grad)

Вывод:

Введите текущий лимит: 140
Введите кредитный рейтинг: 80
У вас низкий кредитный лимит

Описание работы:
Эта программа работает та же, как и предыдущая. Единственное отличие — у вложенного
оператора if теперь есть инструкция else. Теперь если ввести балл GRE меньше, чем 150,
программа выведет: 'У вас низкий кредитный лимит'

40

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

Оператор if-else внутри условия else
Пример 3: программа для определения оценки клиента на основе введенных баллов.

def score maker(score):
if score >= 90:
р^пМ"Отлично! Ваша оценка А")
else:
if score >= 80:
р^пМ"Здорово! Ваша оценка - B")
else:
if score >= 70:
р^пМ"Хорошо! Ваша оценка - C")
else:
if score >= 60:
print("Ваша оценка - D. Стоит повторить материал.")
else:
print("Bbi не сдали экзамен")

if

name
== ' main ':
score = ^ М ^ р и М " В в е д и т е вашу оценку: "))
score maker(score)

Первый вывод:

Введите вашу оценку: 92
Отлично! Ваша оценка А
Второй вывод:

Введите вашу оценку: 72
Хорошо! Ваша оценка - C
Третий вывод:

Введите вашу оценку: 56
Вы не сдали экзамен

Описание работы:
Когда управление программой переходит к оператору if-else, проверяется условие на строке 3
(score >= 90). Если оно возвращает True, в консоль выводится 'Отлично! Ваша оценка А'. Если
значение неверное, управление переходит к условию else на 5 строке. Теперь проверяется
41

условие score >= 80 (6 строка). Если оно верное, тогда в консоли выводится 'Здорово! Ваша оценка
— B'.
В противном случае управление программой переходит к условию else на 8 строке. И здесь снова
имеется вложенный оператор if-else. Проверяется условие (score >= 70). Если оно истинно, тогда в
консоль выводится "Хорошо! Ваша оценка — C".
В противном случае управление переходит к блоку else на 11 строке. В конце концов, проверяется
условие (score >= 60). Если оно возвращает True, тогда в консоль выводится "Ваша оценка — D.
Стоит повторить материал."
Если же False, тогда в консоли будет "Вы не сдали экзамен". На этом этапе управление переходит
к следующим инструкциям, написанным после внешнего if-else.
Хотя вложенные операторы if-else позволяют проверять несколько условий, их довольно сложно
читать и писать. Эти же программы можно сделать более читабельными и простыми с помощью
if-elif-else.
■ Оператор if-elif-else
Оператор if-elif-else — это альтернативное представление оператора if-else, которое позволяет
проверять несколько условий, вместо того чтобы писать вложенные if-else. Синтаксис этого
оператора следующий:

if condition 1:
# блок if
statement
statement
more statement
elif condition 2:
# первый блок elif
statement
statement
more statement
elif condition 3:
# второй блок elif
statement
statement
more statement

statement
statement
more statement

Замечание: многоточие '...' в определении оператора означает, что в данном операторе можно
написать сколько угодно условий eilf.
Описание работы оператора:
Когда исполняется инструкция if-elif-else, в первую очередь проверяется condition_1. Если условие
истинно, тогда исполняется блок инструкций if. Следующие условия и инструкции пропускаются, и
управление переходит к операторам вне if-elif-else.

42

Если condition_1 оказывается ложным, управление переходит к следующему условию elif, и
проверяется condition_2. Если оно истинно, тогда исполняются инструкции внутри первого блока
elif. Последующие инструкции внутри этого блока пропускаются.
Этот процесс повторяется, пока не находится условие elif, которое оказывается истинным. Если
такого нет, тогда исполняется блок else в самом конце.
Программу для определения оценки можно переписать с помощью оператора if-elif-else.

def score maker(score):
if score >= 90:
р^пМ"Отлично! Оценка А")
elif score >= 80:
р^пМ"Здорово! Оценка - B")
elif score >= 70:
print("Хорошо! Оценка - C")
elif score >= 60:
print("Оценка - D. Стоит повторить материал.")
else:
print("экзамен не сдан")

if

name
== ' main ':
score = int(input("Введите вашу оценку: "))
score maker(score)

Первый вывод:

Введите оценку: 78
Хорошо! Оценка - C
Второй вывод:

Введите оценку: 91
Отлично! Оценка А
Третий вывод:

Введите оценку: 55
экзамен не сдан
Такая программа легче читается, чем программа с вложенными if-else.

■ Итерации
Далее рассматривается циклический процесс и его составляющие элементы.

43

Итерация (Iteration) — это одно из повторений цикла (один шаг или один "виток" циклического
процесса). Например, цикл из 3-х повторений можно представить как 3 итерации итерируемого
объекта.
Итерируемый объект (Iterable) — объект, который обеспечивает повторение. Этот объект за
каждую итерацию возвращает по одному результату..
Итератор (iterator) — итерируемый объект. В рамках итератора реализован метод __ next__ ,
который позволяет получать следующий элемент итерации.
Для выполнения итерации интерпретатор python выполняет следующие действия:




Вызывает у итерируемого объекта метод iter(), тем самым получая итератор.
Вызывает метод next(), чтобы получить каждый элемент от итератора.
Когда метод next возвращает исключение StopIteration, цикл останавливается.

■ Схема работы итератора в python
итерируемый объект (iterable)
a = [1, 2, 3]
iter()
итератор (iterator)
next()

next()

next()

next()

1

2

3

StopIteration

■ Операторы ц икла
Цикл — это управляющая конструкция, которая раз за разом выполняет тело цикла
представленное серией команд до тех пор, пока является истинным условие для выполнения
тела. Применение циклов — это возможность многократного исполнения определенного участка
кода. Циклы в python представлены двумя основными конструкциями: while и for.
Цикл while считается универсальным, а цикл for нужен для обхода последовательности
поэлементно.
Оба оператора одинаково применимы и являются важнейшими элементами языка python.
while..condition..block
for..in..iter_object
■ while
Пока условие истинно, оператор while позволяет многократно выполнять блок операторов.
Оператор while является одним из вариантов оператора цикла. В инструкции while есть
необязательное условие else. Но блок else на самом деле избыточен, потому что размещение его
после блока while может служить той же цели.

count = 1 # начальное значение управляющей переменной
while count

после 9-й итерации в count окажется значение 10
это удовлетворяет условию count 10:
print('count {)'.format(count))
count += 1
else:
a = False
print('end the while loop')
Ещё примеры
В Python возможны составные условия. Они могут быть сколь угодно длинными, а в их записи
используются логические операторы (not, and, or):

dayoff = False
sunrise = 6
sunset = 18
worktime = 12
# пример составного условия
while not dayoff and sunrise >>') и вывода
import numpy as np
a = np.array([1, 2, 3])
a
type(a)
При объявлении массива важно, что аргументом функции array() должна быть именно
последовательность, а не несколько аргументов.
Пример некорректного объявления массива:
>>> a = np.array(1, 2, 3)
# Неправильно!!!
Traceback (most recent call last):
File "", line 1, in
ValueError: only 2 non-keyword arguments accepted
>>>
>>> a = np.array([1, 2, 3])
# Правильное объявление
>>> a
array([1, 2, 3])
>>>
>>> a = np.array((1, 2, 3))
# И так тоже правильно
>>> a
array([1, 2, 3])
>>>
без приглашения ('>>>') и вывода
import numpy as np
a = np.array(1, 2, 3)
# Неправильно!!!
Traceback (most recent call last):
File "", line 1, in
ValueError: only 2 non-keyword arguments accepted
a = np.array([1, 2, 3])
a
array([1, 2, 3])

#

Правильное объявление

a = np.array((1, 2, 3))
a
array([1, 2, 3])

#

И так тоже правильно

Уровень вложенности исходной последовательности в выражении создания массива определяет
размерность получаемого массива.

62

Функция array() преобразует последовательности последовательностей в двумерные массивы, а
последовательности последовательностей, которые тоже состоят из последовательностей в
трехмерные массивы.
>>> a = np.array([[2, 4], [6, 8], [10, 12]])
>>> a
array([[ 2, 4],
[ 6, 8],
[10, 12]])
>>>
>>> b = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]])
>>> b
array( [[[ 1, 2],
[ 3, 4]],
[[ 5,
[ 7,

6],
8]],

[[ 9, 10],
[11, 12]]])
>>>
>>> a. ndim
2
>>> b.ndim
3

#

Количество

без приглашения ('>>>') и вывода
a = np.array([[2, 4], [6, 8], [10, 12]])
b = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]])
a.ndim
# Количество осей массива
b. ndim
Функция array() также позволяет определить тип данных массива.

+

СО

LJ.

О

О

+

О

>>> a = np.array([[2, 4]
[6, 8], [10, 12]], dtype = complex
>>> a
array([[ 2.+0 .■j,
4, +0 ■j],
+0 ■j],
[
j,
12
+0 ■j]])
[
>>>
>>> a = np.array([[2, 4], [6, 8], [10, 12]], dtype = float )
>>> a
array([[
2., 4.],
[
6., 8.],
[ 10., 12.]])
Очень часто возникает задача создания массива определенного размера, причем абсолютно
неважно чем заполнен массив. В этом случае можно воспользоваться циклами или генераторами
списков (кортежей), но NumPy для таких случаев предлагает более быстрые и менее затратные
функции-заполнители:
■ функция zeros заполняет массив нулями,
■ функция ones заполняет массив единицами,
■ функция empty заполняет массив случайными числами, которые зависят от состояния
памяти.

63

По умолчанию, тип создаваемого массива - float64.

О

1

О


1 I—
1
+ +
I

CD

CO

о о
+ + +

О

>>> np.zeros((3,3))
array( [[ 0. , 0., 0.] ,
[ 0. , 0., 0.] ,
[ 0. , 0., 0.] ])
>>>
>>> np .ones ((3 ,3))
array( [[ 1. , 1., 1.] ,
[ 1. , 1., 1.] ,
[ 1. , 1., 1.] ])
>>>
>>> np .ones ((3 ,3), dtype =
array( [[ 1.
TU
[ 1.
ZU
.^,
[ 1.
ZU 1.+0 .^,
>>>
>>> np .empty([ 3, 3])
array( [[ -2 .563577 99e- 042,
[ 1.51 6687'96e-314,
[ 2 .61270S
262,

complex)
#
1 +0 .j],
1 +0 .j],
1 +0 .j]])

Можно изменить тип массива

1. 00079160e-313,
0. 00000000e+000,
0. 00000000e+000,

-5.41541116e-070],
1.48219694e-320],
8.36469502e-316]])

Для создания последовательностей чисел NumPy предоставляет функцию arange, которая
возвращает одномерные массивы:
>>> np.arange(10)
# От 0 до указанного числа
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>>
>>> np.arange(10, 20)
# Диапазон
array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19])
>>>
>>> np.arange(20, 100, 10)
# Диапазон с заданным шагом
array([20, 30, 40, 50, 60, 70, 80, 90])
>>>
>>> np.arange(0, 1, 0.1)
# Аргументы могут иметь тип float
array([ 0. , 0.1,
0.2, 0.3, 0.4, 0.5,
0.6, 0.7, 0.8,0.9])
Если функция arange используется с аргументами типа float, то предсказать количество элементов
в возвращаемом массиве не просто. Гораздо чаще возникает необходимость указания не шага
изменения чисел в диапазоне, а количества чисел в заданном диапазоне.
Функция linspace, так же как и arange принимает три аргумента, но третий аргумент, как раз и
указывает количество чисел в диапазоне.
>>> np.linspace(0, 1, 5)
array([ 0. , 0.25, 0.5 , 0.75, 1. ])
>>>
>>> np.linspace(0, 1, 7)
array([ 0.
, 0.16666667,
0.33333333,
0.5
0.83333333, 1.
])
>>>
>>> np.linspace(10, 100, 5)
array([ 10. ,
32.5,
55. ,
77.5, 100. ])

,

0.66666667,

Функция linspace удобна еще и тем, что может быть использована для вычисления значений
функций на заданном множестве точек:
>>> x = np.linspace( 0, 2*np.pi, 10 )

64

>>> x

array([ 0.
, 0.6981317 , 1.3962634 , 2.0943951 , 2.7925268 ,
3.4906585 , 4.1887902 , 4.88692191,
5.58505361,
6.28318531])
>>>
>>> y1 = np.sin(x)
>>> y1
array([ 0.00000000e+00,
6.42787610e-01,
9.84807753e-01,
8.66025404e-01,
3.42020143e-01,
-3.42020143e-01,
-8.66025404e-01, -9.84807753e-01,
-6.42787610e-01,
-2.44929360e-16])
>>>
>>> y2 = np.cos(x)
>>> y2
array([ 1.
, 0.76604444,
0.17364818, -0.5
, -0.93969262,
-0.93969262, -0.5
, 0.17364818,
0.76604444,
1.
])

■ Массив как класс. Перечень методов
Первым аргументом каждого из перечисляемых в этом разделе методов является параметр self ссылка на соответствующий объект-представитель класса array. Другие аргументы методов будут
рассмотрены позже.
■ append(self, ...) добавить любое значение данного типа в массив. Отдельные элементы в
массиве могут быть доступны через индексы. Индексация элементов массива Python
начинается с нуля.
■ insert(self, ...) вставить значение по любому индексу массива.
■ extend(self, ...) расширение массива python (???). Массив Python может быть расширен с
более чем одного значения с помощью метода extend().
■ fromlist(self, ...) добавить элементы из списка в массив, используя метод
■ remove(self, ...) удалить любой элемент массива, используя метод
■ pop(self, ...) удалить последний элемент массива
■ index(self, ...) получить любой элемент через его индекс
■ reverse(self, ...) получить обратный массив в Python. Метод изменяет порядок индексации
элементов массивов.
■ buffer_info(self, ...) позволяет получить информацию о буфере массива. Этот метод
предоставляет начальный адрес буфера массива в памяти и количество элементов в
массиве.
■ count(self, ...) проверка количества вхождений элемента в массиве.
■ tounicode(self, ...) преобразовать массив в строку.
■ tolist(self, ...) преобразовать массив в список Python с теми же элементами. Применяется,
если вместо массива нужен список объектов.
■ __ len(self)__ количество элементов массива.



Индексация в массиве

При работе с индексами массивов используются квадратные скобки ([ ]). С помощью
индексирования можно ссылаться на отдельные элементы, выделяя их или даже меняя значения.
При создании нового массива шкала с индексами создается автоматически. Для получения
доступа к одному элементу на него нужно сослаться через его индекс.

a = np.arange(10, 16)
>>> a
array([10, 11, 12, 13, 14, 15])
>>> a[4]
14

65

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

>>> a[-1]
15
>>> a[-6]
10

Для выбора нескольких элементов в квадратных скобках можно передать массив индексов.

>>> a[[1, 3, 4]]
array([11, 13, 14])

Двухмерные массивы, матрицы, представлены в виде прямоугольного массива, состоящего из
строк и колонок, определенных двумя осями, где ось 0 представлена строками, а ось 1 —
колонками. Таким образом индексация происходит через пару значений; первое — это значение
ряда, а второе — колонки. И если нужно получить доступ к определенному элементу матрицы,
необходимо все еще использовать квадратные скобки, но уже с двумя значениями.

>>> A = np.arange(10, 19).reshape((3, 3))
>>> A
array([[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])

Если нужно удалить элемент третьей колонки во второй строке, необходимо ввести пару [1, 2].

>>> A[1, 2]
15

■ Итерация в массиве
Для перебора по элементам массива применяется конструкция
for in :
>>> for i in A:
print(i)
10
11
12
13
14
15
16

66

17
18

Здесь в случае с двухмерным массивом можно использовать вложенные циклы внутри for.
Первый цикл будет сканировать строки массива, а второй — колонки. Если применить цикл for к
матрице, он всегда будет перебирать в первую очередь по строкам.
>>> for row in A:
print(row)
[10 11 12]
[13 14 15]
[16 17 18]

Если необходимо перебирать элемент
конструкцию, применив цикл for для A.flat:

за

элементом,

можно

использовать

следующую

>>> for item in A.flat:
print(item)
10
11
12
13
14
15
16
17
18

NumPy предлагает и альтернативный способ итерации. Перебор элементов, как правило,
используется в функциях для конкретных рядов, колонок или отдельных объектов. Можно
запустить функцию агрегации, которая вернет значение для каждой колонки или даже для каждой
строки, но существует более оптимальный способ, когда NumPy забирает процесс итерации на
себя: функция apply_along_axis().
Эта функция принимает три аргумента:
■ функцию,
■ ось, для которой нужно применить перебор,
■ сам массив.
Если значение аргумента ось равно 0, функция будет применена к элементам по колонкам, а если
1 — то по строкам. Например, можно посчитать среднее значение сперва по колонкам, а потом
по строкам.

>>> np.apply
array([ 13.,
>>> np.apply
array([ 11.,

along axis(np.mean, axis=0, arr=A)
14., 15.])
along axis(np.mean, axis=1, arr=A)
14., 17.])

Ранее использовались функции из библиотеки NumPy, но можно определить (написать) и
применить собственные.

67

■ Срезы в массиве
Срезы позволяют извлекать части массива, возможно, для создания новых массивов.
Результирующие массивы, которые получаются в результате применения срезов для списков
python являются копиями исходного массива на основе одного и того же буфера.
В зависимости от части исходного массива, которую необходимо извлечь с помощью операции
индексации, нужно использовать срез - последовательность числовых значений, разделенную
двоеточием (:) в квадратных скобках операции индексации.
Например, чтобы получить часть массива от второго до шестого элемента, необходимо ввести
индекс первого элемента — 1 и индекса последнего — 5 (индексация массива начинается с 0),
разделив их двоеточием (:) .

>>> a = np.arange(10, 16)
>>> a
array([10, 11, 12, 13, 14, 15])
>>> a[1:5]
array([11, 12, 13, 14])

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

>>> a[1:5:2]
array([11, 13])

При использовании срезов возможны случаи, когда явные числовые значения не используются.
Если не ввести первое число, NumPy (интерпретатор python) неявно интерпретирует его как 0 (то
есть, первый элемент массива).
Если пропустить второй — он будет заменен на максимальный индекс.
Если пропустить последний — он представляется как 1.
Таким образом, все элементы будут перебираться без интервалов.
>>> a[::2]
array([10, 12, 14])
>>> a[:5:2]
array([10, 12, 14])
>>> a[:5:]
array([10, 11, 12, 13, 14]

Срезы также работают в случае с двухмерными массивами, но их (срезы) нужно определять
отдельно для строк и колонок. Например, если нужно получить только первую строку:
>>> A = np.arange(10, 19).reshape((3, 3))
>>> A
array([[10, 11, 12],
[13, 14, 15],
[16, 17, 18]])

68

Если по второму индексу оставить только двоеточие без числа, будут выбраны все колонки.
>>> A[0,:]
array([10, 11, 12])
Если нужно выбрать все значения первой колонки, то нужно записать обратное.
>>> A[:,0]
array([10, 13, 16])

Если из исходного массива необходимо извлечь матрицу меньшего размера, то нужно явно
указать все интервалы с соответствующими индексами.
>>> A[0:2, 0:2]
array([[10, 11],
[13, 14]])

Если индексы в строках или колонках не последовательны, в выражении индексации исходного
массива указывается массив индексов.
>>> A[[0,2], 0:2]
array([[10, 11],
[16, 17]])

Также в языке определены списки. Список - это структура данных, в которой могут быть записаны
значения разных типов.
Списки python могут содержать значения, соответствующие разным типам данных. При этом
доступ к элементам списка также обеспечивается с помощью индексации. В Python это основное
различие между массивами и списками.
Следующий пример демонстрирует некоторые приёмы работы с массивами:
■ различные способы инициализации массива;
■ добавление значений данного типа в массив;
■ вставка значения определённого типа по данному индексу массива;
■ сохранение массива в поле объекта-представителя данного класса;
■ определение количества элементов в массиве;
■ вывод элементов массива в зависимости от значения аргумента.
from array import *
class xClass:
buff =
array0
array1
array2
array3
def

None
= None
= None
= None
= None

69

ОЭ
ОЭ

[■"■-

init
(self):
self.array0 = array(' i', [0, 1, 2, 3, 4, 5, 6, 7,
self.array1 = array(' f', [0, 1, 2, 3, 4, 5, 6, 7,
self.array2 = [0, 11, 22, 33, 44, 55,
self.array3 = array(' u , [ q , w , e , 'r','t','y',

8, 9])
8, 9])
99]
'u', 'i'

def xprint(self, name):
match
name:
case 'arrayO':
self.buff = self.arrayO
case 'arrayl':
self.buff = self.arrayl
case 'array2':
self.buff = self.array2
case 'array3':
self.buff = self.array3
self.buff.append('z')
self.buff.insert(5, 'x')
array3size = self.buff. len ()
print('size of array3 is ', array3size)
for i in self.buff:
print(i)
def print hi(name):
print(f'Hi, {name}')
xxx = xClass()
xxx.xprint(name)
if

name
print
print
print
print

# Press Ctrl+F8 to toggle the breakpoint.

== ' main
hi('array0')
hi('array1')
hi('array2')
hi('array3')

70

■ Списки
Списки в Python - упорядоченные изменяемые коллекции объектов ПРОИЗВОЛЬНЫХ типов. В
отличие от массивов, в список могут включаться данные различных типов.

■ Создание списков
Чтобы использовать списки, их нужно создать. Существует несколько способов создания списков.
Например, можно обработать итерируемый объект (объект, для которого определена операция
итерации — перебора объекта по составляющим его элементам, например, строка состоит и
символов и для неё определены посимвольные операции) встроенной функцией list:
>>> list('список')
['с', 'п', 'и', 'с', 'о', 'к']

или
list('список')
Список можно создать и при помощи литерала:
>>>
>>> s = [] # Пустой список (массив)
>>> l = ['s', 'p', ['isok'], 2] # символы, массив строк, целое
>>> s
[]
>>> l
['s', 'p', ['isok'], 2]
или
s = [] # Пустой список (массив)
l = ['s', 'p', ['isok'], 2] # символы, массив строк, целое
s
l
Список может содержать любое количество любых объектов (в том числе и вложенные списки),
или не содержать ничего.
Ещё один способ создания списка - применение генератора списков. Генератор списков строит
новый список на основе итерируемого объекта, применяя выражение генератора к каждому
элементу итерируемого объекта.
>>> c = [c * 3 for c in 'list']
>>> c
['lll', 'iii', 'sss', 'ttt']
или

c = [c * 3 for c in 'list']
c
Более сложные конструкции генераторов...
>>>
>>> c = [c * 3 for c in 'list' if c != 'i']

71

>>> c
'ttt']
['lll', sss
>>> c = [c + d for c in list' if c != 'i' for d in 'spam' if d != 'a']
>>> c
['ls', lp', 'lm', 'ss', 'sp', 'sm', 'ts', 'tp', 'tm']

Xсо

или
■H

ъ

II

T>
4-1
■H

i
—i

ft

II

и
4-1
■H

c = [c
for c in 'list'
c # 1
;'lll ', 'sss', 'ttt' ]
c = [c + d for c in 'list' if c != 'i' for d in 'spam'
c # [
, 'lp', 'lm', 'ss', 'sp', 'sm', 'ts',

'a

'tm']

Первый итерируемый объект — строка 'list'. Генератор выбирает из строки каждый символ и
добавляет его в список, предварительно утраивая этот символ, если только это не символ 'i'.
Во втором случае используются два итерируемых объекта — строки 'list' и 'spam'. Составляющие
итерируемые объекты символы попарно объединяются с помощью операции '+', если только это
не символы 'I' или 'a' .
Рекомендуется не усложнять код и не злоупотреблять генераторами, а применять простые
операции генерации, в том числе и операторы цикла for.

■ Функции и методы списков
Списки созданы, как теперь их применыть. Для списков в python доступны основные встроенные
функции, а также методы списков. Таблица методы списков:
Метод

Что делает

list.append(x)

Добавляет элемент в конец списка.

list.extend(L)

Расширяет список list, добавляя в конец все элементы списка L.

list.insert(i, x)

Вставляет на значение x по индексу i-го элемента.

list.remove(x)

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

list.pop([i])

Удаляет i-ый элемент и возвращает его. Если индекс не указан,
удаляется последний элемент.

list.index(x, [start [, end]])

list.count(x)

list.sort([key=функция])
list.reverse()

имеющий значение x.
если
такого
элемента

Возвращает положение первого элемента со значением x (при этом
поиск ведется от start до end).

Возвращает количество элементов со значением x.

Сортирует список на основе функции.
Разворачивает список.

72

list.copy()

Поверхностная копия списка.

list.clear()

Очищает список.

Методы списков, в отличие от строковых методов, изменяют сам список, а потому результат
выполнения не нужно записывать в этот список как в переменную.
>>>
>>> l = [1, 2, 3, 5, 7]
>>> l.sort()
>>> l
[1, 2, 3, 5, 7]
>>> l = l.sort()
>>> print(l)
None
примеры работы со списками:

>>>
>>> a = [66.25, 333, 333, 1, 1234.5]
>>> print(a.count(333), a.count(66.25), a.count('x'))
2 1 0
>>> a.insert(2, -1)
>>> a.append(333)
>>> a
[66.25, 333, -1, 333, 1, 1234.5, 333]
>>> a.index(333)
1
>>> a.remove(333)
>>> a
[66.25, -1, 333, 1, 1234.5, 333]
>>> a.reverse()
>>> a
[333, 1234.5, 1, 333, -1, 66.25]
>>> a.sort()
>>> a
[-1, 1, 66.25, 333, 333, 1234.5]

73

■ Универсальные функции NumPy (ufunc)
NumPy, предоставляет готовые инструменты и математические функции для работы с массивами.
К ним относятся и универсальные функции (специализированные универсальные функции).

■ Специализированные универсальные функции
Универсальная функция - это оболочка над обычной функцией, благодаря которой возможно
вполнять определенные действия над целыми массивами. Данные функции выполняют действия
над массивами поэлементно, поддерживают механизм транслирования, автоматически
преобразуют типы данных и обеспечивают доступ к более тонким настройкам своей работы.
Универсальная функция (сокращенно ufunc) — это функция, которая работает с ndarrays
поэлементно, поддерживая широковещательную рассылку массивов, приведение типов и
некоторые другие стандартные функции. То есть ufunc — это "векторизованная" оболочка для
функции, которая принимает фиксированное количество конкретных входных данных и
производит фиксированное количество конкретных выходных данных.
Большинство универсальных функций Numpy реализованы в скомпилированном C-коде и могут
обрабатывать самые разные объекты. В таком случае перебор по колонкам и по рядам выдает
один и тот же результат так как ufunc при работе с массивами и другими объектами выполняет
перебор элемент за элементом.
Примеры работы универсальных функций:
>>> def foo(x):
return x/2
>>> np.apply along axis(foo, axis=1, arr=A)
array([[5., 5.5, 6.],
[6.5, 7., 7.5],
[8., 8.5, 9.]])
>>> np.apply along axis(foo, axis=0, arr=A)
array([[5., 5.5, 6.],
[6.5, 7., 7.5],
[8., 8.5, 9.]])

В этом случае функция ufunc делит значение каждого элемента надвое вне зависимости от того,
был ли применен перебор к строке или колонке.
# This is a sample Python ufunc script.
import numpy as np
def get middle(arr key):
return arr key/2
def arr build(min max, x y):
a = np.arange(min max[0], min max[1]).reshape((x y[0], x y[1]))
print(a)
return a
def ufunc tst 0(arr key):
print('===0===')
val = np.apply along axis(np.mean, axis=0, arr=arr key)
print(val)
print('===1===')

74

val = np.apply along axis(np.mean, axis=1, arr=arr key)
print(val)
def ufunc tst 1(arr key, get middle):
print('===0===')
val = np.apply along axis(get middle, axis=0, arr=arr key)
print(val)
print('===1===')
val = np.apply along axis(get middle, axis=1, arr=arr key)
print(val)

if

name
== ' main ':
A = arr_build([10, 19], [3, 3])
ufunc tst 0(A)
# применение функции get middle в качестве аргумента
# к функции np.apply along axis: значение аргумента axis
# на результат не влияет.
ufunc tst 1(A, get middle)

Универсальная функция np.sin может обрабатывать самые разные объекты:
>>> import numpy as np
>>>
>>> np.sin(60)
-0.3048106211022167
>>> np.sin([0, 30, 60])
array([
0.
, -0.98803162, -0.30481062])
>>> np.sin((0, 30, 60))
array([
0.
, -0.98803162, -0.30481062])
>>> np.sin(np.array([0, 30, 60]))
array([
0.
, -0.98803162, -0.30481062])
>>> np.sin(np.matrix([0, 30, 60]))
matrix([[ 0.
, -0.98803162, -0.30481062]])

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


Тригонометрические функции

sin(x)
Тригонометрический синус.
cos(x)
Тригонометрический косинус.
tan(x)
Тригонометрический тангенс.
arcsin(x)
Обратный тригонометрический синус.
arccos(x)
Обратный тригонометрический косинус.

75

arctan(x)
Обратный тригонометрический тангенс.
hypot(x1, x2)
Вычисляет длинну гипотенузы по указанным длинам катетов.
arctan2(x1, x2)
Обратный тригонометрический тангенс угла где x1 - противолежащий катет, x2 - прилежащий
катет. В отличие от arctan (x) функция arctan2 (y, x) справедлива для всех углов и поэтому может
быть использована для преобразования вектора в угол без риска деления на ноль, а также
возвращает результат в правильном квадранте.
degrees(x)
Преобразует радианную меру угла в градусную.
radians(x)
Преобразует градусную меру угла в радианную.
unwrap(p[, discont, axis])
Корректировка фазовых углов при переходе через значение pi.
deg2rad(x)
Преобразует градусную меру угла в радианную.
rad2deg(x)
Преобразует радианную меру угла в градусную.
■ Гиперболические функции
sinh(x)
Гиперболический синус.
cosh(x)
Гиперболический косинус.
tanh(x)
Гиперболический тангенс.
arcsinh(x)
Обратный гиперболический синус.
arccosh(x)
Обратный гиперболический косинус.
arctanh(x)
Обратный гиперболический тангенс.
■ Округление
around(a[, decimals, out])
Равномерное (банковское) округление до указанной позиции к ближайшему четному числу.
round (a[, decimals, out])
Эквивалентна around().
rint(x)
Округляет до ближайшего целого.
fix(x[, out])
Округляет до ближайшего к нулю целого числа.
floor(x)

76

Округление к меньшему ("пол").
ceil(x)
Округление к большему ("потолок").
trunc(x)
Отбрасывает дробную часть числа.
■ Суммы, разности, произведения
prod(a[, axis, dtype, out, keepdims])
Произведение элементов массива по заданной оси.
sum(a[, axis, dtype, out, keepdims])
Сумма элементов массива по заданной оси.
nanprod(a[, axis, dtype, out, keepdims])
Произведение элементов массива по заданной оси в котором элементы NaN учитываются как 1.
nansum(a[, axis, dtype, out, keepdims])
Сумма элементов массива по заданной оси в котором элементы NaN учитываются как 0.
cumprod(a[, axis, dtype, out])
Возвращает накопление произведения элементов по заданной оси, т.е. массив в котором каждый
элемент является произведением предшествующих ему элементов по заданной оси в исходном
массиве.
cumsum(a[, axis, dtype, out])
Возвращает накопление суммы элементов по заданной оси, т.е. массив в котором каждый
элемент является суммой предшествующих ему элементов по заданной оси в исходном массиве.
nancumprod(a[, axis, dtype, out])
Возвращает накопление произведения элементов по заданной оси, т.е. массив в котором каждый
элемент является произведением предшествующих ему элементов по заданной оси в исходном
массиве. Элементы NaN в исходном массиве при произведении учитываются как 1.
nancumsum(a[, axis, dtype, out])
Возвращает накопление суммы элементов по заданной оси, т.е. массив в котором каждый
элемент является суммой предшествующих ему элементов по заданной оси в исходном массиве.
Элементы NaN в исходном массиве при суммировании учитываются как 0.
diff(a[, n, axis])
Возвращает n-ю разность вдоль указанной оси.
ediff1d(ary[, to end, to begin])
Разность между последовательными элементами массива.
gradient(f, *varargs, **kwargs)
Дискретный градиент (конечные разности вдоль осей) массива f.
cross(a, b[, axisa, axisb, axisc, axis])
Векторное произведение двух векторов.
trapz(y[, x, dx, axis])
Интегрирование массива вдоль указанной оси методом трапеций.
■ Экспоненцирование и логарифмирование
exp(x, /[, out, where, casting, order, ...])
Экспонента всех элементов массива.

77

expm1(x, /[, out, where, casting, order, ...])
Вычисляет exp(x)-1 всех элементов массива.
exp2(x, /[, out, where, casting, order, ...])
Вычисляет 2**x для всех x входного массива.
log(x, /[, out, where, casting, order, ...])
Натуральный логарифм элементов массива.
log10(x, /[, out, where, casting, order, ...])
Десятичный логарифм элементов массива.
log2(x, /[, out, where, casting, order, ...])
Логарифм элементов массива по основанию 2.
log1p(x, /[, out, where, casting, order, ...])
Вычисляет log(x+1) для всех x входного массива.
logaddexp(x1, x2, /[, out, where, casting, ...])
Натуральный логарифм суммы экспонент элементов входных массивов.
logaddexp2(x1, x2, /[, out, where, casting, ...])
Двоичный логарифм от 2**x1 + 2**x2 для всех элементов входных массивов.
■ Другие специальные функции
i0(x)
Модифицированная функция Бесселя первого рода нулевого порядка.
sinc(x)
Вычисляет нормированный кардинальный синус элементов массива.
■ Операции с плавающей точкой
signbit(x, / [, out, where, casting, order, ...])
Возвращает True для всех элементов массива у которых знаковый бит установлен в отрицательное
значение.
copysign(x1, x2, / [, out, where, casting, ...])
Изменяет знак элементов из массива x1 на знак элементов из массива x2.
frexp(x[, outl, out2], / [, out, where, ...])
Разложение элементов массива в показатель мантиссы и двойки.
ldexp(x1, x2, / [, out, where, casting, ...])
Вычисляет x1*2**x2.
nextafter(x1, x2, / [, out, where, casting, ...])
Возвращает значение c плавающей точкой следующее за элементом из x1 в направлении
элемента из x2.
spacing(x, / [, out, where, casting, order, ...])
Поэлементно вычисляет расстояние между значением из массива x и ближайшим соседним
числом.
■ Арифметические операции
lcm(x1, x2, / [, out, where, casting, order, ...])
Поэлементно вычисляет наименьшее общее кратное массивов x1 и x2.
gcd(x1, x2, / [, out, where, casting, order, ...])

78

Поэлементно вычисляет наибольший общий делитель массивов х1 и x2.
add(x1, x2,

/ [, out, where, casting,

order, ...])

Поэлементная сумма значений массивов.
reciprocal(x, / [, out, where, casting, ...])
Вычисляет обратное значение (1/x) каждого элемента массива.
positive(x,

/ [, out, where, casting,

order, ...])

Эквивалентно простому копированию (numpy.copy) элементов массива, но только для массивов
поддерживающих математические операции. Формально соответствует математической записи b
= +a.
negative(x,

/ [, out, where, casting,

order, ...])

Отрицательное значение элементов массива.
multiply(x1, x2, / [, out, where, casting, ...])
Поэлементное умножение значений массива x1 на значения массива x2.
divide(x1, x2, / [, out, where, casting, ...])
Поэлементное деление значений массива x1 на значения массива x2.
power(x1, x2, / [, out, where, casting, ...])
Поэлементное возведение значений массива x1 в степень равную значениям из массива x2.
subtract(x1, x2, / [, out, where, casting, ...])
Поэлементная разность значений массива x1 и x2.
true divide(x1, x2, / [, out, where, ...])
Поэлементное истинное деление значений массива x1 на значения массива x2.
floor divide(x1, x2, / [, out, where, ...])
Поэлементное целочисленное деление значений массива x1 на значения массива x2.
float power(x1, x2, / [, out, where, ...])
Поэлементное возведение значений массива x1 в степень равную значениям из массива x2,
адаптированное для чисел с плавающей точкой.
fmod(x1, x2, / [, out, where, casting, ...])
Поэлементный остаток от деления значений массива x1 на значения массива x2.
mod(x1, x2, / [, out, where, casting, order, ...])
Поэлементно вычисляет остаток от деления значений массива x1 на значения массива x2.
modf(x[, outl, out2], / [, out, where, ...])
Дробная и целая часть элементов массива.
remainder(x1, x2, / [, out, where, casting, ...])
Элементарный остаток от деления значений массива x1 на значения массива x2.
divmod(x1, x2[, outl, out2], / [[, out, ...])
Результат истинного деления и остаток от деления значений массива x1 на значения массива x2.
■ Операции с комплексными числами
angle(z[, deg])
Вычисляет угол каждого комплексного числа в массиве.
real(val)
Действительная часть комплексного числа.
imag(val)

79

Мнимая часть комплексного числа.
conj(x, / [, out, where, casting, order, ...])
Комплексно-сопряженный элемент.
■ Прочие математические функции
convolve(a, v[, mode])
Дискретная линейная свертка.
clip(a, a min, a max[, out])
Ограничение значений массивов указанным интервалом допустимых значений.
sqrt(x,

/ [, out, where, casting, order,

...])

Квадратный корень элементов массива.
cbrt(x,

/ [, out, where, casting, order,

...])

Кубический корень элементов массива.
square(x, / [, out, where, casting, order, ...])
Квадрат элементов массива.
absolute(x, / [, out, where, casting, order, ...])
Абсолютное значение(модуль) элементов массива.
fabs(x,

/ [, out, where, casting, order,

...])

Возвращает абсолютное значение (модуль) элементов массива в виде чисел с плавающей точкой.
sign(x, /[, out, where, casting, order, ...])
Элементарный указатель на знак числа.
heaviside(x1, x2, / [, out, where, casting, ...])
Ступенчатая функция Хевисайда.
maximum(x1, x2, / [, out, where, casting, ...])
Наибольшие значения после поэлементного сравнения значений массивов.
minimum(x1, x2, /[, out, where, casting, ...])
Наименьшие значения после поэлементного сравнения значений массивов.
fmax(x1, x2, / [, out, where, casting, ...])
Наибольшие значения после поэлементного сравнения значений массивов в виде чисел с
плавающей точкой.
fmin(x1, x2, / [, out, where, casting, ...])
Наименьшие значения после поэлементного сравнения значений массивов в виде чисел с
плавающей точкой.
nan to num(x[, copy])
Заменяет nan на 0, бесконечность и минус-бесконечность заменяются на наибольшее и
наименьшее доступное число с плавающей точкой соответственно.
real if close(a[, tol])
Переводит комплексные числа в вещественные если мнимая часть комплексного числа меньше
машинной эпсилон.
interp(x, xp, fp[, left, right, period])
Одномерная линейная интерполяция.

80

■ Аргументы универсальных функций
Все универсальные функции могут принимать целый ряд необязательных аргументов.
Большинство данных аргументов необходимы для тонкой настройки работы функций и чаще
всего совсем не используются, хотя, в определенных ситуациях некоторые из аргументов могут
оказаться очень полезными.
■ out
Определяет место в которое будет сохранен результат работы функции. Если параметр не указан
или указано None (по умолчанию), то будет возвращен автоматически созданный массив. Если в
качестве параметра указан массив NumPy, то он должен иметь форму совместимую с формой
входного массива. Если универсальная функция создает несколько выходных массивов, то для
каждого из них можно указать место сохранения с помощью кортежа из массивов NumPy или
None.
Данный параметр очень полезен при больших объёмах вычислений, так как позволяет избежать
создания временного массива, записывая результат, непосредственно в то место памяти в
которое необходимо.
>>> import numpy as np
>>>
>>> x = np.arange(0, 100, 15)
>>> x
array([ 0, 15, 30, 45, 60, 75, 90])
>>>
>>> y = np.ones(7)
>>> y
array([1., 1., 1., 1., 1., 1., 1.])
>>>
>>> np.sin(x, out = y)
array([ 0.
, 0.65028784, -0.98803162,
0.85090352, -0.30481062,
-0.38778164, 0.89399666])
>>>
>>> y
array([ 0.
, 0.65028784, -0.98803162,
0.85090352, -0.30481062,
-0.38778164, 0.89399666])
>>>
>>>
>>> # В данном параметре можно указывать представления массивов:

О

о

о

о

>>> y = np.zeros(7)
>>> y
array([0., 0., 0.,
>>>
>>> np.sin(x[: :2], out = y[::2])
array([ 0.
, -0.98803162, -0.30481062,
>>>
>>> y
array([ 0.
, -0.98803162,
, 0.
0.
, 0.89399666])

0.89399666])

0.

, -0.30481062,

Параметр out можно не использовать и просто записывать что-то вроде y[::2] = sin(x[::2]), но такая
запись приводит к созданию временного массива для хранения результата вычислений sin(x[::2]) и
затем к еще одной операции копирования результатов из временного массива в y[::2].
Использование аргумента out при обработке больших массивов может обеспечить значительный
выигрыш в экономии используемой памяти и затраченного на вычисления времени.

81

■ w here
Данный параметр принимает True (по умолчанию), False или массив логических значений в случае
если универсальная функция возвращает несколько результирующих массивов. Значение True
указывает на вычисление универсальной функции с сохранением результата в указанный в
параметре out массив. В случае указания False, будут возвращены значения массива, который
указан в out.

0.85090352, -0.30481062,

О

о

>>> x
array([ 0, 15, 30, 45, 60, 75, 90])
>>>
>>> np.cos(x, where = False)
array([ 0.
, 0 65028784, -0.98803162,
-0.38778164, 0 89399666])
>>>
>>> y = np.zeros(7)
>>> y
array([0., 0.
0., 0., 0.])
>>>
>>> np.cos(x, out = y, where = False)
array([0., 0. , 0., 0., 0., 0., 0.])
>>>
>>> np.cos(x, out = y, where = True)
array([ 1.
, -0 75968791, 0.15425145,
0.92175127, -0 44807362])
>>>
>>> y
array([ 1.
, -0 75968791, 0.15425145,
0.92175127, -0 44807362])

0.52532199, -0.95241298,

0.52532199, -0.95241298,

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

x = np.arange(0, 100, 15)
y = np.zeros(7)
a, b = 0, 1
c = 0.77
#
#

#
#

Границы интервала
Параметр, определяющий необходимость вычислений

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

>>> np.cos(x, out = y, where = a < c < b)
array([ 1.
, -0.75968791,
0.15425145,
0.52532199,
0.92175127, -0.44807362])
>>> y
array([ 1.
, -0.75968791,
0.15425145,
0.52532199,
0.92175127, -0.44807362])
>>>
>>> x = np.arange(0, 70, 10)
# Новые данные в массиве
>>> x
array([ 0, 10, 20, 30, 40, 50, 60])
>>>
>>> # В случае, если бы параметр 'c' оказался в пределах
... # от 0 до 1, то в массиве 'y' мы бы увидели:

82

-0.95241298,

-0.95241298,

'x'

интервала

>>> np.cos(x)
array([ 1.
-0.83907153
0.40808206
0.15425145, -0.66693806
0.96496603, -0.95241298])
>>>
>>> c = 1.01
# Допустим параметр вышел за пределы интервала
>>>
>>> # Тогда в массиве 'y' мы увидим предыдущие значения:
>>> np.cos(x, out = y, where = a < c < b)
array([ 1.
, -0.75968791,
0.15425145,
0.52532199, -0.95241298,
0.92175127, -0.44807362])

■ casting
Позволяет настроить преобразование типов данных при вычислениях. Данный параметр может
принимать следующие значения:


'no' - типы данных не преобразуются;



'equiv' - допускается только изменение порядка байтов;



'safe' - допускаются только те типы данных, которые сохраняют значения;



'same_kind' - допускаются только безопасные преобразования, такие как float64 в float3 2;



'usafe' - допускаются любые преобразования данных.

>>> x = np.arange(0, 70, 10, dtype = np.float96)
>>> y = np.zeros(7, dtype = np.float16)
>>>
>>> x
array([ 0., 10., 20., 30., 40., 50., 60.], dtype=float96)
>>> y
array([0., 0., 0., 0., 0., 0., 0.], dtype=float16)
>>>
>>> np.cos(x)
array([ 1.
, -0.83907153,
0.40808206,
0.15425145, -0.66693806,
0.96496603, -0.95241298], dtype=float96)
>>>
>>> np.cos(x, out = y, casting = 'same kind')
# значение 'casting' по
умолчанию
array([ 1.
, -0.839 , 0.4082, 0.1543, -0.667 , 0.965 , -0.9526],
dtype=float16)
>>>
>>> y
# Произошла потеря точности
array([ 1.
, -0.839 , 0.4082, 0.1543, -0.667 , 0.965 , -0.9526],
dtype=float16)
>>>
>>> np.cos(x, out = y, casting = 'safe')
# Запрещает небезопасные
преобразования
Traceback (most recent call last):
File "", line 1, in
TypeError: ufunc 'cos' output (typecode 'g') could not be coerced to provided
output
parameter (typecode 'e') according to the casting rule ''safe''

83

■ order
Этот параметр определяет в каком порядке выходные массивы должны храниться в памяти:
строчном C-стиле или столбчатом стиле Fortran. Если входной массив не является массивом
NumPy, то созданный массив будет находиться в памяти в строковом С порядке, если указать флаг
'F', то будет храниться в столбчатом порядке 'Fortran'. Если входной массив - это массив NumPy, то
флаг 'K' либо сохраняет порядок исходного массива либо устанавливает самый близкий по
структуре; флаг 'A' установит макет памяти выходного массива в 'F' если массив a является
смежным со столбчатым стилем Fortran, в противном случае макет памяти будетустановлен в 'C'.
По умолчанию флаг установлен в значение 'K'.
>>> x = np.arange(0, 100, 30).reshape(2,2)
>>> x
array([[ 0, 30],
[60, 90]])
>>> y = np.cos(x)
>>>
>>> y.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
>>>
>>> y = np.cos(x, order = 'F')
>>>
>>> y.flags
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False

■ dtype
Позволяет переопределить тип данных выходного массива:
>>> x = np.arange(0, 100, 30)
>>> x
array([ 0, 30, 60, 90])
>>>
>>> y = np.cos(x)
>>> y
array([ 1.
, 0.15425145, -0.95241298, -0.44807362])
>>>
>>> y.dtype
dtype('float64')
>>>
>>> y = np.cos(x, dtype = np.float16)
>>> y
array([ 1.
, 0.1543, -0.9526, -0.448 ], dtype=float16)

84

■ subok
Если установлено значение True, то подклассы будут сохраняться, если False, то результатом будет
базовый класс ndarray:
>>> x = np.matrix([0, 30, 60, 90])
>>> x
matrix([[ 0, 30, 60, 90]])
>>>
>>> y = np.cos(x, subok = True)
# По умолчанию подклассы сохраняются
>>> y
matrix([[ 1.
, 0.15425145, -0.95241298, -0.44807362]])
>>>
>>> y = np.cos(x, subok = False)
>>> y
array([[ 1.
, 0.15425145, -0.95241298, -0.44807362]])

■ signature
Большинство универсальных функций реализованы в скомпилированном C-коде. Основные
вычисления внутри универсальной функции заложены в ее базовом цикле. Точнее, таких циклов
несколько и в зависимости от типа входных данных функция выбирает самый подходящий из них.
Аргумент signature позволяет указать какой именно из этих циклов должен использоваться.
В качестве аргумента signature нужно указать специальную строку-подпись цикла, либо тип
данных, либо кортеж типов данных. Получить список доступных строк-подписей можно с
помощью атрибута types объекта ufunc (np.cos - это объект ufunc и у него атрибут types):
>>> import numpy as np
>>>
>>> np.cos.types
['e->e', 'f->f', 'd->d', 'g->g', 'F->F', 'D->D', 'G->G', 'O->O']
>>>
>>> x = np.arange(0, 100, 30)
>>> x
array([ 0, 30, 60, 90])
>>>
>>> np.cos(x, signature = 'f->f')
array([ 1.
,
0.15425146, -0.95241296, -0.44807363]
dtype=float32)
>>>
>>> np.cos(x, signature = 'F->F')
array([ 1.
-0.j,
0.15425146+0.j, -0.95241296+0.j,
0.44807363-0.j],
dtype=complex64)
>>>
>>> np.cos(x, signature = 'F')
array([ 1.
-0.j,
0.15425146+0.j, -0.95241296+0.j,
0.44807363-0.j],
dtype=complex64)
>>>
>>> np.cos(x, signature = np.complex64)
Traceback (most recent call last):
File "", line 1, in
TypeError: No loop matching the specified signature and casting
was found for ufunc cos
Данный параметр предоставляет небольшую, но все же оптимизацию вычислений - вместо
автоматического поиска подходящего цикла, функция сразу переходит к вычислениям в
указанном цикле. Скорее всего указание данного аргумента будет полезным в вычислениях в
которых вызов универсальных функций происходит очень часто.

85

■ extobj
В качестве данного аргумента выступает список из трёх целых чисел. Первое число определяет
размер буфера данных, второе - режим ошибки, третье - функция обратного вызова ошибки:
>>> np.cos(x, extobj = [160, 1, 1])
array([ 1.
, 0.15425145, -0.95241298, -0.44807362])

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

■ Универсальные функции. Продолжение
Продолжение описания универсальных функций: спасибо документации...
This is documentation for an old release of NumPy (version 1.10.0). Read this page in the
documentation of the latest stable release (version > 1.17).

numpy.tanh
numpy.tanh(x[, out]) =
Compute hyperbolic tangent element-wise.

Equivalent to np.sinh(x)/np.cosh(x) or -1j * np.tan(1j*x).

Parameters:
x : array_like

Input array.

out : ndarray, optional

Output array of same shape as x.

Returns:
y : ndarray

The corresponding hyperbolic tangent values.

Raises:
ValueError: invalid return array shape

if out is provided and out.shape != x.shape (See Examples)

86

Notes

If out is provided, the function writes the result into it, and returns a reference to out. (See Examples)

Пока неясно, что означает эта нотация:
numpy.tanh(x[, out]) =
Это такое объявление? Показывает, что это универсальная функция?
■ num py.sin
numpy.sin(x, *ufunc args) =
Функция sin() вычисляет тригонометрический синус элементов массива.
Параметры:
x - подобный массиву объект
Массив чисел задающих угол в радианах (360 градусов равняются 2п радиан).
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - массив NumPy или его подкласс
Синус элементов х.
Смотреть также: arcsin, sinh, cos, tan
Примеры
>>> import numpy as np
>>>
>>> # Вычисление синуса одного угла:
... np.sin(0)
0.0
>>> np.sin(np.pi/2)
1.0
>>> np.sin(np.pi/6)
0.49999999999999994
>>> np.sin(np.pi)
1.2246467991473532e-16
>>>
>>>
>>> # Массив значений углов, заданных в радианах:
>>> x = np.arange(10)
>>> x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>>
>>> np.sin(x)
array([ 0.
, 0.84147098,
0.90929743,
0.14112001, -0.7568025 ,

87

см.

-0.95892427, -0.2794155 , 0.6569866 , 0.98935825,
0.41211849])
>>>
>>>
>>> # Массив значений углов, заданных в градусах:
>>> x = np.array([0, 30, 45, 60, 90])*np.pi/180
>>> x
array([0.
, 0.52359878, 0.78539816, 1.04719755, 1.57079633])
>>>
>>> np.sin(x)
array([0.
, 0.5
, 0.70710678, 0.8660254 , 1.
])

■ num py.cos
numpy.cos(x, *ufunc_args) =
Функция cos() вычисляет тригонометрический косинус элементов массива.
Параметры:
x - подобный массиву объект
Массив чисел задающих угол в радианах (360 градусов равняются 2п радиан).
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - массив NumPy или его подкласс
Косинус элементов х.
Смотреть также: arccos, cosh, sin, tan
Примеры
>>> import numpy as np
>>>
>>> # Вычисление синуса одного угла:
... np.cos(0)
1.0
>>> np.cos(np.pi/2)
6.123233995736766e-17
>>> np.cos(np.pi/3)
0.5000000000000001
>>>
>>> # Массив значений углов, заданных в радианах:
>>> x = np.arange(10)
>>> x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>>
>>> np.cos(x)
array([ 1.
, 0.54030231, -0.41614684, -0.9899925 , -0.65364362,
0.28366219, 0.96017029,
0.75390225, -0.14550003, -0.91113026])
>>>
>>>
>>> # Массив значений углов, заданных в градусах:
>>> x = np.array([0, 30, 45, 60, 90])*np.pi/180
>>> x

88

см.

array([0.
0.52359878, 0.78539816, 1.04719755, 1.57079633])
>>>
>>> np.cos(x)
array([1.00000000e+00, 8.66025404e-01, 7.07106781e-01, 5.00000000e-01,
6.12323400e-17])

■ num py.tan
numpy.tan(x, *ufunc args) =
Функция tan() вычисляет тригонометрический тангенс элементов массива и эквивалентна
np.sin(x)/np.cos(x).
Параметры:
x - подобный массиву объект
Массив чисел задающих угол в радианах (360 градусов равняются 2п радиан).
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - массив NumPy или его подкласс
Тангенс элементов х.
Смотреть также: arctan, tanh, sin, cos
Примеры
>>> import numpy as np
>>>
>>> # Вычисление синуса одного угла:
... np.tan(0)
0.0
>>> np.tan(np.pi/2)
1.633123935319537e+16
>>> np.tan(np.pi/4)
0.9999999999999999
>>>
>>>
>>> # Массив значений углов, заданных в радианах:
>>> x = np.arange(10)
>>> x
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>>
>>> np.tan(x)
array([ 0.
, 1.55740772, -2.18503986, -0.14254654, 1.15782128,
-3.38051501, -0.29100619, 0.87144798, -6.79971146, -0.45231566])
>>>
>>>
>>> # Массив значений углов, заданных в градусах:
>>> x = np.array([0, 30, 45, 60, 90])*np.pi/180
>>> x
array([0.
, 0.52359878, 0.78539816, 1.04719755, 1.57079633])
>>>
>>> np.tan(x)

89

см.

array([0.00000000e+00, 5.77350269e-01, 1.00000000e+00, 1.73205081e+00,
1.63312394e+16])

■ num p y.arcsin
numpy.arcsin(x, *ufunc args) =
Функция arcsin() вычисляет тригонометрический арксинус (обратный синус), если y = sin(x),
то x = arcsin(y).

Параметры:
x - подобный массиву объект
y - координата или массив y-координат единичной окружности.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - массив NumPy или его подкласс
Арксинус элементов x в интервале [-pi/2, pi/2].
Замечание
arcsin(x) - многозначная функция, т.е. для каждого x существует бесконечное количество значений
углов а при которых sin(a) = x, поэтому принято соглашение о том, что функция numpy.arcsin(x)
возвращает значение угла в интервале [-pi/2, pi/2].

Для комплексных входных значений arcsin так же представляет собой бесконечнозначную
функцию, которая, по соглашению находится на листе D0 с разрезами [-inf, -1] и [1, inf].

Иногда арксинус обозначается как asin или sin-1

Смотреть также: sin, sinh, arccos, arctan
Примеры
>>> import numpy as np
>>>
>>> np.arcsin(0.77)
0.8788411516685797
>>>
>>> x = np.array([-1, -0.5, 0, 0.5, 1])
>>>
>>> np.arcsin(x)
# Значение углов в радианах
array([-1.57079633, -0.52359878, 0.
, 0.52359878,
1.57079633])
>>>
>>> np.arcsin(x)*180/np.pi
# Значение углов в градусах
array([-90., -30.,
0., 30., 90.])

90

■ numpy.arccos
numpy.arccos(x, *ufunc args) =

Функция arccos() вычисляет тригонометрический арккосинус (обратный косинус), если y = cos(x), то
x = arccos(y).

Параметры:
x - подобный массиву объект
y - координата или массив y-координат единичной окружности.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - массив NumPy или его подкласс
Арккосинус элементов x в интервале [0, pi].
Замечание
arccos(x) - многозначная функция, т.е. для каждого x существует бесконечное количество значений
углов а при которых cos(a) = x, поэтому принято соглашение о том, что функция numpy.arccos(x)
возвращает значение угла в интервале [0, pi].

Для комплексных входных значений arccos так же представляет собой бесконечнозначную
функцию, которая, по соглашению находится на листе D0 с разрезами [-inf, -1] и [1, inf].

Иногда арккосинус обозначается как acos или cos-1

Смотреть также: cos, cosh, arcsin, arctan
Примеры
>>> import numpy as np
>>>
>>> np.arccos(0.77)
0.6919551751263169
>>>
>>> x = np.array([-1, -0.5, 0, 0.5, 1])
>>>
>>> np.arccos(x)
# Значение углов в радианах
array([3.14159265, 2.0943951 , 1.57079633, 1.04719755, 0.
>>>
>>> np.arccos(x)*180/np.pi
# Значение углов в градусах
array([180., 120., 90., 60.,
0.])

91

])

■ numpy.arctan
numpy.arctan(x, *ufunc args) =

Функция arctan() вычисляет тригонометрический арктангенс (обратный тангенс), если y = tan(x), то
x = arctan(y).

Параметры:
x - подобный массиву объект
y - координата или массив y-координат единичной окружности.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - массив NumPy или его подкласс
Арктангенс элементов x в интервале [-pi/2, pi/2] (arctan(-inf) = -pi/2 и arctan(inf) = pi/2).
Замечание
arctan(x) - многозначная функция, т.е. для каждого x существует бесконечное количество значений
углов а при которых tan(a) = x, поэтому принято соглашение о том, что функция numpy.arctan(x)
возвращает значение угла в интервале [0, pi].

Для комплексных входных значений arctan так же представляет собой бесконечнозначную
функцию, которая, по соглашению находится на листе D0 с разрезами [1j, infj] и [-1j, -infj].

Иногда арккосинус обозначается как atan или tan-1

Смотреть также: tan, tanh, arcsin, arccos
Примеры
>>> import numpy as np
>>>
>>> np.arctan(0.77)
0.6561787179913949
>>>
>>> x = np.array([-np.inf, -1, -0.5, 0, 0.5, 1, np.inf])
>>>
>>> np.arctan(x)
# Значение углов в радианах
array([-1.57079633, -0.78539816, -0.46364761, 0.
, 0.46364761,
0.78539816, 1.57079633])
>>>
>>> np.arctan(x)*180/np.pi
# Значение углов в градусах
array([-90.
, -45.
, -26.56505118,
0.
,
26.56505118, 45.
, 90.
])

92

■ numpy.hypot
numpy.hypot(x1, x2, *ufunc args) =

Функция hypot() вычисляет длинну гипотенузы по указанным значениям катетов. Эквивалентно
sqrt(x1**2 + x2**2).

Параметры:
x1, x2 - подобные массиву объекты
Длины катетов или массивы значений длин катетов.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - массив NumPy или его подкласс
Длинна гипотенузы прямоугольного треугольника или массив значений длин гипотенузы.
Смотреть также: sin, cos, tan
Примеры

CO

[--•

ОЭ

>>> import numpy as np
>>>
>>> np.hypot(3, 4)
5.0
>>>
>>> np.hypot(3, [4, 5, 6])
# Поддерживает механизм транслирования
array([5.
, 5 .83095189, 6.70820393])
>>>
>>> a = np.array([3, 5, 8, 7])
>>> b = np.array([4, 12, 15, 24])
>>>
>>> np.hypot(a, b)
array([ 5., 13. , 17. , 25 .])
>>>
>>> a = np.arange(1, 17) .reshape(4, 4)
>>> a
array([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16]])
>>>
>>> b = np.arange(16 , 0, -1).reshape(4, 4)
>>> b
array([[16, 15, 14, 13],
[12, 11, 10, 9],
6, 5],
[ 4, 3, 2, 1]])
>>>
>>> np.hypot(a, b)
array([[16.03121954, 15. 13274595, 14.31782106, 13.60147051],
, 12. 52996409, 12.20655562, 12.04159458],
[12.04159458, 12. 20655562, 12.52996409, 13.
],

93

см.

[13.60147051, 14.31782106, 15.13274595, 16.03121954]])

■ num py.arctan2
numpy.arctan2(x, *ufunc args) =
Функция cosh() вычисляет гиперболический
1/2*(np.exp(x) + np.exp(-x)) или np.cos(1j*x).

косинус

элементов

массива,

эквивалентна

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - число или массив NumPy или его подкласс
Гиперболический косинус входного числа или каждого числа входного массива.
Замечание
Часто гиперболический косинус обозначается с к

Смотреть также: arccosh, sinh, tanh
Примеры
>>> import numpy as np
>>>
>>> np.sinh(0)
0.0
>>>
>>> x = np.linspace(0, np.pi, num = 7)*1j
>>> x
array([0.+0.
j, 0.+0.52359878j, 0.+1.04719755j, 0.+1.57079633j,
0.+2.0943951 j, 0.+2.61799388j, 0.+3.14159265j])
>>>
>>> np.cosh(x)
array([ 1.00000000e+00+0.j, 8.66025404e-01+0.j, 5.00000000e-01+0.j,
6.12323400e-17+0.j, -5.00000000e-01+0.j, -8.66025404e-01+0.j,
-1.00000000e+00+0.j])

99

см.

■ num py.tanh
numpy.tanh(x, *ufunc args) =
Функция tanh() вычисляет гиперболический тангенс элементов
np.sinh(x)/np.cosh(x) или -1j*np.tan(1j*x).

массива

и эквивалентна

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - число или массив NumPy или его подкласс
Гиперболический тангенс входного числа или каждого числа входного массива.
Замечание
Часто гиперболический косинус обозначается th.

Смотреть также: arctanh, cosh, sinh
Примеры
>>> import numpy as np
>>>
>>> np.sinh(0)
0.0
>>>
>>> x = np.linspace(0, np.pi/2, num = 7)*1j
>>> x
array([0.+0.
j, 0.+0.26179939j, 0.+0.52359878j, 0.+0.78539816j,
0.+1.04719755j, 0.+1.30899694j, 0.+1.57079633j])
>>>
>>> np.tanh(x)
array([0.+0.00000000e+00j, 0.+2.67949192e-01j, 0.+5.77350269e-01j,
0.+1.00000000e+00j, 0.+1.73205081e+00j, 0.+3.73205081e+00j,
0.+1.63312394e+16j])

■ num py.arcsinh
numpy.arcsinh(x, *ufunc args) =
Функция arcsinh() вычисляет обратный гиперболический синус (ареасинус) элементов массива.

Параметры:
x - число или подобный массиву объект

100

см.

Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - число или массив NumPy или его подкласс
Обратный гиперболический синус входного числа или каждого числа входного массива.
Замечание
arcsinh(x) является многозначной функцией, т.е. для каждого входного числа x существует
бесконечное количество таких чисел y, что sinh(y) = x. По соглашению, данная функция возвращает
число Y у которого мнимая часть лежит в интервале [-pi/2, pi/2].

Для комплексных входных значений arcsinh так же представляет собой бесконечнозначную
функцию, которая, по соглашению находится на листе D0 с разрезами [1j, infj] и [-1j, -infj].

Часто обратный гиперболический синус обозначается arsh, asinh, arsinh или sinh-1.

Смотреть также: arccosh, arctanh, sinh
Примеры
>>> import numpy as np
>>>
>>> np.arcsinh(O)
0.0
>>>
>>> np.arcsinh(l)
0.881373587019543
>>>
>>> np.sinh(0.881373587019543)
1.0
>>>
>>> x = np.linspace(0, np.pi/2, num = 7)*1j
>>> np.arcsinh(x)
array([0.
+0.
j, 0.
+0.26488615j,
0.
+0.55106958j, 0.
+0.90333911j,
0.30604211+1.57079633j, 0.76717348+1.57079633j,
1.02322748+1.57079633j])

■ num py.arccosh
numpy.arccosh(x, *ufunc args) =
Функция arccosh() вычисляет обратный гиперболический косинус (ареакосинус).

Параметры:

101

x - число или подобный массиву объект
Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - число или массив NumPy или его подкласс
Обратный гиперболический косинус входного числа или каждого числа входного массива.
Замечание
arccosh(x) является многозначной функцией, т.е. для каждого входного числа x существует
бесконечное количество таких чисел y, что sinh(y) = x. По соглашению, данная функция возвращает
число y у которого мнимая часть лежит в интервале [-pi, pi], а действительная часть в интервале [0,
inf].

Для комплексных входных значений arccosh так же представляет собой бесконечнозначную
функцию, которая, по соглашению находится на листе D0 с разрезом [-inf, 1].

Часто обратный гиперболический косинус обозначается arch, arcosh, acosh или sinh-1.

Смотреть также: arcsinh, arctanh, cosh
Примеры
>>> import numpy as np
>>>
>>> np.arccosh(O)
main :1: RuntimeWarning: invalid value encountered in arccosh
nan
>>> np.arccosh(1)
O.O
>>>
>>> np.cosh(0)
1.O
>>>
>>> x = np.linspace(0, np.pi/2, num = 7)*1j
>>> np.arccosh(x)
array([0.
+1.57079633j, 0.25889745+1.57079633j,
0.50221899+1.57079633j, 0.72122549+1.57079633j,
0.91435666+1.57079633j, 1.08392469+1.57079633j,
1.23340312+1.57079633j])

■ num py.arctanh
numpy.arctanh(x, *ufunc args) =
Функция arctanh() вычисляет обратный
массива.

гиперболический тангенс (ареатангенс) элементов

102

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - число или массив NumPy или его подкласс
Обратный гиперболический тангенс входного числа или каждого числа входного массива.
Замечание
arctanh(x) является многозначной функцией, т.е. для каждого входного числа x существует
бесконечное количество таких чисел y, что tanh(y) = x. По соглашению, данная функция
возвращает число y у которого мнимая часть лежит в интервале [-pi/2, pi/2].

Для комплексных входных значений arctanh так же представляет собой бесконечнозначную
функцию, которая, по соглашению находится на листе D0 с разрезами [-1, -inf] и [1, inf].

Часто Обратный гиперболический синус обозначается arth, artanh, atanh или tanh-1.

Смотреть также: arcsinh, arccosh, tanh
Примеры
>>> import numpy as np
>>>
>>> np.arctanh(O)
0.0
>>>
>>> np.arctanh(l)
main :1: RuntimeWarning: divide by zero encountered in arctanh
inf
>>>
>>> np.tanh(np.inf)
1.0
>>>
>>> x = np.linspace(0, np.pi/2, num = 7)*1j
>>> np.arctanh(x)
array([0.+0.
j, 0.+0.25605277j, 0.+0.48234791j, 0.+0.66577375j,
0.+0.80844879j, 0.+0.9184308 j, 0.+1.00388482j])

■ num py.around
numpy.around(a, decimals=0, out=None)

103

Функция around() выполняет равномерное (банковское) округление до указанной позиции к
ближайшему четному числу. Реальная и мнимая часть комплексных чисел округляется отдельно.

Параметры:
a - число или подобный массиву объект
Число или последовательность чисел.
decimals - целое число (необязательный аргумент)
Указывает количество знаков после десятичной точки (по умолчанию decimals = 0). Если decimals <
0, то его значение указывает на количество знаков слева от десятичной точки.
out - массив Numpy (необязательный аргумент)
Указывает массив в который будет помещен результат работы функции. Данный массив должен
иметь форму идентичную массиву с результатом работы функции. Подробнее о данном
параметре смотрите на странице универсальные функции в разделе out.
Возвращает:
результат - массив NumPy
Массив с округленными значениями входного массива a. Тип результирующего массива совпадает
с типом входного массива. Если параметр out не указан, то будет создан новый массив.
Смотреть также: rint, fix, trunc
■ num py.round_
numpy.round_(a, decimals=0, out=None)
Функция numpy.round_ полностью эквивалентна (вызывает в своем коде) numpy.around.

Параметры:
a - число или подобный массиву объект
Число или последовательность чисел.
decimals - целое число (необязательный аргумент)
Указывает количество знаков после десятичной точки (по умолчанию decimals = 0). Если decimals <
0, то его значение указывает на количество знаков слева от десятичной точки.
out - массив Numpy (необязательный аргумент)
Указывает массив в который будет помещен результат работы функции. Данный массив должен
иметь форму идентичную массиву с результатом работы функции. Подробнее о данном
параметре смотрите на странице универсальные функции в разделе out.
Возвращает:
результат - массив NumPy
Массив с округленными значениями входного массива a. Тип результирующего массива совпадает
с типом входного массива. Если параметр out не указан, то будет создан новый массив.

104

■ n u m p y.rin t
numpy.rint(x, *ufunc args) =
Функция rint() округляет элементы массива до ближайшего целого числа.

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - число или массив NumPy или его подкласс
Число или массив чисел, которые округлены до ближайшего целого числа.
Смотреть также: fix, floor, ceil, around
Примеры
>>> import numpy as np
>>>
>>> np.rint([-5.5, 5.5])
array([-6., 6.])
>>>
>>> x = np.random.random(5)*10 - 5
>>> x
array([-1.45150824, 2.36081173, -0.01112329,
>>>
>>> np.rint(x)
array([-1., 2., -0., 2., -3.])

1.72539084, -3.1361012 ])

■ num py.fix
numpy.fix(x, out=None)
Функция fix() округляет до ближайшего к нулю целого числа.

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.
out - массив Numpy (необязательный аргумент)
Указывает массив в который будет помещен результат работы функции. Данный массив должен
иметь форму идентичную массиву с результатом работы функции. Подробнее о данном
параметре смотрите на странице универсальные функции в разделе out.
Возвращает:

105

результат - число или массив NumPy или его подкласс
Число или массив чисел, которые округлены к целому числу ближайшему к нулю.
Смотреть также: floor, ceil, around
Примеры
>>> import numpy as np
>>>
>>> np.fix([-5.5, 5.5])
array([-5., 5.])
>>>
>>> x = np.random.random(5)*10 - 5
>>> x
array([ 2.2019152 , 2.11741524, -0.7219455 , -3.11994759, -0.71123005])
>>>
>>> np.fix(x)
array([ 2., 2., -0., -3., -0.])

■ num py.floor
numpy.floor(x, *ufunc args) =
Функция floor() выполняет округление к меньшему целому числу. Данная функция часто
называется пол числа x и обозначается как |_xj.

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

см.

Возвращает:
результат - число или массив NumPy или его подкласс
Число или массив чисел, которые округлены к наименьшему целому числу.
Замечание
Некоторые программы вычисляют "пол" отрицательных чисел как "потолок" т.е. floor(-4.75) = -4. В
NumPy округление отрицательных чисел выполняется не в сторону нуля а к меньшему числу т.е.
floor(-4.75) = -5 т.к. -5 < 4.

Смотреть также: ceil, around, trunc
Примеры
>>> import numpy as np
>>>
>>> np.floor([-5.5, 5.5])
array([-6., 5.])
>>>

106

>>> x = np.random.random(5)*10
>>> x
array([0.4947903 , 1.89080601, 9.30142446, 5.73544876, 5.45138729])
>>>
>>> np.floor(x)
array([0., 1., 9., 5., 5.])

■ num py.ceil
numpy.ceil(x, *ufunc args) =
Функция ceil() округляет к большему целому числу. Данная функция часто называется потолок
числа x и обозначается как |Y |.

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.
*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - число или массив NumPy или его подкласс
Число или массив чисел, которые округлены к наибольшему целому числу.
Смотреть также: arcsinh, arccosh, tanh, tanh
Примеры
>>> import numpy as np
>>>
>>> np.ceil([-5.5, 5.5])
array([-5., 6.])
>>>
>>> x = np.random.random(5)*10
>>> x
array([2.50681892, 1.08993134, 5.21603004, 1.57251841, 7.64590507])
>>>
>>> np.ceil(x)
array([3., 2., 6., 2., 8.])

■ num py.trunc
numpy.trunc(x, *ufunc args) =
Функция trunc() отбрасывает дробную часть числа.

Параметры:
x - число или подобный массиву объект
Число или последовательность чисел.

107

см.

*ufunc_args - аргументы универсальной функции
Аргументы, позволяющие
универсальные функции).

настроить

и оптимизировать

работу функции

(подробнее

Возвращает:
результат - число или массив NumPy или его подкласс
Число или массив чисел с отброшенной дробной частью.
Замечание
Данная функция доступна в NumPy начиная с версии 1.3.0

Смотреть также: rint, ceil, floor
Примеры
>>> import numpy as np
>>>
>>> np.trunc([-5.5, 5.5])
array([-5., 5.])
>>>
>>> x = np.random.random(5)*10
>>> x
array([6.82408707, 6.13384365, 9.59990973, 1.45484855, 4.02585129])
>>>
>>> np.trunc(x)
array([6., 6., 9., 1., 4.])

108

см.

■ Декораторы и декорирование
Функции в python являются объектами. Функции как объекты можно передавать в качестве
аргумента другой функции. Функцию как объект можно возвращать из другой функции в качестве
возвращаемого значения. Функцию как объект можно определить внутри другой функции.

■ Замыкания
Замыкание (closure) - объект, который включает в себя блок кода (например, функцию) и
дополнительные переменные за его пределами. Фактически, запоминается и используется
переменная, которая не является частью локальной области видимости.
Зачем нужны замыкания:



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

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

Код
# внешняя функция
def extern example():
x = 11
# внутренняя функция
def inner():
р ^ п М ^ П е р е м е н н а я из замыкания: {x}')
# возвращение внутренней функции как объекта
return inner
# вызов внешней функции, которая возвращает внутреннюю
# функцию, которая выполняется
extern example()0
# аналогичная конструкция с применением дополнительной переменной
innerFun = extern example()
innerFun()

■ Декоратор
Декоратор - это функция, которая позволяет изменять (дополнять) поведение декорируемой
функции, не изменяя её код. Это функция, которая принимает в качестве аргумента другую
функцию (декорируемую функцию) и, в зависимости от собственных аргументов, что-либо делает
с этой функцией:




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

109




возвращает эти значения,
в зависимости от значений собственных аргументов
декорируемой функции, вызывает другие функции.

и

результатов

выполнения

■ Декорируемые функции
Логика работы декоратора косвенно уже была описана в разделе про замыкания. Далее
уточнение информации о замыканием с дополнением ее особенностями декораторов.
Декоратор - это обёртка над функцией (или другим объектом), которая изменяет ее поведение.
Это удобно (?), так как не нужно менять исходный код (возможно, что чужой).

Схема работы декоратора:






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



возвращается внутренняя
дополненным декорированием.

функция

с

функционалом,

по

умолчанию

# внешняя функция с аргументом
def simple decorator(func):
# внутренняя функция — модификатор аргумента func
def inner():
print('Начало работы декоратора...')
func()
print('Декоратор отработал!')
# возвращение внутренней функции как объекта
return inner
# функция, которую задекорировали
def print hi():
p r i n t ( f ^ ^ функция, которую задекорировали')
print hi() # вызов недекорированной функции
# в декоратор отправляется функция для декорирования
# изменённая (декорированная) функция принимается по ссылке
# на объект декорирования ...
print hi = simple decorator(print hi)
# ... и выполняется
print hi()
Таким образом, чтобы задекорировать функцию (объект), нужно передать его внутрь декоратора,
а затем вызвать.
В данном примере задекорированной функции было присвоено то же имя (print_hi), потому что
так принято в python, хотя можно давать любое другое имя.
Для отображения стандартного вызова внешней функции декоратора
декорируемую функцию в python применяется специальная конструкция:

110

со

ссылкой

на

@Имя_декоратора
Декорируемая_функция

Код из старого примера с применением новой синтаксической конструкции:

# внешняя функция с аргументом
def simple decorator(func):
# внутренняя функция — модификатор аргумента func
def inner():
print('Начало работы декоратора...')
func()
print('Декоратор отработал!')
# возвращение внутренней функции как объекта
return inner
@simple decorator
# функция, которую задекорировали
def print hi():
p r i n t ( f ^ ^ функция, которую задекорировали')
print hi() # здесь выполняется уже декорированная функция
# в этом случае вызов недекорированной функции становится НЕВОЗМОЖЕН
#(синтаксические украшения ограничивают возможности программирования)
Здесь вызывается функция print_hi. Но на самом деле вызывается декоратор simple_decorator,
которому передаётся в виде первого аргумента, целевая функция (print_hi).

■ Применение декораторов
Типичные случаи использования декораторов: оценка времени работы функции, кеширование,
запуск только в определенное время, задание параметров подключения к базе данных и т.д.
Существует множество известных 'стандартных' декораторов для решения конкретных задач
программирования.
Так, декоратор @property, при декорировании метода area в классе Rectangle позволяет
обратиться к члену area экземпляра класса Rectangle как к атрибуту.

class Rectangle:
def
init
(self, a, b):
self.a = a
self.b = b
@property
def area(self):
return self.a * self.b
rect = Rectangle(5, 6)
print(rect.area) # обращение к методу как к свойству
# 30

111

В последней строке кода к члену area экземпляра класса Rectangle можно обратиться как к
атрибуту. То есть не нужно непосредственно вызывать метод area. Вместо этого при обращении к
area как к атрибуту (без использования скобок, ()), соответствующий метод area вызывается
неявным образом. Это возможно благодаря декоратору @property.
class Rectangle:
def
init
(self, a, b):
self.a = a
self.b = b
#

@property
def area(self):
return self.a * self.b

rect = Rectangle(5, 6)
print(rect.area()) # без декоратора простое обращение к методу
# 30

■ К а к это работает?
Размещение конструкции @property перед определением функции равносильно использованию
конструкции вида area = property(area). Таким образом, property — это функция, которая
принимает другую функцию в качестве аргумента и возвращает ещё одну функцию. Именно этим
и занимаются декораторы (@property в том числе). В результате оказывается, что декоратор
меняет поведение декорируемой функции.

■ Декоратор retry
Пусть имеется функция, которую нужно запускать повторно в том случае, если при её первом
запуске происходит сбой. То есть, нужна функция (функция - декоратор, имя которого, retry,
можно перевести как «повтор»), которая вызывает эту функцию один или два раза (это зависит от
того, возникнет ли ошибка при первом вызове функции).
В соответствии с ранее заданным определением можно изменить код ранее описанного
декоратора таким:

def retry(func):
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except:
time.sleep(1)
func(*args, **kwargs)
return wrapper
@retry
def might fail():
print("might_fail")
raise Exception
might fail()
Здесь декоратор носит имя retry. Он принимает в виде аргумента (func) любую функцию.

112

Внутри декоратора определяется внутренняя функция (_wrapper) и обеспечивается возврат этой
функции. Это совершенно корректная синтаксическая конструкция, следствием применения
которой является то полезное обстоятельство, что функция _wrapper видна лишь внутри
пространства имён декоратора retry.
В этом примере декорируется функция might_fail() с использованием конструкции, которая имеет
вид @retry. После имени декоратора нет круглых скобок. В результате получается, что когда, как
обычно, вызывается функция might_fail(), на самом деле, вызывается декоратор retry, которому
передаётся, в виде первого аргумента, целевая функция (might_fail).
Получается, что, в общей сложности, тут поработали три функции:

■ retry
■ _wrapper
■ might_fail

При выполнении функции might_fail всё равно всегда случается исключение (она так
запрограммирована). Но retry и _wrapper (декоратор и внутренняя функция пытаются этому
помешать) или оттянуть момент возникновения исключения на время, заданное методом
time.sleep(1).

■ Аргументы декоратора
В некоторых случаях нужно, чтобы кроме ссылки на декорируемую функцию декораторы
принимали бы дополнительные аргументы. Например, может понадобиться, чтобы декоратор
retry принимал бы число, задающее количество попыток запуска декорируемой функции.
Но при этом декоратор обязательно должен принимать декорируемую функцию в качестве
первого аргумента. Не надо забывать и о том, что вызывать декоратор при декорировании
функции не надо. То есть — о том, что перед определением функции используется конструкция
@retry, а не @retry(). Таким образом:

■ Декоратор — это всего лишь функция (которая, в качестве аргумента, принимает другую
функцию).
■ Декораторами пользуются, помещая их имя со знаком @ перед определением функции, а
не вызывая их.
■ Следовательно, можно ввести в код четвёртую функцию, которая принимает параметр, с
помощью которого настраивается поведение декоратора, и возвращается функция,
которая и является декоратором (то есть — принимает в качестве аргумента другую
функцию).

Предлагается к тестированию следующая конструкция:

def retry(max retries):
def retry decorator(func):
def wrapper(*args, **kwargs):
for
in range(max retries):
try:
func(*args, **kwargs)
except:
time.sleep(l)

113

return wrapper
return retry decorator

@retry(2)
def might fail():
print("might_fail")
raise Exception

might fail()
Разбор этого кода:

■ На первом уровне тут имеется функция retry.
■ Функция retry принимает произвольный аргумент (в нашем случае — max_retries) и
возвращает другую функцию — retry_decorator.
■ Функция retry_decorator — это и есть реальный декоратор.
■ Функция _wrapper работает так же, как и прежде (только теперь она руководствуется
сведениями о максимальном количестве перезапусков декорированной функции).
■ Использование это го кода
Функция might_fail теперь декорируется с помощью вызова функции вида @retry(2).
Вызов retry(2) приводит к тому, что вызывается функция retry, которая и возвращает реальный
декоратор.
В итоге функция might_fail декорируется с помощью retry_decorator, так как именно эта функция
представляет собой результат вызова функции retry(2).
Далее представляется пример декорирования множества декорируемых функций функциейдекоратором.
# декорируемые функции ===================================================
def final function 0():
print('this is final function 0')
def final function 1():
print('this is final function 1')
def final function XXX():
print('this is final function XXX')
# ========================================================================
# декоратор ==============================================================
# декорируемая функция как аргумент декоратора ===========================
def function decorator(function to decorate):
print('...function decorator is here ===>')
def wrapper around the original function():
print('this is the code before the function to decorate call')
function to decorate()
print('this is the code after the function to decorate call')
print('===> function decorator is here...')
return wrapper_around_the_original_function
# ========================================================================
# функции function XYZ, function KLM декорируются во время объявления с
# применением синтаксиса декораторов =====================================
@function_decorator

114

def function XYZ():
print('this is function XYZ')
@function decorator
def function KLM():
print('this is function KLM')
# ======================================================================:
if
name
== ' main ':
# вызываются функции, предназначенные для декорирования ============
final function 0()
final function 1()
# ==================================================================
# вызывается функция - декоратор. Возвращается ссылка на ===========
# внутреннюю функцию. Ссылка обеспечивает вызов декорируемой функции
final function decorated = function decorator(final function 0)
# ==================================================================
# вызов внутренней функции. Запускается final function 0 ===========
final function decorated()
final function decorated = function decorator(final function 1)
# ==================================================================
# вызов внутренней функции. Запускается final function 1 ===========
final function decorated()
# зарядили функцию для декорации final function XXX ================
final function decorated = function decorator(final function XXX)
# ==================================================================
# При вызове внутренней функции запускается декорированная функция
# final function XXX ===============================================
print('*0***********************')
final function decorated()
print('*1***********************')
final function decorated()
print('*2***********************')
final function decorated()
print('*3***********************')
final function decorated()
# ======================================================================
# запуск декорированных функций ========================================
function XYZ()
function KLM()
# ======================================================================

■ Декоратор retry
Написание «собственных» декораторов — для понимания принципов их работы.
Пусть имеется функция, которую надо запустить повторно в том случае, если при её первом
запуске произойдёт сбой. То есть — нам нужна функция (декоратор, имя которого, retry), которая
вызывает данную функцию один или два раза (это зависит от того, возникнет ли ошибка при
первом вызове функции).
В соответствии с ранее приведённым определением можно сделать код декоратора таким:

def retry(func):
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except:

115

return

time.sleep(l)
func(*args, **kwargs)
wrapper

@retry
def might fail():
print("might_fail")
raise Exception
might fail()

Декоратор носит имя retry. Он принимает в качестве аргумента (func) любую функцию. Внутри
декоратора определяется вложенная функция (_wrapper). Декоратор retry осуществляет возврат
этой функции.
■ Напоминание
Имеет место объявление одной функции внутри другой функции. Это совершенно корректная
синтаксическая конструкция, следствием применения которой является тот , что функция _wrapper
видна лишь внутри пространства имён декоратора retry.
В этом примере декорируется функция might_fail() с использованием конструкции, которая
выглядит @retry. После имени декоратора нет круглых скобок. В результате получается, что когда
вызывается функция might_fail(), на самом деле, вызывается декоратор retry, которому
передаётся, в виде первого аргумента, целевая функция (might_fail).
Получается, что, вызов отрабатывают в общей сложности три функции:

retry
_wrapper
might_fail

В некоторых случаях нужно, чтобы декораторы принимали бы дополнительные аргументы.
Например, может понадобиться, чтобы декоратор retry принимал бы число, задающее количество
попыток запуска декорируемой функции. Но декоратор обязательно должен принимать
декорируемую функцию в качестве первого аргумента. Также важно, что при декорировании
функции не надо вызывать декоратор. То есть, что перед определением функции используется
конструкция @retry, а не @retry(). Итог:

Декоратор — это всего лишь функция (которая, в качестве аргумента, принимает другую
функцию).

Декораторами пользуются, помещая их имя со знаком @ перед определением функции, а не
вызывая их.

116

Следовательно, можно ввести в код четвёртую функцию, которая принимает параметр, с
помощью которого надо настраивать поведение декоратора, и возвращает функцию, которая и
является декоратором (то есть — принимает в качестве аргумента другую функцию).
Пример для иллюстрации:
def retry(max retries):
def retry decorator(func):
def wrapper(*args, **kwargs):
for
in range(max retries):
try:
func(*args, **kwargs)
except:
time.sleep(l)
return wrapper
return retry decorator

@retry(2)
def might fail():
print("might_fail")
raise Exception

might fail()

Разбор кода:

На первом уровне имеется функция retry.
Функция retry принимает произвольный аргумент (в нашем случае — max_retries) и возвращает
другую функцию — retry_decorator.
Функция retry_decorator — это и есть реальный декоратор.
Функция _wrapper работает так же, как и прежде (только теперь она руководствуется сведениями
о максимальном количестве перезапусков декорированной функции).

О коде нового декоратора больше сказать нечего. Теперь о его использовании:
Функция might_fail теперь декорируется с помощью вызова функции вида @retry(2).
Вызов retry(2) приводит к тому, что вызывается функция retry, которая и возвращает реальный
декоратор.
В итоге функция might_fail декорируется с помощью retry_decorator, так как именно эта функция
представляет собой результат вызова функции retry(2).

117

■ Множественное декорирование функции
Далее представлен пример декорирования ОДНОЙ функции НЕСКОЛЬКИМИ декораторами. При
таком способе декорирования важен порядок применения декораторов к функции (порядок
декорирования).
# несколько декораторов для одной функции. Важен порядок декорирования:
def final function XXX():
print('this is final function XXX')
def decorator 0(function to decorate):
def wrapper():
print('function decorator 0 is here')
function to decorate()
print('function decorator 0 was here-- 0---')
return wrapper
def decorator 1(function to decorate):
def wrapper():
print('function decorator 1 is here')
function to decorate()
print('function decorator 1 was here-- 1---')
return wrapper
# декорирование с применением синтаксиса декораторов =====================
@decorator 0
@decorator 1
def final function KLM():
print('this is final function KLM')
# ========================================================================
if
name
== ' main ':
final function XXX() # запуск декорируемой функции
final function XXX = decorator 0(decorator 1(final function XXX))
final function XXX() # запуск декорированной функции, заряженной
# последовательно двумя декораторами
print('=========================')
final function KLM() # запуск декорированной функции с применением
# синтаксиса декораторов ========================
print('=========================')

■ Передача аргументов в декорируемую функцию
Передача аргументов декорируемой функции декоратором производится следующим образом.
Вызывается внешняя функция, которая возвращает ссылку на внутреннюю функцию. Внутренняя
функция обеспечивает вызов декорируемой функции и передачу ей аргументов. В следующем
примере при вызове декорируемой функции ей передаётся два аргумента.
# Передача аргументов декорируемой функции.
# декорируемая функция. При вызове Требует два аргумента =================
def final function 000(arg1, arg2):
print('this is final function 000({0},{1})'.format(arg1, arg2))
# ========================================================================
# внешняя функция. возвращает ссылку на внутреннюю функцию.
def a decorator passing arguments(function to decorate):
# Внутренняя функция обеспечивает вызов декорируемой функции
# и передачу ей аргументов
def a wrapper accepting arguments(arg1, arg2):
print("аргументы в декорируемую функцию:", arg1, arg2)
function to decorate(arg1, arg2)
return a wrapper accepting arguments
@a decorator passing arguments
def finalfunction XXX(arg1, arg2):

118

if

print('this is final function XXX({0},{1})'.format(arg1, arg2))
name
== ' main ':
# Вызывается внешняя функция, которая возвращает ссылку
# на внутреннюю функцию. Внутренняя функция обеспечивает вызов
# декорируемой функции и передачу ей аргументов
final function decorated =
a decorator passing arguments(final function 000)
final function decorated(512, 1024)
final function XXX('=quartz perfect=', 795)

■ ООП: декорирование методов
Нестатические методы python — это функции с дополнительным обязательным аргументом self,
который представляет собой ссылку на объект, вызывающий данный метод. И это означает, что
декораторы для методов можно объявлять так же, как и для обычных функций с непустым
списком аргументов.
def method decorator(method to decorate):
def wrapper(self, value):
value *= 3
return method to decorate(self, value)
return wrapper
class superCar:
def
init
(self, value):
self.capacity = value # при создании объекта атрибуту capacity
# присваивается значение аргумента value

if

@method decorator
def sayRealCapacity(self, value):
print('the realCapacity is {0}'.format(self.capacity * value))
name
== ' main ':
the car = superCar(125)
the car.sayRealCapacity(3)
# значение, возвращаемое методом
# sayRealCapacity со значением аргумента = 3, вычисляется следующим
# образом: 3 * 3 * 125 = 1125
#
3 * value * self.capacity = 1125

■ Декорирование с распаковкой аргументов
Декоратор, который можно применить к любой функции или методу, объявляется с распаковкой
аргументов.
# универсальный декоратор, который принимает произвольный
# список аргументов в составе кортежа *args и словаря **kwargs
# декорируемая функция как аргумент декоратора ===========================
def universal decorator passing arbitrary arguments(function to decorate):
# Внутренняя функция обеспечивает вызов декорируемой функции
# и передачу ей аргументов
def wrapper accepting arbitrary arguments(*args, **kwargs):
print("было ли что-либо передано декорируемой функции ?")
if len(args) != 0 or len(kwargs) != 0:
print(args)
print(kwargs)
else:
print('args && kwargs are empty')
function to decorate(*args, **kwargs)
return wrapper accepting arbitrary arguments
# универсальный декоратор декорирует функцию без аргументов

119

@universal decorator passing arbitrary arguments
def function with no argument():
print("Python is cool, no argument here.")
# универсальный декоратор декорирует функцию с аргументами a, b, c
@universal decorator passing arbitrary arguments
def function with arguments(a, b, c):
print(a, b, c)
# универсальный декоратор декорирует функцию с аргументами a, b, c
# и именованным аргументом answer
@universal decorator passing arbitrary arguments
def function with named arguments(a, b, c, answer):
print("делятся ли {}, {} и {} на 17? {}".format(a, b, c, answer))
class Observer:
def
init
(self):
self.age = 31
# универсальный декоратор декорирует метод со значением по умолчанию
@universal decorator passing arbitrary arguments
def sayTheAge(self, lie=-3): # Теперь можно указать
# значение по умолчанию
print("Остаётся {} лет".format(self.age + lie))
# вызовы декорируемых функций и метода ===================================
function with no argument()
function with arguments(1, 2, 3)
function with named arguments("25", "137", "64", answer^^ кто его знает!")
obs = Observer()
obs.sayTheAge()

■ Декораторы с аргументами
Описание процедуры объявления декоратора с аргументами. В процессе декорирования ранее
объявленной функции decorated_function функция, которая запускает процесс декорирования —
decorator_maker — возвращает ссылку на функцию master_decorator. В результате обращения по
этой ссылке возвращается декорированная функция по ссылке decorated_function. Возвращаемый
код включает код контейнера wrapper для декорируемой функции, из которого непосредственно
и вызывается декорируемая функция.
Таким образом, при вызове декорированной функции по ссылке decorated_function — уже не
исходная декорируемая функция. Здесь подключается контейнер wrapper, который выполняет
предварительную работу, запускает исходную декорируемую функцию, перехватывает её
возвращаемое значение и дополнительно его модифицирует. Результирующее значение,
возвращаемое декорированной функцией, распечатывается перед завершением приложения.
Можно передавать в декоратор аргументы. В этом случае добавляется еще один уровень
абстракции, то есть, ещё одна функция-обертка. Таким образом, аргумент передаётся
непосредственно декоратору. После этого функция, которая была возвращена декоратором,
используется для декорации.
# ========================================================================
def decorator with args(name):
print('> decorator with args:', name)
def real decorator(func):
print('>> сам декоратор', func. name )
def decorated(*args, **kwargs):
print('>>> перед функцие', func. name
ret = func(*args, **kwargs)
print('>>> после функции', func. name
return ret

120

)
)

return decorated
return real decorator
# ========================================================================
@decorator with args('test')
def add(a, b):
print('>>>> функция add')
return a + b
# ========================================================================
if
name
== ' main ':
print('старт программы')
r = add(10, 10)
print(r)
print(,конец программы')
Ещё пример:
# Декораторы с аргументами
# Объявление декоратора с аргументами:
# ========================================================================
def decorated function():
print("===== Это декорируемая функция. =====")
return ('it was decorated function')
# ========================================================================
# декоратор ==============================================================
def decorator maker():
print("decorator maker: создание декоратора.")
print("decorator maker вызывается при создании декоратора.")
def master decorator(function):
print("master decorator! Вызывается при декорировании функции.")
def wrapper():
print()
print ("wrapper: контейнер для декорируемой функции.^"
"Вызывается каждый раз при вызове декорированной функции")
ret = function()
xstr = '|'.join(ret)
return (xstr)
print("Возвращается обёрнутая функция.")
return wrapper
print("decorator maker: возвращается master decorator.")
return master decorator
# ========================================================================
@decorator maker()
def decorated function X():
num = 125
print("===== Это декорируемая функция X...{0}... =====".format(num))
val = num*num
return str(val)
# ========================================================================
if
name
== ' main ':
# Создание мастера-декоратора. =======================================
# Происходит в результате вызова декоратора
mstr decorator = decorator maker()
# ====================================================================
# ====================================================================
# Декорирование функции: master decorator возвращает
# decorated function, который включает код контейнера для
# декорируемой функции, из которого и вызывается декорируемая функция.
decorated function = mstr decorator(decorated function)

121

# ====================================================================
# ====================================================================
# Вызов декорированной функции. decorated function - это уже
# не исходная декорируемая функция! Здесь подключается контейнер
# wrapper, который выполняет предварительную работу,
# запускает исходную декорируемую функцию, перехватывает её
# возвращаемое значение и дополнительно его модифицирует.
# Результирующее значение распечатывается перед завершением приложения.
result = decorated function()
# ====================================
print(result)
print('=========================')
result = decorated function X()
print(result)
print('=========================')
Далее приведён результат работы этого приложения
decorator_maker: создание декоратора.
decorator_maker вызывается при создании декоратора.
decorator_maker: возвращается master_decorator.
master_decorator! Вызывается при декорировании функции.
Возвращается обёрнутая функция.
decorator_maker: создание декоратора.
decorator_maker вызывается при создании декоратора.
decorator_maker: возвращается master_decorator.
master_decorator! Вызывается при декорировании функции.
Возвращается обёрнутая функция.

wrapper: контейнер для декорируемой функции.
Вызывается каждый раз при вызове декорированной функции
===== Это декорируемая функция. =====
i|t| |w|a |s | | d |e | c | o | r | a | t | e | d | | f | u | n | c | t | i | o | n

wrapper: контейнер для декорируемой функции.
Вызывается каждый раз при вызове декорированной функции
===== Это декорируемая функция X...125... =====
1 |5 |6 |2 |5

Process finished with exit code 0

122

■ Аргументы декораторов
Можно создавать декораторы "на лету". Для этого используются функции, при вызове которы им
как обычным функциям можно передавать произвольные аргументы.
В случае необходимости можно использовать и распаковку аргументов через *args и **kwargs .
def decorator maker with arguments(deco argl, deco arg2):
р ^ п М " З д е с ь создатся декораторы. И для этого применяются аргументы:",
deco arg1, deco arg2)
def x decorator(func):
p r i n t ^ ^ ^ декоратор. Он может получать аргументы:",
deco arg1, deco arg2)
# Аргументы декораторов и аргументы функций - это разные вещи!
def wrapped(fun arg1, fun arg2):
print ("Это обёртка вокруг декорируемой функции.^"
"Она имет доступ ко всем аргументам^"
"\t- как декоратора: {0} {1}\n"
"\t- так и функции: {2} {3}\n"
"Обёртка может передать аргументы декоратора {0}, {1}\n"
"и функции {2}, {3} дальше\n"
.format(deco arg1, deco arg2, fun arg1, fun arg2))
return func(fun arg1, fun arg2)
return wrapped
return x decorator
@decorator maker with arguments("01234", "56789")
def decorated function with arguments(fun arg1, fun arg2):
print ("Это декорируемая функция, которая знает только о своих
аргументах: {0}"
" {1}".format(fun_arg1, fun_arg2))
decorated function with arguments("ABCDE", "FGHIJ")
Здесь создатся декораторы. И для этого применяются аргументы: 01234 56789
это декоратор. Он может получать аргументы: 01234 56789
Это обёртка вокруг декорируемой функции.
Она имет доступ ко всем аргументам
- как декоратора: 01234 56789
- так и функции: ABCDE FGHIJ
Обёртка может передать нужные аргументы декоратора 01234, 56789
и функции ABCDE, FGHIJ дальше

Это декорируемая функция, которая знает только о своих аргументах: ABCDE FGHIJ

Process finished with exit code 0

■ Особенности работы с декораторами
■ Декораторы замедляют вызов функции.

123

■ Очень сложно "раздекорировать" функцию (да и зачем ???). Если функция декорирована
— это лучше не отменять.
■ Декораторы оборачивают функции и это может затруднить отладку.
Последняя проблема частично решена добавлением в модуле functools функции functools.wraps,
копирующей всю информацию об оборачиваемой функции (её имя, из какого она модуля, её
документацию и т.п.) в функцию-обёртку.
Декораторы могут быть использованы для расширения возможностей функций из сторонних
библиотек (код которых невозможно изменить), или для упрощения отладки (чтобы не изменять
исходный отлаживаемый код).
Также полезно использовать декораторы для расширения различных функций одним и тем же
кодом, без повторного его переписывания.

■ Класс - декоратор
Добавление метода__ call__ в объявлении класса превращает его в вызываемый объект (функция
- это вызываемый объект). А так как декоратор — это функция, то есть, вызываемый объект, с
помощью метода__ call__ класс можно превратить в декоратор.
Если функция, которую требуется декорировать, должна получать аргументы, то для этого
декоратор должен вернуть функцию с той же сигнатурой (списком аргументов), что и у
декорируемой функции.
В случае с классом соответствующая сигнатура добавляется в метод__ call__ .
class xDecorator:
def
init
(self, funct):
print('xDecorator, method
init ')
self.funct = funct # На этапе инициализации объект получает
# ссылку на функцию funct
# В случае с классом
# соответствующая сигнатура добавляется в метод
call .
def
call
(self, *args):
print('> перед вызовом класса...', self.funct. name )
self.funct(*args)
print('> после вызова класса')
# ========================================================================
@xDecorator
def external funct(*args):
print('===== external funct =====')
i = 0
# множество значений, передаваемых функции, представляются в виде
# кортежа, который состоит из одного элемента: списка значений.
arguments = args[0]
# из аргумента - кортежа извлекается список
for a in arguments:
# перебор элементов списка
print('{0} ... {1}'.format(i, a))
i += 1
# ========================================================================
# ========================================================================
if
name
== ' main ':
args = [1, 2.05, 'qwerty', 0]
print('--> start')
external funct(args)
print('end -->')

124

■ Аргументы в классах-декораторах
В классах-декораторах выполняются аналогичные настройки. В этом случае конструктор класса
получает все аргументы декоратора. Метод (магический метод) __ call__ должен возвращать
функцию-обертку, которая будет выполнять декорируемую функцию.
Ранее в качестве аргумента конструктору передавалась ссылка на функцию. В этой версии
объявления декоратора передаётся аргумент.
Метод __ call__ получает ссылку на декорируемую функцию, функция-оболочка (wrapper)
объявляется с той же сигнатурой (списком аргументов), что и декорируемая функция. Она
получает аргументы, соответственно предназначенные декорируемой функции.
Аргумент, передаваемый конструктору класса, функции-оболочке (wrapper) и декорируемой
функции в этой версии декорирования оказывается не известен.
# В этом случае конструктор класса получает все аргументы декоратора.
# Метод
call
должен возвращать функцию-обертку, которая будет
# выполнять декорируемую функцию.
class DecoratorArgs:
# ранее в качестве аргумента конструктору передавалась
# ссылка на функцию. В этой версии объявления передаётся
# аргумент. Это строка.
def
init
(self, xstr):
print('> Декоратор с аргументами
init :', xstr)
self.xstr = xstr
def
call
(self, funct):
# метод
call
получает ссылку на декорируемую функцию
# функция-оболочка (wrapper) объявляется с той же сигнатурой
# (списком аргументов), что и декорируемая функция.
# Она получает аргументы, соответственно предназначенные
# декорируемой функции.
z = self.xstr
print(z)
# Аргумент, передаваемый конструктору класса, функции-оболочке
# (wrapper) и декорируемой функции в этой версии декорирования
# оказывается не известен.
def wrapper(a, b):
print('>>> до обернутой функции. Начало декорирования')
funct(a, b)
print('>>> после обернутой функции. Конец декорирования')
return wrapper
@DecoratorArgs("xxxXXXxxx")
def add(a, b):
print('функция add:', a, b)
print('>> старт')
add(10, 20)
print('>> конец')

125

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

■ Tkinter: общее представление
Tkinter - это пакет для Python, который предназначен для работы с библиотекой Tk. Графическая
кроссплатформенная библиотека на основе средств Tk. Свободное ПО, включено в стандартную
библиотеку языка программирования Python. В состав Tkinter входит множество компонентов.
Библиотека Tk содержит компоненты графического интерфейса пользователя (graphical user
interface - GUI. Библиотека написана на языке Tcl.).

■ Графический интерфейс
Под графическим интерфейсом пользователя (GUI) подразумеваются окна, кнопки, текстовые
поля для ввода, скроллеры, списки, радиокнопки, флажки и прочие элементы, которые
появляются на экране при открытии того или иного приложения.
Все эти элементы интерфейса называются виджетами (widgets). Через них обеспечивается
взаимодействие с программой и управление этой программой.
Приложения, которые создаются для конечного пользователя, имеют GUI.
Tk выбран для Python по умолчанию. Установочный файл интерпретатора Python обычно уже
включает пакет tkinter в составе стандартной библиотеки.
Tkinter можно представить как переводчик с языка Python на язык Tcl. Программа пишется на
Python, а код модуля tkinter переводит инструкции с Python на язык Tcl, который понимает
библиотека Tk.
Библиотеку Tkinter надо импортировать. Стандартные способы импорта модуля Tkinter в Python:
import tkinter
from tkinter import *
import tkinter as tk

Также можно импортировать отдельные классы, но это делают редко. Обычно хватает выражения
from tkinter import *

Если нужно узнать установленную версию Tk, это можно сделать через константу TkVersion:
>>> from tkinter import *
>>> TkVersion
8.6

■ Ошибки
Разработка и выполнение приложения может сопровождаться различными ошибками, к которым
можно отнести:
■ Синтаксические - возникают при разработке кода из-за синтаксических(!) ошибок;
■ Логические - являются результатом ошибок разрабатываемого алгоритма в приложении;

126



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

■ С интаксические ош ибки
Являются результатом нарушения правил языка программирования, например, пропуска круглой,
прямоугольной или фигурной скобки в предполагаемом синтаксисом языка месте
разрабатываемого кода. Обнаруживаются компилятором или интерпретатором, которые
сообщением (обычно стандартной формы) уведомляют разработчика кода (программиста) о
проблеме в создаваемом коде.
■ Л огические ош ибки
Как правило, более сложные в выявлении, так как не "отлавливаются" ни на этапе компиляции
(если таковая существует), ни во время выполнения программного кода (возможно,
интерпретатором). Обычно они вызваны ошибками в логике реализуемого алгоритма, из-за чего
достижение предусматриваемого результата оказывается невозможным.
■ И склю чения
Ещё один вид ошибок, который проявляется в зависимости от наличия обстоятельств, влияющих
на ход выполнения программы. Примером может быть ввод некорректного значения, либо
отсутствие файла, необходимого для корректного выполнения приложения. Как правило,
исключения проявляются во время выполнения приложения.

■ Исключения: преимущества применения
Исключения представляют собой самостоятельный тип данных, при помощи которого
приложение может стандартным образом сообщать (пользователю) о различных ошибках.
Обработка исключений позволяет менять сценарий выполнения приложения с учётом различных
обстоятельств, так или иначе нарушающих его нормальную работу. Для управления
исключениями в языке программирования python предусмотрены специальные синтаксические
конструкции.
Также в языке существует библиотека готовых исключений, а также реализована возможность
создавать собственные исключения.
Программа, которая способна корректно обработать возникающие исключения, завершается
естественным образом, без прерываний заданного алгоритма.
Например, отсутствие файла при выполнении приложения провоцирует исключительную
ситуацию, которая приводит к аварийному завершению работы программы и выводу ошибки на
экран.
Пример приложения, НЕ способного "стандартным" образом реагировать на отсутствие файла при
попытке его открытия или сохранения:
# ========================================================================
from tkinter import *
root = Tk()
print("Program started")
print("Opening file...")
f = open("data.txt")
print("Program finished")
if

name
== ' main
root.mainloop()

':

127

#

При отсутствии файла "data.txt" в ходе выполнения приложения будет выведено сообщение о
возникновении соответствующего исключения:
Traceback (most recent call last):
File "C:/PythonDrom/SeismicModels/SeisModel04/xGui21.py", line 57, in

Program started
Opening file...
f = open("data.txt")
FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'
Process finished with exit code 1

Данное исключение предусмотрено разработчиками среды программирования python (язык,
интерпретатор, библиотечные модули).
Разработчики приложения НЕ предусмотрели в приложении реакцию на отсутствие файла, что и
привело к возникновению исключения.
В этом случае отображается имя файла, номер строки в коде, где было выброшено исключение
FileNotFoundError (FileNotFoundError: [Errno 2] No such file or directory: 'data.txt').

■ Перехват исключений
Для того чтобы исключение не приводило к внезапному завершению приложения, в языке
программирования реализуется механизм, который способен предотвращать непредвиденные
(НО предусмотренные) ситуации.
С помощью специальных конструкций (try:...except:...) языка программирования в приложении
описываются 'критические' фрагменты кода и варианты реагирования на возникающие
исключения. Это помогает избежать сбоев при выполнении приложения.
# ========================================================================
from tkinter import *
root = Tk()
print("Program started")
try:
print("Opening file...")
f = open("data.txt")
except:
print('file not found!')
print("Program finished")
if

name
== ' main
root.mainloop()

':

# ========================================================================
Блок try содержит "опасный код", который способнен привести к ошибке (отсутствие нужного
файла).

128

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

■ except: несколько блоков
Блоков except может быть несколько, в зависимости от того, какой тип исключения нужно
обработать в приложении. Как правило, сначала обрабатываются более частные случаи, затем
общие.
# ========================================================================
from tkinter import *
root = Tk()
print("Program started")
try:
print("Opening file...")
f = open("data.txt")
except FileNotFoundError:
print('file not found!')
except Exception:
print('something gone wrong!')
print("Program finished")
if

name
== ' main
root.mainloop()

':

#

■ Вложенные блоки и else
Для более гибкого управления исключениями блоки try:...except:... могут быть вложенными. В
следующем примере демонстрируется попытка открыть текстовый файл и записать в него некую
строку. Для каждой цели используется отдельный блок try.
Также в данном примере используется конструкция else, которая выполняется в случае, если в
коде не произошло исключений.
В данном случае else сработает при успешном выполнении операции write. По умолчанию файл
открывается на чтение в текстовом режиме. Поэтому при открытии файла используется режим
"w". При этом файл открывается на запись.
Если файла не было — создается новый, если файл был — он перезаписывается.
# ========================================================================
from tkinter import *
root = Tk()
print("Program started")
# внешний блок try - except ==============================================
try:
print("Opening file...")

129

f = open("data.txt", "w")
# вложенный блок try - except - else =
try:
print("writing to file...")
f.write("---- Hello W o r l d ----- ")
except Exception:
print('something gone wrong!')
else:
print("Success!")
# ====================================
except FileNotFoundError:
print('file not found!')
# ========================================
print("Program finished")
if

name
== ' main
root.mainloop()

':

#

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

# ========================================
from tkinter import *
root = Tk()
print("Program started")
# внешний блок try - except ==============
try:
print("Opening file...")
f = open("data.txt", "w")
# вложенный блок try - except - else =
try:
print("writing to file...")
f.write("---- Hello W o r l d ----- ")
except Exception:
print('something gone wrong!')
else:
print("Success!")
finally:
f.close()
print("Closing file...")

130

#

except FileNotFoundError:
print('file not found!')
# ==========================
print("Program finished")

if

name
== ' main
root.mainloop()

#

■ Конструкция with - as
Описываемый ранее подход к работе с исключениями может показаться слишком сложным, так
как код, который его реализует, выглядит громоздким (?). Специально для этого случая (для
"упрощения" кода) в языке python существует конструкция with - as, которая позволяет
автоматизировать некоторые стандартные методы (например, закрытие файла) при работе с
соответствующими объектами. Это позволяет сократить длину кода:
# ========================================================================
from tkinter import *
root = Tk()
print("Program started")
try:
print("Opening file...")
# конструкция with - as
with open("data.txt", "w") as f:
f.write("---- Hello W o r l d ----- ")
except Exception:
print('something gone wrong!')
print('file not found!')
print("Program finished")
if

name
== ' main
root.mainloop()

':

#

■ Пользовательские исключения
Как правило, в корректно написанном приложении, исключения автоматически вызываются в
необходимых ситуациях. При этом в python существует возможность запускать их самостоятельно.
Для этого в python применяется оператор raise. Следом за ним объявляется и создаётся новый
объект типа Exception, с которым в дальнейшем можно работать при помощи уже известных
конструкций try:...except:..., как в следующем примере:
# ========================================================================
from tkinter import *

131

root = Tk()
print("Program started")
try:
raise Exception('User Exception !')
except Exception as e: # эта конструкция позволяет распечатать текст
# исключения (в данном случае
# Exception - 'User Exception !')
print(str(e))
print("Program finished")

if

name
== ' main
root.mainloop()

# ========================================================================
Чтобы описать собственный (пользовательский) тип исключения, нужно создать новый класс,
унаследованный от базового типа Exception. Это позволит запускать особые виды исключений в
ситуациях, когда поведение пользователя не соответствует алгоритму программы. В конструкторе
Exception указывается текст исключения. После того, как исключение сработало и было
перехвачено, его можно получить с помощью str.
В следующем коде представлен процесс генерации исключения NegativeAge, которое не
позволяет ввести отрицательный возраст:
# ========================================================================
from tkinter import *
# объявление пользовательского исключения ================================
class NegativeAge(Exception):
pass
# ========================================================================
root = Tk()
print("Program started")
try:
age = int(input("Tnter yor age: "))
# в случае ввода некорректного значения age - генерация ==============
# пользовательского исключения
if age < 0:
# ====================================================================
raise NegativeAge("Exception: Negative age!")
# ====================================================================
else:
# если значение введено правильно - приложение завершает работу
print("Success!")
except NegativeAge as e:
# в противном случае перехватывается исключение NegativeAge.
# Блок его обработки предусматривает распечатку его текстового
# представления ("Exception: Negative age!")
print(str(e))
print("Program finished")

132

if

name
== ' main
root.mainloop()

':

# ========================================================================

■ Иерархия исключений
В языке программирования python существует строгая иерархия исключений. "Вершиной"
является BaseException, включающий в себя все существующие разновидности исключений:
■ SystemExit - возникает при выходе из программы с помощью sys.exit;
■ KeyboardInterrupt - указывает на прерывание программы пользователем;
■ GeneratorExit - появляется при вызове метода close для объекта generator;
■ Exception - представляет множество обычных несистемных исключений.

■ Класс Exception. Н есистем ны е исклю чения
В следующей таблице приведены несистемные исключения, которые содержит класс Exception.
Название

Описание

ArithmeticError

Порождается арифметическими ошибками (операции с плавающей
точкой, переполнение числовой переменной, деление на ноль)

AssertionError

Возникает при ложном выражении в функции assert

AttributeError

Появляется в случаях, когда нужный атрибут объекта отсутствует

BufferError

Указывает на невозможность выполнения буферной операции

EOFError

Проявляется, когда функция не смогла прочитать конец файла

ImportError

Сообщает о неудачном импорте модуля либо атрибута

LookupError

Информирует про недействительный индекс или ключ в массиве

MemoryError

Возникает в ситуации, когда доступной памяти недостаточно

NameError

Указывает на ошибку при поиске переменной с нужным именем

NotImplementedError

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

OSError

Включает в себя системные ошибки (отсутствие доступа к нужному
файлу или директории, проблемы с поиском процессов)

ReferenceError

Порождается попыткой доступа к атрибуту со 'слабой' ссылкой

RuntimeError

Сообщает об исключении, которое не классифицируется

StopIteration

Возникает во время выполнения функции next при отсутствии
элементов

133

SyntaxError

Представляет собой совокупность синтаксических ошибок

SystemError

Порождается внутренними ошибками системы

TypeError

Указывает на то, что операция не может быть выполнена с объектом

UnicodeError

Сообщает о неправильной кодировке символов в программе

ValueError

Возникает при получении некорректного значения для переменной

Warning

Обозначает предупреждение

134

■ Магические методы
Этот раздел основан на руководстве от Rafe Kettler 1.17 версии.

Содержание раздела:

■ Введение
■ Конструирование и инициализация
■ Переопределение операторов на произвольных классах
■ Магические методы сравнения
■ Числовые магический методы
■ Представление своих классов
■ Контроль доступа к атрибутам
■ Создание произвольных последовательностей
■ Отражение
■ Вызываемые объекты
■ Менеджеры контекста
■ Абстрактные базовые классы
■ Построение дескрипторов
■ Копирование
■ Использование модуля pickle на своих объектах
Приложение 1: Как вызывать магические методы
Приложение 2: Изменения в python 3

■ Введение
Магические методы применяются в объектно-ориентированном python. Это специальные методы,
с помощью которых в классы можно добавить «магию». Это методы всегда обрамлены двумя
нижними подчеркиваниями (например,__ init__ или___ lt__ ).
Предупреждение: они плохо документированы (!!!). То есть, они описаны, но беспорядочно и
почти без всякой системы.
Поэтому, Rafe Kettler, чтобы исправить то, что воспринимется как недостаток документации
python о магических методах, постарался представить подробно, понятно, с большим
количеством примеров.

■ Конструирование и инициализация магических методов
При создании объекта применяется магический метод
__ new

(cls, [...])

# (класс, [список параметров])

Этот метод вызывается первым при создании и инициализации объекта. Он принимает в качестве
параметров класс и любые другие аргументы, которые затем будут переданы в инициализатор
объекта — магический метод__ init__ .
__ new__ редко используется, но иногда бывает полезен, в частности, когда класс наследуется от
неизменяемого (immutable) типа, такого как кортеж (tuple) или строка.

135

Далее вызывается базовый магический метод, __ init__ . Это инициализатор. С его помощью
инициализируются объекты. При выполнении оператора x = SomeClass([...]) с помощью метода
__ new__ создаётся объект-представитель SomeClass, который вызывает __ init__ и передаёт
аргументы, заданные в конструкторе, в инициализатор.
__ init__ (self, [...]), инициализатор класса. Ему передаётся всё, с чем был вызван первоначальный
конструктор (например, при выполнении оператора x = SomeClass(10, 'foo'), нестатический метод
__ init__ получает в качестве аргументов значения 10 и 'foo'. __ init__ всегда (почти всегда)
используется при определении объектов - представителей классов.
На другом конце жизненного цикла объекта находится метод__ del__ (self).
Если __ new__ и __ init__ образуют конструктор объекта, __ del__ это его деструктор. Он не
определяет поведение для выражения del x (поэтому этот код не эквивалентен х.__ del__ ()).
Скорее, он определяет поведение объекта в то время, когда объект попадает в сборщик мусора
(!!!).
Это может быть удобно для объектов, которые могут требовать дополнительных чисток во время
удаления, таких как сокеты или файловыве объекты.__ del__ всегда вызывается по завершении
работы интерпретатора. Однако __ del__ не может служить заменой для хороших
программистских практик (всегда завершать соединение, если объект завершил с ним работу и
тому подобное).
Пример:__ init__ и ___ del__ в действии:
from os.path import join
class FileObject:
I

I

I

Обёртка для файлового объекта, чтобы быть уверенным в том,
что файл будет закрыт при удалении.
I

I

I

def

init
(self, filepath='~', filename='s.txt'):
# открыть файл filename в filepath в режиме чтения и записи
self.file = open(join(filepath, filename), 'r+')

def

del
(self):
self.file.close()
del self.file

def Go(path, name):
fo = FileObject(path, name)
if

name ==' main ':
Go('C:\PythonDrom\Texts 2022\-- ', 'sample.txt')

■ Переопределение операторов на произвольных классах
Одно из преимуществ применения магических методов в python - это то, что они предоставляют
простой (!!!) способ заставить объекты вести себя подобно встроенным типам (классам)).
Это означает, что можно избежать поведения базовых операторов (унылого, нелогичного и
нестандартного) (???).
В некоторых языках обычное явление писать как-нибудь так:
if instance.equals(other instance): # если объект эквивалентен другому

136

# объекту, то ...
# do something

Можно, конечно, так же поступать и в python, но это добавляет путаницы и ненужной
многословности (???). Разные библиотеки могут по разному называть одни и те же операции,
заставляя использующего их программиста совершать больше действий, чем необходимо.
Применение магических методов позволяет определить нужный метод (__ eq__ , в следующем
примере), и так точно выразить, что имелось в виду:
if instance == other instance:
#do something

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

■ Магические методы сравнения
В python много магических методов, созданных для определения интуитивного (!!!) сравнения
между объектами используя операторы, а не неуклюжие (!!!) методы.
Кроме того, они предоставляют способ переопределить поведение python по умолчанию для
сравнения объектов (по ссылке). Вот список этих магических методов и что они делают:

■ __ cmp__ (self, other) Самый базовый из методов сравнения. Он, в действительности,
определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как
это нужно. (например, если эквивалентность двух объектов определяется по одному
критерию, а то что один больше другого по какому-нибудь другому).
__ cmp__ должен вернуть:
отрицательное число, если self < other,
ноль, если self == other,
положительное число в случае self > other.
Но, обычно, лучше определить каждое сравнение, которое нужно, чем определять их всех в
__ cmp__ . В то же время, __ cmp__ может быть хорошим способом избежать повторений и
увеличить ясность, когда все необходимые сравнения оперерируют одним критерием.


eq (self, other) Определяет поведение оператора равенства, ==.

ne (self, other) Определяет поведение оператора неравенства, !=.

lt__ (self, other) Определяет поведение оператора меньше, .

le__ (self, other) Определяет поведение оператора меньше или равно, =.

137

Для примера - класс, описывающий слово. Можно сравнивать слова лексикографически (по
алфавиту), что является поведением, заданным по умолчанию при сравнении строк, но можно
использовать при сравнении какой-нибудь другой критерий, например, длина или количество
слогов. В этом примере реализовано сравнение по длине. Вот возможная реализация:
class Word(str):
'''Класс для слов, определяющий сравнение по длине слов.'''
def

new (cls, word):
# Надо использовать
new , так как тип str неизменяемый
# и он должен быть инициализирован раньше (при создании)
if ' ' in word:
print "Value contains spaces. Truncating to first space."
word = word[:word.index(' ')] # Теперь Word это все символы до
# первого пробела
return str. new (cls, word)

def

gt (self, other):
return len(self) > len(other)
def
lt (self, other):
return len(self) < len(other)
def
ge (self, other):
return len(self) >= len(other)
def
le (self, other):
return len(self) B -> C -> A -> object, а
значение type=B, то super() выполняет поиск объекта C -> A -> object.
Если object-or-type не указан, то возвращается несвязанный объект-посредник.
Если object-or-type является объектом (экземпляром), то будет получен посредник, для которого
isinstance(obj, type) возвращает True.
Если object-or-type является типом (классом), то будет получен посредник, для которого
issubclass(subtype, type) возвращает True.

■ Типичные случаи использования super()
■ И ерархия с единичны м наследованием
В иерархиях с единичным наследованием используется для обращения к родительским классам,
чтобы явно не указывать их имена. Это упрощает поддержку кода в дальнейшем.
Например:
class A:
def some method(self):
print('some method A')
class B(A):
def some method(self):
print('some method B')
x = B()
x. some method()
У = A()
y. some method()
# some method B

Здесь перегружен метод родительского класса. Но что делать, если необходимо немного(???)
дополнить родительский метод, не копируя его полностью? Тут и нужна функция super():
class A:
def some method(self):
print('some method A')
class B(A):
def some method(self):
super().some method()
print('some method B')

# Вызов функции super

151

x = B()
x.some method()
# Результат выполнения функции super
# some method A
# some method B

■ П оддерж ка совместного множ ественного наследования
Для поддержки совместного множественного наследования в динамическом окружении super
делает возможным обращение с ромбовидными иерархиями, при которых несколько базовых
классов задают реализацию метода с одним и тем же именем.
Использование функция super() с обоими аргументами точно определяет объекты и делает
соответствующие ссылки. Без аргументов функция super() работает только внутри определения
класса, а необходимые детали для идентификации класса и доступа к текущему экземпляру для
методов заполняет компилятор.
В дополнение к поиску методов, super() также работает для поиска атрибутов. Одним из
вариантов использования этого является вызов дескрипторов в родительском или родственном
классе.
Функция super() реализована как часть процесса привязки для явного поиска по точечным
атрибутам, таких как super().__ getitem__ (name). Это достигается путем реализации собственного
метода__ getattribute__ () для поиска классов в предсказуемом порядке, который поддерживает
кооперативное множественное наследование__ mro__ . super() и не предназначена для неявных
поисков с использованием инструкций или операторов, таких как super()[name].

■ Примеры получения доступа к унаследованным методам.
■ Ф ун кц и я в единичном наследовании
class Computer:
def
init
(self, computer, ram, ssd):
self.computer = computer
self.ram = ram
self.ssd = ssd
# Если создать дочерний класс 'Laptop', то будет доступ
# к свойству базового класса благодаря функции super().
class Laptop(Computer):
def
init
(self, computer, ram, ssd, model):
super(). init
(computer, ram, ssd)
self.model = model

lenovo = Laptop('lenovo', 2, 512, 'l420')
print('This
print('This
print('This
print('This
#
#
#
#

computer
computer
computer
computer

is:', lenovo.computer)
has ram of', lenovo.ram)
has ssd of', lenovo.ssd)
has this model:', lenovo.model)

Вывод
This computer is: lenovo
This computer has ram of 2
This computer has ssd of 512

152

# This computer has this model: 1420
В следующем примере класс Rectangle является суперклассом, а Square является подклассом,
поскольку методы Square наследуются от Rectangle, то можно вызвать метод __ init __ ()
суперкласса (Rectangle.__ init___ ()) из класса Square используя функцию super().
Далее просто пользоваться методами родителя, не написав ни строчки кода (!!!).
В данном случае квадрат - это частный случай прямоугольника (и его дочерний класс).
class Rectangle:
def
init
(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * self.length + 2 * self.width
class Square(Rectangle):
def
init
(self, length):
# Для квадрата просто нужно передать один параметр length.
# При вызове 'super(). init
()' установим атрибуты 'length' и
'width'.
super(). init
(length, length)
# Класс 'Square' явно не реализует метод 'area()' и
# будет использовать его из суперкласса 'Rectangle'
sqr = Square(4)
print("Area of Square is:", sqr.area())
# Area of Square is: 16
print("Perimeter of Square is:", sqr.perimeter())
# Perimeter of Square is: 16
rect = Rectangle(2, 4)
print("Area of Rectangle is:", rect.area())
# Area of Rectangle is: 8
print("Perimeter of Rectangle is:", rect.perimeter())
# Perimeter of Square is: 12
И наконец пример работы функции super() при использовании множественного наследования:
class A:
def

init
(self):
print('Initializing: class A')

def sub method(self, b):
print('sub method from class A:', b)

class B(A):
def
init
(self):
print('Initializing: class B')
super(). init
()
def sub method(self, b):
print('sub method from class B:', b)

153

super().sub method(b + 1)
class X(B):
def
init
(self):
print('Initializing: class X')
super(). init
()
def sub method(self, b):
print('sub method from class X:', b)
super().sub method(b + 1)
class Y(X):
def
init
(self):
print('Initializing: class Y')
# super() с параметрами
super(X, self). init
()
def sub method(self, b):
print('sub method from class Y:', b)
super().sub method(b + 1)
x = X()
x. sub method(1)
print('Обратите внимание как происходит инициализация')
print('классов при указании аргументов в функции super()')
y = Y()
y. sub method(5)
# Вывод
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

Initializing: class X
Initializing: class B
Initializing: class A
sub method from class X: 1
sub method from class B: 2
sub method from class A: 3
важно, как происходит инициализация
классов при указании аргументов в функции super()
Initializing: class Y
Initializing: class B
Initializing: class A
sub method from class Y: 5
sub method from class X: 6
sub method from class B: 7
sub method from class A: 8

Класс, содержащий атрибут value и реализующий счётчик доступа к нему. Счётчик увеличивается
каждый раз, когда меняется value.

class AccessCounter(object):
I

I

I

Класс, содержащий атрибут value и реализующий счётчик доступа к нему.
Счётчик увеличивается каждый раз, когда меняется value.
I

I

I

154

def

def

init
(self, val):
super(AccessCounter,
super(AccessCounter,

self). setattr
self). setattr

setattr
(self, name, value):
if name == 'value':
super(AccessCounter, self).

('counter', 0)
('value', val)

setattr

('counter',
self.counter + 1)

# Здесь нет никаких условий.
# Нужно предотвратить изменение других атрибутов,
# надо выбросить исключение AttributeError(name)
super(AccessCounter,
self). setattr
(name, value)
def

delattr (self, name):
if name == 'value':
super(AccessCounter, self).
super(AccessCounter,

setattr

self). delattr

('counter',
self.counter + 1)
(name)]

■ Произвольные последовательности
В python существует множество способов заставить классы вести себя как встроенные
последовательности (словари, кортежи, списки, строки и так далее). Это магические методы из-за
высокой степени контроля, которую они дают и той магии, от которой с объектами классов
начинает работать целое множество глобальных функций. Но, до того как будут описаны всякие
важные вещи, надо знать о протоколах.

■ П ротоколы
Теперь, когда речь зашла о создании собственных последовательностей в python, нужно
ознакомиться к протоколам. Протоколы похожи на интерфейсы в других языках тем, что они
предоставляют набор методов, которые необходимо реализовать. Однако протоколы в python
абсолютно ни к чему не обязывают и не требуют обязательной реализации какого-либо
объявления. Наверное, они больше похожи на руководящие указания.
Почему речь о протоколах? Потому, что реализация произвольных контейнерных типов в python
влечёт за собой использование некоторых из них.
Во-первых, протокол для определения неизменяемых контейнеров: чтобы создать неизменяемый
контейнер, нужно только определить__ len__ и ___ getitem__ (продробнее о них дальше). Протокол
изменяемого контейнера требует того же, что и неизменяемого контейнера, плюс__ setitem__ и
__ delitem__ .
Во-вторых, наконец, если необходимо, чтобы объекты можно было перебирать итерацией, нужно
определить __ iter__ , который возвращает итератор. Этот итератор должен соответствовать
протоколу итератора, который требует методов__ iter__ (возвращает самого себя) и next.

■ М агия контейнеров
Магические методы, используемые контейнерами:
■ __ len__ (self)
Возвращает количество элементов в контейнере.
неизменяемого контейнеров.

155

Часть протоколов для

изменяемого и

■ __ getitem__ (self, key)
Определяет поведение при доступе к элементу, используя синтаксис self[key]. Тоже относится и к
протоколу изменяемых и к протоколу неизменяемых контейнеров. Должен выбрасывать
соответствующие исключения: TypeErrorесли неправильный тип ключа и KeyError если ключу не
соответствует никакого значения.

■ __ setitem__ (self, key, value)
Определяет поведение при присваивании значения элементу, используя синтаксис
self[nkey] = value.
Часть протокола изменяемого контейнера. Опять же, нужно выбрасывать KeyError и TypeError в
соответсвующих случаях.

■ __ delitem__ (self, key)
Определяет поведение при удалении элемента (то есть del self[key]). Это часть только протокола
для изменяемого контейнера. Вы должны выбрасывать соответствующее исключение, если ключ
некорректен.

■ __ iter__ (self)
Должен вернуть итератор для контейнера. Итераторы возвращаются в множестве ситуаций,
главным образом для встроенной функции iter() и в случае перебора элементов контейнера
выражением for x in container:. Итераторы сами по себе объекты и они тоже должны определять
метод__ iter__ , который возвращает self.

■ __ reversed__ (self)
Вызывается чтобы определить поведения для встроенной функции reversed(). Должен вернуть
обратную версию последовательности. Реализуйте метод только если класс упорядоченный, как
список или кортеж.

■ __ contains__ (self, item)
__ contains__ предназначен для проверки принадлежности элемента с помощью in и not in. Это не
часть протокола последовательности. Потому что когда__ contains__ не определён, python просто
перебирает всю последовательность элемент за элементом и возвращает True если находит
нужный.

■ __ missing__ (self, key)
__ missing__ используется при наследовании от dict. Определяет поведение для для каждого
случая, когда пытаются получить элемент по несуществующему ключу (так, например, если

156

имеется есть словарь d и пишется d["george"] когда "george" не является ключом в словаре,
вызывается d.__ missing__ ("george")).

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

class FunctionalList:
I

I

I

Класс-обёртка над списком с добавлением некоторой функциональной магии:
head, tail, init, last, drop, take.

def

init
(self, values=None):
if values is None:
self.values = []
else:
self.values = values

def

len (self):
return len(self.values)

def

getitem (self, key):
# если значение или тип ключа некорректны, list выбросит исключение
return self.values[key]

def

setitem (self, key, value):
self.values[key] = value

def

delitem (self, key):
del self.values[key]

def

iter (self):
return iter(self.values)

def

reversed (self):
return FunctionalList(reversed(self.values))

def append(self, value):
self.values.append(value)
def head(self):
# получить первый элемент
return self.values[0]
def tail(self):
# получить все элементы после первого
return self.values[1:]
def init(self):
# получить все элементы кроме последнего
return self.values[:-1]

157

def last(self):
# получить последний элемент
return self.values[-1]
def drop(self, n):
# все элементы кроме первых n
return self.values[n:]
def take(self, n):
# первые n элементов
return self.values[:n]

def DoIt(parameters):
fl = FunctionalList(parameters)
res = fl.take(3)
return res
if

name
== ' main ':
parameters = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
res = DoIt(parameters)
print(res)

Теперь
есть
полезный
(относительно)
пример
реализации
своей
собственной
последовательности. Существуют, конечно, и куда более практичные реализации произвольных
последовательностей, но большое их число уже реализовано в стандартной библиотеке, такие как
Counter, OrderedDict, NamedTuple.
■ О тражение
Вы можно контролировать и отражение, использующее встроенные функции isinstance() и
issubclass(), определив некоторые магические методы:

■ __ instancecheck__ (self, instance)
Проверяет, является ли экземлпяр членом пользовательского класса (isinstance(instance, class),
например.

■ __ subclasscheck__ (self, subclass)
Проверяет, является наследуется ли класс от пользовательского класса (issubclass(subclass, class)).

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

158

■ Вы зы ваем ы е об ъекты
Как уже известно, в python функции являются объектами первого класса. Это означает, что они
могут быть переданы в функции или методы так же, как любые другие объекты. Это невероятно
мощная особенность.
Специальный магический метод позволяет объектам пользовательского класса вести себя так, как
будто они функции, то есть их можно «вызывать» их, передавать их в функции, которые
принимают функции в качестве аргументов и так далее. Это другая удобная особенность, которая
делает программирование на python таким приятным.

■ __ call__ (self, [args...])
Позволяет любому экземпляру класса быть вызванным как-будто он функция. Главным образом
это означает, что x() означает то же, что и x.__ call__ (). При этом ,__ call__ принимает произвольное
число аргументов; то есть, можно определить __ call__ так же как любую другую функцию,
принимающую столько аргументов, сколько это нужно.

__ call__ , в частности, может быть полезен в классах, чьи экземпляры часто изменяют своё
состояние. «Вызвать» объект может быть интуитивно понятным и элегантным способом изменить
состояние объекта. Примером может быть класс, представляющий положение некоторого
объекта на плоскости:
class Entity:
I

I

I

Класс, описывающий объект на плоскости.
"Вызываемый", чтобы обновить позицию объекта.
III
def

init
(self, size, x, y):
self.x, self.y = x, y
self.size = size

def

call
(self, x, y):
# Изменить положение объекта.
self.x, self.y = x, y

■ М енеджеры ко н те кста
В python 2.5 было представлено новое ключевое слово вместе с новым способом повторно
использовать код. Это ключевое слово with. Концепция менеджеров контекста не являлась новой
для python (она была реализована раньше как часть библиотеки), но в PEP 343 достигла статуса
языковой конструкции. Выражения с with:

with open('foo.txt') as bar:
# выполнение каких-нибудь действий с bar

Менеджеры контекста позволяют выполнить какие-то действия для настройки или очистки, когда
создание объекта обёрнуто в оператор with. Поведение менеджера контекста определяется
двумя магическими методами:

159

■ __ enter__ (self)
Определяет, что должен сделать менеджер контекста в начале блока, созданного оператором
with. Заметьте, что возвращаемое__ enter__ значение и есть то значение, с которым производится
работа внутри with.

■ __ exit__ (self, exception_type, exception_value, traceback)
Определяет действия менеджера контекста после того, как блок будет выполнен (или прерван во
время работы). Может использоваться для контроллирования исключений, очистки, любых
действий которые должны быть выполнены незамедлительно после блока внутри with.
Если блок выполнен успешно, exception_type, exception_value, и traceback будут установлены в
None. В другом случае выбор, перехватывать ли исключение или предоставить это пользователю.
Если принято решение перехватить исключение, надо убедиться, что __ exit__ возвращает True
после того как всё сделано. Если не предусматривается, чтобы исключение было перехвачено
менеджером контекста, просто надо позволить ему случиться.

__ enter__ и __ exit__ могут быть полезны для специфичных классов с хорошо описанным и
распространённым поведением для их настройки и очистки ресурсов. Можно использовать эти
методы и для создания общих менеджеров контекста для разных объектов. Вот пример:
class Closer:
I

I

I

Менеджер контекста для автоматического закрытия объекта
вызовом метода close в with-выражении.
I

I

I

def

init
(self, obj):
self.obj = obj

def

enter (self):
return self.obj # привязка к активному объекту with-блока

def

exit
(self, exception type, exception val, trace):
try:
self.obj.close()
except AttributeError: # у объекта нет метода close
print 'Not closable.'
return True # исключение перехвачено

Пример использования Closer с FTP-соединением (сокет, имеющий метод close):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...
conn.dir()
# output omitted for brevity
>>> conn.dir()
# long AttributeError message, can't use a connection that's closed
>>> with Closer(int(5)) as i:
...
i += 1

160

Not closable.
>>> i
6
Здесь видно, как обёртка управляется и с правильными и с неподходящими объектами. В этом
сила менеджеров контекста и магических методов. Стандартная библиотека python включает
модуль contextlib, который включает в себя contextlib.dosing() — менеджер контекста, который
делает приблизительно то же (без какой-либо обработки случая, когда объект не имеет метода
close()).

■ Абстрактные базовые классы
Смотреть http://docs.python.org/2/library/abc.html.

■ Построение дескрипторов
Дескрипторы это такие классы, с помощью которых можно добавить свою логику к событиям
доступа (получение, изменение, удаление) к атрибутам других объектов. Дескрипторы не
подразумевается использовать сами по себе; скорее, предполагается, что ими будут владеть
какие-нибудь связанные с ними классы. Дескрипторы могут быть полезны для построения
объектно-ориентированных баз данных или классов, чьи атрибуты зависят друг от друга. В
частности, дескрипторы полезны при представлении атрибутов в нескольких системах исчисления
или каких-либо вычисляемых атрибутов (как расстояние от начальной точки до представленной
атрибутом точки на сетке).

Чтобы класс стал дескриптором, он должен реализовать по крайней мере один метод и з __ get__ ,
__ set__ или___ delete__ . Эти магические методы подробно:

■ __ get__ (self, instance, instance_class)
Определяет поведение при возвращении значения из дескриптора. instance это объект, для чьего
атрибута-дескриптора вызывается метод. owner это тип (класс) объекта.

■ __ set__ (self, instance, value)
Определяет поведение при изменении значения из дескриптора. instance это объект, для чьего
атрибута-дескриптора вызывается метод. value это значение для установки в дескриптор.

■ __ delete__ (self, instance)
Определяет поведение для удаления значения из дескриптора. instance это объект, владеющий
дескриптором.

■ Пример использования дескрипторов
Теперь пример полезного (???) использования дескрипторов: преобразование единиц измерения.

161

class Meter(object):
III
Дескриптор для метра. У него есть метод
init
(self, value),
значит, он один может принимать параметры при создании объектов
I

I

I

def

init
(self, value=0.0):
self.value = float(value)

def

get
(self, instance,
return self.value

def

set
(self, instance, value):
self.value = float(value)

owner):

class Foot(object):
'''Дескриптор для фута.'''
def

get
(self, instance, owner):
print(f'Foot
get ')
return instance.meter * 3.2808

def

set
(self, instance, value):
print(f'Foot
set ')
instance.meter = float(value) / 3.2808

class Distance(object):
III
Класс, описывающий расстояние.
содержит два статических дескриптора для метров и футов.

# значения передаются ТОЛЬКО в конструктор класса Meter
meter = Meter(10.0)
foot = Foot()

def DoIt():
d = Distance()
print(f'{d.meter} ... {d.foot} ')

if

name
DoIt()

== ' main

■ Копирование
В python оператор присваивания не копирует объекты, а только добавляет ещё одну ссылку. Но
для коллекций, содержащих изменяемые элементы, иногда необходимо полноценное
копирование, чтобы можно было менять элементы одной последовательности, не затрагивая
другую. Здесь применяется copy. И инструкции для python как правильно копировать.

■ __ copy__ (self)

162

Определяет поведение copy.copy() для объекта пользовательского класса . copy.copy() возвращает
поверхностную копию объекта — это означает, что хоть сам объект и создан заново, все его
данные ссылаются на данные оригинального объекта. И при изменении данных нового объекта,
изменения будут происходить и в оригинальном.

■ __ deepcopy__ (self, memodict={})
Определяет поведение copy.deepcopy() для объекта пользовательского класса. copy.deepcopy()
возвращает глубокую копию объекта — копируются и объект и его данные. memodict это кэш
предыдущих скопированных объектов, он предназначен для оптимизации копирования и
предотвращения бесконечной рекурсии, когда копируются рекурсивные структуры данных. Когда
требуется полностью скопировать какой-нибудь конкретный атрибут, на нём вызывается
copy.deepcopy() с первым параметром memodict.

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

■ Использование м одуля p ickle на о б ъектах по льзовательского класса
Pickle это модуль для сериализации структур данных python и он может быть полезен, когда
нужно сохранить состояние какого-либо объекта и восстановить его позже (обычно, в целях
кэширования). Кроме того, это ещё и отличный источник переживаний и путаницы.
Сериализация настолько важна, что кроме своего модуля (pickle) имеет и свой собственный
протокол и свои магические методы. Но для начала о том, как сериализовать с помощью pickle
уже существующие типы данных.
■ Cериализация
Теперь о сериализации. Пусть есть словарь, который нужно сохранить и восстановить позже.
Нужно записать его содержимое в файл, убедившись, что он пишется с правильным синтаксисом,
потом восстановить его, выполнив exec(), или прочитав файл. Но это в лучшем случае рискованно:
если хранить важные данные в тексте, он может быть повреждён или изменён множеством
способов, с целью обрушить программу или, вообще, запустить какой-нибудь опасный код. Лучше
использовать pickle:
import pickle
data = {'foo': [1, 2, 3],
'bar': ('Hello', 'world!'),
'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # записать сериализованные данные в jar
jar.close()
И вот, спустя некоторое время, снова нужен словарь:
import pickle
pkl file = open('data.pkl', 'rb') # открывается
data = pickle.load(pkl file) # сохраняется в переменной
print data

163

pkl file.close()
Что произошло? Точно то, что и ожидалось. data как-будто всегда тут и была.
Теперь, об осторожности: pickle не идеален. Его файлы легко испортить случайно или
преднамеренно. Pickle, может быть, безопаснее чем текстовые файлы, но он всё ещё может
использоваться для запуска вредоносного кода. Кроме того, он несовместим между разными
версиями python, поэтому если распространять объекты с помощью pickle, не обязятельно, что все
смогут их использовать. Тем не менее, модуль может быть мощным инструментом для
кэширования и других распространённых задач с сериализацией.
■ Сериализация со бственны х объектов
Модуль pickle не только для встроенных типов. Он может использоваться с каждым классом,
реализующим его протокол. Этот протокол содержит четыре необязательных метода,
позволяющих настроить то, как pickle будет с ними обращаться (есть некоторые различия для
расширений на C, но это за рамками нашего руководства):

■ __ getinitargs__ (self)
Если необходимо, чтобы после десериализации класса был вызыван __ init__ , то можно
определить __ getinitargs__ , который должен вернуть кортеж аргументов, который будет
отправлен в __ init__ . Этот метод работает только с классами старого стиля.

■ __ getnewargs__ (self)
Для классов нового стиля можно определить, какие параметры будут переданы в __ new__ во
время десериализации. Этот метод так же должен вернуть кортеж аргументов, которые будут
отправлены в __ new__ .

■ __ getstate__ (self)
Вместо стандартного атрибута __ dict__ , где хранятся атрибуты класса, можно вернуть
произвольные данные для сериализации. Эти данные будут переданы в __ setstate__ во время
десериализации.

■ __ setstate__ (self, state)
Если во время десериализации определён__ setstate__ , то данные объекта будут переданы сюда,
вместо того чтобы просто записать всё в __ dict__ . Это парный метод д л я __ getstate__ : когда оба
определены, вы можете представлять состояние вашего объекта так, как вы только захотите.

■ __ reduce__ (self)
Если определён собственный тип (с помощью Python's C API), надо сообщить python как его
сериализовать, если нужно, чтобы он его сериализовал. __ reduce__ () вызывается когда
сериализуется объект, в котором этот метод был определён. Он должен вернуть или строку,
содержащую имя глобальной переменной, содержимое которой сериализуется как обычно, или
кортеж. Кортеж может содержать от 2 до 5 элементов: вызываемый объект, который будет
вызван, чтобы создать десериализованный объект, кортеж аргументов для этого вызываемого

164

объекта, данные, которые будут переданы в __ setstate__ (опционально), итератор списка
элементов для сериализации (опционально) и итератор словаря элементов для сериализации
(опционально).

■ __ reduce_ex__ (self, protocol)
Иногда полезно знать версию протокола, реализуя __ reduce__ . И этого можно добиться,
реализовав вместо него __ reduce_ex__ . Если __ reduce_ex__ реализован, то предпочтение при
вызове отдаётся ему (при этом всё равно нужно реализовать __ reduce__ для обратной
совместимости).

■ Пример
Для примера описывается грифельная доска (Slate), которая запоминает что и когда было на ней
записано. Впрочем, конкретно эта доска становится чистой каждый раз, когда она сериализуется:
текущее значение не сохраняется.

import time
class Slate:
'''Класс, хранящий строку и лог изменений. И забывающий своё значение
после
сериализации.'''
def

init
(self, value):
self.value = value
self.last change = time.asctime()
self.history = {}

def change(self, new value):
# Изменить значение. Зафиксировать последнее значение в истории.
self.history[self.last change] = self.value
self.value = new value
self.last change = time.asctime()
def print changes(self):
print 'Changelog for Slate object:'
for k, v in self.history.items():
print '%s\t %s' % (k, v)
def

getstate (self):
# Намеренно не возвращаем self.value or self.last change.
# Мы хотим "чистую доску" последесериализации.
return self.history

def

setstate (self, state):
self.history = state
self.value, self.last change = None, None

■ Дополнение 1: Как вызывать магические методы
Некоторые из магических методов напрямую связаны со встроенными функциями; в этом случае
совершенно очевидно как их вызывать. Однако, так бывает не всегда. Это дополнение посвящено
тому, чтобы раскрыть неочевидный синтаксис, приводящий к вызову магических методов.

165

Магический метод

Когда вызывается (пример)

__ new__ (cls [,...])

instance = MyClass(arg1, arg2) __ new__ вызывается при создании
объекта

__ init__ (self [,...])

instance = MyClass(arg1, arg2) __ init__ вызывается при создании
объекта

__ cmp__ (self, other)

self == other, self > other, etc.

Вызывается для любого сравнения

__ pos__ (self)

+self

Унарный знак плюса

__ neg__ (self)

-self

Унарный знак минуса

__ invert__ (self)

~self

Побитовая инверсия

__ index__ (self)

x[self]

Преобразование,
когда
используется как индекс

__ nonzero__ (self)

bool(self), if self:

Булевое значение объекта

__ getattr__ (self, name)

self.name
#
определено

__ setattr__ (self, name, val)

self.name = val

Присвоение любому атрибуту

__ delattr__ (self, name)

del self.name

Удаление атрибута

__ getattribute__ (self, name)

self.name

Получить любой атрибут

__ getitem__ (self, key)

self[key]

Получение элемента через индекс

__ setitem__ (self, key, val)

self[key] = val

Присвоение элементу через индекс

__ delitem__ (self, key)

del self[key]

Удаление элемента через индекс

__ iter__ (self)

for x in self

Итерация

__ contains__ (self, value)

value in self, value not in self

Проверка
помощью in

__ call__ (self [,...])

self(args)

«Вызов» объекта

__ enter__ (self)

with self as x:

with
оператор
контекста

менеджеров

__ exit__ (self, exc, val, trace)

with self as x:

with
оператор
контекста

менеджеров

__ getstate__ (self)

pickle.dump(pkl_file, self)

Сериализация

name

166

Объяснение

объект

не Пытаются получить несуществующий
атрибут

принадлежности

с

setstate__ (self)

data = pickle.load(pkl_file)

Сериализация

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

Следующие разделы про магические методы основываются на статьях Сергея Балакирева

■ Магический метод __call
После объявления любого класса:

class Counter:
def
init
(self):
self. counter = 0

Можно создавать его объекты с помощью оператора:
c = Counter()

После имени класса - круглые скобки. В общем случае - это оператор вызова. Так, например,
можно вызываются функции. Но так можно вызывать и классы. Когда происходит вызов класса, то
автоматически запускается магический метод__ call__ и в данном случае он создает новый объект
— представитель (экземпляр) этого класса.
c = Counter()
__ call__ (self, *args, **kwargs):
obj = self.__ new__ (self, *args, **kwargs)
self.__ init__ (self, *args, **kwargs)
return obj

Это упрощенная схема реализации метода__ call__ . Всё в действительности несколько сложнее,
но принцип тот же: сначала вызывается магический метод__ new__ для создания самого объекта
в памяти, а затем, метод
init
- для инициализации этого объекта. То есть, класс можно
вызывать подобно функции благодаря встроенной для него реализации магического метода
_ _ call__.
А вот объекты (экземпляры классов) так вызывать уже нельзя. То есть, при попытке выполнения
оператора
c()
, то возникнет ошибка: «TypeError: 'Counter' object is not callable».
Эта ситуация может быть исправлена. Для этого в классе Counter надо явно прописать магический
метод__ call__ , например, так:
class Counter:

167

def

init
(self):
self. counter = 0

def

call
(self, *args, **kwargs):
print(' call
is here')
self. counter += 1
return self. counter

Здесь выводится сообщение, что был вызван метод__ call__ , затем увеличивается счетчик counter
для текущего объекта на 1 и возвращается.
Если снова запустить этот код, никаких ошибок не будет, а в консоли отобразится строка
«__ call__ », что означает вызов и выполнение магического метода __ call__ . То есть, благодаря
добавлению этого магического метода в класс Counter, можно вызывать его экземпляры
(обращаться к ним) подобно функциям через оператор круглые скобки. Классы, объекты которых
можно вызывать подобно функциям, называются функторами.
В данном случае метод __ call__ возвращает значение счетчика, поэтому с объектом можно
работать, следующим образом:
c = Counter()
c()
c()
res = c()
print(f'{res}')
Здесь три раза был вызван метод__ call__ и счетчик___ counter трижды увеличился на единицу.
Поэтому в консоли видно значение 3. Если создать еще один объект-счетчик:
c0 = Counter()
c1 = Counter()
c0()
c0()
res0 = c0()
c1()
c1()
res1 = c1()
print(f'{res0}, {res1}')
То они будут работать совершенно независимо и подсчитывать число собственных вызовов.
В определении метода__ call__ записаны параметры *args, **kwargs. Это значит, что при вызове
объектов можно передавать им произвольное количество аргументов. Например, в данном
случае можно указать значение изменения счетчика при текущем вызове. Для этого метод
__ call__ можно переписать , следующим образом:
def

call
(self, step = 1, *args, **kwargs):
print(' call
is here')
self. counter += step
return self. counter

Здесь появился в явном виде первый параметр step с начальным значением 1. То есть, можно
вызывать объекты, например, так:
c = Counter()
c(2)
c(10)
res = c(-5)

168

print(f'{res}')

Вот общий принцип работы магического метода __ call__ . Но здесь остается, как всегда, один
важный вопрос: зачем это нужно и где может пригодиться? Несколько примеров его
использования.

■ Класс с методом _call_ вместо замыканий функций
Использование класса с методом __ call__ вместо замыканий функций. Можно объявить класс
StripChars, который бы удалял вначале и в конце строки заданные символы
II м II

Использование класса с методом
call
вместо замыканий функций.
Можно объявить класс StripChars, который бы удалял вначале и в конце
строки заданные символы
II II II

class StripChars:
def init
(self, chars):
self. chars = chars
def

call
(self, *args, **kwargs):
if not isinstance(args[0], str):
raise ValueError("Аргумент должен быть строкой")
return args[0].strip(self.

chars)

# Для этого в инициализаторе сохраняется строка
chars - удаляемые символы,
# а затем, при вызове метода
call
символы удаляются через строковый метод
# strip для символов
chars. Таким образом, теперь можно создать объект
# (экземпляр) класса и указать те символы, которые следует убирать
si = StripChars("?:!.; ")
# А затем вызвать объект si подобно функции
res = s1(" Hello World! ")
print(res)

В результате объект s1 будет отвечать за удаление указанных символов в начале и конце строки.
Но ничего не мешает определять другие объекты этого класса с другим набором символов:
si = StripChars("?:!.; ")
s2 = StripChars(" ")
res = s1(" Hello World! ")
res2 = s2(" Hello World! ")
print(res, res2, sep='\n')

■ Реализация декораторов с помощью классов
Второй пример - это реализация декораторов с помощью классов. Ранее создавались декораторы
для вычисления значения производной функции в определенной точке х. Далее - подобная
реализация, но с использованием класса.
Сначала — объявление класса:
class Derivate:
def
init
(self, func):
self. fn = func

169

def

call
(self, x, dx=0.0001, *args, **kwargs):
return (self. fn(x + dx) - self. fn(x)) / dx

Здесь в инициализаторе сохраняется ссылка на декорируемую функцию, а в методе __ call__
принимается один обязательный параметр x - точку, где вычисляется производная и dx - шаг
изменения при вычислении производной. Далее, определяется функция, например, просто синус:
def df sin(x):
return math.sin(x)

и ее вызов пока без декорирования:
print(df sin(math.pi/4))

После запуска программы увидим значение примерно 0.7071. Теперь добавляется декоратор. Это
можно сделать двумя способами. Первый, прописать все в явном виде:
Df sin = Derivate(df sin)
Теперь Df_sin - это объект класса Derivate, а не исходная функция. Поэтому, когда она будет
вызываться, то запустится метод__ call__ и вычислится значение производной в точке math.pi/4.
print(Df sin(math.pi/4))
Второй способ - это воспользоваться оператором @ перед объявлением функции:
@Derivate
def dDf sin(x):
return math.sin(x)
print(dDf sin(math.pi/4))
Получается абсолютно тот же самый результат. Вот принцип создания декораторов функций на
основе классов. Все достаточно просто (!!!) - запоминается ссылка на функцию, а затем,
расширяется ее функционал в магическом методе _ _ call__.
В реальных задачах, проектах, нужно принимать решения о том, какие механизмы, приемы
следует применять для решения конкретных задач. Привести алгоритмы решений на все случаи
жизни просто невозможно - это уже навык алгоритмизации, которым должен владеть каждый
программист. И вырабатывается он, в основном, на решении практических задач. Так что надо
больше практиковаться параллельно с изучением возможностей языка python.

■ Магические методы _str_, _repr_, _len_, __abs__
Каждый магический метод автоматически срабатывает в определенный момент времени,
например:
■ __ str__ () - магический метод для отображения информации об объекте класса для
пользователей (например, для функций print, str);
■ __ repr__ () - магический метод для отображения информации об объекте класса в режиме
отладки (для разработчиков).

Для описания работы этих методов, объявдяется класс для описания собак:
class Dog:
def
init

(self, name):

170

self.name

name

dog = Dog('MyxTap')
print(dog.name)
# имя собачки
print(dog)
# служебная информация об объекте dog

Если нужно эту информацию переопределить и отобразить в другом виде (формате), то, как раз
для этого и используются магические методы__ str__ и ___ repr__ . Для начала - переопределение
метода__ repr__ , который должен отразится на выводе служебной информации о классе.
def

repr (self):
return f"{self.

class

}: {self.name}"

Этот метод должен возвращать строку, поэтому здесь записан оператор return и формируемая
строка. Что именно возвращать — это решение программиста. В данном случае - это название
класса и имя собаки.
При переопределении класса Dog и создании объекта-представителя этого класса видна другая
информация при его выводе:
class Dog:
def

init
(self, name):
self.name = name

def

repr (self):
return f'{self.

class

} : {self.name}'

dog = Dog('Мухтар')
print(dog)
Это как раз то, что было определено в магическом методе__ repr__ . То же самое можно увидеть и
при использовании функции print и str. Здесь должен отрабатывать магический метод__ str__ , но
так как он еще не переопределен, то автоматически выполняется метод__ repr__ .
Добавить второй магический метод__ str__ , что бы было видно, как это повлияет на отображение
данных:
class Dog:
def init (self, name):
self.name = name
def

def

repr (self):
print(' repr ')
return f'{self. class

} : {self.name}'

str
(self):
print(' str ')
return f'{self. class

} = {self.name}'

dog = Dog('Мухтар')
print(dog)

171

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

■ Методы __len__ и __abs__
Следующие два магических метода:



len
abs

() - позволяет применять функцию len() к объектам класса;
() - позволяет применять функцию abs() к объектам класса.

Их использование достаточно простое и очевидное. Для примера можно объявить класс Point,
который может хранить произвольный вектор координат точек:

class Point:
def
init
(self, *args):
self. coords = args
p = Point(1, 2)
# print(len(p))
# print(len(p. coords))
А далее (по программе) было бы желательно определять размерность координат с помощью
функции len(), следующим образом:
p = Point(1, 2)
print(len(p))
Сейчас это невыполнимо, так как функция len не применима к экземплярам классов по
умолчанию. Чтобы изменить это поведение, можно переопределить магический метод__ len__ () и
в данном случае это можно сделать так:

def

len (self):
return len(self.

class Point:
def
init
self.
def

coords)

(self, *args):
coords = args

len (self):
return len(self.

coords)

p = Point(1, 2)
print(len(p))
Здесь возвращается размер списка__ coords и если после этого запустить программу, то как раз
это значение и будет выведено в консоль. То есть, магический метод__ len__ указал, что нужно
возвращать, в момент применения функции len() к экземпляру класса.

172

Следующий магический метод__ abs__ работает аналогичным образом, только активируется в
момент вызова функции abs для экземпляра класса, например, так:

print(abs(p))

Опять же, если сейчас выполнить программу, то увидим ошибку, т.к. функция abs не может быть
напрямую применена к экземпляру. Но, если переопределить магический метод:
def

abs
(self):
return list( map(abs, self.

coords) )

который возвращает список из абсолютных значений координат точки, то программа отработает в
штатном режиме и с ожидаемым результатом.
class Point:
def
init
self.
def

def

(self, *args):
coords = args

len
(self):
return len(self.

coords)

abs
(self):
return list(map(abs, self.

coords) )

p = Point(1, 2)
print(len(p))
print(abs(p))

■ Магические методы _add_, _sub_, _mul_, __truediv
Это методы для работы с арифметическими операторами:






__ add__ () - для операции сложения;
__ sub__ () - для операции вычитания;
__ mul__ () - для операции умножения;
__ truediv__ () - для операции деления.

Объяснить работу этих методов проще всего на конкретном примере. Предположим, что
требуется определить класс для работы со временем. Его экземпляры (объекты) будут хранить
часы, минуты и секунды текущего дня. Начальной точкой отсчета будет 00:00 часов ночи. Время
будет храниться в виде секунд с максимальным значением 86400 - число секунд в одном дне.
Поэтому перед присвоением в инициализаторе будем применяться остаток от деления на это
значение:

class Clock:
DAY = 86400
def

# число секунд в одном дне

init
(self, seconds: int):
if not isinstance(seconds, int):

173

raise ТуреЕггог("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY
Здесь важно, как записан параметр seconds. После него стоит двоеточие и указан ожидаемый тип
данных. Эта нотация языка Python подсказывает программисту, какой тип данных следует
передавать в качестве seconds. Конечно, можно передавать и другие типы данных, строгого
ограничения здесь нет, это лишь пометка для программиста и не более того. Поэтому далее
внутри инициализатора делается проверка, что параметр seconds должен являться целым числом.
Если это не так, генерируется исключение TypeError.
Далее в этом же классе определяется метод get_time для получения текущего времени в виде
форматной строки:

def get time(self):
s = self.seconds % 60
# секунды
m = (self.seconds // 60) % 60
# минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self. get formatted(h)}:{self. get formatted(m)}:
{self. get formatted(s)}"

@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
Здесь дополнительно определен метод класса для форматирования времени (добавляется
незначащий первый ноль, если число меньше 10).
Далее, можно применить этот класс, например, так:

c1 = Clock(1000)
print(c1.get time())

Если понадобится изменить время в объекте c1, то сейчас это можно сделать через локальное
свойство seconds:

c1.seconds = c1.seconds + 100

class Clock:
DAY = 86400
def

# число секунд в одном дне

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY

def get
s =
m =
h =

time(self):
self.seconds % 60 # секунды
(self.seconds // 60) % 60 # минуты
(self.seconds // 3600) % 24 # часы

174

{self.

return f"{self. get formatted(h)}: {self.
get formatted(s)}"

get formatted(m)}:

@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
cl = Clock(1000)
print(c1.get time())
cl.seconds = cl.seconds + 100
print(c1.get time())
Было добавлено 100 секунд. Но если бы это изменение можно было бы прописать вот так:
c1 = c1 + 100
то было бы очень хорошо.

Конечно, при запуске программы возникнет ошибка, так как оператор сложения не работает с
экземплярами класса Clock. Однако, это можно поправить, если добавить в этот класс магический
метод__ add__ . Его можно записать в следующем виде:

def

add (self, other):
if not isinstance(other, int):
raise ArithmeticError("Правый операнд должен быть типом int")
return Clock(self.seconds + other)

И теперь, при запуске программы, все работает так, как и задумывалось...

class Clock:
DAY = 86400

# число секунд в одном дне

def

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY

def

add (self, other):
if not isinstance(other, int):
raise ArithmeticError("Правый операнд должен быть типом int")
return Clock(self.seconds + other)

def get time(self):
s = self.seconds % 60 # секунды
m = (self.seconds // 60) % 60 # минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self. get formatted(h)}: {self.
{self. get formatted(s)}"
@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
c1 = Clock(1000)
print(c1.get time())

175

get formatted(m)}:

cl.seconds = cl.seconds + 100
print(c1.get time())
c1 = c1 + 100
print(c1.get time())
Вначале есть объект класса Clock со значением секунд 1000. Затем, арифметическая операция
c l = c1 + 100 фактически означает выполнение команды:

с1 = cl.

add

(100)

В результате активируется м етод__ add__ и параметр other принимает целочисленное значение
100. Проверка проходит и формируется новый объект класса Clock со значением секунд 1000+100
= 1100. Этот объект возвращается методом__ add__ и переменная c1 начинает ссылаться на этот
новый экземпляр класса. На прежний уже не будет никаких внешних ссылок, поэтому он будет
автоматически удален сборщиком мусора.
Сложность процессов, когда всего лишь нужно прибавить 100 секунд к уже имеющемуся
значению может удивить. Но эта сложность оправданна. Чтобы это понять, нужно расширить
функционал оператора сложения и допустить, что можно складывать два разных объекта класса
Clock, следующим образом:
cl = Clock(1000)
c2 = Clock(2000)
c3 = cl + c2
print(c3.get time())
Конечно, если сейчас запустить программу, то возбудится исключение ArithmeticError, так как
параметр other не соответствует целому числу. Это можно поправить и немного изменить
реализацию метода__ add__ :

def

add (self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Правый операнд должен быть типом int или
объектом Clock")
sc = other if isinstance(other, int) else other.seconds
return Clock(self.seconds + sc)
Теперь в программе можно складывать и отдельные целые числа и объекты классов Clock. И это
удобно. Кроме того, можно прописывать и более сложные конструкции при сложении, например,
такие:
class Clock:
DAY = 86400

# число секунд в одном дне

def

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY

def

add (self, other):
if not isinstance(other,

(int, Clock)):

176

raise ArithmeticError("npaBbrn операнд должен быть типом int или
объектом Clock")
sc = other if isinstance(other, int) else other.seconds
return Clock(self.seconds + sc)
def get time(self):
s = self.seconds % 60 # секунды
m = (self.seconds // 60) % 60 # минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self. get formatted(h)}: {self.
{self. get formatted(s)}"

get formatted(m)}:

@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
c1 = Clock(1000)
print(c1.get time())
c1.seconds = c1.seconds + 100
print(c1.get time())
c1 = c1 + 100
print(c1.get time())
c2 = Clock(1000)
c3 = Clock(2000)
c4 = Clock(3000)
c5 = c2 + c3 + c4
print(c5.get time())
И она сработала благодаря тому, что м ето д__ add__ возвращает каждый раз новый экземпляр
класса Clock. Детальнее все выглядит так:
Сначала идет сложение объектов c1 + c2, в результате формируется новый объект класса Clock со
значением секунд 1000 + 2000 = 3000. Пусть на этот класс ведет внутренняя переменная t1. Затем,
для этого нового объекта вызывается снова м етод__ add__ и идет сложение с объектом t1 + c3.
Получаем еще один объект с числом секунд 6000. На этот объект, как раз и будет ссылаться
переменная c4, а объект с t1 будет автоматически уничтожен сборщиком мусора.
Если бы не создавались экземпляры классов Clock в методе __ add__ и не возвращались, то
конструкцию с двумя сложениями было бы невозможно реализовать.
Еще одним важным нюансом работы оператора сложения для объектов классов, является
порядок их записи. Мы всегда прописывали его в виде:
c1 = c1 + 100
то есть, сначала шел объект, а затем, число. Если записать наоборот, то возникнет ошибка:
c1 = 100 + c1
и это очевидно, так как здесь, фактически идет вызов метода:
100.__add__(c1)
но он не существует для объекта int и экземпляров класса Clock. Как выйти из этой ситуации?
Очень просто. Язык python предоставляет специальный набор магических методов с добавлением
буквы r:
radd

()

177

Он автоматически вызывается, если не может быть вызван м е то д __ add__ (). Его определение
можно добавить в класс Clock:
def

radd (self, other):
return self + other

Здесь записана команда сложения текущего объекта класса Clock с параметром other, который
может быть или числом или тоже объектом класса Clock. В свою очередь будет вызван метод
__ add__ , но с правильным порядком типов данных, поэтому сложение пройдет без ошибок.
class Clock:
DAY = 86400
def

# число секунд в одном дне

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY

def

add (self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Правый операнд должен быть типом int или
объектом Clock")
sc = other if isinstance(other, int) else other.seconds
return Clock(self.seconds + sc)
def

radd (self, other):
return self + other

def get time(self):
s = self.seconds % 60 # секунды
m = (self.seconds // 60) % 60 # минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self. get formatted(h)}: {self.
{self. get formatted(s)}"

get formatted(m)}:

@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
c1 = Clock(1000)
print(c1.get time())
c1.seconds = c1.seconds + 100
print(c1.get time())
c1 = c1 + 100
print(c1.get time())
c1 = 100 + c1
print(c1.get time())
c2 = Clock(1000)
c3 = Clock(2000)
c4 = Clock(3000)
c5 = c2 + c3 + c4
print(c5.get time())
Наконец, у всех магических методов, связанных с арифметическими операторами, есть еще одна
модификация с первой буквой i:
iadd

()

178

Она вызывается для команды:
cl += 100
Если запустить сейчас программу, то никаких ошибок не будет и отработает метод__ add__ (). Но в
методе__ add__ создается новый объект класса Clock, тогда как при операции += этого делать не
обязательно. Поэтому в класс Clock надо добавить еще один магический метод__ iadd__ :

def

iadd (self, other):
print("__iadd__”)
if not isinstance(other, (int, Clock)):
raise ArithmeticError("Правый операнд должен быть типом int или
объектом Clock”)
sc = other if isinstance(other, int) else other.seconds
self.seconds += sc
return self
Здесь не создаётся нового объекта, а меняется число секунд в текущем. Это логичнее, так как
вызывать цепочкой операцию += не предполагается и, кроме того, она изменяет (по смыслу)
состояние текущего объекта. Поэтому и добавляется этот магический метод.
class Clock:
DAY = 86400
def

# число секунд в одном дне

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError(,,Секунды должны быть целым числом”)
self.seconds = seconds % self. DAY

def

add (self, other):
if not isinstance(other, (int, Clock)):
raise ArithmeticError(”npaBbm операнд должен быть типом int или
объектом Clock”)
sc = other if isinstance(other, int) else other.seconds
return Clock(self.seconds + sc)
def

radd (self, other):
return self + other

def

iadd (self, other):
print(”__iadd__”)
if not isinstance(other, (int, Clock)):
raise ArithmeticError(”npaBbm операнд должен быть типом int или
объектом Clock”)
sc = other if isinstance(other, int) else other.seconds
self.seconds += sc
return self
def get
s =
m =
h =

time(self):
self.seconds % 60 # секунды
(self.seconds // 60) % 60 # минуты
(self.seconds // 3600) % 24 # часы

179

return f"{self. get formatted(h)}: {self.
get formatted(s)}"

{self.

get formatted(m)}:

@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
cl = Clock(1000)
print(c1.get time())
cl.seconds = cl.seconds + 100
print(c1.get time())
c1 = c1 + 100
print(c1.get time())
c1 = 100 + c1
print(c1.get time())
c1 += 100
print(c1.get time())
c2 = Clock(1000)
c3 = Clock(2000)
c4 = Clock(3000)
c5 = c2 + c3 + c4
print(c5.get time())
c1 += c2
print(c1.get time())
Вот и была подробно рассмотрена работа одного арифметического магического метода__ add__ ()
с его вариациями__ radd__ () и __ iadd__ (). По аналогии используются и все остальные подобные
магические методы:
Метод оператора

x +у

__ add__ (self, other)

x += у

__ iadd__ (self, other)

x —у

__ sub__ (self, other)

x -= у

__ isub__ (self, other)

x*у

__ mul__ (self, other)

x *= у

__ imul__ (self, other)

x/ у

__ truediv__ (self, other)

x /= у

__ itruediv__ (self, other)

x // у

__ floordiv__ (self, other)

x //= у

__ ifloordiv__ (self, other)

x%у

__ mod__ (self, other)

<

Оператор

X

Метод оператора

чр
ON
II

Оператор

__ imod__ (self, other)

■ Методы сравнений _eq_, _ n e _ ,_lt_, __gt__ и другие
Магические методы для реализации операторов сравнения:

eq___() - для равенства ==

ne__ () - для неравенства !=
■ __ It__ () - для оператора меньше <

le___() - для оператора меньше или равно

ge___() - для оператора больше или равно >=

180

Работа этих методов будет рассматриваться на примере ранее определённого класса Clock:
class Clock:
DAY = 86400
def

# число секунд в одном дне

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY

def get time(self):
s = self.seconds % 60
# секунды
m = (self.seconds // 60) % 60
# минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self. get formatted(h)}:{self. get formatted(m)}:
{self. get formatted(s)}"
@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
Изначально для класса реализован только один метод сравнения на равенство, например:
c1 = Clock(1000)
c2 = Clock(1000)
print(c1 == c2)
Но здесь объекты сравниваются по их id (адресу в памяти), а хотелось, чтобы сравнивались
секунды в каждом из объектов c1 и c2. Для этого следующим образом переопределяется
магический метод__ eq__ ():
def

eq (self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")
sc = other if isinstance(other, int) else other.seconds
return self.seconds == sc

Теперь, после запуска программы видим значение True, т.к. объекты содержат одинаковое время.
class Clock:
DAY = 86400
def

# число секунд в одном дне

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY

def get time(self):
s = self.seconds % 60 # секунды
m = (self.seconds // 60) % 60 # минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self. get formatted(h)}:{self.
{self. getformatted(s)}"
def

get formatted(m)}:

eq (self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")

181

sc = other if isinstance(other, int) else other.seconds
return self.seconds == sc
@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")
cl = Clock(1000)
c2 = Clock(1000)
print(c1 == c2)
Кроме того, можно выполнять проверку и на неравенство:
print(c1 != c2)
Если интерпретатор языка Python НЕ находит определение метода ==, то он пытается выполнить
противоположное сравнение с последующей инверсией результата. То есть, в данном случае
находится оператор == и выполняется инверсия:

not (a == b)
В этом можно убедиться, поставив точку останова в м е то д __ eq__ и запустить программу. Он
срабатывает и результат в последствии меняется на противоположный.
Объекты класса Clock, сравниваются, на равенство и неравенство, а также с целыми числами.
Однако, сравнение на больше или меньше пока не работает. Строчка программы:

print(c1 < c2)
приведет к ошибке. Поэтому надо добавить эту операцию сравнения:
def

lt (self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")
sc = other if isinstance(other, int) else other.seconds
return self.seconds < sc

Как здесь получается дублирование кода. Поэтому нужно, вынести общее для методов сравнения
в отдельный метода класса:
@classmethod
def
verify data(cls, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")
return other if isinstance(other, int) else other.seconds
А сами методы примут вид:
def

eq (self, other):
sc = self. verify data(other)
return self.seconds == sc

182

def

lt (self, other):
sc = self. verify data(other)
return self.seconds < sc

Итак, мы определили сравнение на равенство и меньше. Теперь, можно сравнивать объекты
класса Clock на эти операции и дополнительно на неравенство и больше. Сейчас команда:
class Clock:
DAY = 86400
def

# число секунд в одном дне

init
(self, seconds: int):
if not isinstance(seconds, int):
raise TypeError("Секунды должны быть целым числом")
self.seconds = seconds % self. DAY

def get time(self):
s = self.seconds % 60 # секунды
m = (self.seconds // 60) % 60 # минуты
h = (self.seconds // 3600) % 24 # часы
return f"{self. get formatted(h)}:{self.
{self. get formatted(s)}"

get formatted(m)}:

@classmethod
def
verify data(cls, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или Clock")
return other if isinstance(other, int) else other.seconds
def

eq (self, other):
sc = self. verify data(other)
return self.seconds == sc

def

lt (self, other):
sc = self. verify data(other)
return self.seconds < sc

# def
#
#
Clock")
#
#
#
#
# def
#
#
Clock")
#
#
#

eq (self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или

sc = other if isinstance(other, int) else other.seconds
return self.seconds == sc
lt (self, other):
if not isinstance(other, (int, Clock)):
raise TypeError("Операнд справа должен иметь тип int или

sc = other if isinstance(other, int) else other.seconds
return self.seconds < sc

@classmethod
def
get formatted(cls, x):
return str(x).rjust(2, "0")

183

cl = Clock(1000)
c2 = Clock(1000)
print(c1 == c2)
cl = Clock(1000)
c2 = Clock(2000)
print(c1 < c2)
Выдаст True, так как первое время меньше второго. И также можно делать проверку на больше:

print(c1 > c2)
Здесь сработает тот же метод меньше, но для объекта c2:

c2 < cl
То есть, в отличие от оператора ==, где применяется инверсия, здесь меняется порядок
операндов. Но при этом надо определить в классе метод больше:
def

gt (self, other):
sc = self. verify data(other)
return self.seconds > sc

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

le (self, other):
sc = self. verify data(other)
return self.seconds ',
#
connectionstyle='arc3, rad= .2')
#
)
ax.annotate(r'$2x+1=%s$'% y0,
xy=(_x0, _y0),
xycoords='data',
xytext=(+30,-30),
textcoords='offset points',
fontsize=16,
arrowprops=dict(arrowstyle='->',
connectionstyle='arc3, rad= .2')
)
# method 2
plt.text(x,y,s,fontdict,)
x, y: где разместить текст
s: str текстовое содержимое.
fontdict: словарь, используемый для переопределения атрибутов текста
по умолчанию (пользовательский стиль текста)
I I I I II

# ===================================================================
plt.text(-3.7,
5,
r'$This\ is\ the\ some\ text.\ \mu\ \sigma i\ \alpha t$',
fontdict={'size':16,'color':'r'})
plt.show()

296

■ Прозрачность ли нии
Координаты иногда скрыты линиями графика — установить прозрачность линии, чтобы они
отображалась.
# Координаты иногда скрыты линиями графика — установить
# прозрачность линии, чтобы они отображалась.
import matplotlib.pyplot as plt
import numpy as np
# ==============================================================
def get data 0(start, stop, shape):
# В указанном интервале [start, stop] возвращает множество чисел
# с равным интервалом в количестве shape.
x=np.linspace(start, stop, shape)
# (-3,3,50)
y=0.1*x
return(x, y)
# ==============================================================
x, y = get data 0(-3, 3, 50)
plt.figure()
plt.ylim(-2, 2)
ax=plt.gca()
#alpha: коэффициент прозрачности
ax.plot(x, y, linewidth=10, color='green', alpha=0.5)
ax.spines['right'].set color('none')
ax.spines['top'].set color('none')
ax.xaxis.set ticks position('bottom')
ax.spines['bottom'].set position(('data', 0))
ax.yaxis.set ticks position('left')
ax.spines['left'].set position(('data', 0))
ax.grid()
# Изменить метки (координаты) осей x и y,
# установить размер шрифта 12,
# метки поместить в рамки (красные с зелёной каёмочкой, полупрозрачные)
for label in ax.get xticklabels() + ax.get yticklabels():
label.set fontsize(12)
#pass
label.set bbox(dict(facecolor='red',
edgecolor='green',
#edgecolor='None',
alpha=0.5)
)
plt.show()

■ Точечная диаграм м а
# Точечная диаграмма ====================================================
import matplotlib.pyplot as plt
import numpy as np
n=1024
X=np.random.normal(0,1,n)
Y=np.random.normal(0,1,n)
T=np.arctan2(Y,X)
#for color value
IIMII
np.arange()
Для параметра значение параметра - это конечная точка,
начальная точка - значение по умолчанию 0,
а длина шага - значение по умолчанию 1.
В случае двух параметров первый параметр является начальной точкой,
второй параметр - конечной точкой, длина шага - значением по умолчанию 1.
При наличии трех параметров первый параметр является начальной точкой,
второй параметр - конечной точкой, а третий параметр - длиной шага.
Если размер шага поддерживает десятичные дроби (?)

297

I I I I II

plt.scatter(X,Y,s=75,c=T,alpha=0.5)
# plt.scatter Настроить точечную диаграмму.
#plt.scatter(np.arange(5),np.arange(5))
#plt.xlim((-1.5,1.5))
#plt.ylim((-1.5,1.5))
# Не передавать параметры, удалить периферийные координаты
# plt.xticks(())
# plt.yticks(())
plt.grid(True)
plt.show()

■ Барная гистограм м а
Барная гистограмма случайная выборка из равномерного распределения [низкий, высокий),
важно, что домен является закрытым слева и открытым справа, то есть содержит низкий, а не
высокий (?), n - количество точек выборки.
# Барная гистограмма =====================================================
import matplotlib.pyplot as plt
import numpy as np
def get data(n):
n = n
X = np.arange(12)
# 1/float(2)=0.5
# np.random.uniform: Случайная выборка из равномерного распределения
# [низкий, высокий), важно, что домен является закрытым слева
# и открытым справа, то есть содержит низкий, а не высокий,
# n - количество точек выборки
Y1 = (1 - X / float(n)) * np.random.uniform(0.5, 1.0, n)
Y2 = (1 - X / float(n)) * np.random.uniform(0.5, 1.0, n)
return(X, Y1, Y2)
X, Y1, Y2 = get data(12)
# bar: Репрезентативная гистограмма
ax = plt.gca()
# plt.bar(X, +Y1, facecolor='red', edgecolor='white')
# plt.bar(X, -Y2, facecolor='blue', edgecolor='white')
ax.bar(X, +Y1, facecolor='red', edgecolor='white')
ax.bar(X, -Y2, facecolor='blue', edgecolor='white')
# zip: x, y выражаются в единицах кортежей,
# а значения последовательно берутся в массивах X и Y.
for x, y in zip(X, Y1):
# ha:horizontal alignment
# plt.text(x + 0.4, y + 0.05, '%.2f' % y, ha='center', va='bottom')
ax.text(x + 0.4, y + 0.05, '%.2f' % y, ha='center', va='bottom')
for x, y in zip(X, Y2):
# ha:horizontal alignment
# plt.text(x + 0.4, -y - 0.05, '%.2f' % y, ha='center', va='top')
ax.text(x + 0.4, -y - 0.05, '%.2f' % y, ha='center', va='top')
# plt.xticks(())
# plt.yticks(())
ax.grid()
plt.show()

■ Диаграмма вы сокого и низкого потенциала

Диаграмма высокого и низкого потенциала энергии
298

np.linspace (x, y, n): делить поровну на n как общее
np.arange (x, y, n): делить поровну на n как размер шага
I I II II

import matplotlib.pyplot as plt
import numpy as np
# ======================================================
def f(x,y,r1,r2):
#b = (1-x/2+x**5+y**3)*np.exp(-x**2-y**2)
b = (r2-x/r1+x**5+y**3)*np.exp(-x**2-y**2)
#the height function
return b
# ======================================================
n = 256
x = np.linspace(-3,3,n)
y = np.linspace(-3,3,n)
#meshgrid: Сетка
II II II

np.meshgrid(x, y) возвращает координаты двумерной сетки
на основе координат, содержащихся
в векторе x и векторе y.
x - вектор [1 2 3], y - вектор [1 2 3 4 5];
Каждая строка матрицы X равна x, то есть [1 2 3],
общая длина (y) = 5 строк;
Каждый столбец матрицы Y равен y, то есть [1 2 3 4 5],
общая длина (x) = 3 столбца.
X =
Y =
1 2 3
1
1
1
1 2 3
2
2
2
1 2 3
3
3
3
1 2 3
4
4
4
1 2 3
5
5
5
IIIIII
X, Y = np.meshgrid(x, y)
# Контур (contour) ==========================
# use plt.contourf to filling contours
# X,Y and value for( X,Y) point
# 8: 8 делений
# cmap = color map contourf: заливка контура.
ax = plt.,gca()
r1 = (np.,random. uniform(3, 9, 1) )
r1=: int(i 1)
r2 = (np.,random. uniform(2, 6, 1) )
r2= int(r2)
# plt.contourf(X,Y,f(X,Y),8,alpha=0.75,cmap=plt.cm.hot)
ax.contourf(X, Y, f(X, Y, r1, r2), 8, alpha=0.75, cmap=plt.cm.hot)
# использовать plt.contour, чтобы добавить контурные линии contour:
# позиционирование линий
# C=plt.contour(X,Y,f(X,Y),8,colors='black',linewidths=0.5)
C = ax.contour(X, Y, f(X, Y, r1, r2), 8, colors='black', linewidths=0.5)
#adding label
#inline: Добавить ярлык онлайн
#plt.clabel(C,inline=True,fontsize=10)
ax.clabel(C, inline=True, fontsize=10)
#plt.grid()
ax.grid()
# plt.xticks(())
# plt.yticks(())
plt.show()

■ Цветовой б лок и аннотация градиентной гистограм м ы
# Цветовой блок и аннотация градиентной гистограммы =========
299

import matplotlib.pyplot as plt
import numpy as np
#image data
# Указать цвет по номеру, передть массив, а затем изменить форму (3,3),
# чтобы изменить форму матрицы 3X3
a=np.array([0.313660827978,0.365348418405,0.423733120134,
0.365348418405,0.439599930621,0.525083754405,
0.423733120134,0.525083754405,0.651536351379]).reshape(3,3)
plt.figure(figsize=(8,5),facecolor='white')
IIIIII
fornthe value of"interpolation",check this:
http://matplotlib.org/examples/images contours and fields/interpolation metho
ds.html
II II II

#cmap:color map;
# cmap = 'bone': установите белый цвет изображения.
#origin отображается относительно начала и конца исходной матрицы 3X3, origin
= 'upper'
#interpolation='nearest'
plt.imshow(a,cmap='bone',origin='lower')
#shrink: определение коэффициента сжатия панели комментариев относительно
цветового блока.
plt.colorbar(shrink=0.9)
plt.grid()
# plt.xticks(())
# plt.yticks(())
plt.show()

■ 3D изображение и отображение плоскостей
Для рисования трехмерных графиков функций (поверхностей) и применения средств настройки
внешнего вида графиков нужны библиотека Matplotlib и математическая библиотека numpy.
Библиотека numpy также позволяет значительно сократить количество строк кода, а некоторые
методы классов рисования трехмерных графиков в качестве параметров ожидают экземпляры
класса numpy.array.
Для трехмерного графика в первую очередь надо создать трехмерные оси. Чтобы создать
трехмерные оси, сначала надо загрузить модуль (модуль ???) Axes3D. Затем с помощью функции
figure() создаётся экземпляр класса matplotlib.figure.Figure.
Дальше у экземпляра этого класса вызывается метод add_subplot(), который может принимать
различное количество параметров, но в данном случае ему передаётся один именованный
параметр - projection, который описывает тип осей. Для создания трехмерных осей значение
параметра projection должно быть строкой "3d". В результате получается экземпляр (объект)
класса осей, с которым можно в дальнейшем работать.
Этот экземпляр принадлежит к классу, производному от Axes3D (он включает класс
matplotlib.axes._subplots.Axes3DSubplot, но этот класс создается динамически в библиотеке, и его
описания нет в документации). С помощью объекта этого класса будут рисоваться графики и
настраиватся их внешний вид.

# И ничего кроме осей! ==================================================
import matplotlib.pyplot as plt
from mpl toolkits.mplot3d import Axes3D
if
name
== ' main ':
fig = plt.figure()
# возможны 2 варианта создания трехмерных осей от экземпляра ========
# класса Figure. Но сначала надо загрузить модуль (модуль ???) Axes3D

300

axes = fig.add subplot(projection='3d')
#axes = Axes3D(fig)
# =====================================
plt.show()
# Полученные оси можно вращать мышкой.

Полученные оси можно вращать мышкой. Также должен быть график. Для этого примера —
функция от двух координат, которая будет рисоваться на осях:
f(x, y) = sin(x) * sin(y) / x*y
Нужно подготовить данные для рисования. Для этого нужны три двумерные матрицы: матрицы X
и Y, которые будут хранить координаты сетки точек, в которых будет вычисляться приведенная
выше функция, а матрица Z будет хранить значения этой функции в соответствующей точке (x,y).
Дальше нужно подготовить прямоугольную сетку на плоскости XY, в узлах которой будут
рассчитаны значения значения по оси Z (значения отображаемой функции). Для создания такой
сетки можно применить функцию numpy.meshgrid(). В простейшем случае эта функция, принимает
несколько одномерных массивов. Здесь нужны ДВА массива для осей X и Y, которые содержат
значения координат узлов вдоль соответствующей оси, и могут иметь разный размер. Эта функция
возвратит две двумерные матрицы, описывающие координаты X и Y на двумерной сетке.
Первая матрица будет создана 'размножением' первого переданного параметра в функцию
numpy.meshgrid() вдоль СТРОК первой возвращаемой матрицы, вторая матрица создается
'размножением' второго переданного одномерного массива вдоль СТОЛБЦОВ второй матрицы.
Пример:

#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

X, Y = numpy.meshgrid([1, 2, 3], [4, 5, 6, 7])
X = array([
[1,2,3],
[1,2,3],
[1,2,3],
[1,2,3]
])
Y = array([
[4,4,4],
[5,5,5],
[6,6,6],
[7,7,7]
])
по индексу узла сетки можно узнать реальные координаты:
X[0][0] = 1, Y[0][0] = 4 и т.д.

Такие двумерные матрицы позволяют легко (???) рассчитывать значения функций от двух
аргументов, используя поэлементные операции (векторизацию). Эти матрицы требуются для
рисования трехмерных поверхностей в Matplotlib.
Функция makeData возвращает три двумерные матрицы: x, y, z.

301

Координаты x и у лежат на отрезке от -10 до 10 с шагом 0.1.
Для задания интервала по осям X и Y использована функци numpy.linspace(), которая создает
одномерный массив со значениями из заданного интервала [-10; 10] с указанным количеством
отсчетов в ней (в данном случае - 100). По умолчанию правый конец интервала также включается
в результат (если правый конец не нужно включать в результат, то можно передать параметр
endpoint, равный False). Для простоты по осям берется четное количество отсчетов, благодаря
этому особая (!) точка с координатами (0, 0) не попадает в создаваемую сетку, поэтому
дополнительная проверка не проводится.

from mpl toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy
from array import *

def makeData(x, y):
xgrid, ygrid = numpy.meshgrid(x, y)
z = numpy.sin(xgrid) * numpy.sin(ygrid) / (xgrid * ygrid)
return xgrid, ygrid, z
# Чтобы отобразить полученные данные, достаточно вызвать =================
# метод plot surface() экземпляра класса Axes3D, в который надо передать
# полученные с помощью функции makeData() двумерные матрицы.
if
name
== ' main ':
# Строится сетка в интервале от -10 до 10, имеющая 100 отсчетов по
# обоим координатам
x = numpy.linspace(-10, 10, 100)
y = numpy.linspace(-10, 10, 100)
X, Y, Z = makeData(x, y)
fig = plt.figure()
axes = Axes3D(fig)
#axes.plot surface(X, Y, Z)
# Шаг сетки
# Есть два инструмента для изменения прореживания данных:
# (rcount, ccount), (rstride, cstride)
# По умолчанию метод plot surface() использует разрежение исходных
# данных, чтобы ускорить отображение. Регулировать степень разрежения
# (или даже отключить его) можно с помощью пар именованных параметров,
# передаваемых в метод plot surface().
# С помощью параметров rcount и ccount можно задать количество
# отсчетов по двум осям (по строкам и по столбцам в исходных данных).
# По умолчанию используются значения rcount = ccount = 50.
# Здесь rcount = ccount = 100.
# Пара параметров rstride и cstride задают степень
# прореженности (децимации) по осям, то есть сколько отсчетов
# надо пропустить.
axes.plot surface(X, Y, Z, rcount = 100, ccount = 100)
plt.show()
# ====================================================================

■ Изменение цвета графика
Можно изменить цвет поверхности с помощью параметра color. Значение этого параметра
представляет собой строку, которая описывает цвет. Строка цвета может задаваться разными
способами.
Цвет можно определить английским словом для соответствующего цвета или одной буквой:
'b' или 'blue'

302

'g' или 'green'
'r' или 'red'
'c' или 'cyan'
'm' или 'magenta'
'y' или 'yellow'
'k' или 'black'
'w' или 'white'
серый цвет, его яркость можно задать с помощью строки, содержащей число в интервале от 0.0 до
1.0 (0 - белый, 1 - черный). Например, можно написать следующую строку:
axes.plot_surface(x, y, z, color='0.7')
Можно задавать цвет так, как это принято в HTML после символа решетки ('#'). Например:
axes.plot_surface(x, y, z, color='#11aa55')

■ Применение ц ветовы х к а р т (colorm ap)
Цветовые карты используются, если нужно указать в какие цвета должны окрашиваться участки
трехмерной поверхности в зависимости от значения Z в этой области (задание цветового
градиента). Это применение градиентов.
Чтобы при выводе графика использовался градиент, в качестве значения параметра cmap (от
colormap - цветовая карта) нужно передать экземпляр класса matplotlib.colors.Colormap или
производного от него.
from matplotlib.colors import LinearSegmentedColormap
from mpl toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy
from array import *
def makeData(x, y):
xgrid, ygrid = numpy.meshgrid(x, y)
z = numpy.sin(xgrid) * numpy.sin(ygrid) / (xgrid * ygrid)
return xgrid, ygrid, z
if
name
== ' main ':
# Строится сетка в интервале от -10 до 10, имеющая 100 отсчетов по
# обоим координатам
x = numpy.linspace(-10, 10, 100)
y = numpy.linspace(-10, 10, 100)
X, Y, Z = makeData(x, y)
fig = plt.figure()
axes = Axes3D(fig)
# !!! для создания цветовой карты используется статический метод
# LinearSegmentedColormap.from list(). Принимает три параметра:
# = Имя создаваемой карты
# = Список цветов, начиная с цвета для минимального значения
#
на графике (голубой - 'b'), через промежуточные цвета
#
(здесь это белый - 'w') к цвету для максимального значения
#
функции (красный - 'r').
# = Количество цветовых переходов. Чем это число больше,
#
тем более плавный градиент,
#
но тем больше памяти он занимает.
cmap = LinearSegmentedColormap.from list('red blue', ['b','w','r'], 256)

303

axes.plot surface(X,Y,Z,color='#11aa55',
cmap=cmap,
rcount = 100,
ccount=100)
plt.show()

■ Список встр оенн ы х ц ветовы х ка р т и и х применение
['Accent', 'Accent_r', 'Blues', 'Blues_r', 'BrBG', 'BrBG_r', 'BuGn',
'BuGn_r', 'BuPu', 'BuPu_r', 'CMRmap', 'CMRmap_r', 'Dark2', 'Dark2_r',
'GnBu', 'GnBu_r', 'Greens', 'Greens_r', 'Greys', 'Greys_r', 'LUTSIZE',
'MutableMapping', 'OrRd', 'OrRd_r', 'Oranges', 'Oranges_r', 'PRGn',
'PRGn_r', 'Paired', 'Paired_r', 'Pastel1', 'Pastel1_r', 'Pastel2',
'Pastel2_r', 'PiYG', 'PiYG_r', 'PuBu', 'PuBuGn', 'PuBuGn_r', 'PuBu_r',
'PuOr', 'PuOr_r', 'PuRd', 'PuRd_r', 'Purples', 'Purples_r', 'RdBu',
'RdBu_r', 'RdGy', 'RdGy_r', 'RdPu', 'RdPu_r', 'RdYlBu', 'RdYlBu_r',
'RdYlGn', 'RdYlGn_r', 'Reds', 'Reds_r', 'ScalarMappable', 'Set1',
'Set1_r', 'Set2', 'Set2_r', 'Set3', 'Set3_r', 'Spectral', 'Spectral_r',
'Wistia', 'Wistia_r', 'YlGn', 'YlGnBu', 'YlGnBu_r', 'YlGn_r', 'YlOrBr',
'YlOrBr_r', 'YlOrRd', 'YlOrRd_r',
'_api', '_cmap_registry', '_gen_cmap_registry', 'afmhot', 'afmhot_r',
'autumn', 'autumn_r', 'binary', 'binary_r', 'bone', 'bone_r', 'brg',
'brg_r', 'bwr', 'bwr_r', 'cbook', 'cividis', 'cividis_r', 'cmap_d',
'cmaps_listed', 'colors', 'cool', 'cool_r', 'coolwarm', 'coolwarm_r',
'copper', 'copper_r', 'cubehelix', 'cubehelix_r', 'datad', 'flag',
'flag_r', 'get_cmap', 'gist_earth', 'gist_earth_r', 'gist_gray',
'gist_gray_r', 'gist_heat', 'gist_heat_r', 'gist_ncar', 'gist_ncar_r',
'gist_rainbow', 'gist_rainbow_r', 'gist_stern', 'gist_stern_r',
'gist_yarg', 'gist_yarg_r', 'gnuplot', 'gnuplot2', 'gnuplot2_r',
'gnuplot_r', 'gray', 'gray_r', 'hot', 'hot_r', 'hsv', 'hsv_r',
'inferno', 'inferno_r', 'jet', 'jet_r', 'ma', 'magma', 'magma_r', 'mpl',
'nipy_spectral', 'nipy_spectral_r', 'np', 'ocean', 'ocean_r', 'pink',
'pink_r', 'plasma', 'plasma_r', 'prism', 'prism_r', 'rainbow',

304

'rainbow_r', 'register_cmap', 'seismic', 'seismic_r', 'spring',
'spring_r', 'summer', 'summer_r', 'tab10', 'tab10_r', 'tab20',
'tab20_r', 'tab20b', 'tab20b_r', 'tab20c', 'tab20c_r', 'terrain',
'terrain_r', 'turbo', 'turbo_r', 'twilight', 'twilight_r',
'twilight_shifted', 'twilight_shifted_r', 'unregister_cmap', 'viridis',
'viridis_r', 'winter', 'winter_r']

# В примере строится два трехмерных графика в одном окне.
# Для левого графика цветовая карта jet, а для правого - Spectral.
from matplotlib.colors import LinearSegmentedColormap, LightSource
# from matplotlib.colors import LightSource
from mpl toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy
from array import *
def makeData():
# Сетка в интервале от -10 до 10, имеет 100 отсчетов по обоим координатам
x = numpy.linspace(-10, 10, 100)
y = numpy.linspace(-10, 10, 100)
# Создаётся двумерная матрица-сетка
xgrid, ygrid = numpy.meshgrid(x, y)
# В узлах рассчитываем значение функции
zgrid = numpy.sin(xgrid) * numpy.sin(ygrid) / (xgrid * ygrid)
return xgrid, ygrid, zgrid
if
name
== ' main ':
x, y, z = makeData()
fig = plt.figure(figsize=(10, 8))
axes 1 = fig.add subplot(1, 2, 1, projection='3d')
axes 2 = fig.add subplot(1, 2, 2, projection='3d')
# !N
axes 1.plot surface(x, y, z, cmap='jet')
axes 1.set title("cmap='jet'")
# !!T
axes 2.plot surface(x, y, z, cmap='Spectral')
axes 2.set title("cmap='Spectral'")
plt.show()

■ Цвета и то л щ и н ы ли н и й ли н и и сетки трехмерной поверхности
Можно сделать более выделяющимися линии сетки трехмерной поверхности. Цвет линий в
методе plot_surface() задаётся с помощью именованного параметра edgecolors, а толщина линий с помощью параметра linewidth.
Далее линии сетки можно сделать более жирными и черными. Для наглядности сетка может быть
сделана более редкой с помощью параметров rcount и ccount, а для раскраски поверхности с
помощью параметра cmap задается цветовая карта "jet".

from matplotlib.colors import LinearSegmentedColormap, LightSource
# from matplotlib.colors import LightSource
from mpl toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy
from array import *
def makeData():
# Строится сетка в интервале от -10 до 10,
# имеющая 100 отсчетов по обоим координатам

305

if

x = numpy.linspace(-10, 10, 100)
y = numpy.linspace(-10, 10, 100)
# Создаётся двумерная матрицу-сетку
xgrid, ygrid = numpy.meshgrid(x, y)
# В узлах рассчитываются значения функции
z = numpy.sin(xgrid) * numpy.sin(ygrid) / (xgrid * ygrid)
return cxgrid, ygrid, z
name
== ' main ':
x, y, z = makeData()
fig = plt.figure()
axes = fig.add subplot(projection='3d')
# !!!
axes.plot surface(x, y, z, rcount=40,
ccount=40,
cmap='jet',
linewidth=0.5,
edgecolors='k')
plt.show()



Пример 3d изображ ения и отображ ения плоскостей

# 3D изображение и отображение плоскостей ==
import matplotlib.pyplot as plt
import numpy as np
from mpl toolkits.mplot3d import Axes3D
fig = plt.figure(facecolor='white')
ax = Axes3D(fig)
#X,Y value
x=np.arange(-4,4,0.25)
y=np.arange(-4,4,0.25)
X,Y=np.meshgrid(x,y)
R=np.sqrt(X**2+Y**2)
#height value
Z=np.cos(R)
# r, cstride: плотность черных линий
# rstride: интервал по горизонтальной оси;
# cstride: интервал по вертикальной оси
# радуга: цвета радуги
ax.plot surface(X,
Y,
Z,
rstride=1,
cstride=1,
edgecolor='black',
cmap=plt.get cmap('rainbow')
)
ax.contourf(X,Y,Z,
zdir='z',
offset=-2,
cmap='rainbow'
)
ax.set zlim(-2, 2)
plt.show()

■ Анимация
Далее приводится несколько примеров отображения графиков, которые обеспечиваются
функциями из пакета pyplot библиотеки matplotlib, обозначенного как plt.

306

■ Аним ация в цикле
В этом модуле отображение графиков (результат выполнения функции gaussian) обеспечивается
функциями из пакета pyplot библиотеки matplotlib, обозначенного как plt. При импорте пакета
pyplot создается готовый к работе объект plt (???) со всеми его графическими возможностями.
Нужно всего лишь использовать функцию plot() и передать ей массив (возможно кортеж)
значений, по которым строится график. В результате будет создан объект Line2D. Это линия,
которая представляет собой последовательность точек, нанесённую на график. Далее с помощью
функции plt.show() нужно показать этот график.

import matplotlib.pyplot as plt
import numpy
import time
# ========================================================================
def gaussian(x, delay, sigma):
IIMII
Функция, график которой будет отображаться в процессе анимации
IIIIII
return numpy.exp(-((x - delay) / sigma) ** 2)
# ========================================================================
def gaussmake(data, delay, sigma, maxSize, plt):
y = gaussian(data, delay, sigma)
# новый гауссов график на чистую ТЕКУЩУЮ фигуру =
plt.clf()
# !!! Очистить текущую (НЕ НОВУЮ !)
# фигуру. Использование новой фигуры приводит к
# неограниченному росту окон приложения ==========
plt.plot(data, y)
# Отображение графика
# Установка отображаемых интервалов по осям ======
plt.xlim(0, maxSize)
# по оси X
plt.ylim(-1.1, 1.1)
# по оси Y
# добавление сетки по осям =======================
plt.axis([0, maxSize, -1.1, 1.1])
plt.grid(True)
# ================================================
# !!! Вызовы для обновления графика
plt.draw()
plt.gcf().canvas.flush events()
# Метод plt.gcf()
# возвращает объект типа Figure,
# который отвечает за окно графика.
return y
# возвращаемое значение
# ========================================================================
if
name
== ' main ':
# Параметры отображаемой функции
maxSize = 200
sigma = 10.0
# Диапазон точек для расчета графика функции [0, maxSize)
xforward = numpy.arange(0, maxSize, 1)
xbackward = numpy.arange(maxSize, 0, -1)
# Значения графика функции
y = numpy.zeros(maxSize)
# 200 нулей
# ========================================================================
# !!! Включить интерактивный режим для анимации
plt.ion()
# ===================================
# У функции gaussian будет меняться параметр delay
factor = 1.0
for i in range(0, 10):
if factor > 0:
for delay in numpy.arange(0.0, 200.0, factor):
y = gaussmake(xforward, delay, sigma, maxSize, plt)
# возвращаемое значение здесь не применяется !
# Задержка перед следующим обновлением
time.sleep(0.01)
factor = factor * -1
if factor < 0.0:

307

for delay in numpy.arange(200.0, 0.0, factor):
y = gaussmake(xbackward, delay, sigma, maxSize, plt)
# возвращаемое значение здесь не применяется !
# Задержка перед следующим обновлением
time.sleep(0.01)
factor = factor * -1.0
# Отключить интерактивный режим по завершению анимации
plt.ioff()
# ==================================
# Нужно, чтобы график не закрывался после завершения анимации
plt.show()

В примере до начала цикла график matplotlib с помощью функции ion() переводится в
интерактивный режим. Затем в цикле производится очистка графика и добавление новой кривой с
помощью функции plot(). В результате выполнения приложения график последовательно
перемещается в обе стороны.
Отличие от простого рисования графиков заключается в том, что вместо функции show()
вызывается функция draw(), после которой нужно дать возможность matplotlib обработать
внутренние события, в том числе и для отображения графиков. Для этого используется вызов
plt.gcf().canvas.flush_events().
Функция gcf() возвращает экземпляр типа Figure, который отвечает за окно графика. После
завершения цикла с помощью функции ioff() выключается интерактивный режим, а затем
вызывается функция show(), чтобы показать пользователю окно с последним кадром анимации.
Этот пример на каждом шаге анимации он удаляет все объекты кривых с графика, а затем
создаетновую кривую.

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

import time
import matplotlib.pyplot as plt
import numpy
# ================================================================
def gaussian(x, delay, sigma):
IIMII
Функция, график которой будет отображаться в процессе анимации
IIIIII
return numpy.exp(-((x - delay) / sigma) ** 2)
# ================================================================
def gaussmake(data, delay, sigma, line, fig):
y = gaussian(data, delay, sigma)
# Обновить данные на графике
# (старые данные заменить на новые)
line.set ydata(y)
try:
# !!! Вызовы для обновления графика
fig.canvas.draw()
fig.canvas.flush events()
return line
except:
return None
# ================================================================
if
name
== ' main ':

308

# Параметры отображаемой функции
maxSize = 200
sigma = 10.0
# Диапазон точек для расчета графика функции
x = numpy.arange(0, maxSize)
# Значения графика функции
y = numpy.zeros(maxSize)
# !!! Включить интерактивный режим для анимации
plt.ion()
# Создание окна и области рисования для графика. Здесь деления
# на осях и сетка устанавливаются по другому =================
fig, ax = plt.subplots()
# ax.grid() # можно и так: сначала сетка, затем интервалы
# Установка отображаемых интервалов по осям
ax.set xlim(0, maxSize)
ax.set ylim(-1.1, 1.1)
ax.grid()
# установка сетки
# Отобразить график фукнции в начальный момент
# времени (возвращается кортеж линий)
line, = ax.plot(x, y)
factor = 1.0
for i in range(0, 25, 1):
# У функции gaussian будет меняться параметр delay (задержка)
if factor > 0.0:
for delay in numpy.arange(-50.0, 200.0, factor):
line = gaussmake(x, delay, sigma, line, fig)
if line == None:
exit()
# Задержка перед следующим обновлением
time.sleep(0.01)
factor *= -1
if factor < 0:
for delay in numpy.arange(200.0, -50.0, factor):
line = gaussmake(x, delay, sigma, line, fig)
if line == None:
exit()
# Задержка перед следующим обновлением
time.sleep(0.01)
factor *= -1
# Отключить интерактивный режим по завершению анимации
plt.ioff()
plt.show()

Сначала с помощью функции subplots() создается окно с графиком. Эта функция возвращает
объекты Figure (окно графика) и Axes (область рисования в окне графика). Затем с помощью
метода plot() класса Axes создается график. Этот метод возвращает список объектов Line2D.
Поскольку известно, что добавляется только один график, то первый элемент сразу
распаковывается в переменную line. В цикле вместо очистки графика и добавления новой кривой
используется метод set_ydata() класса Line2D. Этот метод обновляет данные для отображаемой
кривой. После этого с помощь методов draw() и flush_events() обновляется график.

■ Применение класса Animation
Для упрощения создания анимаций в Matplotlib применяются два специальных класса,
производные от базового класса matplotlib.animation.Animation:



matplotlib.animation.FuncAnimation,
matplotlib.animation.ArtistAnimation.

Имеется также промежуточный класс matplotlib.animation.TimedAnimation.

309

В документации к Matplotlib есть такая схема наследования:
ArtistAnimation
Animation

TimedAnimation
FuncAnimation

FuncAnimation используется, если нужно последовательно рассчитывать и обновлять график.
ArtistAnimation используется, если все кривые (или другие анимированные
рассчитываются заранее, а затем в каждом кадре последовательно сменяют друг друга

объекты)

■ Создание анимации с помощью класса FuncAnimation
Идея использования класса FuncAnimation состоит в том, что конструктору этого класса помимо
других параметров, о которых будет сказано позднее, передаются экземпляр класса
matplotlib.figure.Figure (он отвечает за окно с графиком) и функция, которая будет вызываться для
каждого кадра анимации. Эта функция должна производить какие-то вычисления и возвращать
список объектов, которые изменились в новом кадре. Эта функция должна возвращать список
объектов, производных от базового класса matplotlib.artist.Artist. Все объекты на графике
являются производными от этого класса.
■ Пример использования класса FuncAnim ation
Функция main_func() принимает как минимум один параметр frame, который будет меняться от
кадра к кадру. В данном примере это смещение функции Гаусса по оси X. Эта функция передается
в качестве ВТОРОГО параметра конструктору класса FuncAnimation. Остальные далее
перечисленные параметры являются необязательными:
■ frames - задает изменяемую последовательность, каждый элемент которой для каждого
кадра передается в функцию создания кадра. Этот параметр может быть:
списком любых объектов;
итератором;
целым числом (это равносильно значению range(N));
функцией-генератором;
None (равносильно передаче в качестве параметра itertools.count).
■ fargs - кортеж с дополнительными параметрами, которые передаются
в функцию
создания кадра.
В данном примере, чтобы функция main_func() не использовала
глобальные переменные
(как это часто делают в примерах в документации), ей
передаются те параметры, которые ей понадобятся для расчета и обновления кривой.
Если не требуется передавать
дополнительные параметры, этот параметр можно
опустить, что будет равносильно
передаче значения None.
■ interval - задержка между кадрами в миллисекундах.
■ blit - использовать ли буферизацию для уменьшения моргания графика при обновлении.
■ repeat - если этот параметр равен True, то анимация начнется заново после достижения
конца последовательности frames.

Кроме перечисленных параметров есть еще и другие необязательные параметры, которые
позволяют изменять параметры кэширования данных и задержку между перезапуском анимации
(параметр repeat_delay), если значение repeat равно True.
Результат этого примера не отличается от предыдущих.

# =============================
#import time
import matplotlib.pyplot as plt

310

from matplotlib.animation import FuncAnimation
import numpy
# =================================================================
def gaussian(x, delay, sigma):
IIIIII
Функция, график которой будет отображаться в процессе анимации.
IIIIII
return numpy.exp(-((x - delay) / sigma) ** 2)
# =================================================================
# Функция, вызываемая для каждого кадра ===========================
def main func(frame, line, x, sigma):
IIIIII
frame - параметр, который изменяется от кадра к кадру.
line - кривая, для которой изменяются данные.
x - список точек по оси X, для которых рассчитывается функция Гаусса.
sigma - отвечает за ширину функции Гаусса.
IIIIII
y = gaussian(x, frame, sigma)
# Обновить данные на графике
# (старые данные заменить на новые)
line.set ydata(y)
return [line]
#
if
name
== ' main ':
# Параметры отображаемой функции ===========
maxSize = 200
sigma = 10.0
# Диапазон точек для расчета графика функции
x = numpy.arange(maxSize)
# Значения графика функции
y = numpy.zeros(maxSize)
# ==========================================
# Создание окна для графика ================
fig, ax = plt.subplots()
ax.grid()
# Установка отображаемых интервалов по осям
ax.set xlim(0, maxSize)
ax.set ylim(-1.1, 1.1)
# ==============================================================
# Создание линии, которая будет анимироваться
line, = ax.plot(x, y)
# !!! Параметр, который будет меняться от кадра к кадру
frames = numpy.arange(-50.0, 200.0, 1.0)
# !!! Задержка между кадрами в мс
interval = 30
# !!! Использовать ли буферизацию для устранения мерцания
blit = True
# !!! Будет ли анимация циклической
repeat = True
# !!! Создание анимации
animation = FuncAnimation(
fig,
# Figure (это ссылка на глобальную fig)
# Объект рисунка, используемый для получения
# необходимых событий, таких как
# рисование или изменение размера.
func=main func,
# Обновить данные на графике
frames=frames,
# Источник данных для передачи
# в func - функцию (main func),
# которая вызывается в каждом кадре
# анимации.
# numpy.arrange() используется
# для генерации последовательности
# чисел в линейном пространстве с
# одинаковым размером шага.
fargs=(line, x, sigma),# fargs: tuple or None (optional)
# Additional arguments to pass to
# each call to func.

311

interval=interval,
blit=blit,
repeat=repeat

# Временной интервал между кадрами

)
plt.show()

■ Создание аним ации с помощью класса A rtistA n im atio n
При использовании класса matplotlib.animation.ArtistAnimation нужно заранее создать список
списков объектов для каждого кадра. При этом длина списка первого уровня соответствует
количеству кадров, а каждый элемент этого списка - список объектов, которые будут показаны в
данном кадре.
При смене кадра объекты, предназначенные для других кадров скрываются.

В примере создается список списков frames, каждый элемент которого - список из одного
экземпляра класса matplotlib.lines.Line2D, поскольку от кадра к кадру меняется только один
объект на графике.
■ Применение класса ArtistAnimation
Конструктор ArtistAnimation кроме экземпляра класса Figure и списка объектов для кадров
принимает параметры, аналогичные параметрам класса FuncAnimation: interval, repeat,
repeat_delay и blit.
Пример выглядит практически так же, как и предыдущие за исключением того, что цвет линии
графика будет отличаться. Это связано с тем, что голубой цвет, заданный параметром '-b' метода
plot() отличается от цвета графика по умолчанию.
Но если этот параметр не указывать, каждая новая линия будет создаваться с новым цветом и при
выполнении анимации цвет линии будет меняться.

#import time
import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
import numpy
def gaussian(x, delay, sigma):
IIMII
Функция, график которой будет отображаться процессе анимации
IIIIII
return numpy.exp(-((x - delay) / sigma) ** 2)
if
name
== ' main ':
# Параметры отображаемой функции ==========================
maxSize = 200
sigma = 10.0
# Диапазон точек для расчета графика функции
x = numpy.arange(maxSize)
# Значения графика функции
y = numpy.zeros(maxSize)
# ==========================================================
# Создание окна для графика ================================
fig, ax = plt.subplots()
ax.grid()
# Установка отображаемых интервалов по осям
ax.set xlim(0, maxSize)
ax.set ylim(-1.1, 1.1)
# ==========================================================

312

# !!! Создание списка линий, которые будут последовательно ==========
# переключаться и предъявляться при изменении номера кадра
frames = []
for delay in numpy.arange(-50.0, 200.0, 1.0):
y = gaussian(x, delay, sigma)
# список рисунков линий ==========================================
# голубой цвет линии задаётся параметром '-b'
line, = ax.plot(x, y, '-b')
# !!! Поскольку на каждом кадре может меняться несколько объектов,
# каждый элемент списка - это список изменяемых объектов (?????)
frames.append([line])
# ===================================================
# Задержка между кадрами в мс
interval = 30
# Использовать ли буферизацию для устранения мерцания
blit = True
# Будет ли анимация циклической
repeat = True
# !!! Создание анимации
animation = ArtistAnimation(
fig,
# Figure (это ссылка на глобальную fig)
# Объект рисунка, используемый для получения
# необходимых событий, таких как
# рисование или изменение размера.
frames,
# Источник данных - список линий, которые
# будут последовательно переключаться и
# предъявляться при изменении номера кадра
interval=interval, # Временной интервал между кадрами
blit=blit,
# Использовать буферизацию
# для устранения мерцания
repeat=repeat
)
plt.show()

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










Красный - 'red', 'r', (1.0, 0.0, 0.0);
Оранженвый - 'orange';
Жёлтый - 'yellow', 'y', (0.75, 0.75, 0);
Зелёный - 'green', 'g', (0.0, 0.5, 0.0);
Голубой - 'cyan', 'c', (0.0, 0.75, 0.75);
Синий - 'blue', 'b', (0.0, 0.0, 1.0);
Фиолетовый - 'magenta', 'm', (0.75, 0, 0.75);
Чёрный - 'black', 'k', (0.0, 0.0, 0.0);
Белый -'white', 'w', (1.0, 1.0, 1.0).

Цветовую гамму можно разнообразить за счёт различной степени прозрачности, которая
обеспечивается соответствующим значением параметра alpha. Но для задания определённого
цвета требуется пользовательская настройка. Matplotlib может отображать цвета, заданные в
различных форматах: как в формате RGB, так и в HEX. Существуют различные таблицы цветов, в

313

которых представленным оттенкам соответствует код в RGB или HEX формате. Ими удобно
пользоваться при составлении небольших пользовательских цветовых палитр.

■ Способы задания цветов. RGB и HEX
Список способов задания цвета в Matplotlib:






С помощью однобуквенной строки (например, 'g').
С помощью словесного описания цвета (например, 'goldenrod').
С помощью словесного описания цвета из таблицы xkcd (например, 'xkcd:moss green').
С помощью указания компонент цвета в формате #RRGGBB (например, '#31D115').
С помощью указания компонент цвета в формате #RRGGBBTT (например, '#31D1155C').
Последняя пара символов в строке определяет степень прозрачности заданного цвета
(transparency).
■ С помощью указания компонент цвета в формате #RGB (например, '#AF5').
■ С помощью указания компонент цвета в виде кортежа или списка трех чисел в диапазоне
0.0 - 1.0 (например, (0.5, 0.2, 0.3)).
■ С помощью указания компонент цвета и альфа-канала в виде кортежа или списка четырех
чисел в диапазоне 0.0 - 1.0 (например, (0.5, 0.2, 0.3, 0.8)).

Для задания серого цвета можно применить строку с числом с плавающей точкой в диапазоне
[0.0 - 1.0]
(например, '0.3').
Цвет в формате RGB представляет собой триплет или кортеж, состоящий из трёх целых значений
от 0 до 255 для красного, зелёного и синего цветов. Различные сочетания этих трёх значений
позволяет получить огромное количество цветов и оттенков.
В matplotlib цвета rgb формата задаются в относительных единицах, в диапазоне от [0, 1]. Если
необходимый цвет задан в диапазоне [0, 255], то значение нужно поделить на 255.
Цвет, представленный в формате HEX, передаётся в виде шестнадцатеричной html строки (символ
# перед шестизначным значением обязателен).

# ========================================================================
import matplotlib.pyplot as plt
import numpy as np
# ========================================================================
N = 10
x = np.arange(1, N+1, 1)
y = 20.*np.random.rand(N)
# ========================================================================
fig = plt.figure()
# matplotlib.pyplot ================================
ax = fig.add subplot(111)
# добавление области рисования ax
# Цвет в формате RGB: триплет или кортеж, состоящий из трёх ЦЕЛЫХ значений
# от 0 до 255 для красного, зелёного и синего цветов
x rgb = np.array([204, 255, 51]) / 725.
ax.bar(x, y, color=x rgb, alpha=0.75, align='center')
x hex = '#660099' # символ '#' перед строкой со значением цвета
ax.plot(x, y, color=x hex)
# фиолетовый график
ax.set xticks(x)
# установка делений на оси OX
ax.set xlim(np.min(x)-1, np.max(x)+1)
# ограничение области изменения
# по оси OX
ax.grid(True)
plt.show()
# ========================================================================

314

Для создания множества областей для рисования удобно не просто добавлять их на рисунок
последовательно, по одному, а всего одной строчкой разбить форму на несколько областей
рисования.
Это делается методом plt.subplots(), и при его вызове надо указать количество строк и столбцов
создаваемой таблицы областей, каждая из ячеек которой и есть объект-экземпляр subplots.
Метод возвращает объект типа figure и массив из созданных subplots. Их можно перебрать в
цикле, но лучше применить перебор из списка fig.axes, куда автоматически добавляются все
области рисования текущего рисунка fig.
В следующем примере создаются 9 пар прямоугольников, которые отображаются в 9 областях
рисования. "нижний" прямоугольник в паре всегда чёрный. "верхний" прямоугольник рисуется
поверх "нижнего" чёрного прямоугольника.
В каждой паре верхний прямоугольник обладает различной степенью прозрачности, которая
определяется последней парой символов в шестнадцатеричной html строке определения цвета в
формате #RRGGBBTT.
Строки определения цвета "верхних" прямоугольников в формате #RRGGBBTT различаются
последними двумя символами, которые определяют прозрачность цвета.
colors = [
'#33DD1100',

# полностью прозрачный цвет (невидимый)

'#33DD1111',
'#33DD1133',
'#33DD1155',
'#33DD1177',
'#33DD1199',
'#33DD11AA',
'#33DD11CC',
'#33DD11FF'

# полностью непрозрачный цвет

]

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

Для создания множества областей для рисования удобно не просто добавлять
их на рисунок последовательно, по одному, а всего одной строчкой разбить
форму на несколько областей рисования.
Это делается методом plt.subplots(),
и при его вызове надо указать количество строк и столбцов создаваемой
таблицы областей, каждая из ячеек которой и есть объект-экземпляр
subplots.
Метод возвращает объект типа figure и массив из созданных subplots.
Их можно перебрать в цикле, но лучше применить перебор из списка
fig.axes, куда автоматически добавляются все области рисования

315

текущего рисунка fig.
IIIIII
import numpy as np
import matplotlib.lines
import matplotlib.patches
import matplotlib.path
import matplotlib.pyplot as plt
# функция для отрисовки прямоугольников. Отображает прямоугольники
# различной степени прозрачности
def drawRect(ax, color, xlim, ylim):
plt.xlim(xlim)
plt.ylim(ylim)
ax.grid(True)
# Создаётся "нижний" прямоугольник. Он всегда чёрный.
rect back = matplotlib.patches.Rectangle((-1.0, -1.0), 2, 2, color='k')
ax.add patch(rect back)
# Создаётся "верхний" прямоугольник, который рисуется поверх "нижнего"
# прямоугольника. Он обладает различной степенью прозрачности, которая
# определяется последней парой символов в шестнадцатеричной html
# строке определения цвета в формате HEX
rect front = matplotlib.patches.Rectangle((-1.5, -1.5), 3, 3,
color=color)
ax.add patch(rect front)
# ========================================================================
# список кодов цветов. Различаются последними двумя числами, которые =====
# определяют прозрачность цвета. Верхний прямоугольник может быть
# абсолютно прозрачным и нижний прямоугольник просвечивает через абсолютно
# прозрачный верхний прямоугольник. Верхний прямоугольник может быть
# абсолютно непрозрачным и нижний прямоугольник полностью скрывается
# абсолютно непрозрачным верхним прямоугольником. ========================
colors = [
'#33DD1100',
# полностью прозрачный цвет (невидимый)
'#33DD1111',
'#33DD1133',
'#33DD1155',
'#33DD1177',
'#33DD1199',
'#33DD11AA',
'#33DD11CC',
'#33DD11FF'
# полностью непрозрачный цвет
]
# ========================================================================
fig, subplots = plt.subplots(nrows=3, ncols=3, sharex=True, sharey=True)
i = 0
for ax in fig.axes:
drawRect(ax, colors[i], (-2,2), (-2,2))
ax.text(0.0, 0.0, str(i), color='k')
i += 1
plt.show()
# ========================================================================
I I I I II

Способы задания цвета в Matplotlib:
С помощью однобуквенной строки (например, 'g').
С помощью словесного описания цвета (например, 'goldenrod').
С помощью словесного описания цвета из таблицы xkcd
(например, 'xkcd:moss green').
С помощью указания компонентцвета в формате #RRGGBB
(например, '#31D115').
С помощью указания компонент цвета в формате #RRGGBBTT
(например, '#31D1155C'). Последняя пара символов в строке определяет
степень прозрачности заданного цвета (transparency).
С помощью указания компонент цвета в формате #RGB (например, '#AF5').
С помощью указания компонент цвета в виде кортежа или списка трех чисел
в диапазоне 0.0 - 1.0 (например, (0.5, 0.2, 0.3)).

316

С помощью указания компонент цвета и альфа-канала в виде кортежа или
списка четырех чисел в диапазоне 0.0 - 1.0
(например, (0.5, 0.2, 0.3, 0.8)).
Для задания серого цвета можно использовать строку с числом с плавающей
точкой в диапазоне 0.0 - 1.0 (например, '0.3').

#

■ Цветовая п али тр а colorm ap
Последовательность или набор цветов образует цветовую палитру colormap. Чаще всего она
используется при отрисовке трёхмерных данных. Но и автоматический подбор цветов при
добавлении каждого нового экземпляра plot также осуществляется из цветовой палитры по
умолчанию.
Для
получения
текущей
цветовой
палитры
можно
воспользоваться
методом
plt.get_cmap('название палитры'). Список всех предустановленных палитр можно получить с
помощью метода plt.cm.datad. В настройках matplotlibrc можно также изменить цветовую палитру
с помощью параметра image.cmap. В интерактивном режиме её можно поменять через
plt.rcParams['image.cmap']='имя_палитры' или через plt.set_cmap('имя_палитры'). При этом
plt.set_cmap(...) позволяет изменить палитру текущего рисунка уже после вызова графических
команд.
# =======================================================================
import matplotlib.pyplot as plt
import numpy as np
dat = np.random.random(200).reshape(20, 10) # создаётся матрица значений
# Создаётся список цветовых палитр из словаря
maps = [m for m in plt.cm.datad]
# или так ===============================================================
# maps = []
#
# for i, m in enumerate(plt.cm.datad):
#
maps.append(m)
#
print('%d - %s' % (i, m))
# =======================================================================
print(u'Предустановленные цветовые палитры:', maps)
# =======================================================================
# четыре картинки на одной фигуре: 2 строки, 2 столбца ==================
fig, axes = plt.subplots(
nrows=2,
ncols=2,
sharex=True,
sharey=True
)
# =======================================================================
cmaplist = plt.cm.datad
# =======================================================================
for ax in fig.axes:
random cmap = np.random.choice(maps)
cf = ax.contourf(dat, cmap=plt.get cmap(random cmap))
ax.set title('%s colormap' % random cmap)
fig.colorbar(cf, ax=ax)
# =======================================================================
plt.suptitle(u'Различные цветовые палитры')
# единый заголовок рисунка
plt.show()
# =======================================================================

317

■ П лавная цветовая п алитра
Плавная цветовая палитра представляет собой результат линейной интерполяции между
последовательностью цветов, составляющих основу палитры.
Различные методы отображения трёхмерных данных (разные графические команды)
умолчанию используют разные типы цветовых палитр.

по

Так методы plt.imshow() и plt.pcolor() будут сопровождаться плавной цветовой палитрой:
# =====================================================================
import matplotlib.pyplot as plt
import numpy as np
dat = np.random.random(200).reshape(20, 10) # создаём матрицу значений
# Список цветовых палитр из словаря ===================================
# maps = [m for m in plt.cm.datad]
# или так
maps = []
for m in plt.cm.datad:
maps.append(m)
# =====================================================================
fig, axes = plt.subplots(
nrows=2,
ncols=1,
sharex=True
)
# Про рисунки с несколькими областями рисования ...
cmaplist = plt.cm.datad
for i, ax in enumerate(fig.axes):
random cmap = np.random.choice(maps)
if (i == 0):
cf = ax.pcolor(dat, cmap=plt.get cmap(random cmap))
else:
cf = ax.imshow(dat, cmap=plt.get cmap(random cmap))
ax.set title('%s colormap' % random cmap)
fig.colorbar(cf, ax=ax)
plt.suptitle(u'Различные цветовые палитры')
plt.show()
# =====================================================================

Для создания пользовательской плавной цветовой палитры нужно определиться с её основными
цветами. И если было выбрано следующее сочетание цветов: розовый, синий, зелёный,
оранжевый и красный, то в представлении RGB это сочетание означает список следующих
значений:
■ розовый - rgb(150, 0, 0),
■ синий - rgb(0, 0, 255),
■ зелёный - rgb(0, 255, 0),
■ оранжевый - rgb(255, 150, 0),
■ красный - rgb(255, 0, 0).

Matplotlib работает с rgb в относительных единицах, поэтому значения rgb триплетов нужно
поделить на 255.
После этого нужно создать "словарь палитры" cdict. Это словарь для каждого из оттенков RGB
('red', 'blue', 'green'), где каждому оттенку приписан список или кортеж (x, y0, y1):
x - определяет положение в палитре в диапазоне [0,1]. Проще представить будущую палитру в
виде шкалы от 0 до 1;

318

y0 - значение цвета в относительных единицах ([0, 255] -> [0, 1]) с одной стороны (слева) от
положения x;
y1 - значение цвета ([0, 255] -> [0, 1]) с другой стороны (справа) от положения x.
Между заданными позициями x метод LinearSegmentedColormap линейно интерполирует
значения цветов между (x_i - y1_i) и (x_i+1 - y0_i). Обычно y0 и y1 совпадают, но, используя разные
значения, можно добиваться более сложных эффектов, например разрывов в палитре.

о

О

о

О

о

О

О


О
О

О
О

# ========================================================================
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
dat = np.random.random(200).reshape(20,10)
# создаётся матрица значений
0 .1]) *255
# Создаётся список цветовых палитр
,rray([0.,5,
:int('{0}'.format(xx))
i}'.format( xx))
#
' 1 Ромашка (бело -жёлтый) =
{'red':
((0.0, 1.0, 1.0)
(1.0, 1.0, 1.0)
'green': (
1.0, 1.0),
(1.0, 1.0, 1.0))
'blue':
(
1.0, 1.0),
(1.0, 0.0, 0.0))
}
cmap1 = mpl.colors.LinearSegmentedColormap('cmap1', cdict1)
# =========================================================
# Вариант 2 Светофор(красный-жёлтый-зелёный) ==============
cdict2 = {'red':
0.0,
1.0, 1.0),
(1.0, 1.0, 1.0))
green'
1.0, 1.0),
1.0, 1.0),
(1.0, 0.0, 0.0)),
'blue'
0.0, 0.0),
0.0, 0.0),
(1.0, 0.0, 0.0))
}
cmap2 = mpl.colors.LinearSegmentedColormap('cmap2', cdict2)
# =========================================================
fig, axes = plt.subplots(
nrows=2,
ncols=1,
sharex=True
)
cmaplist = plt.cm.datad
cmaps = [cmap1, cmap2]
for i, ax in enumerate(fig.axes):
cf = ax.imshow(dat, cmap=cmaps[i])
fig.colorbar(cf, ax=ax)
plt.suptitle(u'Создание цветовых палитр')
plt.show()
# =======================================
О


О
О

о


О
о

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

# ======================
import matplotlib as mpl

319

import matplotlib.pyplot as plt
import numpy as np
# =========================================
# розовый [255, 204, 255] -> [1., 0.8, 1.]
# фиолетовый [153, 0, 255] -> [0.6, 0., 1.]
# синий [0, 0, 255] -> [0., 0., 1.]
# красный [255, 0, 0] -> [1., 0., 0.]
# =========================================
dat =
random. random 200) reshape(20 , 10) # создание матрицы значений
cdict1
{'red':
# red in RGB of pink, розовый
((0. 0, 1 0, 1..0),
# red in RGB of blue синий
(0. 2, 0 6, 0..6),
# red in RGB of violet фиолетовый
(0. 8, 0 0, 0..0),
(1. 0, 1 0, 1.■0)), # red in RGB of red красный
'green' : ((0. 0, 0 8, 0.■8),
# green in RGB of pink, розовый
# green in RGB of blue синий
(0. 2, 0 0, 0..0),
# green in RGB of violet фиолетовый
(0. 8, 0 0, 0..0),
(1. 0, 0 0, 0..0)), # green in RGB of red красный
'blue':
# blue in RGB of pink, розовый
((0. 0, 1 0, 1..0),
# blue in RGB of blue синий
(0. 2, 1 0, 1..0),
# blue in RGB of violet фиолетовый
(0. 8, 1 0, 1..0),
# blue in RGB of red красный
(1. 0, 0 0, 0..0))
}
cmap1 = mpl.colors.LinearSegmentedColormap('cmap1', cdict1)
plt.register cmap(cmap=cmap1)
# ===========================
fig = plt.figure()
ax = fig.add subplot(111)
cmaplist = plt.cm.datad
cf = ax.pcolor(dat, cmap=cmap1)
cbar = fig.colorbar(cf, ax=ax)
plt.suptitle(u,Розово-сине-фиолетово-красная палитра')
plt.show()
# ==========================================

Далее приводится код и пример применения функции, которая преобразует заданный список
цветов RGB в относительные единицы и может создавать как равномерную, так и неравномерную
цветовую палитры.

def make cmap(colors, position=None, bit=False, debug=False):
IIIIII

Пример функции, которая преобразует заданный список цветов RGB
в относительные единицы и создаёт как равномерную,
так и не равномерную цветовую палитры.
make cmap takes a list of tuples which contain RGB values. The RGB
values may either be in 8-bit [0 to 255] (in which bit must be set to
True when called) or arithmetic [0 to 1] (default). make cmap returns
a cmap with equally spaced colors.
Arrange your tuples so that the first color is the lowest value for the
colorbar and the last is the highest.
position contains values from 0 to 1 to dictate the location of each
color.
# импорт модулей возможен из любого места кода
import sys
import matplotlib as mpl
import numpy as np
bit rgb = np.linspace(0, 1, 256)
if position == None:
position = np.linspace(0, 1, len(colors))
else:
320

if len(position) != len(colors):
sys.exit("position length must be the same as colors")
elif position[0] != 0 or position[-1] != 1:
sys.exit("position must start with 0 and end with 1")
if (debug):
print ('position:', position)
print ('len colors', len(colors))
if bit:
for i in range(len(colors)):
colors[i] = (bit rgb[colors[i][0]],
bit rgb[colors[i][1]],
bit rgb[colors[i][2]])
if (debug):
print ('colors', colors)
cdict = {'red': [], 'green': [], 'blue': []}
for pos, color in zip(position, colors):
cdict['red'].append((pos, color[0], color[0]))
cdict['green'].append((pos, color[1], color[1]))
cdict['blue'].append((pos, color[2], color[2]))
if (debug):
print ('red', cdict['red'])
print ('green', cdict['green'])
print ('blue', cdict['blue'])
cmap = mpl.colors.LinearSegmentedColormap('test colormap', cdict, 256)
return cmap
### An example of how to use make cmap
# пример использования функции make cmap
import matplotlib.pyplot as plt
import numpy as np
import sys
fig = plt.figure()
ax = fig.add subplot(311)
### Create a list of RGB tuples
# 'r', 'y', 'w', 'g', 'b'
colors = [(255, 0, 0), (255, 255, 0), (255, 255, 255), (0, 157, 0), (0, 0,
255)]
# This example uses the 8-bit RGB Call the function make cmap
# which returns the colormap
my cmap = make cmap(colors, bit=True)
### Use your colormap
plt.pcolor(np.random.rand(25, 50), cmap=my cmap)
plt.colorbar()
ax = fig.add subplot(312)
colors = [(1, 1, 1), (0.5, 0, 0)] # This example uses the arithmetic RGB
### If you are only going to use your colormap once you can take out a step.
# Если планируется использовать цветовую карту только один раз,
# можно сделать шаг.
plt.pcolor(np.random.rand(25, 50), cmap=make cmap(colors))
plt.colorbar()
ax = fig.add subplot(313)
colors = [(0.4, 0.2, 0.0), (1, 1, 1), (0, 0.3, 0.4)]
### Create an array or list of positions from 0 to 1.
# создаётся список значений от 0 до 1
position = [0, 0.3, 1]
plt.pcolor(np.random.rand(25, 50), cmap=make cmap(colors, position=position))
plt.colorbar()
plt.show()

■ Д искретная цветовая п алитра
При отображении трёхмерных поверхностей в виде плоских карт и 3D графиков плавная цветовая
палитра хорошо смотрится. Однако в научной графике часто требуется не столько красота, сколько

321

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

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
dat = np.random.random(200).reshape(20, 10) # создаётся матрица значений
N = 10
# Список цветов в HEX формате
cpool = [ '#bd2309', '#bbb12d', '#1480fa', '#14fa2f', '#000000'
'#faf214', '#2edfea', '#ea2ec4', '#ea2e40', '#cdcdcd'
'#577a4d', '#2e46c0', '#f59422', '#219774', '#8086d9' ]
# Создание дискретных цветовых палитр (colormap)
cmap1 = mpl.colors.ListedColormap(
'b', 'violet'], 'indexed'
['r', 'orange', 'y', 'g', 'c'
) # 7 цветов
cmap2 = mpl.colors.ListedColormap(
cpool[5:5+N], 'indexed'
) # 10 цветов
fig, axes= plt.subplots(nrows=2, ncols=1, sharex=True)
cmaps = [cmap1, cmap2]
# ==========================================================
for i, ax in enumerate(fig.axes):
cf = ax.pcolor(dat, cmap=cmaps[i])
fig.colorbar(cf, ax=ax)
# ==================================================
fig.suptitle(u'Создание дискретных цветовых палитр')
plt.show()

При задании дискретной палитры необходимо обращать внимание на согласованность границ
делений цветовой шкалы и границ цветов, иначе анализ рисунка будет серьёзно затруднён. На
плавной цветовой шкале это не так заметно, как на дискретной.
Для задания положения делений в методе fig.colorbar() или plt.colorbar() необходимо указать
параметр ticks. Число положений делений должно быть на 1 больше числа цветов в палитре
(делений на отрезке на одну больше, чем промежутков между ними)!

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
dat = np.random.random(200).reshape(20, 10) # создаётся матрица значений
N = 10
# Список цветов в HEX формате
cpool = [ '#bd2309', '#bbb12d', '#1480fa', '#14fa2f', #000000',
'#faf214', '#2edfea', '#ea2ec4', '#ea2e40', #cdcdcd',
'#577a4d', '#2e46c0', '#f59422', '#219774', #8086d9' ]
# Создание дискретных цветовых палитр (colormap)
cmap1 = mpl.colors.ListedColormap(
['r', 'orange', 'y', 'g', 'c', 'b', 'violet'] 'indexed'
) # 7 цветов
cmap2 = mpl.colors.ListedColormap(
cpool[5:5 + N], 'indexed'
) # 10 цветов
fig, axes = plt.subplots(nrows=2, ncols=1, sharex=True)
cmaps = [cmap1, cmap2]
322

# =============================================================
for i, ax in enumerate(fig.axes):
cf = ax.pcolor(dat, cmap=cmaps[i])
if (i == 0):
fig.colorbar(cf, ax=ax, ticks=np.linspace(0, 1, 7 + 1))
else:
fig.colorbar(cf, ax=ax)
# =============================================================
plt.suptitle(u'Создание дискретных цветовых палитр')
plt.show()

■ Координатные оси
Есть координатные оси (контейнер Axis). Есть деления (ticks) на координатных осях. Деления
неотделимы от координатной оси на которой они находятся. При этом свойства самих делений
(цвет, длина, толщина и др.), их подписей (кегль, поворот, цвет шрифта, шрифт и др.), а также
связанные с ними линии вспомогательной сетки grid, хранятся в отдельном хранилище­
контейнере (контейнер Ticks), а не в контейнере Axis. Деления и оси тесно связаны и одни
отдельно от других не имеют смысла. Тем более, что для удобства пользователей разработчики
сделали множество методов для работы с делениями из контейнеров более высокого уровня
(Axes, Axis).
■ Д ополнительная координатн ая ось
Иногда требуется нанести на один рисунок две величины, которые имеют общие единицы
измерения по одной оси, но разные по другой. Это могут быть временные ряды сильно
отличающихся по масштабу величин. Или ряды величин, имеющих разные единицы измерения. В
таких случаях удобно рисовать дополнительную координатную ось (обычно - ординат).
Такую возможность обеспечивает метод ax.twinx() для оси OX и метод ax.twiny() для оси OY. В
результате создаётся ещё одна область рисования, совпадающая по размерам с исходной. На
каждой области рисуются соответствюущие значения, при этом с помощью пользовательской
настройки подписей осей ординат можно добиться, чтобы вспомогательные сетки grid обеих
областей совпадали.

from matplotlib import rcParams
import matplotlib.pyplot as plt
import numpy as np
# ========================================================================
def fun 0(arg0, arg1):
x = np.arange(-2*arg0, 2*arg0, arg1)
y = np.sin(x) * np.cos(x)
f = np.sin(x) + np.cos(x)
return x, y, f
# ========================================================================
# ========================================================================
def do it():
# ====================================================================
fig = plt.figure()
# ====================================================================
x, y, f = fun 0(np.pi, 0.2)
# ====================================================================
ax1 = fig.add subplot(111) # Явно задаётся область рисования
ax1.plot(x, f, color='green')
ax1.plot(-0.0, -1.0, label = и'Сумма cos и sin', color='green')
axi.set ylabel(u'Функция 1', color='green')
ax1.grid(True, color='green')
ax1.legend(loc=2)
axi.set title(u'Несколько разномасштабных переменных')
# Легенда для всего рисунка fig
y = y * 333

323

ax2 = ax1.twinx()
# Создаётся вторая шкала ax2
ax2.plot(x, y, color='red')
ax2.plot(0.0, 200.0, color='red')
ax2.set ylabel(u'Функция 2', color='red')
ax2.grid(True, color='red')
# =====================================================================
if
name
== ' main ':
do it()
plt.show()

■ Л еген да до полнительной оси
Если график с дополнительной координатной осью, выполнен в чёрно-белом варианте, понять
какой график относится к левой шкале, а какой к правой невозможно. В таком случае для
идентификации данных используют легенду. Имеются особенности при работе с легендой такого
графика. Одна легенда отобразилась для одной координатной оси из двух. Вторая легенда
отобразились для второй координатной оси и перезатёрла предыдущую. Для неё можно было
указать другое место расположения. Чтобы каждая подпись была в своём месте, нужно
воспользоваться методом ax.legend(), а не plt.legend().
Расположение легенды задаётся параметром loc метода legend(), который принимает значения в
виде цифр (1-9), начиная от верхнего левого угла и заканчивая нижним правым, и в виде условных
обозначений. Значение loc='best' автоматически выберет место на рисунке, где легенда меньше
всего будет "портить" график. Также можно убрать рамку вокруг легенды для визуального
уменьшения места (frameon=False), занимаемого легендой.

from matplotlib import rcParams
import matplotlib.pyplot as plt
import numpy as np
# =======================================================================
def fun 0(arg0, arg1):
x = np.arange(-2*arg0, 2*arg0, arg1)
y = np.sin(x) * np.cos(x)
f = np.sin(x) + np.cos(x)
return x, y, f
# =======================================================================
# =======================================================================
def do it():
# ===================================================================
fig = plt.figure()
# ===================================================================
x, y, f = fun 0(np.pi, 0.2)
# ===================================================================
ax1 = fig.add subplot(111) # Явно задаётся область рисования
ax1.plot(x, f, color='green')
ax1.plot(-5.0, 1.5, label=u'Сумма cos и sin', color='green')
axi.set ylabel(u'Функция 1', color='green')
ax1.grid(True, color='green')
ax1.legend(loc=1)
y = y * 333
ax2 = ax1.twinx()
# Создаётся вторая шкала ax2
ax2.plot(x, y*3, color='red')
ax2.plot(5.0, 400.0, label=u,Произведение cos и sin', color='red')
ax2.set ylabel(u'Функция 2', color='red')
ax2.grid(True, color='red')
ax2.legend(loc=2)
plt.title(u'Несколько разномасштабных переменных')
# Легенда для всего рисунка fig
# =======================================================================
if
name
== ' main ':
do it()
324

plt.show()

■ Еди ная леген да
В случае необходимости можно объединить обе легенды.

from matplotlib import rcParams
import matplotlib.pyplot as plt
import numpy as np
# =======================================================================
def fun 0(arg0, argl):
x = np.arange(-2*arg0, 2*arg0, arg1)
y = np.sin(x) * np.cos(x)
f = np.sin(x) + np.cos(x)
return x, y, f
# =======================================================================
# =======================================================================
def do it():
# ===================================================================
fig = plt.figure()
# ===================================================================
x, y, f = fun 0(np.pi, 0.2)
# ===================================================================
ax1 = fig.add subplot(111) # Явно задаётся область рисования
ax1.plot(x, f, color='green')
line1 = ax1.plot(0, 0, label=u'Сумма cos и sin', color='green')
axi.set ylabel(u'Функция 1', color='green')
ax1.grid(True, color='green')
y = y * 333
ax2 = ax1.twinx()
# Создаётся вторая шкала ax2
ax2.plot(x, y*3, color='red')
line2 = ax2.plot(0, 0, label=u'Произведение cos и sin', color='red')
ax2.set ylabel(u'Функция 2', color='red')
ax2.grid(True, color='red')
# общая единая легенда ==============================================
legends = line1 + line2
labels = [l.get label() for l in legends]
ax1.legend(legends, labels, loc=3, frameon=False)
# ===================================================================
plt.title(u'Несколько разномасштабных переменных')
# Легенда для всего рисунка fig
# =======================================================================
if
name
== ' main ':
do it()
plt.show()

■ Л огариф м ические ко ординатны е оси
Если ось ординат имеет логарифмическую шкалу, то график функции, которая равна экспоненте от
аргумента, будет выглядеть как прямая линия.
Поэтому иногда на координатных осях бывает очень удобно использовать не стандартную
равномерную шкалу, а логарифмическую.
Сделать шкалу координатной оси логарифмической можно с помощью методов
plt.xscale('log')/plt.yscale('log')
или для областей рисования -

325

ax.set_xscale('log')/ax.set_yscale('log').
Методы plt.loglog() и ax.loglog() позволяют выразить обе координатные оси в логарифмических
шкалах.

import numpyimport matplotlib.pyplot as plt
import numpy as np
def set data(value):
x = np.arange(value)
y = np.exp(x)
return x, y
def do it 221(fig, x, y):
# 1 x и y=x
ax = fig.add subplot(221)
ax.plot(x, x)
ax.grid(True)
ax.set xlabel('x', fontsize=14)
ax.set ylabel('y = x', fontsize=14)
def do it 222(fig, x, y):
# 2 x и y=exp(x)
ax = fig.add subplot(222)
ax.plot(x, y)
ax.grid(True)
ax.set xlabel('x', fontsize=14)
ax.set ylabel('y = exp(x)', fontsize=14)
def do it 223(fig, x, y):
# 3 x и y=exp(x). OY с log шкалой
ax = fig.add subplot(223)
ax.set yscale('log')
# log здесь - натуральный логарифм!
# для работы с типом axis -> ax.set yscale('log')
ax.plot(x, y)
ax.grid(True)
ax.set xlabel('x', fontsize=14)
ax.set ylabel('y = exp(x)', fontsize=14)
ax.set title(u'OY log шкала', loc='center')
def do it 224(fig, x, y):
# 4 x и y=x. OX с log шкалой
ax = fig.add subplot(224)
ax.set xscale('log')
# log здесь - натуральный логарифм!
ax.plot(x, x)
ax.grid(True)
ax.set xlabel('x', fontsize=14)
ax.set ylabel('y = x', fontsize=14)
ax.set title(u'OX log шкала', loc='center')
if
name
== ' main ':
fig = plt.figure()
x,y = set data(20)
do it 221(fig, x, y)
do it 222(fig, x, y)
do it 223(fig, x, y)
do it 224(fig, x, y)
# Автоматическое форматирование риснука
plt.tight layout()
plt.show()
matplotlib.pyplot as plt
import numpy as np
def set data(value):
x = np.arange(value)
y = np.exp(x)
return x, y
def do it 221(fig, x, y):
# Обычные шкалы
326

ax = fig.add subplot(211)
ax.plot(x, y)
ax.set xlabel('x', fontsize=14)
ax.set ylabel('y = exp(x)', fontsize=14)
ax.set title(u'06bi4Hbie шкалы')
ax.grid(True)
def do it 212(fig, x, y):
# Log шкалы
ax = fig.add subplot(212)
ax.loglog()
ax.plot(x, y)
ax.set xlabel('x', fontsize=14)
ax.set ylabel('y = exp(x)', fontsize=14)
ax.set title(u'OY log и OX log шкалы')
ax.grid(True)
if

name
== ' main ':
fig = plt.figure()
x, y = set data(50)
do it 221(fig, x, y)
do it 212(fig, x, y)
# Автоматическое форматирование рисунка
plt.tight layout()
plt.show()

327

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

■ Понятия и определения
Процесс - это исполняемый экземпляр какой-либо программы. Процесс включает следующие
элементы:

образ машинного кода;

область памяти, в которую включается исполняемый код, данные процесса
(входные и выходные данные);

стек вызовов и куча (области памяти для хранения динамически создаваемых
данных);

дескрипторы операционной системы (например, файловые дескрипторы);

информация о состоянии процесса.

Процесс имеет собственное ядро и выделенную ему память, которые НЕ используется совместно с
другими процессами. Процесс может клонировать себя, создавая в одном ядре процессора
несколько экземпляров этого процесса. В современных операционных системы каждый процесс
имеет прямой доступ только к своим собственным ресурсам. В целях стабильности и
безопасности, доступ к ресурсам других процессов возможен через межпроцессное
взаимодействие (например, посредством файлов, при помощи именованных и неименованных
каналов и многих других средств).
В multiprocessing (мультипроцессной) среде процесс - представляет собой также независимую
последовательность выполнения вычислений.
Сам процесс может быть разделен на потоки.
Поток (поток выполнения, thread) является наименьшей единицей обработки, исполнение
которой может быть назначено ядром операционной системы. Потоки существуют внутри одного
процесса и имеют доступ к ресурсам этого процесса. Каждый поток обладет собственным
набором регистров и собственным стеком вызова, но доступ к ним также имеют и другие потоки.
Это независимая последовательность выполнения каких-то вычислений. В рамках одного ядра
процессора поток thread может делить память, а также его процессорное время со всеми другими
потоками, которые создаются выполняемой программой в рамках ресурсов, выделенных
программе ядру процессора. Таким образом, потоки могут использовать одну и ту же
выделенную память.
Когда несколько потоков выполняются одновременно, нельзя предусмотреть порядок, в котором
потоки будут обращаться к общим данным. Результат доступа к совместно используемым данным
зависит от алгоритма планирования, который решает, какой поток и когда запускать. Если такого
алгоритма нет, то возможный результат вычислений может быть не такими как он ожидался.
Например, если объявлена общая переменная

a = 2
И два потока, thread_one и thread_two, которые выполняют следующие операции:

a = 2
# функция 1 потока
def thread one():
328

global a
a = a + 2
# функция 2 потока
def thread two():
global a
a = a * 3
Если поток thread_one получит доступ к общей переменной

первым и thread_two вторым, то результат будет 12:

2 + 2 = 4;
4 * 3 = 12.

Если сначала запустится thread_two, а затем thread_one, то будет получен другой результат:

2 * 3 = 6;
6 + 2 = 8.

Таким образом, порядок выполнения операций потоками имеет принципиальное значение. Без
алгоритмов планирования доступа потоков к общим данным такие ошибки очень трудно найти и
произвести отладку. Кроме того, они, как правило, происходят случайным образом, вызывая
беспорядочное и непредсказуемое поведение.
Ещё более худший вариант развития событий, который может произойти без встроенной в Python
блокировки потоков GIL может произойти, например, если оба потока начнут читать глобальную
переменную

a
одновременно. При этом оба потока увидят, что

a = 2
, а дальше, в зависимости от того какой поток произведет вычисления последним, значение
переменной

a
будет равно 4 или 6.

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

329

Второе - независимое исполнение операций. Например, если есть приложение с графическим
интерфейсом, в котором весь код выполняется в одном потоке. При выполнении какой-нибудь
долгой операции (например, копирования файла большого размера) интерфейс приложения
перестанет отвечать на действия (запросы) пользователя до тех пор, пока не завершится долгий
процесс копирования. В таком случае в один поток помещается работа графического интерфейса,
в другой - остальные вычисления. В этом случае поток интерфейса позволит проводить другие
операции даже во время выполнения в его потоке операции копирования.
Программы на языке программирования python по умолчанию имеют один основной поток.
Этому способствует реализация интерпретатора GIL (Global Interpreter Lock), которая имеет
встроенный механизм, который обеспечивает выполнение одного потока в любой момент
времени. GIL облегчает реализацию интерпретатора (такую программу проще написать), защищая
объекты выполняемой программы от одновременного доступа из нескольких потоков. Поэтому
создание нескольких потоков не приведет к их одновременному исполнению на разных ядрах
процессора. Однако в выполняющей среде python можно создать больше потоков и позволить
переключаться между ними. При этом некоторые модули, как стандартные, так и сторонние, были
созданы для освобождения GIL при выполнении тяжелых вычислительных операций (например,
сжатия или хеширования). К тому же, GIL всегда свободен при выполнении операций вводавывода.

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

def master(n):
for x in range(1, n):
for y in range(1, n):
res = x**y
Функция master() представляет собой вложенный цикл, который выполняет возведение в степень.
При выполнении математических вычислений можно увидеть загрузку процессора близкую к
100%.
Функция master() будет запускаться по-разному, тем самым демонстрируя различия между
обычной однопоточной программой Python, многопоточностью и мультипроцессорностью.
■ О днопоточны й режим работы
Каждая программа Python имеет по крайней мере один основной поток. Ниже представлен
пример кода для запуска функции master() в одном основном потоке одного ядра процессора,
который производит все операции последовательно и будет служить эталоном с точки зрения
скорости выполнения:

import time
def master(n, i):
for x in range(1, n):
for y in range(1, n):
result = x**y
print(f"\nЦикл № {i}. Результат {result)\n")
# последовательный запуск функции master
330

def sequential(n):
for i in range(n):
master(10, i)
print(f"{n} циклов вычислений закончены")
if

name
== " main ":
start = time.time()
sequential(50)
end = time.time()
print(f"\nОбщее время работы: {end — start}")

■ Пример использования потоков с применением б иблиотеки threading
В следующем примере будет использоваться несколько потоков для выполнения функции
master(). Работа с потоками осуществляется при помощи стандартной библиотеки threading.
Будет произведено 400 циклов вычислений. Для этого вычисления разделяются на 4 блока по 10
потоков, в каждом из которых будет запущено по 10 циклов. Метод .join() вызывается от имени
потока. Он позволяет указать одному выполняемому потоку, что необходимо дождаться
завершения другого потока (других потоков). При использовании этого метода поток, от имени
которого происходт вызов метода .join() остановится и будет ждать завершения (завершение —
это НЕ остановка) других потоков. Метод join() может принимать один аргумент - таймаут в
секундах. Если таймаут задан, join() бликирует работу потока на указанное время. Если по
истечении времени ожидаемый поток не будет завершен, join() все равно разблокирует работу
потока, вызвашего его. Проверить, исполняется ли поток можно с помощью метода is_alive().

from threading import Thread
import time
import numpy as np
def master(i, n):
for x in range(1, n):
for y in range(1, n):
result = x**y
print(f"\nЦикл № {i}. Результат (result)\n")
# запуск функции master на нескольких потоках
def sequential(nthSet,nth ,calc, master):
for i in range(calc):
print(f"Запускается поток № {nth) в блоке {nthSet} операция {i}")
master(i, 10)
print(f"\n{calc} циклов вычислений в блоке {nthSet} потока {nth}
закончены.")
def threaded(nthSet, nth, calc):
# nthSet - количество блоков
# nth - количество потоков в блоке
# calc - количество операций на поток
threads = [[], [], [], []]
# вычисления делятся на 4 блока,
# в блоке 10 потоков
for thElem in range(0, nthSet):
for nthElem in range(0, nth):
331

threads[thElem].append(Thread(target=sequential,
args=(thElem,nthElem ,calc, master, )))
# каждый поток должен быть запущен
for thElem in range(0, nthSet):
for nthElem in range(nth):
print(f'\n-- блок {thElem}, поток {nthElem}: start ! -- ')
threads[thElem][nthElem].start()
# Ожидание завершения работы для всех потоков
for thElem in range(0, nthSet):
for nthElem in range(0, nth):
threads[thElem][nthElem].join()
if

name
== " main ":
start = time.time()
# вычисления разделяются на 4 блока потоков
# в каждом из которых по 10 потоков,
# которые делают по 20 циклов
threaded(nthSet=4, nth = 10, calc=20)
end = time.time()
print("Общее время работы: ", end — start)

Однопоточный режим работы оказывается быстрее, потому что один поток не имеет накладных
расходов на создание потоков (в данном случае создается 4 потока) и переключение между ними.
Если бы у Python не было GIL, то вычисления функции master() происходили бы быстрее, а общее
время выполнения программы стремилось ко времени выполнения однопоточной программы.
Причина, по которой многопоточный режим в данном примере не будет работать быстрее
однопоточного заключается в вычислениях, связанных с процессором и заключаются в GIL.
Если бы функция master() имел много блокирующих операций, таких как сетевые вызовы или
операции с файловой системой, то применение многопоточного режима работы было бы
оправдано и (возможно) дало увеличение скорости.
Это утверждение можно проверить смоделировав операции ввода-вывода при помощи функции
time.sleep().

■ threading: д е та ли применения
В библиотеке threading представлен класс Thread для создания потока выполнения.
Конструктор класса Thread имеет следующие аргументы:
■ group должно быть None; зарезервировано для будующих реализаций Python 3;
■ target является исполняемым объектом (по умолчанию равен None, ничего не
исполняется);
■ name обозначет имя потока (по умолчанию имя генерируется автоматически);
■ args - кортеж аргументов для исполняемого объекта;
■ kwargs - словарь именованных аргументов для исполняемого объекта;
■ daemon равное True обозначет служебный поток (служебные потоки завершаются
принудительно при завершении процесса); по умолчанию False.
После того, как объект создан, поток запускается путем вызова метода start(). Задание
исполняемого кода в отдельном потоке возможно двумя способами:



передача исполняемого объекта (функции) в конструктор класса;
переопределение функции run() в классе-наследнике.

332

import threading
import sys

def thread job(number):
print(f'Hello {format(number)}')
sys.stdout.flush()
# thread job на нескольких потоках
def run threads(count):
threads = [
threading.Thread(target=thread job, args=(i,))
for i in range(0, count)
]
# каждый поток из списка потоков должен быть запущен
for thread in threads:
thread.start()
# ожидание исполнения ВСЕХ потоков
for thread in threads:
thread.join()
run threads(5)
print('finish')
Выполнение программы в python кончается, когда завершены все неслужебные потоки.
И ещё один пример корректно работающего кода (метод .join здесь не вызывается):

from threading import Thread
import time
def thread job(number):
time.sleep(2) # поток "усыпляется" на 2 сек
print(f'\nHello {number}', flush=True)
def run threads(count):
threads = [
Thread(target=thread job, args=(i, ))
for i in range(0, count)
]
# каждый поток должен быть запущен
for thread in threads:
thread.start()
run threads(5)
print('finish')
В этом примере строка "finish" печатается раньше строки "Hello 0", т.к. главный поток теперь не
ждет завершения работы других потоков. Метод join() используется для блокирования
исполнения родительского потока до тех пор, пока созданный поток не завершится. Это требуется
в случаях, когда для работы потока-родителя важен результат работы потока-потомка. В этом
случае вычисление итогового значения выполняется в главном потоке, но это возможно только
после завершения вычислений в побочных потоках. В таком случае главный поток нужно просто
приостановить до тех пор, пока не завершатся все побочные потоки.

333

■ multiprocessing
Библиотека multiprocessing позволяет организовать параллелизм вычислений за счет создания
подпроцессов. Так как каждый подпроцесс (процесс) выполняется независимо от других, этот
метод параллелизма позволяет избежать проблем с GIL. Предоставляемый библиотекой API
библиотеки схож с тем, что есть в threading, хотя есть уникальные вещи. Создание процесса
происходит путём создания объекта класса Process. Аргументы конструктора аналогичны
аргументам, объявтеленным в конструкторе Thread. В том числе аргумент daemon позволяет
создавать служебные процессы. Эти процессы завершаются вместе с родительским процессом и
НЕ могут порождать собственные подпроцессы.
Пример работы с библиотекой:

from multiprocessing import Process
def f(name):
print('hello', name)
if

name
== ' main ':
p = Process(target=f, args=('tst',))
p.start()
p.join()

Каждый процесс имеет свой ID и в этом можно убедиться, запустив код:

from multiprocessing import Process
import os

def info(title):
print(title)
print('module name:',
name )
print('parent process:', os.getppid())
print('process id:', os.getpid())
def f(name):
info('function f')
print('hello', name)
if

name
== ' main ':
info('main line')
p = Process(target=f, args=('bob',))
p.start()
p.join()

Как и всегда, конструкция __ name__ == '___ main__ ' нужна для того, чтобы модуль можно было
безопасно подключать в другие модули и при этом без ведома программистов не создавались бы
новые процессы.
■ Взаим одействие меж ду процессами
multiprocessing предоставляет ТРИ вида межпроцессного обмена данными: очереди, каналы
данных (pipe), файлы.

Очереди (класс Queue) аналогичны структуре данных "очередь".

334

from multiprocessing import Process, Queue

def f(q):
q.put([42, None, 'hello'])
if

name
== ' main ':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print(q.get())
# выводит "[42, None, 'hello']"
p.join()

Класс Pipe (канал) отвечает за канал обмена данными (по умолчанию, двунаправленный),
представленный двумя объектами класса Connection, концами канала. С одним концом канала
работает родительский процесс, а с другим концом - подпроцесс.

"""Класс Pipe (канал) отвечает за канал обмена данными (по умолчанию,
двунаправленный), представленный двумя объектами класса Connection,
концами канала. С одним концом канала работает родительский процесс,
с другим концом - подпроцесс. """
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if

name
== ' main ':
parent conn, child conn = Pipe()
# подпроцесс готов посылать по каналу child conn
p = Process(target=f, args=(child conn,))
p.start() # запуск подпроцесса
# родительский процесс по каналу parent conn получает procTxt
procTxt = parent conn.recv()
print(procTxt)
# выводит "[42, None, 'hello']"
p.join()

Еще один вид обмена данными может быть достигнут путем записи/чтения обычных файлов.
Чтобы исключить одновременную работу двух процессов с одним файлом, в библиотеке есть
классы аналогичные threading.

from multiprocessing import Process, Lock
def fun(lck, i):
lck.acquire()
try:
print('hello world', i)
finally:
lck.release()
if

name
== ' main ':
lock = Lock()
for num in range(10):
Process(target=fun, args=(lock, num)).start()
335

■ Класс Pool и m ultiprocessing
Класс Pool представляет механизм распараллеливания выполняемых функций, распределения
входных данных по процессам и т.д.
Наиболее важные методы класса: Pool.apply, Pool.map, Pool.apply_async, Pool.map_async.
Пример работы класса Pool:

from multiprocessing import Pool
def cube(x):
return x**3
if

name
== " main ":
pool = Pool(processes=4) # создаётся пул из 4 процессов
# в apply можно передать несколько аргументов
results = [pool.apply(cube, args=(x,)) for x in range(1,7)]
# числа раскидываются от 1 до 7 по 4 процессам
print(results)
pool = Pool(processes=4)
# то же самое, но с map. разбивает итерируемый объект (range(1,7))
# на chunks и раскидывает аргументы по процессам
results = pool.map(cube, range(1, 7))
print(results)

map, apply - блокирующие вызовы. Главная программа будет заблокирована, пока процесс не
выполнит работу.
map_async, apply_async - неблокирующие. При их вызове, они сразу возвращают управление в
главную программу (возвращают ApplyResult как результат). Метод get() объекта ApplyResult
блокирует основной поток, пока функция не будет выполнена.

pool = mp.Pool(processes=4)
results = [pool.apply async(cube, args=(x,)) for x in range(1,7)]
output = [p.get() for p in results]
print(output)

■ Применение многопоточности: взгляд на звёзды
От простой модели из двух целевых функций к объектно ориентированному приложению

■ Первый этап разработки прилож ения
Телескопы пытаются рассмотреть звёзды целевой функцией starSearching.

import random
from collections import defaultdict
from threading import Thread

#
STARS = (None,
'жёлтыйКарлик',
'красныйГигант',

звёзды и телескопы

336

двойнаяЗвезда',
новаяЗвезда', )
SCOPES = ('rScopeO', 'rScopel', 'rScope2')
#
Обработка результатов поиска звёзд. Работают 2 телескопа
def starSearching(scopename, attempts):
catch = defaultdict(int)
# счётчик результатов словарь (key, value)
# телескопа scopename для учёта найденных звёзд
# (какая звезда, сколько таких звёзд)
for attempt in range(attempts):
print(f'\n{scopename) {attempt} попытка ... идёт поиск', flush=True)
# таким образом отрабатывается пауза (время работы для телескопа scopename)
= 5 ** (random.randint(25, 75) * 10000)
# =========================================================================
star = random.choice(STARS)

# телескоп отработал положенный
# интервал времени. И что то нашёл?

# разбор результатов поиска:
if star is None:
print(f'{scopename) {attempt) пустая попытка', flush=True)
else:
print(f'{scopename) {attempt) объект найден', flush=True)
catch[star] += 1 # увеличен счётчик результатов телескопа
# scopename
print(f'\nна телескопе {scopename) найдено:', flush=True)
for star, count in catch.items():
print(f'
{star), {count)
', flush=True)
print()
def doIt():
# 0-й телескоп (rScope0) - в поток и запуск из потока.
# kwargs - аргументы функции target (целевой функции starSearching)
thread = Thread(target=starSearching, kwargs=dict(scopename=SCOPES[0],
attempts=10))
thread.start()
# 1-й телескоп. Прямой запуск и присоединение к потоку
starSearching(scopename=SCOPES[1], attempts=10)
thread.join() # ожидание окончания работы потоков
if

name
doIt()

== ' main

':

■ Второй этап разработки прилож ения
Здесь не объявлены классы.

import random
from collections import defaultdict
from threading import Thread
337

звезды и телескопы
#
STARS = (None,
'желтыйКарлик',
'красныйГигант',
'двойнаяЗвезда',
'новаяЗвезда', )
SCOPES = ('rScope0', 'rScope1', 'rScope2')
def starSearching(scopename, attempts, catch):
#
телескоп
количество попыток
#
счетчик звезд
#
(значение для возврата из потока)
#

for attempt in range(attempts):
print(f'{scopename} {attempt} попытка ... идёт поиск', flush=True)
# таким образом отрабатывается пауза (время работы для телескопа scopename)
= 5 ** (random.randint(25, 75) * 10000)
#

star = random.choice(STARS)

# телескоп отработал положенный
# интервал времени. И что то нашёл?

# разбор результатов поиска:
if star is None:
print(f'\n{scopename} {attempt} пустая попытка^', flush=True)
else:
print(f'\n{scopename} {attempt} объект найден^', flush=True)
catch[star] += 1 # увеличен счётчик результатов
# телескопа scopename

def doIt(scopes, starList):
# 0-й телескоп - в поток и запуск из потока.
#
kwargs - аргументы функции target
rScope0 catch = defaultdict(int)
# счётчик результатов словарь
# (key, value)
# телескопа scopename для учёта
# найденных звёзд
# (какая звезда, сколько таких звёзд)
starList.append(rScope0 catch)
# это запуск потока
#
starSearching
#
аргументы (scopename, attempts, catch)
thread = Thread(target=starSearching, kwargs=dict(scopename=scopes[0],
attempts=10,
catch=starList[0]))
thread.start()
# 1-й телескоп. Это прямой запуск и присоединение к потоку
rScopel catch = defaultdict(int) # счётчик результатов
# словарь (key, value)
338

# телескопа scopename для учёта
# найденных звёзд
# (какая звезда, сколько таких звёзд)
starList.append(rScope1 catch)
starSearching(scopename=scopes[1], attempts=10, catch = starList[1])
thread.join() # ожидание окончания работы потоков

if

name
== ' main ':
lst = []
doIt(scopes = SCOPES, starList = lst)
# итоги работы телескопов
# nScope = 0
# for name, catch in ((SCOPES[0], lst[0]), (SCOPES[1], lst[1])):
#
print(f'\nнателескопе {SCOPES[nScope]} найдено:', flush=True)
#
for star, count incatch.items():
#
print(f'
{star}, {count}
', flush=True)
#
nScope += 1
#
print()
nScope = 0
for nScope in range(0, 2):
catch = lst[nScope]
print(f'\nна телескопе {SCOPES[nScope]} найдено:', flush=True)
for star, count in catch.items():
print(f'
{star}, {count}
', flush=True)
nScope += 1
print()

■ Т р е ти й этап разработки прилож ения

import random
from collections import defaultdict
from threading import Thread
#
звёзды и телескопы
STARS = (None,
'жёлтыйКарлик',
'красныйГигант',
'двойнаяЗвезда',
'новаяЗвезда', )
SCOPES = ('rScope0', 'rScope1', 'rScope2')
#=============================================
class rScope():
def

init (self, scopename, attempt s):
self.scopename = scopename
self.attempts = attempts
self.stars = defaultdict(int) # счётчик результатов словарь
# (key, value)
# телескопа scopename для учёта
# найденных звёзд
# (какая звезда, сколь ко таких звёзд )

def run(self):
339

for attempt in range(0, self.attempts):
print(f'\n{self.scopename} {attempt} попытка . идёт поиск',
flush=True)
# таким образом отрабатывается пауза (время работы для телескопа
#
self.scopename)
= 5 ** (random.randint(25, 75) * 10000)
#============================================================================
star = random.choice(STARS)

# телескоп отработал положенный
# интервал времени. И что он нашёл?

# разбор результатов поиска:
if star is None:
print(f'\n{self.scopename} {attempt} объект НЕ найден^',
flush=True)
else:
print(f'\n{self.scopename} {attempt} объект найден^',
flush=True)
self.stars[star] += 1 # увеличен счётчик результатов
# телескопа self.scopename
def doIt(lstScopes, nScopes):
for sc in range(0, nScopes):
# формируется список телескопов. У каждого по 10 попыток
# для звёздного поиска
lstScopes.append(rScope(SCOPES[sc], attempts=10))
#
#
#
#
#
#
#
#
#

это подготовка к запуску потока
run - целевая функция
thread = Thread(target=lstScopes[0].run)
thread.start()
# это непосредственный запуск целевой функции
lstScopes[1].run()
thread.join()

threads = []
# это подготовка к запуску 3-х потоков
#
run - целевая функция
threads.append(Thread(target=lstScopes[0].run))
threads.append(Thread(target=lstScopes[1].run))
threads.append(Thread(target=lstScopes[2].run))
# threads[0].start()
# threads[1].start()
# threads[2].start()
for t in range(0, len(threads)):
threads[t].start()
# threads[0].join()
# threads[1].join()
# threads[2].join()
for t in range(0, len(threads)):
threads[t].join()
340

if

name
== ' main ':
lstScopes = []
doIt(lstScopes, nScopes = 3)
# итоги работы телескопов
for rScope catch in lstScopes:
print(f'\nна телескопе {rScope catch.scopename) найдено:',
flush=True)
for star in rScope catch.stars:
print(f'
{star), {rScope catch.stars[star]} ')
print()

■ Ч етвёр ты й этап разработки прилож ения
В приложении учитывается реакция на возможные нештатные ситуации.

import random
from collections import defaultdict
from threading import Thread
#
звёзды и телескопы
STARS = (None,
'жёлтыйКарлик',
'красныйГигант',
'двойнаяЗвезда',
'новаяЗвезда', )
SCOPES = ('rScopeO', 'rScopel', 'rScope2')
#
Отработка ошибок в потоке (вероятные поломки телескопов)
#=============================================================
class rScope():
def

init (self, name, attempts):
self.thread = Thread(target=self.run)

self.dice = None
self.scopename = name
self.attempts = attempts
self.stars = defaultdict(int)

#
#
#
#
#

# поток объявляется элементом
# объекта
# метод run - целевая функция

счётчик результатов
словарь (key, value)
телескопа scopename для учёта
найденных звёзд
(какая звезда, сколько таких звёзд)

# поиск (поток) выделен в отдельный метод.
#
И в нём могут возникать ошибки (поломки телескопов)
def starSearching(self):
for attempt in range(0, self.attempts):
print(f'\n{self.scopename) {attempt) попытка ... идёт поиск')
# таким образом отрабатывается пауза
#
(время работы для телескопа self.scopename)
= 5 ** (random.randint(25, 75) * 10000)
# возможная поломка телескопа self.scopename - возбуждается ошибка
341

self.dice = random.randint(0, 10)
if self.dice == 0:
raise ValueError(f'{self.scopename) попытка {attempt} ...')

star = random.choice(STARS)

# телескоп отработал положенный
# интервал времени. И что он нашёл?

# разбор результатов поиска:
if star is None:
print(f'\n{self.scopename) {attempt) объект НЕ найден\п',
flush=True)
else:
print(f'\n{self.scopename) {attempt) объект найден\п',
flush=True)
self.stars[star] += 1 # увеличен счётчик результатов
# телескопа self.scopename
# перехват ошибки, которая имеет вид исключения, осуществляется в потоке
def run(self):
try:
self. starSearching()
except Exception as exc:
print(f'\nHEEITATHAH СИТУАЦИЯ: {exc}')
def doIt(lstScopes, nScopes):
# формируется список телескопов. У каждого по 10 попыток
for sc in range(0, nScopes):
lstScopes.append(rScope(SCOPES[sc], attempts=10))
# запуск потоков
for sc in range(0, len(lstScopes)):
lstScopes[sc].thread.start()
# контроль потоков
for sc in range(0, len(lstScopes)):
lstScopes[sc].thread.join()
if

name
== ' main ':
lstScopes = []
doIt(lstScopes, nScopes=3)

# итоги работытелескопов
for rScope catch in lstScopes:
print(f'\nна телескопе {rScope catch.scopename) найдено:',
flush=True)
for star in rScope catch.stars:
print(f'
{star), {rScope catch.stars[star]} ')
print()

■ П яты й этап разработки прилож ения
Неудавшийся результат поиска при попытке (а их всего 10, и они могут возникать при поломке
телескопа или в результате неточного прицеливания) — тоже результат, который учитывается в
данной версии.

342

import random
import threading
from collections import defaultdict
from threading import Thread
from multiprocessing import Queue
#
звёзды и телескопы
STARS = ('None', 'жёлтыйКарлик', 'красныйГигант', 'двойнаяЗвезда',
'новаяЗвезда', )
SCOPES = ('rScope0', 'rScope1', 'rScope2', 'rScope3', 'rScope4')
nScopes = 5
#
Отработка ошибок в потоке
#
Очереди для обмена данными
#=================================================================
class rScope():
def

init (self, name, attempts):
# поток объявляется элементом объекта
# метод scopeRun - целевая функция
self.thread = Thread(target=self.scopeRun)
self.attempts = attempts
self.dice = None
self.scopename = name
self.stars = {STARS[0]
STARS[1]
STARS[2]
STARS[3]
STARS[4]

0,
0,
0,
0,
0}

=
=
=
=
=

О

self.stars = dict()
self.stars[STARS[0]]
self.stars[STARS[1]]
self.stars[STARS[2]]
self.stars[STARS[3]]
self.stars[STARS[4]]

о

#
#
#
#
#
#

о

о

# прочие варианты объявления словар
# self.stars = dict(zip([STARS[0],
STARS[1],
#
STARS[2],
#
STARS[3],
#
STARS[4]],
#
#

0
0
0
0
0

# счётчик результатов словарь (key, value)
# телескопа scopename для учёта найденных звёзд
# (какая звезда, сколько таких звёзд)
# перехват ошибки, которая имеет вид исключения, осуществляется в потоке
def scopeRun(self):
try:
return self. starSearching()
except Exception as exc:
print(f'\nНЕШТАТНАЯ СИТУАЦИЯ: {exc}')
return 'ERROR'
343

# поиск (поток) выделен в отдельный метод. И в нём могут возникать ошибки
# (поломки телескопов)
def starSearching(self):
if self.attempts > 0:
print(f'\n{self.scopename} {self.attempts} попытка ... идёт
поиск')
# таким образом отрабатывается пауза
# (время работы для телескопа self.scopename)
= 5 ** (random.randint(25, 75) * 10)
# возможная поломка телескопа self.scopename - возбуждается ошибка ======
self.dice = random.randint(0, 10)
if self.dice == 0:
raise ValueError(f'{self.scopename) попытка
{self.attempts} ...')
#========================================================================
star = random.choice(STARS)

# телескоп отработал положенный
# интервал времени. И что он нашёл?

# разбор результатов поиска:
if star == 'None':
print(f'\n{self.scopename) {self.attempts) объект НЕ
найден^', flush=True)
elif star == 'ERROR':
print(f'\n{self.scopename) {self.attempts) ERROR\n',
flush=True)
else:
print(f'\n{self.scopename) {self.attempts) {self.stars[star])
объект найден^', flush=True)
self.stars[star] += 1 # увеличен счётчик результатов
# телескопа self.scopename
return star
class rRoll(threading.Thread):
def

init (self, lscScopes):
super(). init ()
self.scopes = lscScopes
self.nStars = 0
self.receiver = Queue()
self.stars = defaultdict(int)

#
#
#
#
#

счётчик результатов словарь
(key, value)
телескопа scopename для учёта
найденных звёзд
(какая звезда, сколько таких звёзд)

def startScopes(self):
# старт телескопов из списка телескопов
print(f' Телескопы - в работу^', flush=True)
for nsc in range(0, len(self.scopes)):
print(f'{self.scopes[nsc].scopename) стартует...')
self.scopes[nsc].thread.start()
def goScope(self):
344

S17E

( ) ^ u iid
(,

{ [lE^ S]SIE^ S'Sd oO Sl}

' {IE^S}

: { 9mEU9 d 0 D S ' S d 0 3 S l }

,j)^ u iid

: s i b ^s •s d o o s u
( s n j i = ' q s n _[3

' , : 1г э ш е н

ui

i e

^s

jo j

{ s u r e u s d o o s • s d o o s u } и о 5ю э 1гэ;ьи\ , j ) ^ u i i d
: s s d o o s •x i o i

( s n j i = ' q s n _[3

' , { s i e ^s u ' x t o i } t f s s a s

ui

sdoosu

jo j

ousoa , j ) ^ u ii d

a o u o H D S i r a i Hiboged и а о л и #

() s d o o s o b • J I M
() s a d o o s ^ i E ^ s • J I M
(ss d o o s ^ s x )X I°a j
( ( o i = sq.c[uisq.q.B

= Tiou

1 [ o s ] S3