Какие версии python являются несовместимыми по операциям ввода и вывода
Перейти к содержимому

Какие версии python являются несовместимыми по операциям ввода и вывода

  • автор:

Основы программирования на языке Python. Часть 1

С чем у вас ассоциируется слово «питон»? Если со змеей, то пора мыслить шире. В этой статье мы узнаем, в чем особенности программирования на «змеином» языке, выберем себе необходимые инструменты для работы и напишем свою первую программу.

Вводная информация. Инструменты для работы

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

Как подготовиться к написанию программ?

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

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

  • IDLE. Эта среда разработки идет в комплекте с интерпретатором и является самой простой из всех. Во всех смыслах слова «простой».
  • малый вес и небольшое количество потребляемой памяти позволят запустить его на любом устройстве;
  • максимально простая организация — отдельный файл открывается в отдельной вкладке.
  • никакого дополнительного функционала — это просто блокнот, который лишь немного выделит код для удобства и запустит его.
  • PyCharm. Это как танк Т-34, по сравнению с которым IDLE — трехколесный велосипед.
  • мощный функционал, в том числе более удобная подсветка кода, автоматическая помощь при написании кода, удобное выделение ошибок прямо в коде и так далее.
  • требовательность — установленная среда разработки весит около 1 гигабайта и потребляет большое количество ресурсов при своем запуске;
  • сложность внутренней организации — программа не позволяет запускать отдельно файлы с программами. Эти файлы должны объединяться в проекты, а настройка проектов иногда может быть довольно проблематичным занятием.

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

  • Sublime Text 3. Промежуточное звено между двумя предыдущими вариантами, которое вобрало по чуть-чуть всего хорошего из них, но с одним дополнительным минусом.
  • относительная «легкость» самой среды и малое количество потребляемых ресурсов;
  • отдельные файлы открываются в одной вкладке в разных окнах;
  • выделение ошибочных строк и небольшая помощь при написании кода.
  • все преимущества этой программы, похожие на преимущества PyCharm, работают не так хорошо, как у PyCharm;
  • невозможность ввода данных с клавиатуры. Если вы еще не знаете, что это и как — не расстраивайтесь, эта программа тоже не знает. О вводе данных с клавиатуры мы поговорим чуть ниже.

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

Вывод и ввод данных. Переменные

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

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

Вывод данных — это процесс отображения программой обработанных данных. В качестве способа вывода мы будем использовать консоль, то есть текстовое приложение, которое уже встроено в среды разработки — те самые IDLE, PyCharm и Sublime Text 3. С помощью этого приложения программа может отображать текст на экране компьютера.

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

print(“Hello world”)

После ее запуска будет выведено сообщение: Hello world.

Поздравляем, вы стали программистом!

А теперь начнем становиться хорошими программистами и разберемся с тем, что мы написали:

  • print – функция вывода данных на экран.
    После функции в скобках нужно записать текст, который мы хотим вывести. Если вписать в скобках несколько значений через запятую, они будут выведены на экран в одну строку через пробел.
  • “Hello world” – это строка (= просто кусочек текста), которую мы создали и сразу передали в функцию print.

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

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

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

Переменная — это именованная информация, которая хранится в компьютере при работе программы.

Схема создания переменной:

имя_переменной = значение

Давайте сохраним строку “Hello world” как переменную, присвоив ей имя s.

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

    Имя переменной может состоять только из букв латинского алфавита, цифр и символа «_».

  1. Хорошей практикой является давать переменным осмысленные имена.

Последний совет стоит раскрыть. Например, для строки “Hello world” можно было бы выбрать имя string (строка) или подобное, а для переменной, которая что-то считает — count (счет). Главное, чтобы было по названию понятно, что в ней.

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

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

Важно сразу запомнить: любые данные, введенные в программу с помощью команды input, будут переданы в формате строки.

Типы данных

Выше мы разобрались, что являет собой переменная s. А что еще можно записать в переменную?

Типы данных — виды значений, которые может принимать переменная.

Большую часть из типов данных мы подробно будем изучать по мере необходимости. Для начала неплохо хотя бы знать о существовании основных и об их обозначениях в Python:

    Целые числа (int) – хранят любые целые числа. Это ноль, а также все положительные и все отрицательные числа, используемые при счете.

Один тип данных может быть приведен к другому по обозначению. Например:

  • Дробное число можно привести к целому, прописав его внутри команды int — тогда его дробная часть просто отбрасывается. Если привести целое число к дробному командой float, оно получит десятичную дробную часть, равную 0.

  • Строки, состоящие только из цифр, можно привести к целочисленному типу командой int (допустим, для корректной работы математических операций), и наоборот — число можно сделать строкой командой str.

  • Логические значения имеют соответствие с целыми числами: значение True соответствует 1, а False – 0. Такие результаты мы и получим при переводе из числа в логическое значение командой bool, или наоборот — командой int.

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

Математические операции

Как работать с числами и какие математические действия применимы к строкам?

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

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

Например: 20 / 6 = 3 (ост. 2) – целой частью от деления будет 3, остатком от деления — 2.

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

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

Так, следующий код

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

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

Например, в коде

переменная а имеет тип int, переменная b имеет тип float, и их сумма, записанная в переменную с, тоже имеет тип float.

Какой приоритет операций будет соблюдаться в программе? Такой же, как в обычной математике:

  1. Возведение в степень.
  2. Умножение/деление.
  3. Сложение/вычитание.

Для изменения порядка операций также можно использовать скобки.

Сложение и умножение также применимы к строкам:

  • Складывать можно несколько строк — они будут «слипаться», создавая одну большую строку.

  • Возможно умножение строки на целое число. Тогда будет создана новая строка, состоящая из повторяющейся исходной.

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

Практика

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

Попробуем написать простую программу.

Пользователь вводит в программу 2 числа, программа возвращает ему их сумму.

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

Вариант 2. Эту же задачу можно решить другими способами:

В этом решении, в отличие от предыдущего, мы сохранили сумму в отдельную переменную summ и вывели на экран уже ее.

Вариант 3.

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

Вариант 4. Можно даже так:

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

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

Впереди нас ждет знакомство с еще большим количеством особенностей языка и их применением. Приглашаем вас продолжить изучение данной темы в статье «Основы программирования на языке Python. Часть 2».

Фактчек

  • Для работы программ, написанных на языке Python, необходим интерпретатор языка. Написание программы можно производить в любой из удобных сред разработки.
  • Для вывода данных на экран используется функция print(); для ввода данных с клавиатуры используется функция input().
  • Для долгосрочной работы с данными используются переменные, которым могут быть присвоены различные типы данных. Между типами данных возможен перевод, для этого используются их обозначения.
  • Python поддерживает все основные математические операторы для работы с числами.

Проверь себя

Задание 1.
Файл с расширением .py — это…

  1. ссылка на русскоязычный сайт
  2. файл программы Python
  3. установочный файл интерпретатора Python
  4. такого не бывает

Задание 2.
Выберите допустимое имя переменной:

  1. 123number
  2. constant!
  3. eto_Stroka
  4. count-strings

Задание 3.
Результатом записи выражения “23” * 2 будет:

Задание 4.
Результатом записи int(2.88) ** 4 будет:

Ответы: 1. — 2; 2. — 3; 3. — 2; 4. — 3.

Подробности о Python 2.6 и Python 3.0

Разработчики популярного языка веб-программирования Python готовят два больших релиза, запланированных на 2008 год.

Первым появится Python 2.6, который станет промежуточным звеном перед выходом Python 3.0, известного также под кодовым названием Python 3000. Альфа-версия Python 3.0 вышла в августе 2007 года, а финальный релиз ожидается в августе 2008 года. Бета-версия 2.6 появится примерно в феврале, финальная версия — в марте-апреле.

В третьей версии реализовано несколько важных нововведений, некоторые из которых несовместимы с 2.x. Об этом рассказал Дэвид Гуджер (David Goodger), директор Python Software Foundation. Кроме основной работы по развитию языка, эта организация занимается популяризацией Python и управлением правами на интеллектуальную собственность.

Целью релиза 2.6 является помочь разработчикам исследовать свой код на предмет несовместимостей с Python 3.0. В комплекте будет специальный инструмент для облегчения процесса миграции на новую платформу.

Главные особенности 3.0 — это тотальная поддержка Unicode, несколько новых операторов, новая библиотека ввода-вывода для лучшей совместимости со всеми операционными системами.

Большая презентация Python 3.0 состоится на конференции PyCon в Чикаго 14-16 марта 2008 года, а проведёт её ведущий разработчик Python, лично Гвидо ван Россум (Guido van Rossum). В своём блоге он подробно рассказывает, чего можно ждать от новой версии.

  • Python 2.6
  • Python 3.0
  • Python 3000
  • Гвидо ван Россум

Python 3.5 Асинхронное программирование с использованием asyncio

В последние годы асинхронное программирование приобрело большую популярность. Python 3.5 наконец-то получил некоторые синтаксические функции, закрепляющие концепции асинхронных решений. Но это не значит, что асинхронное программирование стало возможным только начиная с Python 3.5. Многие библиотеки и фреймворки были предоставлены намного раньше, и большинство из них имеют происхождение в старых версиях Python 2. Существует даже целая альтернативная реализация Python, называемая Stackless (см. Главу 1 «Текущее состояние Python»), которая сосредоточена на этом едином подходе программирования. Для некоторых решений, таких как Twisted, Tornado или Eventlet, до сих пор существуют активные сообщества, и их действительно стоит знать. В любом случае, начиная с Python 3.5, асинхронное программирование стало проще, чем когда-либо прежде. Таким образом, ожидается, что его встроенные асинхронные функции заменят большую часть старых инструментов, или внешние проекты постепенно превратятся в своего рода высокоуровневые фреймворки, основанные на встроенных в Python.

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

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

  • Green threads — Зеленые потоки (greenlet, gevent или eventlet проекты)
  • Coroutines — сопрограммы (чистое асинхронное программирование на Python 3.5)
  • Tasklets (Stackless Python) Это, в основном, те же понятия, но часто реализуемые немного по-другому.
Совместная многозадачность и асинхронный ввод / вывод

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

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

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

Чтобы избежать путаницы (из-за терминологии Python), теперь мы будем называть такие параллельные задачи сопрограммами. Самая важная проблема в совместной многозадачности — когда передать контроль. В большинстве асинхронных приложений управление передается планировщику или циклу событий при операциях ввода-вывода. Независимо от того, читает ли программа данные из файловой системы или осуществляет связь через сокет, такая операция ввода-вывода всегда связана с некоторым временем ожидания, когда процесс становится бездействующим. Время ожидания зависит от внешнего ресурса, поэтому это хорошая возможность освободить управление, чтобы другие сопрограммы могли выполнять свою работу, пока им тоже не придется ждать, что такой подход несколько похожим по поведению на то, как многопоточность реализована в Python. Мы знаем, что GIL сериализует потоки Python, но он также освобождается при каждой операции ввода-вывода. Основное различие заключается в том, что потоки в Python реализованы как потоки системного уровня, поэтому операционная система может в любой момент выгрузить текущий запущенный поток и передать управление другому.

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

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

Ключевые слова async и await в Python

Ключевые слова async и await являются основными строительными блоками в асинхронном программировании Python.

Ключевое слово async, используемое перед оператором def, определяет новую сопрограмму. Выполнение функции сопрограммы может быть приостановлено и возобновлено в строго определенных обстоятельствах. Его синтаксис и поведение очень похожи на генераторы (см. Главу 2 «Рекомендации по синтаксису» ниже уровня класса). Фактически, генераторы должны использоваться в более старых версиях Python для реализации сопрограмм. Вот пример объявления функции, которая использует ключевое слово async:

async def async_hello(): print("hello, world!") 

Функции, определенные с помощью ключевого слова async, являются специальными. При вызове они не выполняют код внутри, а вместо этого возвращают объект сопрограммы:

>>>> async def async_hello(): . print("hello, world!") . >>> async_hello()


Объект сопрограммы (coroutine object) ничего не делает, пока его выполнение не запланировано в цикле событий. Модуль asyncio доступен для предоставления базовой реализации цикла событий, а также множества других асинхронных утилит:

>>> import asyncio >>> async def async_hello(): . print("hello, world!") . >>> loop = asyncio.get_event_loop() >>> loop.run_until_complete(async_hello()) hello, world! >>> loop.close() 


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

Новые задачи можно добавить в цикл, вызвав метод loop.create_task () или предоставив другой объект для ожидания использования функции asyncio.wait (). Мы будем использовать последний подход и попытаемся асинхронно напечатать последовательность чисел, сгенерированных с помощью функции range ():

import asyncio async def print_number(number): print(number) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete( asyncio.wait([ print_number(number) for number in range(10) ]) ) loop.close() 

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

$ python asyncprint.py 0 7 8 3 9 4 1 5 2 6 


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

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

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

  • Подождать случайное количество секунд
  • Распечатать некоторый текст, предоставленный в качестве аргумента, и количество времени, проведенного в ожидании. Начнем с простой реализации, в которой есть некоторые проблемы параллелизма, которые мы позже попытаемся улучшить с помощью дополнительного использования await:

import time import random import asyncio async def waiter(name): for _ in range(4): time_to_sleep = random.randint(1, 3) / 4 time.sleep(time_to_sleep) print( "<> waited <> seconds" "".format(name, time_to_sleep) ) async def main(): await asyncio.wait([waiter("foo"), waiter("bar")]) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close() 
$ time python corowait.py bar waited 0.25 seconds bar waited 0.25 seconds bar waited 0.5 seconds bar waited 0.5 seconds foo waited 0.75 seconds foo waited 0.75 seconds foo waited 0.25 seconds foo waited 0.25 seconds real 0m3.734s user 0m0.153s sys 0m0.028s 

Как мы видим, обе сопрограммы завершили свое выполнение, но не асинхронно. Причина в том, что они обе используют функцию time.sleep (), которая блокирует, но не освобождает элемент управления в цикле событий. Это будет работать лучше в многопоточной установке, но мы не хотим сейчас использовать потоки. Итак, как мы можем это исправить?

Ответ заключается в том, чтобы использовать asyncio.sleep (), которая является асинхронной версией time.sleep (), и ожидать результата с помощью ключевого слова await. Мы уже использовали это утверждение в первой версии функции main (), но это было только для улучшения ясности кода. Это явно не делало нашу реализацию более параллельной. Давайте посмотрим на улучшенную версию сопрограммы waiter (), которая использует await asyncio.sleep():

async def waiter(name): for _ in range(4): time_to_sleep = random.randint(1, 3) / 4 await asyncio.sleep(time_to_sleep) print( "<> waited <> seconds" "".format(name, time_to_sleep) ) 

Запустив обновленный скрипт, мы увидим, как выходные данные двух функций чередуются друг с другом:

$ time python corowait_improved.py bar waited 0.25 seconds foo waited 0.25 seconds bar waited 0.25 seconds foo waited 0.5 seconds foo waited 0.25 seconds bar waited 0.75 seconds foo waited 0.25 seconds bar waited 0.5 seconds real 0m1.953s user 0m0.149s sys 0m0.026s 

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

Asyncio в предыдущих версиях Python

Модуль asyncio появился в Python 3.4. Так что это единственная версия Python, которая имеет серьезную поддержку асинхронного программирования до Python 3.5. К сожалению, похоже, что этих двух последующих версий достаточно, чтобы представить проблемы совместимости.

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

Начиная с Python 3.5, вы можете использовать async и await:

async def main (): await asyncio.sleep(0) 

Однако в Python 3.4, прийдется дополнительно применить asyncio.coroutine декоратор и yield в тексте сопрограммы:

@asyncio.couroutine def main(): yield from asyncio.sleep(0) 

Другим полезным фактом является то, что оператор yield from был введен в Python 3.3, а в PyPI имеется асинхронный бэкпорт. Это означает, что вы также можете использовать эту реализацию совместной многозадачности с Python 3.3.

Практический пример асинхронного программирования

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

В целях обеспечения согласованности мы попытаемся решить ту же проблему, которую решили с помощью многопоточности и многопроцессорности. Поэтому мы попытаемся асинхронно извлечь некоторые данные из внешних ресурсов через сетевое соединение. Было бы здорово, если бы мы могли использовать тот же пакет python-gmaps, что и в предыдущих разделах. К сожалению, мы не можем.

Создатель python-gmaps был немного ленив и взял лишь название. Чтобы упростить разработку, он выбрал пакет запросов в качестве своей клиентской библиотеки HTTP. К сожалению, запросы не поддерживают асинхронный ввод-вывод с async и await. Есть некоторые другие проекты, которые нацелены на обеспечение некоторого параллелизма для проекта запросов, но они либо полагаются на Gevent (grequests, см. Https://github.com/ kennethreitz / grequests), либо на выполнение пула потоков / процессов (запросы-futures, обратитесь к github.com/ross/requests-futures). Ни один из них не решает нашу проблему.

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

Зная об ограничениях библиотеки, которую было так легко использовать в предыдущих примерах, нам нужно создать что-то, что заполнит этот пробел. Google MapsAPI действительно прост в использовании, поэтому мы создадим на скорую руку асинхронную утилиту только для иллюстрации. В стандартной библиотеке Python версии 3.5 по-прежнему отсутствует библиотека, которая бы выполняла асинхронные HTTP-запросы так же просто, как вызов urllib.urlopen (). Мы определенно не хотим создавать полную поддержку протокола с нуля, поэтому мы будем использовать небольшую справку из пакета aiohttp, доступного в PyPI. Это действительно многообещающая библиотека, которая добавляет как клиентские, так и серверные реализации для асинхронного HTTP. Вот небольшой модуль, построенный поверх aiohttp, который создает одну вспомогательную функцию geocode (), которая выполняет запросы геокодирования к службе Google Maps API:

import aiohttp session = aiohttp.ClientSession() async def geocode(place): params = < 'sensor': 'false', 'address': place >async with session.get( 'https://maps.googleapis.com/maps/api/geocode/json', params=params ) as response: result = await response.json() return result['results'] 

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

  1. Выполните все запросы к внешней службе параллельно, используя функцию fetch_place ().
  2. Отобразите все результаты в цикле, используя функцию present_result ().
import asyncio # note: local module introduced earlier from asyncgmaps import geocode, session PLACES = ( 'Reykjavik', 'Vien', 'Zadar', 'Venice', 'Wrocław', 'Bolognia', 'Berlin', 'Słubice', 'New York', 'Dehli', ) async def fetch_place(place): return (await geocode(place))[0] async def present_result(result): geocoded = await result print("25s>, , ".format( geocoded['formatted_address'], geocoded['geometry']['location']['lat'], geocoded['geometry']['location']['lng'], )) async def main(): await asyncio.wait([ present_result(fetch_place(place)) for place in PLACES ]) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) # aiohttp will raise issue about unclosed # ClientSession so we perform cleanup manually loop.run_until_complete(session.close()) loop.close() 

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

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

  • Все еще низкое внедрение Python 3 и некоторых его расширенных возможностей
  • Низкое понимание различных концепций параллелизма среди начинающих изучать Python

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

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

Объединение асинхронного кода с асинхронным использованием фьючерсов

Так что делать, если у вас есть код, который выполняет длинные синхронные операции ввода-вывода, которые вы не можете или не хотите переписывать. Или что делать, когда вам приходится выполнять некоторые тяжелые операции с процессором в приложении, разработанном в основном с учетом асинхронного ввода-вывода? Ну… тебе нужно найти обходной путь. И под этим я подразумеваю многопоточность или многопроцессорность.

Это может звучать не очень хорошо, но иногда лучшим решением может быть то, от чего мы пытались убежать. Параллельная обработка ресурсоемких задач в Python всегда выполняется лучше благодаря многопроцессорности. И многопоточность может справляться с операциями ввода-вывода одинаково хорошо (быстро и без больших затрат ресурсов), как асинхронное и ожидающее, если правильно настроена и обрабатывается с осторожностью.

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

К счастью для нас, стандартная библиотека Python предоставляет модуль concurrent.futures, который также интегрирован с модулем asyncio. Эти два модуля вместе позволяют вам планировать функции блокировки, выполняемые в потоках или дополнительных процессах, как если бы это были асинхронные неблокирующие сопрограммы.

Исполнители (executors) и фьючерсы (futures)

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

Наиболее важными классами в модуле concurrent.futures являются Executor и Future.

Executor представляет собой пул ресурсов, которые могут обрабатывать рабочие элементы параллельно. Это может показаться очень похожим по назначению на классы из многопроцессорного модуля — Pool и dummy.Pool — но имеет совершенно другой интерфейс и семантику. Это базовый класс, не предназначенный для реализации и имеющий две конкретные реализации:

  • ThreadPoolExecutor: который представляет пул потоков
  • ProcessPoolExecutor: который представляет пул процессов
  • submit (fn, * args, ** kwargs): планирует функцию fn для выполнения в пуле ресурсов и возвращает объект Future, представляющий выполнение вызываемого объекта
  • map (func, * iterables, timeout = None, chunksize = 1): функция func выполняется над итерацией аналогично многопроцессорной обработке. Метод Pool.map ()
  • shutdown (wait = True): это выключает Executor и освобождает все его ресурсы.
>>> def loudy_return(): . print("processing") . return 42 . >>> from concurrent.futures import ThreadPoolExecutor >>> with ThreadPoolExecutor(1) as executor: . future = executor.submit(loudy_return) . processing >>> future >>> future.result() 42 

Если вы хотите использовать метод Executor.map (), он не отличается по использованию от метода Pool.map () класса Pool многопроцессорного модуля:

def main(): with ThreadPoolExecutor(POOL_SIZE) as pool: results = pool.map(fetch_place, PLACES) for result in results: present_result(result) 
Использование Executor в цикле событий

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

Ядром этого обходного пути является метод BaseEventLoop.run_in_executor (executor, func, * args) класса цикла событий. Это позволяет планировать выполнение функции func в процессе или пуле потоков, представленном аргументом executor. Самым важным в этом методе является то, что он возвращает новый ожидаемый объект (объект, который можно ожидать с помощью оператора await). Таким образом, благодаря этому вы можете выполнить блокирующую функцию, которая не является сопрограммой в точности как сопрограмма, и она не будет блокировать, независимо от того, сколько времени потребуется, чтобы закончить. Он остановит только функцию, ожидающую результатов от такого вызова, но весь цикл событий будет продолжаться.

И полезным фактом является то, что вам даже не нужно создавать свой экземпляр executor. Если вы передадите None в качестве аргумента executor, класс ThreadPoolExecutor будет использоваться с числом потоков по умолчанию (для Python 3.5 это число процессоров, умноженное на 5).

Итак, давайте предположим, что мы не хотели переписывать проблемную часть пакета python-gmaps, которая была причиной нашей головной боли. Мы можем легко отложить блокирующий вызов до отдельного потока с помощью вызова loop.run_in_executor (), при этом оставляя функцию fetch_place () в качестве ожидаемой сопрограммы:

async def fetch_place(place): coro = loop.run_in_executor(None, api.geocode, place) result = await coro return result[0] 

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

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

После этого мы обнаружили, что с многопроцессорным модулем использовать несколько процессов намного проще, чем базовые потоки с многопоточностью. Но только после этого мы поняли, что можем использовать один и тот же API с потоками, благодаря multiprocessing.dummy. Таким образом, выбор между многопроцессорностью и многопоточностью теперь зависит только от того, какое решение лучше соответствует проблеме, а не какое решение имеет лучший интерфейс.

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

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

Предыдущий вывод — отличное введение в тему следующей главы, Глава 14 «Полезные шаблоны проектирования». Так как нет единого шаблона, который решит все ваши проблемы. Вы должны знать как можно больше, потому что в конечном итоге вы будете использовать их каждый день.

  • Python
  • Параллельное программирование

2. История языка

Бытующий, кажется, до сих пор миф, что python — это такой новый модный язык программирования, который захватывает мир, наполовину неверен. Питон, конечно, захватывает мир, и, конечно, модный, но он не очень новый.

История развития питона корнями уходит ещё в 80-ые годы. Гвидо Ван Россум [создатель языка] уже совсем немолодой человек ещё из поколения хакеров.

Гвидо участвовал в разработке обучающего языка ABC (конец 80-ых), а в какой-то момент решил создать язык полноценный. Так появился Python (названный в честь «Летающего цирка Монти Пайтона»).

Пока Гвидо понемногу дорабатывал язык, питоном стали активно пользоваться. В 2000 году была выпущена уже совсем приличная версия Python 2.0, а в 2008 — версия Python 3.0, синтаксически отличная от версии 2.

2.2. Python 2 vs. Python 3

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

  1. Во втором питоне ещё оставался налёт «игрушечности», «обучательности»;
  2. В нём было реализовано несколько неудачных архитектурных решений (в частности, объектная модель была немного странной).

В результате был выпущен Python 3, который является уже совсем индустриальным [т.е. заточенным под реальные прикладные задачи], и именно о нём мы и будем говорить.

2.3. Великодушный диктатор

Автором и бессменным руководителем проекта всё это время (вплоть до июля 2018 года) был Гвидо Ван Россум. Гвидо, imho, очень талантливый программист, в первую очередь на C; так что если кто хочет научиться программировать на C — смотрите исходники питона.

Всё это время Гвидо был BDFL (Benevolent Dictator For Life, «Великодушным пожизненным диктатором») проекта. Это означает, что все решения принимались коллегиально, но последнее слово всегда оставалось за Гвидо (что очень удобно при разрешении споров).

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

3. О языке в общих чертах

3.1. Питон — интерпретируемый язык

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

В интерпретируемом же языке исполнителем является сама программа, которая анализирует исходный код. Python — интерпретируемый язык (и исполнителем кода на нём является программа под названием «python»). Из этого следуют несколько важных свойств.

3.1.1. Большинство ошибок возникает на этапе выполнения программы

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

Если же язык интерпретируемый, он обладает динамической семантикой (т.е. одни и те же команды могут значить разное в зависимости от состояния интерпретатора); поэтому на этапе синтаксического анализа вылавливаются только самые базовые ошибки. БОльшая же часть ошибок, как будет продемонстрировано, возникает во время выполнения программы.

3.1.2. Более гибкий и плотный код

Обратная (положительная) сторона — можно писать менее загруженный различными проверками код. (Кстати: если программа на питоне у вас получилась большая, скорее всего, вы что-то сделали неправильно.)

3.2. Питон — высокоуровневый язык

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

Высокоуровневые языки же полностью отгорожены от того фактического исполнителя, которого им подсунут. Их вычислительная модель герметична; код, написанный на Java или на Python, будет работать одинаково вне зависимости от того исполнителя, который находится внизу. Код на Python — кроссплатформенный, и может работать на всех известных ОС (и даже иногда без них).

Единственный случай, когда приходится вспоминать, на какой операционной системе выполняется код — когда приходится в нём обращаться непосредственно к ОС.

3.3. Типизация в Python

3.3.1. Строгая типизация

Типизация в питоне строгая. У каждого объекта есть один конкретный тип; не бывает так, что мы не знаем, какого типа объект, или что объект относится к некоторому «неопределённому типу», который будет разрешён впоследствии, или что у нас есть объекты некого «универсального» типа.

3.3.2. Динамическая типизация

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

Таким образом, объекты-то типизированные, а вот имена, они же идентификаторы, абсолютно не привязаны к типу.

3.3.3. Утиная типизация (duck typing)

  • Если это крякает и это плавает, значит, это — утка.

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

3.4. Питон — объектно-ориентированный язык

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

Пожалуйста, не путайте: объектно-ориентированный язык — всего лишь язык, который поддерживает объектно-ориентированную парадигму планирования разработки. Никакой специальной «объектности» в самом языке, вообще говоря, нет. «Объектность» — это способ планирования вашей программы.

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

3.5. Питон — динамически развивающийся язык

С каждой новой версией языка происходят какие-нибудь изменения. Иногда (редко) они обратно несовместимы, однако это касается только «большого» изменения версий; минорные версии обратно совместимы.

В язык постоянно добавляют разные новые штуки; пропустил пару анонсов, смотришь — а там уже что-то ещё новое придумали.

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

3.5.1. Немного о процессе нововведений

Сначала вносится предложение какого-нибудь изменения в язык. Делается это в виде PEP (Python Enhancement Proposal).

  1. Что он хочет добавить;
  2. Зачем — с какими целями и почему раньше было плохо;
  3. И как он собирается это сделать.

После чего этот PEP обсуждается в сообществе, а потом приходит (приходил) Гвидо и всех банил и одобрял или не одобрял предложенные изменения.

3.5.2. NEVER STOP

  • Нельзя просто взять и выучить питон.

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

3.5.3. Больше модулей!

Помимо самого языка, в который входят порядка 200 модулей (т.е. дополнений), есть ещё PyPI (Python Package Index), где на данный момент находится более 150000 готовых проектов. Мало того, что они там есть, так авторы ещё и потрудились встроить их в структуру PyPI, снабдили документацией и т.д.

  1. живые проекты,
  2. исходный код которых доступен
  3. под свободной лицензией — и это важно!

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

3.5.4. Документация

В питоне с документацией всё довольно неплохо. На официальном сайте есть Tutorial, который я горячо рекомендую, есть справочник по питону (это такая настольная книга для программиста, которую надо читать долго и внимательно), а для тех, кто фанатеет от БНФов, есть Language Reference.

4. Интерпретатор командной строки

Запускаем интерпретатор python:

>>>python3

Он называется python3, потому что в linux много программ написано на python 2; они неохотно переходят на python 3 (либо не переходят совсем). Как правило, в linux-системах присутствуют оба питона: один из них называется python, другой — python3.

Интерпретатор командной строки устроен таким образом, что вы даёте какую-то команду, а интерпретатор на неё реагирует. Фактически это диалог человека и машины.

4.1. Как это устроено

  1. ввод команды;
  2. её лексический, затем — синтаксический разбор;
  3. её интерпретация;
  4. вывод результата.

5. Арифметика в Python

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

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