{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Стандартная библиотека" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Стандартная библиотека Python представляет собой множество пакетов и модулей, доступных для использования любой программе на языке Python. Эта библиотека является такой же неотъемлемой частью языка Python, как и его интерпретатор, о чем свидетельствует тот факт, что она полностью описана в самом стандарте языка." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этой лекции мы рассмотрим несколько важнейших модулей стандартной библиотеки, однако это будет лишь небольшая ее часть. Практически любую задачу на языке Python можно легко решить средствами, предоставляемыми стандартной библиотекой, поэтому когда вам понадобится какая-то функция (или класс), не спешите самостоятельно ее реализовывать - для начала лучше поищите аналог в документации [стандартной библиотеки](https://docs.python.org/3/library/index.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Стоит также отметить, что в сети можно найти огромное количество других библиотек, не включенных в состав стандартной, которые можно легко добавить к своей конфигурации Python и использовать в программах." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Содержание лекции" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* [Введение](#Введение)\n", "* [Модуль builtins](#Модуль-builtins)\n", "* [Модули math и random](#Модули-math-и-random)\n", "* [Модуль datetime](#Модуль-datetime)\n", "* [Модуль threading](#Модуль-threading)\n", "* [Задание](#Задание)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Введение" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Перед тем, как начать изучение стандратной библиотеки, хотелось бы рассказать о двух функциях, которые очень помогут в этом деле. С одной мы уже встречались - речь идет о функции `dir`, возвращающей все имена из указанного пространства имен (например, из модуля или класса). Вторая называется `help`, и используется для того, чтобы получить справочную информацию, связанную с некоторым именем." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вместе эти функции неплохо заменяют традиционную документацию: достаточно с помощью `dir` узнать, какие функции и классы есть в модуле, а затем с помощью `help` получить информацию о том, как их использовать. Пусть, например, нам нужна функция, которая вычисляет логарифм. Логично предположить, что она может быть в модуле `math` (подробнее о нем чуть ниже). Выяснить это можно с помощью функции `dir`:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc']\n" ] } ], "source": [ "import math\n", "print(dir(math))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Судя по имени, функция, которая нам нужна - это `log`. Теперь осталось прочитать о том, как ее правильно использовать:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Help on built-in function log in module math:\n", "\n", "log(...)\n", " log(x[, base])\n", " \n", " Return the logarithm of x to the given base.\n", " If the base not specified, returns the natural logarithm (base e) of x.\n", "\n" ] } ], "source": [ "help(math.log)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этой лекции при описании функций и методов мы используем следующее соглашение:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Все обязательные параметры функций и методов указываются всегда.\n", "2. Необязательные параметры выделяются курсивом и для них указывается значение по умолчанию.\n", "3. Некоторые необязательные параметры могут быть опущены, чтобы описание функции оставалось коротким. Об этом сигнализирует последовательность \"...\" в списке параметров. Для получения полной информации обращайтесь к справочному руководству или функции `help`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Модуль builtins" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Этот модуль предоставляет базовую функциональность всем программам на языке Python. Он отличается от всех остальных тем, что импортируется автоматически самим интерпретатором при старте программы, при этом все имена из него попадают во встроенное пространство имен, а значит к ним можно обращаться без указания имени модуля." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В следующей таблице собраны функции для различных преобразований значений одного типа к значениям другого:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
bool(x)
| Создает из x значение с типом bool; если x не задан, возвращает False |\n", "|
int(x)
| Создает из числа или строки x значение с типом int; если x не задан, возвращает 0 |\n", "|
float(x)
| Создает из числа или строки x значение с типом float; если x не задан, возвращает 0.0 |\n", "|
str(x)
| Возвращает строковую версию объекта x; если x не задан, возвращает пустую строку |\n", "|
bin(x)
| Преобразует целое числое x в строку, содержащую его двоичное представление |\n", "|
oct(x)
| Преобразует целое числое x в строку, содержащую его восьмеричное представление |\n", "|
hex(x)
| Преобразует целое числое x в строку, содержащую его шестнадцатеричное представление |\n", "|
chr(x)
| Возвращает строку с символом, имеющим позицию x в таблице Unicode |\n", "|
ord(x)
| Возвращает позицию в таблице Unicode для символа x |" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10\n", "100\n", "0b1010\n", "A\n" ] } ], "source": [ "num = 10\n", "s = '100'\n", "\n", "print(str(num))\n", "print(int(s))\n", "print(bin(num))\n", "print(chr(65))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В модуле `builtins` существует специальная функция, с помощью которой можно попросить пользователя ввести какие-нибудь данные:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
input(prompt='', ...)
| Возвращает строку c данными, введенными пользователем (если prompt не пустой, отображает его рядом с полем для ввода)|" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Enter you name: Bob\n", "Hello, Bob\n" ] } ], "source": [ "name = input('Enter you name: ')\n", "print('Hello, {}'.format(name))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функции для работы с коллекциями мы уже рассматривали, но для полноты перечислим их здесь еще раз:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
len(col)
| Возвращает количество элементов в коллекции |\n", "|
all(col)
| Возвращает True, если все элементы коллекции в логическом контексте оцениваются как True |\n", "|
any(col)
| Возвращает True, если хотя бы один элемент коллекции в логическом контексте оцениваeтся как True | \n", "|
max(col, ...)
| Возвращает наибольший элемент в коллекции |\n", "|
min(col, ...)
| Возвращает наименьший элемент в коллекции |\n", "|
sum(col, start=0)
| Возвращает сумму элементов коллекции плюс элемент start |\n", "|
sorted(col, key=None, reverse=False)
| Возвращает список отсортированных в прямом или обратном (reverse=True) порядке элементов коллекции |\n", "|
range(start=0, stop, step=1)
| Возвращает итератор на последовательность целых чисел от start до stop с шагом step |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "То же самое касается и функций для работы с иерархией наследования:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
isinstance(object, class)
| Возвращает True, если object является экземпляром class |\n", "|
issubclass(subclass, class)
| Возвращает True, если subclass является подклассом class |\n", "|
super(...)
| Возвращает специальный объект, с помощью которого можно вызывать в наследнике методы базового класса |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Следующие функции могут использоваться для выполнения кода на языке Python:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
eval(expression, ...)
| Выполняет выражение Python, записанное в строке expression |\n", "|
exec(code_block, ...)
| Выполняет произвольный блок кода Python, записанный в строке code_block |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функция `eval` позволяет выполнить простые выражения языка Python, записанные в обычной строке:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "24\n" ] } ], "source": [ "x = 7\n", "y = 5\n", "eval('print((x + y) * (x - y))')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функция `exec` обладает гораздо большими возможностями. По сути, с ее помощью можно выполнить текст любой корректной программы на языке Python:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(3.0, 2.0)\n" ] } ], "source": [ "program = '''\n", "def get_roots(a, b, c):\n", " d = b**2 - 4*a*c \n", " x1 = (-b + d**0.5) / (2*a)\n", " x2 = (-b - d**0.5) / (2*a)\n", " return x1, x2\n", " \n", "print(get_roots(1, -5, 6))\n", "'''\n", "\n", "exec(program)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В рассмотренном примере программа, записанная в строке, создает функцию `get_roots` для вычисления корней квадратного уравнения и затем вызывает ее. В функции `exec` это программа выполняется так, словно была написана в нашем исходном коде обычным образом, а это значит, что теперь в пространстве имен основной программы есть функция `get_roots` и можно писать:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(5.0, -2.0)\n" ] } ], "source": [ "print(get_roots(1, -3, -10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь расскажем о том, как происходит работа с файлами в Python. Для этого в модуле `builtins` есть следующая функция:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
open(filename, mode='r', ...)
| Открывает файл с именем filename в режиме mode ('r', 'w', 'a' - чтение, запись, добавление в конец).
Возвращает объект, который будет использоваться для работы с файлом. | " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. В режиме чтения **r** информацию можно только прочитать из файла, записать в него ничего нельзя. Если указанный файл не существует, интерпретатор генерирует исключение `FileNotFoundError`.\n", "2. В режиме записи **w** в файл можно только записать данные. Если указанный файл не существует, то он создается. Если же существует, то немедленно полностью очищается\n", "3. Режим добавления **a** отличается от режима записи только то, что в случае добавления, файл не очищается при открытии, а все записываемые данные вставляются в его конец." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По умолчанию, функция `open` считает, что файл, который нужно открыть, является текстовым. Примером таких файлов являются файлы *.txt*, *.html*, *.xml* и многие другие. Их отличает то, что информация в них хранится в виде строк печатных символов (букв, цифр и т.д.). Большинство файлов, однако, являются двоичными, т.е. содержащими произвольный набор байтов, а не только печатные символы. Указать функции `open`, что открыть файл нужно как двоичный, можно, добавив к аргументу `mode` символ \"b\", например \"rb\" (прочитать двоичный файл) или \"wb\" (записать двоичный файл). Работа с двоичными файлами в целом аналогична работе с текстовыми, поэтому мы не будем рассматривать ее отдельно." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "При открытии текстового файла, функция `open` возвращает объект класса `TextIOWrapper`, с помощью которого можно получить доступ к следующим методам (часть из них определена в самом классе, а часть - в его родителях):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
read(size=-1)
| Считывает не более size байт из файла (по умолчанию - все) |\n", "|
readline(...)
| Считывает одну строку из файла (если весь файл прочитан, возвращает пустую строку) |\n", "|
readlines(...)
| Возвращает список, содеражащий строки файла |\n", "|
write(s)
| Записывает строку в файл |\n", "|
writelines(l)
| Записывает список строк в файл |\n", "|
flush()
| Сохраняет данные из внутреннего буфера на диск |\n", "|
close()
| Сохраняет данные из внутреннего буфера на диск и закрывает файл |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Стоит подробнее рассказать про функции `flush` и `close`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Первая нужна потому, что когда вы записываете что-то в файл, данные на самом деле не попадают сразу на жесткий диск. Вместо этого они помещаются в некоторый буфер в оперативной памяти, который целиком сохраняется на диск, только если он становится достаточно большим, либо вызывается функция `flush`. Такой алгоритм используется потому, что операции с жестким диском выполняются очень медленно (на 3 порядка медленнее операций с оперативной памятью), причем значительное влияние на это оказывается не только размером данных, но и другими факторами. Проще говоря, 10 записей на диск одного байта будут выполняться значительно дольше, чем одна запись 10 байт. Буфер как раз и позволяет накопить много данных, а потом сохранить их все одной операцией." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Функция `close` используется для того, чтобы закрыть файл. При закрытии, во-первых, происходит сохранение внутреннего буфера (автоматически вызывается `flush`), а во вторых - освобождаются некоторые системные ресурсы, которые потребляет каждый открытый файл. Вообще говоря, явно вызывать метод `close` для объекта `TextIOWrapper` необязательно, потому что он будет вызван автоматически, когда объект станет не нужен и будет удален сборщиком мусора. Но мы все же рекомендуем явно делать это, потому что сборщик мусора может достаточно долго не удалять объект, а значит все это время файл будет оставаться открытым, и часть изменений будет храниться в буфере. Это чревато также тем, что если возникнет необрабатываемое исключение, и программа аварийно завершится, то буфер вообще не будет сохранен, и все изменения, которые были в нем, будут утеряны." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Давайте теперь перейдем к простым примерам. Для начала создадим в нашей операционной системе простой текстовый файл *text.txt*, в который поместим несоколько строк." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Следующий код прочитает файл и выведет его на экран:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "My uncle — high ideals inspire him;\n", "\n", "but when past joking he fell sick,\n", "\n", "he really forced one to admire him —\n", "\n", "and never played a shrewder trick.\n" ] } ], "source": [ "file = open('test.txt')\n", "\n", "while True:\n", " line = file.readline()\n", " \n", " if len(line) != 0:\n", " print(line)\n", " else:\n", " # all read\n", " break\n", "\n", "file.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Тип `TextIOWrapper` является итерируемым (итерация происходит по строкам), поэтому пример можно реализовать изящнее:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "My uncle — high ideals inspire him;\n", "\n", "but when past joking he fell sick,\n", "\n", "he really forced one to admire him —\n", "\n", "and never played a shrewder trick.\n" ] } ], "source": [ "file = open('test.txt')\n", "for line in file:\n", " print(line)\n", "file.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Попробуем теперь записать что-нибудь в текстовый файл:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "file = open('test2.txt', 'w') # режим записи!\n", "file.write('this is first line\\n') # '\\n' означает переход на новую строку\n", "file.write('this is second line')\n", "file.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В заключение рассмотрим еще один пример:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "ename": "SomeError", "evalue": "", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mSomeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[0mfile\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'line1\\n'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 12\u001b[0m \u001b[0mfile\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'line2'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 13\u001b[1;33m \u001b[0msome_processing\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 14\u001b[0m \u001b[0mfile\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclose\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;32m\u001b[0m in \u001b[0;36msome_processing\u001b[1;34m()\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;31m# выполнения которой может быть сгенерировано исключение\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0msome_processing\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mSomeError\u001b[0m\u001b[1;33m;\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;31m# далее идет реализация основной программы\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mSomeError\u001b[0m: " ] } ], "source": [ "class SomeError(Exception): pass\n", "\n", "# эта функция моделирует какую-то полезную работу, во время\n", "# выполнения которой может быть сгенерировано исключение\n", "def some_processing():\n", " raise SomeError;\n", "\n", "# далее идет реализация основной программы\n", " \n", "file = open('test3.txt', 'w')\n", "file.write('line1\\n')\n", "file.write('line2')\n", "some_processing()\n", "file.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если посмотреть на результат выполнения этой программы, то окажется, что файл *test3.txt* создан, но пустой. Это произошло потому, что записанные в него данные не были сохранены на диск, а находились в буфере, когда было сгенерировано исключение. Это исключение прервало выполнение программы, и инструкция `file.close()`, которая записала бы данные из буфера на диск, так и не была выполнена. Чтобы такой ошибки не возникало, мы можем воспользоваться блоком `try...except...finally`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "file = None\n", "try:\n", " file = open('test3.txt', 'w')\n", " file.write('line1\\n')\n", " file.write('line2')\n", " some_processing()\n", "except:\n", " print('Error occurred')\n", "finally:\n", " if file:\n", " file.close()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь все отработало правильно - две строки оказались записаны в файл *test3.txt*. Можно было бы на этом остановиться, но мы хотим рассказать про более элегантный способ достижения такого же результата. Он основывается на инструкции `with`, имеющей следующий синтаксис:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n",
    "with expression as variable:\n",
    "    code_block\n",
    "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Инструкция `with` используется для создания **менеджера контекста** - объекта, который реализует специальные методы `__enter__` и `__exit__`. Метод `__enter__` вызывается, когда менеджер контекста создается с помощью выражения *expression*, а метод `__exit__` - когда прекращается выполнение *code_block* (в том, числе, из-за возникшего исключения)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Объекты, возвращаемые функций `open`, являются менеджерами контекста, причем в своей реализации метода `__exit__` они закрывают файл, для которого были созданы. Это позволяет переписать предыдущую версию программы без блока `finally`:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error occurred\n" ] } ], "source": [ "try:\n", " with open('test3.txt', 'w') as file:\n", " file.write('line1\\n')\n", " file.write('line2')\n", " some_processing()\n", "except:\n", " print('Error occurred')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Мы рекомендуем использовать такой подход для работы с файлами, потому что получающийся в результате код короче, чем дла подхода с блоком `finally`, и столь же безопасен." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Модули math и random" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Модуль `math` предоставляет различные математические функции и константы." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
ceil(x)
| Возвращает наименьшее целое число большее или равное числу x |\n", "|
floor(x)
| Возвращает наибольшее целое число меньшее или равное числу x |\n", "|
gcd(x, y)
| Возвращает наибольший общий делитель целых чисел x и y |\n", "|
exp(x)
| Возвращает экспоненту в степени x |\n", "|
log(x, base=e)
| Возвращает логарифм числа x по основанию base |\n", "|
pow(x, y)
| Возвращает число x, возведенное в степень y |\n", "|
sqrt(x)
| Возвращает квадратный корень из x |\n", "|
cos(x)
| Возвращает косинус для угла в x радиан |\n", "|
sin(x)
| Возвращает синус для угла в x радиан |\n", "|
tan(x)
| Возвращает тангенс для угла в x радиан |\n", "|
acos(x)
| Возвращает арккосинус числа x в радианах |\n", "|
asin(x)
| Возвращает арксинус числа x в радианах |\n", "|
atan(x)
| Возвращает арктангенс числа x в радианах |\n", "|
degrees(x)
| Преобразует угол x из радиан в градусы |\n", "|
radians(x)
| Преобразует угол x из градусов в радианы |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Среди констант, определенных в модуле `math` есть $\\pi$ (`math.pi`) и $e$ (`math.e`)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Рассмотрим небольшой пример использования функций из модуля `math`:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 3 4\n", "0.7071067811865476\n", "4.668783061171329\n", "2.0\n" ] } ], "source": [ "import math\n", "x = 3.77\n", "\n", "# исторически сложилось, что функция, которая выполняет обычное округление\n", "# в соответствии с правилами арифметики, определена в модуле builtins\n", "print(math.ceil(x), math.floor(x), round(x))\n", "\n", "angle = 45 # в градусах\n", "print(math.cos(math.radians(45)))\n", "\n", "print(math.pow(3.5, 1.23))\n", "print(math.log(100, 10))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Модуль `random` предоставляет функции для генерации псевдослучайных величин с различными распределениями. Псевдослучайность означает, что на самом деле внутри функций модуля используется некоторая очень сложная, но тем не менее, вполне детерминированная математическая формула для получения следующего \"случайного\" значения. По этой причине, функции из модуля `random` не подходят для решения задач, связанных с шифрованием данных, но вполне могут быть использованы для моделирования каких-либо случайных событий или процессов." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
seed(data=None, ...)
| Инициализирует с помощью data начальное значение, с которого начнется генерация случайных чисел |\n", "|
choice(seq)
| Возвращает случайный элемент из последовательности |\n", "|
shuffle(seq, ...)
| Перемешивает случайным образом элементы последовательности |\n", "|
random()
| Возвращает следующее случайное число из диапазона [0.0, 1.0) |\n", "|
uniform(a, b)
| Возвращает случайное число, распределенное равномерно в интервале [a, b] |\n", "|
expovariate(lambda)
| Возвращает случайное число, распределенное по экспоненциальному закону с параметром lambda |\n", "|
normalvariate(mu, sigma)
| Возвращает случайное число, распределенное по нормальному закону с параметрами mu и sigma |\n", "|
randint(a, b)
| Возвращает целое случайное число из диапазона [a, b] |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Немного поясним функцию `seed`. Как уже было сказано, все функции модуля `random` генерируют псевдослучайные числа, высчитывая каждое следующее из предыдущего по сложной формуле. Функция `seed` задает начальное значение, из которого потом будут получаться все \"случайные\" числа. Это означает, что для одного и того же аргумента `seed` будет возвращаться одна и та же последовательность чисел:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.6555960613392863 0.7346312914910028 0.25037423249421464\n", "0.6555960613392863 0.7346312914910028 0.25037423249421464\n" ] } ], "source": [ "import random\n", "\n", "random.seed('test')\n", "print(random.random(), random.random(), random.random())\n", "\n", "random.seed('test')\n", "print(random.random(), random.random(), random.random())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По этой причине функцию `seed` как правило вызывают без аргументов - в этом случае используется текущее время, которое постоянно меняется, и следовательно с каждым запуском программы последовательность \"случайных\" числе будет другая." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Модуль datetime" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Этот модуль содержит реализацию нескольких типов для работы с датой и временем:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Класс `date` представляет дату и имеет такие атрибуты, как `year`, `month` и `day`.\n", "2. Класс `time` представляет время и имеет такие атрибуты, как `hour`, `minute`, `second`, `microsecond`.\n", "3. Класс `datetime` представляет дату и время, объединяя предыдущие два типа и их атрибуты.\n", "4. Класс `timedelta` представляет разницу между двумя моментами времени и имеет такие атрибуты, как `days`, `seconds` и `microseconds`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В классе `datetime` существует несколько методов, которые можно вызывать без объекта (они определены с помощью декоратора [`classmethod`](08_Classes_And_Exceptions.ipynb#classmethod). Они реализуют разные способы создания объекта класса `datetime`:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
today()
| Возвращает объект класс datetime для текущего времени |\n", "|
combine(date, time)
| Возвращает объект класса datetime, созданный из объектов классов date и time |\n", "|
fromtimestamp(timestamp, ...)
| Возвращает объект класса datetime, созданный из timestamp (см. далее) |\n", "|
strptime(datetime_str, format_spec)
| Возвращает объект класса datetime, созданный из строки datetime_str |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Метод `fromtimestamp` создает объект `datetime` из таймстэмпа (этот термин в русском языке употребляется именно так, если вы скажете \"временная отметка\", вас не поймут), представляющего собой количество секунд, прошедших с начала времен. Это начало времен может отличаться для разных операционных систем, но в большинстве случаев в качестве него используется 01/01/1970 00:00:00 по Гринвичу. Таймстампы являются удобным вариантом хранения информации о дате и времени, и часто используются в операционных системах, базах данных и т.д." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Метод `strptime` принимает в качестве параметра строку `format_spec`, которая описывает, как именно компоненты даты и времени записаны в `datetime_str`. Для этого в `format_spec` можно использовать следующие заполнители: *%Y* (год, 4 знака), *%y* (год, 2 знака), *%m* (месяц), *%A* (день недели), *%d* (день), *%H* (час), *%M* (минута), *%S* (секунда) и другие." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2017.2.1 23:59:59\n" ] } ], "source": [ "from datetime import datetime\n", "datetime_str = '01.02.17 23:59:59'\n", "dt = datetime.strptime(datetime_str, '%d.%m.%y %H:%M:%S')\n", "print('{}.{}.{} {}:{}:{}'.format(\\\n", " dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Перечислим некоторые методы экземпляров класса `datetime`:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
date()
| Возвращает объект класса date для представления даты |\n", "|
time()
| Возвращает объект класса time для представления времени |\n", "|
timestamp()
| Возвращает таймстэмп, соответствующий объекту |\n", "|
weekday()
| Возвращает число, обозначающее день недели (0 - понедельник, 1 - вторник, ...) |\n", "|
strftime(format_spec)
| Возвращает строковое представление даты, сформированное в соотвествии с format_spec |" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Today is Wednesday, 04. Current time is 14:43:27\n" ] } ], "source": [ "from datetime import date\n", "dt = datetime.today()\n", "print(dt.strftime('Today is %A, %d. Current time is %H:%M:%S'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Класс `timedelta` дополняет функциональность `datetime`, предоставляя возможность удобно изменять объекты последнего, а также вычислять разницу между произвольными моментами времени. Объекты класса `timedelta` имеют атрибуты `days`, `seconds` и `microseconds`. Его конструктор может принимать любой набор из следующих именованных аргументов: `days`, `seconds`, `microseconds`, `milliseconds`, `minutes`, `hours` и `weeks`." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2018-08-08 15:34:29.521114\n", "1076769809\n" ] } ], "source": [ "from datetime import datetime, timedelta\n", "delta = timedelta(days=35, minutes=51)\n", "dt = datetime.today()\n", "\n", "# какое время будет через 35 дней и 51 минуту?\n", "\n", "dt += delta \n", "print(dt)\n", "\n", "# сколько секунд между 21-05-1984 и сегодняшним днем?\n", "\n", "dt1 = datetime.strptime('21-05-1984','%d-%m-%Y')\n", "dt2 = datetime.today()\n", "\n", "delta = dt2 - dt1\n", "seconds_in_day = 86400\n", "print(delta.days * 86400 + delta.seconds)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Методы классов `date` и `time` повторяют те, которые есть в классе `datetime`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Модуль threading" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Этот модуль позволяет создавать программы, способные в один момент времени параллельно выполнять несколько задач. Перед тем, как мы приступим к изучению функций и типов, предоставляемых им, нам нужно дать несколько определений." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Операционной системой называют совокупность множества программ, предназначенных для управления ресурсами компьютера и выполняющимися пользовательскими программами, которые называются **процессами**. Пока процесс выполняется, ОС следит за ним и отслеживает все его обращения к общим ресурсам компьютера (таким как оперативная память, жесткий диск, сетевая карта и т.д.). Цели, которые достигаются благодаря этому, следующие:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Изоляция одного процесса от всех остальных. Это нужно для того, чтобы процессы не могли случайно или намеренно повредить работе друг друга (например, изменив участок памяти, используемый другим процессом).\n", "* Контроль ресурсов позволяет ОС точно знать, какие из них используются каждым процессом. Это позволяет обнаруживать \"подозрительные\" процессы (потребляющие слишком много ресурсов), а также гарантировать освобождение занятых ресурсов, когда процесс завершается (обычным образом или же из-за возникшего исключения)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Каждый процесс имеет один или несколько **потоков** (англ. *thread*), которые явлются наименьшей единицей обработки, выполнение которой ОС может поручить центральному процессору. Возможно у вас возникнет вопрос, как операционная система позволяет одновременно работать нескольким программам, многие из которых (особенно браузеры), создают массу потоков. Даже если считать, что у нас в компьютере установлен процессор с несколькими ядрами, каждое из которых работает параллельно, этого все равно не хватит - потоков будет намного больше." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![Потоки](./images/10/threads.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ответ заключается в том, что в параллельность в современных операционных системах (Windows, Linux, MacOS) достигается с помощью алгоритма **вытесняющей многозадачности**. Суть его в том, что ОС предоставляет каждому потоку небольшой квант времени (несколько миллисекунд), в течение которого он выполняется процессором. Когда это время заканчивается, ОС заменяет выполняющийся поток на другой." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Современные процессоры уже вплотную приблизились к максимально возможной производительности, поэтому все большее значение для быстродействия программ приобретает грамотное использование параллельных вычислений. Когда запускается программа Python, для нее создается процесс и один поток, в котором будет выполняться ее исходный код. С помощью модуля `threading` можно запустить дополнительные потоки, и выполнить некоторые части исходного кода параллельно с основной программой." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Самым важным типом данных, предоставляемым модулем `threading` является класс `Thread`, имеющий следующие методы:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
__init__(group=None, target=None, ...)
| Конструктор класса, в параметре target нужно передать вызываемый объект |\n", "|
start()
| Запускает поток, а затем начинает выполнять в нем метод __call__ вызываемого объекта target |\n", "|
join()
| Ждет завершения потока, которое происходит тогда, когда метод __call__
вызываемого объекта target выполняет инструкцию return |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В следующем примере мы используем функцию `sleep` из модуля `time`, которая заставляет поток \"уснуть\" на указанное время. Остальные функции этого модуля не представляют большого интереса." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "second thread: 14:43:34 -- 0\n", "\n", "main thread: 14:43:34 -- 0\n", "\n", "main thread: 14:43:37 -- 1\n", "\n", "second thread: 14:43:39 -- 1\n", "\n", "main thread: 14:43:40 -- 2\n", "\n", "main thread: 14:43:43 -- 3\n", "\n", "second thread: 14:43:44 -- 2\n", "\n", "main thread: 14:43:46 -- 4\n", "\n", "second thread: 14:43:49 -- 3\n", "\n", "second thread: 14:43:54 -- 4\n", "\n", "all done\n" ] } ], "source": [ "import time\n", "import datetime\n", "import threading\n", "\n", "class ThreadOperation:\n", " def __call__(self): # этот метод будет выполняться в отдельном потоке!\n", " for i in range(5):\n", " current_time = datetime.datetime.today().time()\n", " \n", " print('')\n", " print('second thread: {} -- {}'.format(\\\n", " current_time.strftime('%H:%M:%S'), i))\n", " \n", " time.sleep(5) # спим 5 секунд\n", "\n", "\n", "# основной поток\n", "\n", "second_thread = threading.Thread(target=ThreadOperation())\n", "second_thread.start()\n", "\n", "for i in range(5):\n", " current_time = datetime.datetime.today().time()\n", " \n", " print('')\n", " print('main thread: {} -- {}'.format(\\\n", " current_time.strftime('%H:%M:%S'), i))\n", " \n", " time.sleep(3) # спим 3 секунды\n", "\n", "# нужно ОБЯЗАТЕЛЬНО дожидаться завершения дополнительных потоков,\n", "# иначе при завершении основной программы они будут остановлены\n", "# операционной системой, и могут не успеть выполнить свою работу\n", "second_thread.join()\n", "\n", "# на эту строку кода мы перейдем после того, как завершится second_thread\n", "print('')\n", "print('all done')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "При разработке многопоточных программ особое внимание нужно уделять защите общих для нескольких потоков данных. Если ни один из потоков не изменяет эти общие данные, то проблем никаких нет. Но если хотя бы один из них производит модификацию, то может произойти очень неприятная ошибка. Рассмотрим пример:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1193943\n" ] } ], "source": [ "import threading\n", "\n", "# это общая переменная для двух потоков\n", "common_variable = 0\n", "\n", "class ThreadOperation:\n", " def __call__(self):\n", " global common_variable\n", " for i in range(1000000):\n", " common_variable = common_variable + 1\n", "\n", "\n", "# основной поток\n", "\n", "second_thread = threading.Thread(target=ThreadOperation())\n", "second_thread.start()\n", "\n", "for i in range(1000000):\n", " common_variable = common_variable + 1\n", "\n", "second_thread.join()\n", "\n", "print(common_variable)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Давайте проанализируем результат. Казалось бы, оба потока должны были увеличить значение `common_variable` на миллион, значит в результате значением переменной должно было стать два миллиона, но это не так. Более того, если вы несколько раз запустите программу, то увидите, что результаты будут разными. Почему это происходит?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для ответа на этот вопрос нужно вспомнить, как работает вытесняющая многозадачность. Основной ее принцип в том, что каждый поток работает немного, а потом операционная система вместо него запускает другой, причем происходит это без согласия вытесняемого потока и в произвольный момент его работы. Например, вот так может выглядеть последовательность инструкций, которые выполнялись на процессоре:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. `common_variable` = 0\n", "2. основной поток прочитал значение `common_variable` (0)\n", "3. основной поток прибавил 1 к прочитанному значению, получил 1\n", "4. ОС вытеснила основной поток с процессора и поместила туда второй\n", "5. второй поток прочитал значение `common_variable` (оно по-прежнему 0, ведь основной поток не успел записать 1 в память!)\n", "6. второй поток прибавил 1 к прочитанному значению, получил 1\n", "7. второй поток записал 1 в `common_variable`\n", "8. ОС вытеснила второй поток с процессора и поместила туда основной\n", "9. основной продолжил свое выполнение с того места, где его прервали и записал 1 в `common_variable`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видите, оба потока по разу прибавили 1 к `common_variable`, но его значение стало 1, а не 2!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Подобные ошибки случаются сплошь и рядом в многопоточных программах. Обнаруживать их очень сложно, потому что они воспроизводятся случайным образом и обычно приводят к аварийному завершению программы. Для того, чтобы предыдущий пример работал правильно, нам нужно **синхронизировать** доступ потоков к общим данным таким образом, чтобы когда один поток изменяет их, другой не мог к ним обратиться." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Простейший способ добиться этого - использовать **блокировку** (англ. *lock*), представляющую собой специальный объект для синхронизации доступа к общим данным. Типом блокировки является класс `Lock`, который предоставляет два метода:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. `acquire(...)` - захватить блокировку. Если блокировка уже захвачена, то поток, который пытается ее захватить, будет ждать, пока она не освободится.\n", "2. `release()` - освободить блокировку. После этого она может быть захвачена другим потоком." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Покажем, как переписать наш пример, чтобы не было ошибки:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2000000\n" ] } ], "source": [ "import threading\n", "\n", "# общая переменная для двух потоков\n", "common_variable = 0\n", "\n", "# блокировка для синхронизации доступа к общей переменной\n", "common_lock = threading.Lock()\n", "\n", "class ThreadOperation:\n", " def __call__(self):\n", " global common_variable\n", " global common_lock\n", " \n", " for i in range(1000000):\n", " common_lock.acquire()\n", " common_variable = common_variable + 1\n", " common_lock.release()\n", "\n", "\n", "# основной поток\n", "\n", "second_thread = threading.Thread(target=ThreadOperation())\n", "second_thread.start()\n", "\n", "for i in range(1000000):\n", " common_lock.acquire()\n", " common_variable = common_variable + 1\n", " common_lock.release()\n", "\n", "second_thread.join()\n", "\n", "print(common_variable)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видите, теперь доступ к переменной `common_variable` полностью синхронизирован - любое обращение к ней происходит только тогда, когда поток захватил блокировку, а значит другой поток не сможет это сделать. Из этого вытекают следующие правила работы с блокировками:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Захват блокировки должен происходить **до** любого обращения к общим данным, а освобождение - **после**.\n", "2. Нужно стремиться к тому, чтобы размер области, охраняемой блокировкой, был небольшой и не содержал долго выполняемых инструкций. Если один поток долго не осовобождает блокировку, то другие просто ждут и ничего не делают. По сути, многопоточная программа превращается в однопоточную.\n", "3. Если хотя бы один из потоков нарушает правила использования блокировок при доступе к общим данным, то вся система неэффективна." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Еще одним полезным типом, используемым для синхронизации потоков, является `Condition`. Он используется тогда, когда потоку нужно ждать наступления некоторого события, которое инициируется другим потоком. Объект `Condition` использует объект `Lock` для реализации следующих методов:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "|
Название
| Описание |\n", "|----------------------------------|----------|\n", "|
__init__(lock=None)
| Конструктор класса, в параметре lock можно передать блокировку, которую будет
использовать объект (если None, то блокировка будет создана автоматически) |\n", "|
acquire(...)
| Захватывает блокировку, связанную с объектом |\n", "|
release()
| Освобождает блокировку, связанную с объектом |\n", "|
wait(timeout=None)
| Ждет, пока не придет нотификация или не истечет таймаут (None трактуется как бесконечный таймаут) |\n", "|
notify(...)
| Нотифицирует один поток из тех, что ждут нотификации (поток выбирается случайным образом) | \n", "|
notify_all()
| Нотифицирует все потоки, которые ждут нотификации |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Методы `acquire` и `release` работают так же, как и для блокировок (собственно, они просто вызывают соответствующий метод у блокировки)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Метод `wait` должен вызываться только после того, как была захвачена блокировка с помощью метода `acquire`. В этом случае, он освобождает ее, а затем поток, который вызывал метод `wait` \"засыпает\" до тех пор, пока он не будет нотифицирован с помощью метода `notify` или `notify_all`. Когда это происходит, поток \"просыпается\", снова захватывает блокировку и только после этого происходит возврат из функции `wait`. Важно запомнить, что при выходе из `wait` блокировка уже захвачена, и можно спокойно модифицировать общие данные." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Методы `notify` и `notify_all` также должны вызываться тогда, когда блокировка уже захвачена с помощью метода `acquire`. Методы \"пробуждают\" один или все потоки, вызвавшие `wait` для этого объекта `Condition`. После этого происходит возврат из методов `notify`и `notify_all`, причем блокировка остается захваченной. Чтобы потоки, которые были \"разбужены\", смогли выполнить свой код, ее нужно освободить с помощью метода `release`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В качестве примера рассмотрим типичную задачу, возникающую при создании многопоточных программ. Она заключается в том, что один поток создает какие-то данные, а другой - обрабатывает их. Между этими потоками находится список или другая подходящая коллекция, в которую первый поток помещает данные, а второй затем забирает их из нее и выполняет обработку. Эта модель иногда называется \"producer-consumer\", и без объекта `Condition` ее реализация может выглядеть так:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "14:44:54 processed: 1\n", "14:44:55 processed: 4\n", "14:44:56 processed: 9\n", "14:44:56 processed: 16\n", "14:45:00 processed: 25\n", "14:45:03 processed: 0\n", "all done\n" ] } ], "source": [ "import time\n", "import random\n", "\n", "from datetime import datetime\n", "from threading import Thread\n", "from threading import Lock\n", "\n", "\n", "# общий буфер и блокировка для него\n", "buffer = list()\n", "buffer_lock = Lock()\n", "\n", "class ProducerOp:\n", " def __call__(self):\n", " for i in range(1, 6):\n", " buffer_lock.acquire()\n", " buffer.append(i)\n", " buffer_lock.release()\n", " time.sleep(random.randint(0, 5)) # пусть поток спит 0-5 секунд\n", " \n", " # вставляем в список специальное значение, по которому\n", " # consumer-поток определит, что больше данных не будет\n", " buffer_lock.acquire()\n", " buffer.append(0)\n", " buffer_lock.release()\n", "\n", "class ConsumerOp:\n", " def __call__(self):\n", " should_exit = False\n", " \n", " # постоянно в цикле пытаемся обнаружить новый элемент в буфере\n", " while not should_exit:\n", " buffer_lock.acquire()\n", " \n", " if len(buffer) != 0:\n", " value = buffer.pop(0)\n", " time_str = datetime.today().time().strftime('%H:%M:%S')\n", " print(time_str, 'processed:', value ** 2)\n", " \n", " if value == 0:\n", " should_exit = True\n", " \n", " buffer_lock.release()\n", "\n", "\n", "# основной поток\n", "\n", "producer = Thread(target=ProducerOp())\n", "consumer = Thread(target=ConsumerOp())\n", "\n", "producer.start()\n", "consumer.start()\n", "\n", "producer.join()\n", "consumer.join()\n", "\n", "print('all done')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этой реализации есть один очень существенный недостаток - поток, обрабатывающий данные, вынужден постоянно \"крутиться\" в цикле, чтобы обнаруживать и обрабатывать новые элементы. Такие потоки обеспечивают процессору загрузку, близкую к 100%. Гораздо лучше было бы, чтобы все время, пока данные в списке отсутствуют, поток просто \"спал\" и не загружал процессор вообще. Добиться этого и позволяет объект `Condition`, которым вы воспользуемся в новой реализации нашей программы:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "14:45:14 processed: 1\n", "14:45:15 processed: 4\n", "14:45:16 processed: 9\n", "14:45:20 processed: 16\n", "14:45:22 processed: 25\n", "14:45:26 processed: 0\n", "all done\n" ] } ], "source": [ "import time\n", "import random\n", "\n", "from datetime import datetime\n", "from threading import Thread\n", "from threading import Condition\n", "\n", "\n", "buffer = list()\n", "buffer_cond = Condition()\n", "\n", "class ProducerOp:\n", " def __call__(self):\n", " for i in range(1, 6):\n", " # захватываем блокировку, чтобы записать элемент в общий буфер\n", " buffer_cond.acquire()\n", " buffer.append(i)\n", " \n", " # нотифицируем один поток, что в буфере теперь есть элемент\n", " buffer_cond.notify()\n", " \n", " # осовобождаем блокировку, чтобы consumer мог обработать элемент\n", " buffer_cond.release()\n", " \n", " time.sleep(random.randint(0, 5)) # пусть поток спит 0-5 секунд\n", " \n", " # вставляем в список специальное значение, по которому\n", " # consumer-поток определит, что больше данных не будет\n", " buffer_cond.acquire()\n", " buffer.append(0)\n", " buffer_cond.notify()\n", " buffer_cond.release()\n", "\n", "class ConsumerOp:\n", " def __call__(self):\n", " should_exit = False\n", " \n", " while not should_exit:\n", " buffer_cond.acquire()\n", " \n", " if len(buffer) == 0:\n", " # буфер пустой, поэтому ждем, пока появится следующий элемент\n", " buffer_cond.wait()\n", " \n", " # дождались, обрабатываем элемент (блокировка уже захвачена, не\n", " # нужно опять вызывать acquire!)\n", " \n", " value = buffer.pop(0)\n", " time_str = datetime.today().time().strftime('%H:%M:%S')\n", " print(time_str, 'processed:', value ** 2)\n", " \n", " if value == 0:\n", " should_exit = True\n", " \n", " # освобождаем блокировку, чтобы producer смог добавить новый элемент\n", " buffer_cond.release()\n", "\n", "\n", "# основной поток\n", "\n", "producer = Thread(target=ProducerOp())\n", "consumer = Thread(target=ConsumerOp())\n", "\n", "producer.start()\n", "consumer.start()\n", "\n", "producer.join()\n", "consumer.join()\n", "\n", "print('all done')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Вопросы для самоконтроля" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Объясните, как происходит запись данных в файл? Для чего нужен метод `flush`?\n", "2. Что такое менеджер контекста? Для чего его можно использовать?\n", "3. Безопасно ли использовать функции из модуля `random` для генерации пароля пользователя в какой-нибудь системе? Объясните ответ.\n", "4. Что такое процесс и поток?\n", "5. Каким образом в совеременных ОС реализовано параллельное выполнение множества программ?\n", "6. Что такое синхронизация потоков? Для чего она нужна? Какие объекты в Python используются для обеспечения ее?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Задание" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1. Создайте программу, которая получает на вход список, в котором перечислено произвольное количество текстовых файлов *file1*, ..., *fileN*, а в результате создает файлы *file1_stat*, ..., *fileN_stat*, в которых представлена информация о том, сколько раз встречается то или иное слово в соответствующем файле *fileK*. Эта программа должна содержать минимум три потока:\n", " * поток, который считывает данные из файла *fileK* в строку\n", " * поток, который подсчитывает, сколько раз встречается каждое слово в файле\n", " * поток, который записывает эту информацию в файл *fileK_stat* в виде строк \"слово: сколько\\_раз\\_встречается\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- - -\n", "[Предыдущая: Коллекции](09_Collections.ipynb) |\n", "[Содержание](00_Overview.ipynb#Содержание) |\n", "[Следующая: Библиотеки NumPy, Matpotlib, pandas](11_NumPy_Matplotlib_Pandas.ipynb)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }