Кеннет Рейтц – Автостопом по Python (страница 55)
$ pip install fabric
Рассмотрим полный модуль Python, определяющий две задачи Fabric: memory_usage и deploy:
# fabfile.py
from fabric.api import cd, env, prefix, run, task
env.hosts = ['my_server1', 'my_server2'] # С чем соединяться по SSH
@task
def memory_usage():
····run('free — m')
@task
def deploy():
····with cd('/var/www/project-env/project'):
········with prefix('../bin/activate'):
············run('git pull')
············run('touch app.wsgi')
Оператор with вкладывает команды друг в друга, поэтому в конечном счете метод deploy() для каждого хоста начинает выглядеть так:
$ ssh имя_хоста cd /var/ww/project-env/project &&../bin/activate && git pull
$ ssh имя_хоста cd /var/ww/project-env/project &&../bin/activate && \
> touch app.wsgi
Учитывая предыдущий код, сохраненный в файле fabfile.py (имя модуля по умолчанию, которое ищет fab), мы можем проверить использование памяти с помощью нашей новой задачи memory_usage:
$ fab memory_usage
[my_server1] Executing task 'memory'
[my_server1] run: free — m [my_server1] out:················ total···· used···· free·· shared· buffers·· cached
[my_server1] out: Mem:··········· 6964···· 1897···· 5067······· 0····· 166····· 222
[my_server1] out: — /+ buffers/cache:······ 1509···· 5455
[my_server1] out: Swap:············· 0······· 0······· 0
[my_server2] Executing task 'memory'
[my_server2] run: free — m [my_server2] out:················ total···· used···· free·· shared· buffers·· cached
[my_server2] out: Mem:··········· 1666····· 902····· 764······· 0····· 180····· 572
[my_server2] out: — /+ buffers/cache:······· 148···· 1517
[my_server2] out: Swap:··········· 895······· 1····· 894
И мы можем выполнить развертывание:
$ fab deploy
Дополнительная функциональность включает параллельное исполнение, взаимодействие с удаленными программами и группирование хостов. В документации к Fabric (http://docs.fabfile.org/) приведены понятные примеры.
Luigi
Luigi (https://pypi.python.org/pypi/luigi) — это инструмент для управления конвейером, разработанный и выпущенный компанией Spotify. Помогает разработчикам управлять целым конвейером крупных долгоиграющих пакетных задач, объединяя запросы Hive, запросы к базе данных, задачи Hadoop Java, задачи pySpark и многие другие задачи, которые вы можете написать самостоятельно. Они не должны быть приложениями, работающими с большими данными, API позволяет запланировать что угодно. Spotify позволил запускать задачи с помощью Hadoop, поэтому разработчики предоставляют все необходимые вспомогательные программы в пакете luigi.contrib (http://luigi.readthedocs.io/en/stable/api/luigi.contrib.html). Установите его с помощью pip:
$ pip install luigi
Включает в себя веб-интерфейс, поэтому пользователи могут фильтровать свои задачи и просматривать графики зависимостей для рабочего потока конвейера и их продвижение. В репозитории GitHub вы можете найти примеры задач Luigi, также можете просмотреть документацию к Luigi.
Скорость
В этом разделе перечислены наиболее популярные способы оптимизации скорости, используемые сообществом Python. В табл. 8.1 показаны варианты оптимизации, которые вы можете применить после того, как попробуете простые методы вроде запуска профайлера (https://docs.python.org/3.5/library/profile.html) и сравнения параметров для сниппетов кода (https://docs.python.org/3.5/library/timeit.html).
Вы, возможно, уже слышали о глобальной блокировке интерпретатора (global interpreter lock, GIL) (http://wiki.python.org/moin/GlobalInterpreterLock). Это способ, с помощью которого реализация C для Python позволяет нескольким потокам работать одновременно.
Управление памятью в Python не полностью потокобезопасно, поэтому GIL нужен для того, чтобы помешать нескольким потокам запускать одновременно один и тот же код.
GIL зачастую называют ограничением Python, но он не является такой уж большой проблемой — мешает лишь тогда, когда процессы связаны с ЦП (в этом случае, как и для NumPy или криптографических библиотек, которые мы рассмотрим далее, код переписан на C со связыванием с Python). Для всего прочего (для ввода/вывода в сетях или файлах) узким местом является код, блокирующий один поток, ожидающий завершения операции ввода/вывода. Вы можете решить проблему с блокировкой с помощью потоков или событийно-ориентированного программирования.
Отметим, что в Python 2 имеются медленная и быстрая версии библиотек — StringIO и cStringIO, ElementTree и cElementTree. Реализации на C работают быстрее, но их нужно явно импортировать. Начиная с версии Python 3.3 обычные версии импортируют быструю реализацию там, где это возможно, а библиотеки, чье имя начинается с C, считаются устаревшими.
Джефф Напп (Jeff Knupp), автор книги
Многопоточность и другие способы оптимизации, показанные в табл. 8.1, более подробно рассматриваются в следующих разделах.
Многопоточность
Библиотека для работы с потоками в Python позволяет создать несколько потоков. Из-за GIL (во всяком случае в CPython) только один процесс Python может быть запущен для каждого интерпретатора; это означает, что прирост производительности появится только в том случае, если хотя бы один поток заблокирован (например, для ввода/вывода). Еще один вариант для ввода/вывода — обработка событий. Чтобы узнать подробнее, прочтите абзацы, связанные с asyncio, в подразделе «Производительность сетевых инструментов из стандартной библиотеки Python» раздела «Распределенные системы» главы 9.
Когда у вас есть несколько потоков Python, ядро замечает, что один из потоков заблокирован для ввода-вывода, и оно переключается, чтобы позволить следующему потоку использовать процессор до тех пор, пока он не будет заблокирован или не завершится. Все это происходит автоматически, когда вы запускаете ваши потоки. Есть хороший пример применения многопоточности на сайте Stack Overflow (http://bit.ly/threading-in-python), для серии Python Module of the Week написана отличная статья на тему многопоточности (https://pymotw.com/2/threading/). Вы также можете просмотреть документацию стандартной библиотеки, посвященную работе с потоками (https://docs.python.org/3/library/threading.html).
Модуль multiprocessing
Модуль multiprocessing (https://docs.python.org/3/library/multiprocessing.html) стандартной библиотеки Python предоставляет способ обойти GIL путем запуска дополнительных интерпретаторов Python. Отдельные процессы могут общаться друг с другом с помощью запросов multiprocessing.Pipe или multiprocessing.Queue; также они могут делиться памятью с помощью запросов multiprocessing.Array или multiprocessing.Value, что автоматически реализует блокировки. Осторожно делитесь данными; эти объекты реализуют блокировку для того, чтобы предотвратить одновременный доступ разных процессов.
Рассмотрим пример, иллюстрирующий прирост скорости, который появляется в результате использования пула работников. Существует компромисс между сэкономленным временем и временем, затрачиваемым на переключение между интерпретаторами. В примере используется метод Монте-Карло для оценки значения числа пи[101]:
>>> import multiprocessing
Идея заключается в том, что существуют накладные расходы при создании нескольких процессов, но инструменты, позволяющие запустить с помощью Python несколько процессов, довольно надежны. Для получения более подробной информации просмотрите документацию о библиотеке multiprocessing в стандартной библиотеке (https://docs.python.org/3.5/library/multiprocessing.html), а также прочитайте статью Джеффа Наппа (Jeff Knupp) о том, как обойти GIL (пара абзацев посвящены этой библиотеке) (http://bit.ly/pythons-hardest-problems).
Subprocess
Библиотека subprocess (https://docs.python.org/3/library/subprocess.html) была представлена в версии стандартной библиотеки для Python 2.4, определена в PEP 324 (https://www.python.org/dev/peps/pep-0324). Выполняет системный вызов (вроде unzip или curl), как если бы она была вызвана из командной строки (по умолчанию, не вызывая системную оболочку (http://bit.ly/subprocess-security)), а разработчик выбирает, что нужно сделать с входным и выходным конвейерами subprocess. Мы рекомендуем пользователям Python 2 получить обновленную версию пакета subprocess32, в которой исправляются некоторые ошибки. Установите его с помощью pip: