Книга: Простой Python. Современный стиль программирования. 2-е изд.
Назад: Глава 18. Распутываем Всемирную паутину
Дальше: Глава 20. Пи-Арт

Глава 19. Быть питонщиком

Всегда хотели отправиться во времени назад, чтобы сразиться с более молодой версией себя? Карьера в разработке ПО — то, что вам нужно!

Эллиот Лох

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

О программировании

Для начала я хочу сказать пару слов о программировании с высоты личного опыта.

Я начинал свою деятельность в области науки и обучился программированию, чтобы анализировать и отображать экспериментальные данные. Мне казалось, программирование окажется похожим на бухгалтерский учет: будет точным и скучным. Но я удивился, когда понял, что мне это нравится. Одними из интересных для меня аспектов стали логическая — программирование похоже на складывание пазлов — и творческая составляющие. Вы должны написать программу так, чтобы получить правильные результаты, но вольны написать ее тем способом, который вам больше нравится. Такое соотношение использования левого и правого полушарий мозга необычно.

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

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

Ищем код на Python

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

Стандартная библиотека Python () широка, глубока и довольно понятна.

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

Так где же, помимо стандартной библиотеки, следует искать хороший код Python?

Первое место, на которое вы должны обратить внимание, — это каталог пакетов Python (Python Package Index, PyPI) (/). Ранее носивший имя Cheese Shop в честь скетча Monty Python, данный сайт постоянно обновляется — на момент написания этой книги он содержит более 113 000 пакетов. Когда вы используете pip (см. следующий раздел), он ищет пакет на сайте PyPI. Основная страница PyPI показывает самые свежие пакеты. Вы также можете выполнить прямой поиск, введя что-нибудь в строку поиска посередине главной страницы PyPI. Например, по ключевому слову genealogy определяется 21 совпадение, а по слову movies — 528.

Еще один популярный репозиторий — GitHub. Взгляните, какие пакеты Python популярны в данный момент ().

Сайт Popular Python recipes () содержит более 4000 коротких программ Python на любую тему.

Установка пакетов

Существует множество способов установить пакет Python.

Использовать pip, если есть такая возможность. На данный момент это самый распространенный метод установки. С помощью pip вы можете установить большинство пакетов.

• Задействовать pipenv, в нем объединены pip и virtualenv.

• Иногда вы можете применить менеджер пакетов своей операционной системы.

• Использовать conda, если вы выполняете большое количество научной работы и хотите задействовать дистрибутив Python, который называется Anaconda. Прочтите раздел «Устанавливаем Anaconda» приложения Б на с. 536 для получения более подробной информации.

С помощью исходного кода.

Если вам нужно установить несколько пакетов из одной области, то вы можете обнаружить дистрибутив, который уже содержит их. Например, глава 22 позволяет попробовать поработать с несколькими числовыми и научными программами, которые было бы трудно устанавливать вручную, но все они включены в дистрибутивы, такие как Anaconda.

pip

Использование пакетов Python имело несколько ограничений. Более ранний инструмент для установки easy_install был заменен pip, но ни один из них не находился в стандартном пакете Python. Если мы должны устанавливать пакеты с помощью pip, то где же его взять? Начиная с Python 3.4, pip наконец-то включили в стандартный пакет Python, чтобы избежать подобного экзистенциального кризиса. Если вы работаете с более ранней версией Python и у вас не установлен pip, то можете скачать его, перейдя по ссылке .

Простейший вариант использования pip — установка последней версии одного пакета с помощью следующей команды:

$ pip install flask

Вы увидите детали происходящего, просто чтобы не подумали, будто pip ленится: скачивание, запуск setup.py, установка файлов на диск и др.

Вы также можете дать pip команду установить определенную версию:

$ pip install flask==0.9.0

Или минимальную версию (это полезно, когда некая особенность, без которой вы жить не можете, появляется только в определенной версии):

$ pip install 'flask≥0.9.0'

В предыдущем примере одинарные кавычки не дают оболочке интерпретировать символ >, чтобы перенаправить поток выходной информации в файл с именем =0.9.0.

Если вы хотите установить более одного пакета, то можете воспользоваться файлом требований (). Несмотря на обилие вариантов, простейший вариант использования — список пакетов, в каждой строке по одному, опционально содержащий точные или относительные версии:

$ pip -r requirements.txt

Например, файл requirements.txt может содержать следующее:

flask==0.9.0

django

psycopg2

Еще несколько примеров:

установите последнюю версию: pipinstall--upgradeпакет;

удалите пакет: pipuninstallпакет.

virtualenv

Стандартный способ установки сторонних пакетов — это использование pip и virtualenv. Я покажу, как устанавливать virtualenv, в разделе «Установка virtualenv» приложения Б на с. 535.

Виртуальное окружение — это обычный каталог, содержащий интерпретатор Python, несколько других программ наподобие pip, а также некие пакеты. Вы можете активизировать его, запустив сценарий оболочки, который лежит в каталоге bin этого виртуального окружения. Это изменит значение переменной окружения $PATH, и теперь ваша оболочка будет знать, где искать программы. Активизируя виртуальное окружение, вы помещаете его каталог bin перед остальными каталогами в переменной $PATH. В результате, когда вы введете такие команды, как pip или python, ваша оболочка сначала найдет те программы, которые располагаются в вашем виртуальном окружении, а не те, что находятся в системных каталогах наподобие /bin, /usr/bin или /usr/local/bin.

Вы не захотите устанавливать программы в эти системные каталоги в любом случае, поскольку:

у вас нет разрешения на запись в них;

даже если бы вы могли выполнить запись, переписывание стандартных программ вашей системы (такой как python) может вызвать проблемы.

pipenv

Недавно появился пакет pipenv (), в котором объединены уже знакомые нам pip и virtualenv. Вдобавок он решает проблемы с зависимостями, которые могут возникнуть при использовании pip в разных окружениях (например, на локальной машине, а также на этапе подготовки и развертывания):

$ pip install pipenv

Использовать этот пакет рекомендует Python Packaging Authority (/) — рабочая группа, которая старается улучшить процесс взаимодействия с пакетами в Python. Эта группа не занимается разработкой ядра Python, поэтому pipenv не входит в состав стандартной библиотеки.

Менеджер пакетов

Операционная система macOS от Apple содержит сторонние менеджеры пакетов homebrew (brew) (/) и ports (/). Они работают примерно так же, как и pip, но не ограничены пакетами Python.

В операционных системах семейства Linux имеется отдельный менеджер пакетов для каждого дистрибутива. Самые популярные — apt-get, yum, dpkg и zypper.

В операционных системах семейства Windows имеются Windows Installer и файлы пакетов с суффиксом .msi. Если вы устанавливали Python для Windows, то, скорее всего, файл пакета имел формат MSI.

Установка из исходного кода

Иногда пакет еще совсем новый или же автор просто не сделал его доступным через pip. Чтобы создать пакет, вы, как правило, делаете следующее:

загружаете код;

• извлекаете файлы с помощью zip, tar или другого подходящего инструмента, если они заархивированы или сжаты;

запускаете команду pythonsetup.pyinstall в каталоге, который содержит файл setup.py.

109955.png

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

Интегрированные среды разработки

Для написания программ, представленных в данной книге, я использовал текстовый интерфейс, но это не значит, что вы должны запускать весь код в консоли или текстовом окне. Существует множество бесплатных и коммерческих интегрированных сред разработки (Integrated Development Environment, IDE), которые являются графическими интерфейсами, поддерживающими такие инструменты, как текстовые редакторы, отладчики, поиск по библиотеке и т.д.

IDLE

IDLE () — это IDE, предназначенная только для Python, которая поставляется со стандартным дистрибутивом. Она основана на интерфейсе tkinter и имеет простой GUI.

PyCharm

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

19_01.tif 

Рис. 19.1. Начальный экран PyCharm

IPython

IPython (/) начал свой путь как улучшенная терминальная (текстовая) IDE для Python, но затем эволюционировал и приобрел графический интерфейс, похожий на блокнот. Он интегрировал множество пакетов, рассмотренных в этой книге, включая Matplotlib и NumPy, и стал популярным инструментом для научных вычислений.

Вы можете установить базовую текстовую версию (как уже догадались) с помощью команды pipinstallipython. Запустив ее, вы увидите нечто похожее на следующий блок:

$ ipython

Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:39:00)

Type 'copyright', 'credits' or 'license' for more information

IPython 7.3.0 -- An enhanced Interactive Python. Type '?' for help.

 

In [1]:

Как вам известно, стандартный интерпретатор Python использует приглашения >>> и ..., чтобы указать, где и когда вам нужно вводить код. IPython отслеживает все, что вы вводите, в списке, который называется In, а все выходные данные — в списке Out. Каждый элемент списка In может быть длиннее одной строки, поэтому отправлять свои данные следует, удерживая нажатой клавишу Shift и нажимая клавишу Enter.

Рассмотрим однострочный пример:

In [1]: print("Hello? World?")

Hello? World?

 

In [2]:

Элементы в списках In и Out нумеруются автоматически, позволяя получить доступ ко всем входным и выходным данным.

Если после имени переменной вы введете знак ?, то IPython подскажет вам ее тип, значение, способы создания переменной подобного типа, а также некоторые пояснения:

In [4]: answer = 42

 

In [5]: answer?

 

Type:       int

String Form:42

Docstring:

int(x=0) -> integer

int(x, base=10) -> integer

 

Convert a number or string to an integer, or return 0 if no arguments

are given.  If x is a number, return x.__int__().  For floating point numbers, this truncates towards zero.

 

If x is not a number or if base is given, then x must be a string,

bytes, or bytearray instance representing an integer literal in the

given base.  The literal can be preceded by '+' or '-' and be surrounded

by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.

Base 0 means to interpret the base from the string as an integer literal.

>>> int('0b100', base=0)

4

Поиск по имени — это популярная возможность такой IDE, как IPython. Если вы нажмете клавишу Tab после того, как введете некие символы, то IPython покажет все переменные, ключевые слова и функции, начинающиеся с этих символов. Определим некоторые переменные, а затем найдем все, что начинается с буквы f:

In [6]: fee = 1

 

In [7]: fie = 2

 

In [8]: fo = 3

 

In [9]: fum = 4

 

In [10]: ftab

%%file    fie       finally   fo        format    frozenset

fee       filter    float     for       from      fum

Если вы введете fe, а затем нажмете клавишу Tab, то увидите только переменную fee, единственную переменную в этой программе, которая начинается с буквосочетания fe:

In [11]: fee

Out[11]: 1

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

Jupyter Notebook

Jupyter (/) — это эволюционировавший IPython. В его названии объединены языки Julia, Python и R — все они популярны в области науки о данных и научных вычислений. Jupyter Notebooks — современный способ разработки и публикации вашего кода, содержащий документацию к этим языкам.

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

Чтобы установить Jupyter Notebook локально, введите команду pipinstalljupyter. Запустите его с помощью jupyternotebook.

JupyterLab

JupyterLab — это Jupyter Notebook нового поколения, в конечном счете он заменит старую версию. Как и в случае с Notebook, вы сначала можете попробовать () JupyterLab в своем браузере. Установить его локально можно с помощью команды pipinstalljupyterlab, а запустить — с помощью jupyterlab.

Именование и документирование

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

>>> # Здесь я собираюсь присвоить значение 10 переменной "num":

... num = 10

>>> # Надеюсь, это сработало.

... print(num)

10

>>> # Фух.

Вместо этого напишите, почему вы присвоили значение 10. Укажите, почему дали переменной именно имя num. Если вы пишете почтенный преобразователь температуры от шкалы Фаренгейта к шкале Цельсия, то вам следует назвать переменные так, чтобы было понятно их функционирование, а не производить на свет кучу волшебного кода. Небольшой тест также не повредит (пример 19.1):

Пример 19.1. ftoc1.py

def ftoc(f_temp):

    "Convert Fahrenheit temperature <f_temp> to Celsius and return it."

    f_boil_temp = 212.0

    f_freeze_temp = 32.0

    c_boil_temp = 100.0

    c_freeze_temp = 0.0

    f_range = f_boil_temp - f_freeze_temp

    c_range = c_boil_temp - c_freeze_temp

    f_c_ratio = c_range / f_range

    c_temp = (f_temp - f_freeze_temp) * f_c_ratio + c_freeze_temp

    return c_temp

 

if __name__ == '__main__':

    for f_temp in [-40.0, 0.0, 32.0, 100.0, 212.0]:

        c_temp = ftoc(f_temp)

        print('%f F => %f C' % (f_temp, c_temp))

Запустим тесты:

$ python ftoc1.py

 

-40.000000 F => -40.000000 C

0.000000 F => -17.777778 C

32.000000 F => 0.000000 C

100.000000 F => 37.777778 C

212.000000 F => 100.000000 C

Мы можем сделать как минимум два улучшения.

В языке Python нет констант, но таблица стилей PEP-8 рекомендует () использовать прописные буквы и подчеркивания (например, ALL_CAPS) при именовании переменных, которые должны считаться константами. Переименуем эти «константные» переменные в нашем примере.

Поскольку мы заранее вычислили значения, основываясь на константах, перенесем их в верхнюю часть модуля. Таким образом, они будут рассчитываться только один раз при каждом вызове функции ftoc().

В примере 19.2 показан переделанный код.

Пример 19.2. ftoc2.py

F_BOIL_TEMP = 212.0

F_FREEZE_TEMP = 32.0

C_BOIL_TEMP = 100.0

C_FREEZE_TEMP = 0.0

F_RANGE = F_BOIL_TEMP - F_FREEZE_TEMP

C_RANGE = C_BOIL_TEMP - C_FREEZE_TEMP

F_C_RATIO = C_RANGE / F_RANGE

def ftoc(f_temp):

    "Convert Fahrenheit temperature <f_temp> to Celsius and return it."

    c_temp = (f_temp - F_FREEZE_TEMP) * F_C_RATIO + C_FREEZE_TEMP

    return c_temp

 

if __name__ == '__main__':

    for f_temp in [-40.0, 0.0, 32.0, 100.0, 212.0]:

        c_temp = ftoc(f_temp)

        print('%f F => %f C' % (f_temp, c_temp))

Добавление подсказок типов

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

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

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

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

def num_to_str(num: int) -> str:

    return str(num)

Это лишь подсказки, они не меняют способ работы Python. Они нужны для документирования, однако люди придумывают и другое применение. Например, веб-фреймворк FastAPI (/) использует подсказки для того, чтобы сгенерировать веб-документацию, а также лайв-формы для тестирования.

Тестирование кода

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

Самый простой способ протестировать программы, написанные на Python, — добавить операторы print(). Read-Evaluate-Print Loop (REPL) интерактивного интерпретатора позволяет быстро изменять код и тестировать изменения. Однако в производственном коде операторы print() использовать не стоит, поэтому вам нужно помнить о том, что их все следует удалять.

Программы pylint, pyflakes, flake8 или PEP-8

Следующий шаг перед созданием настоящих программ для тестирования — использование контролера кода Python. Самыми популярными являются pylint (/) и pyflakes (). Вы можете установить любой из них (или даже оба) с помощью pip:

$ pip install pylint

$ pip install pyflakes

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

Пример 19.3. style1.py

a = 1

b = 2

print(a)

print(b)

print(c)

Так выглядит выходная информация от pylint:

$ pylint style1.py

No config file found, using default configuration

************* Module style1

C:  1,0: Missing docstring

C:  1,0: Invalid name "a" for type constant

  (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$)

C:  2,0: Invalid name "b" for type constant

  (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$)

E:  5,6: Undefined variable 'c'

Если пролистать дальше, к разделу Globalevaluation, то можно увидеть наш счет (10.0 — это высший балл):

Your code has been rated at -3.33/10

Ой! Сначала исправим ошибку. Строка вывода pylint, начинающаяся с E, указывает на Error, которая заключается в том, что мы не присвоили значение переменной до ее вывода на экран. Взгляните на пример 19.4, чтобы увидеть, как можно это исправить.

Пример 19.4. style2.py

a = 1

b = 2

c = 3

print(a)

print(b)

print(c)

 

$ pylint style2.py

No config file found, using default configuration

************* Module style2

C:  1,0: Missing docstring

C:  1,0: Invalid name "a" for type constant

  (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$)

C:  2,0: Invalid name "b" for type constant

  (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$)

C:  3,0: Invalid name "c" for type constant

  (should match (([A-Z_][A-Z0-9_]*)|(__.*__))$)

Отлично, больше строк, начинающихся с Е, нет. Наш счет увеличился с –3,33 до 4,29:

Your code has been rated at 4.29/10

Контролер pylint хочет увидеть строку документации (короткий текстовый фрагмент в верхней части модуля или функции, описывающий код) и считает, что короткие имена переменных, такие как a, b и c, не очень аккуратны. Сделаем pylint более счастливым, а наш код — еще лучше (пример 19.5):

Пример 19.5. style3.py

"Module docstring goes here"

 

def func():

    "Function docstring goes here. Hi, Mom!"

    first = 1

    second = 2

    third = 3

    print(first)

    print(second)

    print(third)

 

func()

 

    $ pylint style3.py

    No config file found, using default configuration

Жалоб нет. А какой у нас счет?

Your code has been rated at 10.00/10

Не так уж и плохо?

Еще один контролер стиля — pep8 (), вы можете установить его привычным способом:

$ pip install pep8

Что он скажет о нашей последней версии кода?

$ pep8 style3.py

style3.py:3:1: E302 expected 2 blank lines, found 1

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

Пакет unittest

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

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

Стандартная библиотека содержит не один, а целых два пакета для тестирования приложений. Начнем с unittest (). Мы напишем модуль, который записывает слова с прописной буквы. Наша первая версия будет использовать стандартную строковую функцию capitalize(), что, как вы увидите, приведет к неожиданным результатам. Сохраните этот файл под именем cap.py (пример 19.6).

Пример 19.6. cap.py

def just_do_it(text):

    return text.capitalize()

Основная идея тестирования заключается в том, чтобы понять, какой результат вы хотите получить при определенных входных данных (в нашем примере вы хотите получить введенный текст записанным с прописной буквы), отправить результат функции тестирования, а затем проверить, получен ли ожидаемый результат. Ожидаемый результат называется утверждением (assertion), поэтому в рамках пакета unittest вы проверяете результат с помощью методов, чьи имена начинаются со слова assert, скажем метода assertEqual, показанного в примере 19.7.

Сохраните этот сценарий тестирования под именем test_cap.py.

Пример 19.7. test_cap.py

import unittest

import cap

 

class TestCap(unittest.TestCase):

 

    def setUp(self):

        pass

 

    def tearDown(self):

        pass

 

    def test_one_word(self):

        text = 'duck'

        result = cap.just_do_it(text)

        self.assertEqual(result, 'Duck')

 

    def test_multiple_words(self):

        text = 'a veritable flock of ducks'

        result = cap.just_do_it(text)

        self.assertEqual(result, 'A Veritable Flock Of Ducks')

 

if __name__ == '__main__':

    unittest.main()

Перед каждым методом тестирования вызывается метод setUp(), а после каждого из методов тестирования — метод tearDown(). Их задача — выделение и освобо­ждение внешних ресурсов, необходимых для тестов, таких как соединение с базой данных или создание неких тестовых данных. В нашем случае тесты автономны, и нам даже не нужно определять методы setUp() и tearDown(), однако создать их пустые версии не повредит. Сердце наших тестов — две функции с именами test_one_word() и test_multiple_words(). Каждая из них запускает определенную нами функцию just_do_it() с разными входными параметрами и проверяет, получен ли ожидаемый результат. О’кей, запустим тест. Эта команда вызовет два наших метода тестирования:

$ python test_cap.py

 

F.

======================================================================

FAIL: test_multiple_words (__main__.TestCap)

----------------------------------------------------------------------

Traceback (most recent call last):

   File "test_cap.py", line 20, in test_multiple_words

  self.assertEqual(result, 'A Veritable Flock Of Ducks')

AssertionError: 'A veritable flock of ducks' != 'A Veritable Flock Of Ducks'

- A veritable flock of ducks

?  ^         ^     ^  ^

+ A Veritable Flock Of Ducks

?  ^         ^     ^  ^

 

----------------------------------------------------------------------

Ran 2 tests in 0.001s

 

FAILED (failures=1)

Пакет устроил результат первой проверки (test_one_word), но не результат второй (test_multiple_words). Стрелки вверх (^) показывают, какие строки отличаются.

Что такого особенного в примере с несколькими словами? После прочтения документации для строковой функции capitalize () мы поняли причину проблемы: она увеличивает только первую букву первого слова. Возможно, нам сразу нужно было начать с чтения документации.

Нам нужна другая функция. После прочтения той страницы мы нашли функцию title() (). Изменим файл cap.py так, чтобы в нем вместо функции capitalize() использовалась функция title() (пример 19.8).

Пример 19.8. Возвращаемся к cap.py

def just_do_it(text):

    return text.title()

Повторите тесты и взгляните на результат:

$ python test_cap.py

 

..

----------------------------------------------------------------------

Ran 2 tests in 0.000s

 

OK

Все прошло отлично. Хотя на самом деле нет. Нам нужно добавить в файл test_cap.py как минимум еще один метод (пример 19.9).

Пример 19.9. Возвращаемся к test_cap.py

def test_words_with_apostrophes(self):

    text = "I'm fresh out of ideas"

    result = cap.just_do_it(text)

    self.assertEqual(result, "I'm Fresh Out Of Ideas")

Запустите тесты еще раз:

$ python test_cap.py

 

..F

======================================================================

FAIL: test_words_with_apostrophes (__main__.TestCap)

----------------------------------------------------------------------

Traceback (most recent call last):

   File "test_cap.py", line 25, in test_words_with_apostrophes

     self.assertEqual(result, "I'm Fresh Out Of Ideas")

AssertionError: "I'M Fresh Out Of Ideas" != "I'm Fresh Out Of Ideas"

- I'M Fresh Out Of Ideas

?   ^

+ I'm Fresh Out Of Ideas

?   ^

 

----------------------------------------------------------------------

Ran 3 tests in 0.001s

 

FAILED (failures=1)

Наша функция увеличила букву m в конструкции I'm. В документации к функции title() мы обнаружили, что она плохо работает с апострофами. Нам действительно стоило сначала прочитать текст документации целиком.

В самом конце документации стандартной библиотеки, касающейся строк, мы находим еще одного кандидата — вспомогательную функцию с именем capwords(). Используем ее в файле cap.py (пример 19.10).

Пример 19.10. Снова возвращаемся к cap.py

def just_do_it(text):

    from string import capwords

    return capwords(text)

 

$ python test_cap.py

 

...

----------------------------------------------------------------------

Ran 3 tests in 0.004s

 

OK

Наконец-то мы это сделали! Э-э-э, на самом деле нет. Нужно добавить еще один тест в файл test_cap.py (пример 19.11).

Пример 19.11. Снова возвращаемся к test_cap.py

def test_words_with_quotes(self):

    text = "\"You're despicable,\" said Daffy Duck"

    result = cap.just_do_it(text)

    self.assertEqual(result, "\"You're Despicable,\" Said Daffy Duck")

Сработало?

$ python test_cap.py

 

...F

======================================================================

FAIL: test_words_with_quotes (__main__.TestCap)

----------------------------------------------------------------------

Traceback (most recent call last):

   File "test_cap.py", line 30, in test_words_with_quotes

     self.assertEqual(result, "\"You're

     Despicable,\" Said Daffy Duck") AssertionError: '"you\'re Despicable,"

         Said Daffy Duck'

  != '"You\'re Despicable," Said Daffy Duck' - "you're Despicable,"

         Said Daffy Duck

?  ^ + "You're Despicable," Said Daffy Duck

?  ^

----------------------------------------------------------------------

Ran 4 tests in 0.004s

 

FAILED (failures=1)

Выглядит так, будто первая двойная кавычка смутила даже функцию capwords, нашего текущего фаворита. Она попробовала увеличить символ " и уменьшить все остальное (You're). Нам также нужно проверить, оставила ли функция-увеличитель остальную часть строки нетронутой.

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

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

Пакет doctest

Второй пакет для тестирования стандартной библиотеки — doctest (). Он позволяет писать тесты внутри строки документации, которые и сами будут служить документацией. Он выглядит как интерактивный интерпретатор: символы >>>, за ними следует вызов, а затем результаты в следующей строке. Вы може­те запустить некоторые тесты в интерактивном интерпретаторе и просто вставить результат в свой тестовый файл. Модифицируем файл cap.py (убрав тот проблемный тест с кавычками) и сохраним его под именем cap2.py, как показано в примере 19.12.

Пример 19.12. cap2.py

def just_do_it(text):

    """

    >>> just_do_it('duck')

    'Duck'

    >>> just_do_it('a veritable flock of ducks')

    'A Veritable Flock Of Ducks'

    >>> just_do_it("I'm fresh out of ideas")

    "I'm Fresh Out Of Ideas"

    """

    from string import capwords

    return capwords(text)

 

if __name__ == '__main__':

    import doctest

    doctest.testmod()

Когда вы его запустите, в случае успеха он не выведет ничего:

$ python cap2.py

Запустите его с опцией -v, чтобы увидеть произошедшее на самом деле:

$ python cap2.py -v

Trying:

     just_do_it('duck')

Expecting:

     'Duck'

ok

Trying:

      just_do_it('a veritable flock of ducks')

Expecting:

      'A Veritable Flock Of Ducks'

ok

Trying:

      just_do_it("I'm fresh out of ideas")

Expecting:

      "I'm Fresh Out Of Ideas"

ok

1 items had no tests:

      __main__

1 items passed all tests:

     3 tests in __main__.just_do_it

3 tests in 2 items.

3 passed and 0 failed.

Test passed.

Пакет nose

Сторонний пакет nose () — еще одна альтернатива пакету unittest. Команда, позволяющая установить его, выглядит так:

$ pip install nose

Вам не нужно создавать класс, который содержит тестовые методы, как мы делали при работе с unittest. Любая функция, содержащая в своем имени слово test, будет запущена. Модифицируем наш последний тестировщик Unittest и сохраним его под именем test_cap_nose.py (пример 19.13).

Пример 19.13. test_cap_nose.py

import cap2

from nose.tools import eq_

 

def test_one_word():

    text = 'duck'

    result = cap.just_do_it(text)

    eq_(result, 'Duck')

 

def test_multiple_words():

    text = 'a veritable flock of ducks'

    result = cap.just_do_it(text)

    eq_(result, 'A Veritable Flock Of Ducks')

 

def test_words_with_apostrophes():

    text = "I'm fresh out of ideas"

    result = cap.just_do_it(text)

    eq_(result, "I'm Fresh Out Of Ideas")

 

def test_words_with_quotes():

    text = "\"You're despicable,\" said Daffy Duck"

    result = cap.just_do_it(text)

    eq_(result, "\"You're Despicable,\" Said Daffy Duck")

Запустим тесты:

$ nosetests test_cap_nose.py

 

...F

======================================================================

FAIL: test_cap_nose.test_words_with_quotes

----------------------------------------------------------------------

Traceback (most recent call last):

   File "/Users/.../site-packages/nose/case.py", line 198, in runTest

     self.test(*self.arg)

   File "/Users/.../book/test_cap_nose.py", line 23, in test_words_with_quotes

     eq_(result, "\"You're Despicable,\" Said Daffy Duck")

AssertionError: '"you\'re Despicable," Said Daffy Duck' !=

'"You\'re Despicable," Said Daffy Duck'

----------------------------------------------------------------------

Ran 4 tests in 0.005s

 

FAILED (failures=1)

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

Другие фреймворки для тестирования

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

tox (/);

• py.test (/);

green ().

Постоянная интеграция

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

Эти системы велики, и я не буду рассматривать детали их установки и использования. Если они вам когда-нибудь понадобятся, то вы будете знать, где их искать:

buildbot (/) — эта система контроля версий, написанная на Python, автоматизирует построение, тестирование и выпуск кода;

• jenkins (/) — система написана на Java, в данный момент выглядит наиболее предпочтительным инструментом для постоянной интеграции;

• travis-ci (/) — эта система автоматизирует проекты, размещенные на GitHub, бесплатна для проектов с открытым исходным кодом;

circleci (/) — эта система коммерческая, но бесплатна для частных проектов и проектов с открытым исходным кодом.

Отладка кода

Отладка — почти как расследование преступления, где главный преступник — тоже вы.

Филипе Фортес

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

Брайан Керниган

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

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

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

Далее мы рассмотрим некоторые приемы отладки, начиная с быстрых и «грязных» и заканчивая медленными и зачастую такими же «грязными».

Функция print()

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

>>> def func(*args, **kwargs):

...    print(vars())

...

>>> func(1, 2, 3)

{'args': (1, 2, 3), 'kwargs': {}}

>>> func(['a', 'b', 'argh'])

{'args': (['a', 'b', 'argh'],), 'kwargs': {}}

Зачастую на экран следует выводить locals() и globals().

Если в вашем коде также встречается и стандартный вывод данных, то можете написать собственные сообщения об ошибке в поток вывода для ошибок с помощью конструкции print(stuff,file=sys.stderr).

Отладка с помощью декораторов

Как вы уже знаете из раздела «Декораторы» главы 9, декоратор может вызывать код, располагающийся до или после функции, не модифицируя его внутри нее самой. Это значит, что вы можете использовать декоратор для выполнения какого-либо действия до или после вызова любой функции, а не только тех, которые написали вы. Определим декоратор dump, позволяющий вывести на экран входные аргументы и выводимые значения любой функции по мере ее вызова (разработчики знают, что выходные данные нужно декорировать), как показано в примере 19.14.

Пример 19.14. dump.py

def dump(func):

    "Print input arguments and output value(s)"

    def wrapped(*args, **kwargs):

        print("Function name:", func.__name__)

        print("Input arguments:", ' '.join(map(str, args)))

        print("Input keyword arguments:", kwargs.items())

        output = func(*args, **kwargs)

        print("Output:", output)

        return output

    return wrapped

Перейдем к декорируемой части. Это функция с именем double(), которая принимает именованные или безымянные числовые аргументы и возвращает их удвоенные значения в списке (пример 19.15).

Пример 19.15. test_dump.py

from dump import dump

 

@dump

def double(*args, **kwargs):

    "Double every argument"

    output_list = [ 2 * arg for arg in args ]

    output_dict =  { k:2*v for k,v in kwargs.items() }

    return output_list, output_dict

 

if __name__ == '__main__':

                output = double(3, 5, first=100, next=98.6, last=-40)

Запустите пример:

$ python test_dump.py

 

Function name: double

Input arguments: 3 5

Input keyword arguments: dict_items([('first', 100), ('next', 98.6),

    ('last', -40)])

Output: ([6, 10], {'first': 200, 'next': 197.2, 'last': -80})

Отладчик pdb

Эти приемы полезны, но иногда ничто не сможет заменить настоящий отладчик. Большинство IDE содержат отладчики, возможности и пользовательские интерфейсы которых могут варьироваться. В данном подразделе я опишу использование стандартного отладчика Python pdb ().

109970.png

Если вы запускаете программу с флагом -i, то при ее неудачном завершении Python вернет вас в интерактивный интерпретатор.

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

Мы собираемся считать файл, содержащий названия стран и их столиц, разделенные запятыми, и вывести их на экран в формате «столица, страна». Прописные буквы в них могут быть расставлены неправильно, так что нам нужно исправить это при выводе на экран. В файле также могут быть лишние пробелы, следует избавиться и от них. Наконец, несмотря на то, что было бы логично считать весь файл до конца, по какой-то причине наш менеджер сказал нам остановиться, если мы встретим слово quit (состоящее из смеси прописных и строчных букв). В примере 19.16 показан файл с данными.

Пример 19.16. cities.csv

France, Paris

venuzuela,caracas

  LithuaniA,vilnius

     quit

Разработаем алгоритм (способ решения задачи). Это псевдокод, он выглядит как программа, но является лишь способом выразить логику простым языком до преобразования его в настоящую программу. Одна из причин, по которым программисты любят Python, — он выглядит очень похожим на псевдокод, поэтому его не так трудно преобразовать в рабочую программу, когда приходит время:

для каждой строки в текстовом файле:

    считать строку

    удалить пробелы в начале и конце строки

    если найдена строка "quit" в строке, записанной в нижнем регистре:

        остановиться

    иначе:

        разделить страну и столицу символом запятой

        удалить пробелы в начале и конце

        записать страну и столицу с прописной буквы

        вывести на экран столицу, запятую и страну

Нам нужно удалить из имен начальные и конечные пробелы, поскольку таково требование к программе. Аналогично мы поступаем со сравнением со строкой quit и записью названий страны и города с прописной буквы. Имея это в виду, напишем файл capitals.py, который точно будет работать корректно (пример 19.17).

Пример 19.17. capitals.py

def process_cities(filename):

    with open(filename, 'rt') as file:

        for line in file:

            line = line.strip()

            if 'quit' in line.lower():

                return

            country, city = line.split(',')

            city = city.strip()

            country = country.strip()

            print(city.title(), country.title(), sep=',')

 

if __name__ == '__main__':

    import sys

    process_cities(sys.argv[1])

Протестируем программу с помощью файла, созданного ранее. На старт, внимание, марш:

$ python capitals.py cities.csv

Paris,France

Caracas,Venuzuela

Vilnius,Lithuania

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

Пример 19.18. cities2.csv

argentina,buenos aires

bolivia,la paz

brazil,brasilia

chile,santiago

colombia,Bogotá

ecuador,quito

falkland islands,stanley

french guiana,cayenne

guyana,georgetown

paraguay,Asunción

peru,lima

suriname,paramaribo

uruguay,montevideo

venezuela,caracas

quit

Несмотря на то что в файле было 15 строк, программа завершается после вывода всего пяти:

$ python capitals.py  cities2.csv

Buenos Aires,Argentina

La Paz,Bolivia

Brazilia,Brazil

Santiago,Chile

Bogotá,Colombia

Что случилось? Мы можем продолжать редактировать файл capitals.py, размещая выражения print() в местах потенциального возникновения ошибки, но посмотрим, сможет ли помочь отладчик.

Чтобы использовать отладчик, импортируйте модуль pdb из командной строки, введя –mpdb, например, так:

$ python -m pdb capitals.py cities2.csv

 

> /Users/williamlubanovic/book/capitals.py(1)<module>()

-> def process_cities(filename):

(Pdb)

Это запустит программу и разместит вас на первой строке. Если вы введете символ с (от слова continue — «продолжить»), то программа будет работать, пока не завершится либо естественным образом, либо из-за ошибки:

(Pdb) c

 

Buenos Aires,Argentina

La Paz,Bolivia

Brazilia,Brazil

Santiago,Chile

Bogotá,Colombia

The program finished and will be restarted

> /Users/williamlubanovic/book/capitals.py(1)<module>()

-> def process_cities(filename):

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

Введите s (step — «шаг»), чтобы пройти по отдельным строкам кода. Это позволит пройти по всем строкам кода Python: вашим, стандартной библиотеки и любых других применяемых вами модулей. Используя команду s, вы также входите во все функции и проходите каждую построчно. Введите n (next — «следующий»), чтобы идти по шагам, но не заходить внутрь функций: когда вы находитесь на строке, где вызывается функция, эта команда выполняет всю функцию и вы оказываетесь на следующей строке. Используйте s, если не уверены в том, где конкретно есть проблема, а n — будучи уверенными, что некая функция не вызывает проблем, особенно когда она длинная. Зачастую вы будете проходить построчно весь свой код и пропускать библиотечный, поскольку подразумевается, что он хорошо протестирован. Используем s с целью начать двигаться от начала программы к функции process_cities():

(Pdb) s

 

> /Users/williamlubanovic/book/capitals.py(12)<module>()

-> if __name__ == '__main__':</pre>

 

(Pdb) s

 

> /Users/williamlubanovic/book/capitals.py(13)<module>()

-> import sys

 

(Pdb) s

 

> /Users/williamlubanovic/book/capitals.py(14)<module>()

-> process_cities(sys.argv[1])

 

(Pdb) s

 

--Call--

> /Users/williamlubanovic/book/capitals.py(1)process_cities()

-> def process_cities(filename):

 

(Pdb) s

> /Users/williamlubanovic/book/capitals.py(2)process_cities()

-> with open(filename, 'rt') as file:

Введите l (list — «перечислить»), чтобы увидеть следующие несколько строк своей программы:

(Pdb) l

 

  1      def process_cities(filename):

  2  ->     with open(filename, 'rt') as file:

  3             for line in file:

  4                 line = line.strip()

  5                 if 'quit' in line.lower():

  6                     return

  7                 country, city = line.split(',')

  8                 city = city.strip()

  9                 country = country.strip()

10                 print(city.title(), country.title(), sep=',')

11

(Pdb)

Стрелка (->) указывает на текущую строку.

Мы могли бы и дальше применять команды s или n в надежде что-то найти, но используем одну из главных особенностей отладчика — точки останова. Такая точка останавливает выполнение программы на указанной вами строке. В данном случае мы хотим узнать, почему функция process_cities() вызывает завершение программы до прочтения всех введенных строк. Строка 3 (forlineinfile:) будет считывать каждую строку входного файла, поэтому выглядит невинно. Единственное место, где мы можем вернуться из функции до прочтения всех данных, — это строка 6 (return). Поставим точку останова на этой строке:

(Pdb) b 6

 

Breakpoint 1 at /Users/williamlubanovic/book/capitals.py:6

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

(Pdb) c

 

Buenos Aires,Argentina

La Paz,Bolivia

Brasilia,Brazil

Santiago,Chile

Bogotá,Colombia

> /Users/williamlubanovic/book/capitals.py(6)process_cities()

-> return

Ага, она остановилась на точке останова в строке 6. Это показывает, что программа хочет завершиться после прочтения страны, которая идет вслед за Колумбией. Выведем значение переменной line с целью увидеть только что прочитанное нами:

(Pdb) p line

 

'ecuador,quito'

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

Если после этого у вас все еще есть работа, то можете просмотреть все точки останова с помощью команды b:

(Pdb) b

 

Num Type         Disp Enb   Where

1   breakpoint   keep yes   at /Users/williamlubanovic/book/capitals.py:6

    breakpoint already hit 1 time

Команда l покажет строки кода, текущую строку (->) и все имеющиеся точки останова (B). Вызов команды l без аргументов выведет все строки, начиная с точки предыдущего вызова этой команды, поэтому включите в вызов опциональный параметр — стартовую строку (в нашем примере начнем с 1):

(Pdb) l 1

 

  1      def process_cities(filename):

  2         with open(filename, 'rt') as file:

  3             for line in file:

  4                 line = line.strip()

  5                 if 'quit' in line.lower():

  6 B->                 return

  7                 country, city = line.split(',')

  8                 city = city.strip()

  9                 country = country.strip()

10                 print(city.title(), country.title(), sep=',')

11

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

Пример 19.19. capitals.py

def process_cities(filename):

    with open(filename, 'rt') as file:

        for line in file:

            line = line.strip()

            if 'quit' == line.lower():

                return

            country, city = line.split(',')

            city = city.strip()

            country = country.strip()

            print(city.title(), country.title(), sep=',')

 

if __name__ == '__main__':

    import sys

    process_cities(sys.argv[1])

Запустим программу еще раз:

$ python capitals2.py cities2.csv

 

Buenos Aires,Argentina

La Paz,Bolivia

Brasilia,Brazil

Santiago,Chile

Bogotá,Colombia

Quito,Ecuador

Stanley,Falkland Islands

Cayenne,French Guiana

Georgetown,Guyana

Asunción,Paraguay

Lima,Peru

Paramaribo,Suriname

Montevideo,Uruguay

Caracas,Venezuela

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

Помните: больше тестов — меньше отладки.

Функция breakpoint()

В Python 3.7 появилась новая встроенная функция breakpoint(). Если вы добавите ее в свой код, то отладчик запустится автоматически и будет останавливаться в каждой локации. Без нее вам придется запускать такой отладчик, как pdb, и расставлять точки останова вручную, как вы уже видели ранее.

Отладчик, используемый по умолчанию, вам уже знаком (pdb), но его можно изменить, установив переменную окружения PYTHONBREAKPOINT. Например, вы можете использовать удаленный отладчик, работающий на основе веб-подключения web-pdb ():

$ export PYTHONBREAKPOINT='web_pdb.set_trace'

Официальная документация несколько суховата, но хорошие обзоры этой функции можно найти здесь () и здесь ().

Записываем в журнал сообщения об ошибках

В какой-то момент вам может понадобиться перейти от использования выражений print() к записи сообщений в журнал. Журнал, как правило, представляет собой системный файл, в котором накапливаются сообщения, содержащие полезную информацию, например временную метку или имя пользователя, запустившего программу. Зачастую журналы ежедневно ротируются (переименовываются) и сжимаются, благодаря чему не переполняют ваш диск и не создают проблем. Если ваша программа работает «не так», то вы можете просмотреть соответствующий файл журнала и увидеть, что произошло. Содержимое исключений особенно полезно записывать в журнал, поскольку оно подсказывает номер строки, после выполнения которой программа завершилась, и причину такого завершения.

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

сообщение, которое вы хотите сохранить в журнал;

• уровни приоритета и соответствующие функции — debug(), info(), warn(), error() и critical();

• один или несколько объектов журналирования для основной связи с модулем;

• обработчики, которые направляют значение в терминал, файл, базу данных или куда-либо еще;

• средства форматирования выходных данных;

фильтры, принимающие решения в зависимости от входных данных.

Рассмотрим простейший пример журналирования — просто импортируем модуль и воспользуемся некоторыми из его функций:

>>> import logging

>>> logging.debug("Looks like rain")

>>> logging.info("And hail")

>>> logging.warn("Did I hear thunder?")

WARNING:root:Did I hear thunder?

>>> logging.error("Was that lightning?")

ERROR:root:Was that lightning?

>>> logging.critical("Stop fencing and get inside!")

CRITICAL:root:Stop fencing and get inside!

Вы заметили, что вызовы debug() и info() не сделали ничего, а два других вывели на экран строку УРОВЕНЬ:root: перед каждым сообщением? Пока они выглядят как оператор print(), имеющий несколько личностей.

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

Глубокое погружение в документацию отвечает на первую загадку (на вторую мы ответим уже через пару страниц): уровень приоритета по умолчанию — WARNING, он будет записан в журнал, когда мы вызовем первую функцию (logging.debug()). Мы можем указать уровень по умолчанию с помощью функции basicConfig(). Самый низкий уровень — DEBUG, это дает возможность поймать более высокие уровни:

>>> import logging

>>> logging.basicConfig(level=logging.DEBUG)

>>> logging.debug("It's raining again")

DEBUG:root:It's raining again

>>> logging.info("With hail the size of hailstones")

INFO:root:With hail the size of hailstones

Мы сделали это с помощью стандартных функций журналирования, не создавая специализированный объект. Каждый объект журналирования имеет имя. Создадим объект, который называется bunyan:

>>> import logging

>>> logging.basicConfig(level='DEBUG')

>>> logger = logging.getLogger('bunyan')

>>> logger.debug('Timber!')

DEBUG:bunyan:Timber!

Если имя объекта журналирования содержит точки, то они разделяют уровни иерархии таких объектов, каждый из которых потенциально имеет разные приоритеты. Это значит, что объект с именем quark выше объекта quark.charmed. На вершине иерархии находится корневой объект журналирования с именем ''.

До сего момента мы только выводили сообщения, и это практически не отличается от функции print(). Чтобы направить сообщения в разные места назначения, используем обработчики. Самое распространенное место — файл журнала, направить туда сообщения можно так:

>>> import logging

>>> logging.basicConfig(level='DEBUG', filename='blue_ox.log')

>>> logger = logging.getLogger('bunyan')

>>> logger.debug("Where's my axe?")

>>> logger.warn("I need my axe")

>>>

Ага, строки больше не показываются на экране, вместо этого попадают в файл blue_ox.log:

DEBUG:bunyan:Where's my axe?

WARNING:bunyan:I need my axe

Вызов функции basicConfig() и передача имени файла в качестве аргумента создали для вас объект типа FileHandler и сделали его доступным объекту журналирования. Модуль журналирования содержит как минимум 15 обработчиков для отправки сообщений в разные места, такие как электронная почта, веб-серверы, экраны и файлы.

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

WARNING:root:Message...

Если вы предоставите строку format функции basicConfig(), то можете изменить формат по собственному желанию:

>>> import logging

>>> fmt = '%(asctime)s %(levelname)s %(lineno)s %(message)s'

>>> logging.basicConfig(level='DEBUG', format=fmt)

>>> logger = logging.getLogger('bunyan')

>>> logger.error("Where's my other plaid shirt?")

2014-04-08 23:13:59,899 ERROR 1 Where's my other plaid shirt?

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

Модуль logging распознал количество имен переменных в строке формата fmt. Мы использовали asctime (дата и время как строка ISO 8601), levelname, lineno (номер строки) и само сообщение в переменной message. Существуют и другие встроенные переменные, вы можете предоставить и собственные переменные.

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

Оптимизация кода

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

Измеряем время

Вы уже видели, что функция time модуля time возвращает текущее время в формате epoch как число секунд с плавающей точкой. Быстрый способ засечь время — получить текущее, что-то сделать, получить новое время и вычесть из него первое. Напишем соответствующий код, показанный в примере 19.20, и назовем файл (как бы вы думали?) time1.py.

Пример 19.20. time1.py

from time import time

 

t1 = time()

num = 5

num *= 2

print(time() - t1)

Здесь мы измеряем время, которое требуется на присвоение значения 5 переменной num и умножение его на 2. Данный пример не является реалистичным тестом производительности, это лишь образец того, как замерить время выполнения произвольного кода. Попробуйте запустить его несколько раз, чтобы увидеть — время может варьироваться:

$ python time1.py

2.1457672119140625e-06

$ python time1.py

2.1457672119140625e-06

$ python time1.py

2.1457672119140625e-06

$ python time1.py

1.9073486328125e-06

$ python time1.py

3.0994415283203125e-06

Программа работала 2–3 миллионные доли секунды. Попробуем выполнить что-то помедленнее, скажем функцию sleep(). Если мы усыпим выполнение на секунду, то наш таймер покажет значение чуть больше секунды. В примере 19.21 показан код, сохраните его в файле под именем time2.py.

Пример 19.21. time2.py

from time import time, sleep

 

t1 = time()

sleep(1.0)

print(time() - t1)

Чтобы быть уверенными в результатах, запустим программу несколько раз:

$ python time2.py

1.000797986984253

$ python time2.py

1.0010130405426025

$ python time2.py

1.0010390281677246

Как и ожидалось, программе для работы требуется около секунды. Если бы это оказалось не так, то либо нашему таймеру, либо функции sleep() должно было стать стыдно.

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

В примерах этого подраздела код должен находиться в кавычках, чтобы он выполнялся не после нажатия клавиши Return, а лишь внутри функции timeit(). (В следующем подразделе вы увидите, как можно измерить время выполнения некой функции, передав ее имя в функцию timeit().) Запустим предыдущий пример и измерим время его выполнения. Назовем этот файл timeit1.py (пример 19.22).

Пример 19.22. timeit1.py

from timeit import timeit

print(timeit('num = 5; num *= 2', number=1))

Запустим его несколько раз:

$ python timeit1.py

2.5600020308047533e-06

$ python timeit1.py

1.9020008039660752e-06

$ python timeit1.py

1.7380007193423808e-06

Эти две строки кода снова выполняются примерно за две миллионные доли секунды. Мы можем использовать аргумент repeat функции repeat() модуля timeit, чтобы выполнить код большее количество раз. Сохраните этот файл под именем timeit2.py (пример 19.23).

Пример 19.23. timeit2.py

from timeit import repeat

print(repeat('num = 5; num *= 2', number=1, repeat=3))

Попробуйте запустить его, чтобы увидеть дальнейшее развитие событий:

$ python timeit2.py

[1.691998477326706e-06, 4.070025170221925e-07, 2.4700057110749185e-07]

Первый запуск занял две миллионные доли секунды, а второй и третий про­шли быстрее. Почему? Так могло произойти по многим причинам. Например, мы тестировали очень небольшой фрагмент кода и скорость его выполнения зависит от того, что компьютер делал в эти моменты, как система Python оптимизировала вычисления, и от многого другого.

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

Определим ленивую функцию snooze(), которая дремлет одну секунду, как и мы все время от времени.

Для начала обернем саму функцию snooze(). Нам нужно включить аргументы globals=globals() (это поможет Python найти функцию snooze) и number=1 (запустить ее только один раз; по умолчанию используется значение 1000000, а у нас нет столько времени):

>>> import time

>>> from timeit import timeit

>>>

>>> def snooze():

...     time.sleep(1)

...

>>> seconds = timeit('snooze()', globals=globals(), number=1)

>>> print("%.4f" % seconds)

1.0035

Или мы можем использовать декоратор:

>>> import time

>>>

>>> def snooze():

...     time.sleep(1)

...

>>> def time_decorator(func):

...     def inner(*args, **kwargs):

...         t1 = time.time()

...         result = func(*args, **kwargs)

...         t2 = time.time()

...         print(f"{(t2-t1):.4f}")

...         return result

...     return inner

...

>>> @time_decorator

... def naptime():

...     snooze()

...

>>> naptime()

1.0015

Еще один вариант — задействовать менеджер контекста:

>>> import time

>>>

>>> def snooze():

...     time.sleep(1)

...

>>> class TimeContextManager:

...     def __enter__(self):

...         self.t1 = time.time()

...         return self

...     def __exit__(self, type, value, traceback):

...         t2 = time.time()

...         print(f"{(t2-self.t1):.4f}")

...

>>>

>>> with TimeContextManager():

...     snooze()

...

1.0019

Метод __exit()__ принимает три дополнительных аргумента, которые мы здесь не использовали; мы могли бы применить *args вместо них.

О’кей, мы узнали множество способов измерить время, за которое выполняется код. Теперь измерим производительность, сравнив эффективность нескольких алго­ритмов (программной логики) и структур данных (механизмов хранения).

Алгоритмы и структуры данных

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

В следующем упражнении мы создадим список разными способами, сравнив скорость, читабельность и стиль. Перед вами файл time_lists.py (пример 19.24).

Пример 19.24. time_lists.py

from timeit import timeit

 

def make_list_1():

    result = []

                for value in range(1000):

        result.append(value)

    return result

 

def make_list_2():

    result = [value for value in range(1000)]

    return result

 

print('make_list_1 takes', timeit(make_list_1, number=1000), 'seconds')

print('make_list_2 takes', timeit(make_list_2, number=1000), 'seconds')

В каждой функции мы добавляем в список 1000 элементов и вызываем каждую функцию 1000 раз. Обратите внимание: в этом тесте мы вызываем функцию timeit(), передавая ей имя функции в качестве первого аргумента вместо кода. Запустим ее:

$ python time_lists.py

 

make_list_1 takes 0.14117428699682932 seconds

make_list_2 takes 0.06174145900149597 seconds

Включение списка отработало как минимум в два раза быстрее, чем добавление элементов в список с помощью функции append(). Как правило, включение быстрее, чем создание вручную.

Используйте эти идеи, чтобы ускорить свой код.

Cython, NumPy и расширения C

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

Cython (/) — гибрид языков Python и C, разработанный для преобразования Python: в скомпилированный код языка С внесены некоторые улучшения производительности. Эти аннотации относительно малы, они похожи на объявление типов некоторых переменных, аргументов функций или возвращаемых функциями значений. Подобные подсказки значительно ускорят научные вычисления, выполняющиеся в циклах, — в 1000 раз. Документацию и примеры см. в Cython wiki ().

В главе 22 вы можете подробнее узнать о NumPy. Это математическая библиотека Python, написанная для ускорения на С.

Многие части Python и его стандартной библиотеки написаны на С для скорости и обернуты кодом на Python для удобства. При написании приложений эти приемы доступны и вам. Если вы знаете С и Python и действительно хотите, чтобы ваш код «летал», то напишите расширение на языке С — это труднее, но улучшение оправдает затраченные усилия.

PyPy

Около 20 лет назад, когда язык Java только появился, он был медленным, как шнауцер, больной артритом. Но когда он стал дорого стоить компании Sun и прочим, они вложили миллионы в оптимизацию интерпретатора Java и лежащей в его основе виртуальной машины Java (Java Virtual Machine, JVM), заимствуя приемы из уже существовавших тогда языков Smalltalk и LISP. Компания Microsoft также вложила много усилий в оптимизацию своего языка C# и .NET VM.

У языка Python нет владельца, поэтому никто так сильно не старается сделать его быстрее. Возможно, вы используете стандартную реализацию Python. Она написана на С и часто называется CPython (не путать с Cython).

Как и языки PHP, Perl и даже Java, Python не компилируется в машинный код, он преобразуется в промежуточный язык (называемый байт-кодом или p-кодом), который затем интерпретирует виртуальная машина.

PyPy (/) — это новый интерпретатор Python, который пользуется некоторыми приемами, ускорившими язык программирования Java. Тесты производительности интерпретатора (/) показывают, что PyPy в каждом тесте быстрее CPython в среднем более чем в шесть раз и до 20 раз в отдельных случаях. PyPy работает с Python 2 и 3. Вы можете скачать его и использовать вместо CPython. PyPy постоянно улучшается и однажды может заменить CPython. Чтобы узнать, подходит ли он вам, посетите его официальный сайт.

Numba

Вы можете использовать пакет Numba (/), чтобы динамически скомпилировать свой код в машинный и ускорить его.

Устанавливается этот пакет так же, как и другие:

$ pip install numba

Сначала измерим время выполнения функции, которая высчитывает гипотенузу:

>>> import math

>>> from timeit import timeit

>>> from numba import jit

>>>

>>> def hypot(a, b):

...     return math.sqrt(a**2 + b**2)

...

>>> timeit('hypot(5, 6)', globals=globals())

0.6349189280000189

>>> timeit('hypot(5, 6)', globals=globals())

0.6348589239999853

Используйте декоратор @jit, чтобы ускорить все вызовы после первого:

>>> @jit

... def hypot_jit(a, b):

...     return math.sqrt(a**2 + b**2)

...

>>> timeit('hypot_jit(5, 6)', globals=globals())

0.5396156099999985

>>> timeit('hypot_jit(5, 6)', globals=globals())

0.1534771130000081

Примените декоратор @jit(nopython=True), чтобы избежать накладных расходов, связанных с использованием обычного интерпретатора Python:

>>> @jit(nopython=True)

... def hypot_jit_nopy(a, b):

...     return math.sqrt(a**2 + b**2)

...

>>> timeit('hypot_jit_nopy(5, 6)', globals=globals())

0.18343535700000757

>>> timeit('hypot_jit_nopy(5, 6)', globals=globals())

0.15387067300002855

Пакет Numba особенно полезен в связке с NumPy и другими пакетами, выполняющими вычисления.

Управление исходным кодом

Работая над небольшой группой программ, вы обычно можете отслеживать изменения, внесенные собственноручно, — до тех пор пока не сделаете глупую ошибку и не потеряете несколько дней работы. Системы управления исходным кодом защитят ваш код от сил зла в лице вас самих. Если вы работаете в группе, то управление исходным кодом становится необходимостью. Для этой области было создано множество коммерческих и бесплатных решений. Наиболее популярные в мире открытого исходного кода (где и живет Python) — Mercurial и Git. Они оба являются примерами распределенных систем контроля версий, которые создают несколько копий репозиториев кода. Ранние системы наподобие Subversion работают на одном сервере.

Mercurial

Mercurial (/) написан на Python. Научиться пользоваться им довольно легко, он имеет множество подкоманд для скачивания кода из репозитория Mercurial, добавления файлов, проверки на наличие изменений и объединения изменений из разных источников. Сайт bitbucket (/) и другие предлагают бесплатный или коммерческий хостинг.

Git

Изначально Git (/) создавался для разработки ядра Linux, но теперь является доминирующим в области открытого исходного кода в целом. Он похож на Mercurial, хотя некоторые считают, что обучиться ему сложнее. GitHub (/) — это самый крупный хостинг для git, содержащий более миллиона репозиториев, но существует и множество других хостов ().

Отдельные примеры программ из этой книги доступны в публичном репозитории git на GitHub (). Если у вас установлена программа git, то вы можете скачать примеры с помощью следующей команды:

$ git clone

Вы также можете скачать код, нажав следующие кнопки на странице GitHub:

Clone in Desktop (Клонировать на рабочий стол), чтобы открыть версию git, установленную на ваш компьютер;

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

Если у вас нет git, но вы хотите попробовать поработать с ним, то прочтите инструкцию по установке (). Здесь я буду говорить о версии с командной строкой, но вам могут быть интересны сайты наподобие GitHub, предоставляющие дополнительные услуги, которые в ряде случаев использовать было бы проще: git имеет много возможностей, но не всегда интуитивно понятен.

Проведем тест-драйв. Далеко уходить не будем, просто посмотрим, как работают некоторые команды.

Создадим новый каталог и перейдем в него:

$ mkdir newdir

$ cd newdir

Создадим локальный репозиторий git в текущем каталоге newdir:

$ git init

 

Initialized empty Git repository in /Users/williamlubanovic/newdir/.git/

Создадим в каталоге newdir файл с кодом, который называется test.py (показан в примере 19.25), содержащий следующее.

Пример 19.25. test.py

print('Oops')

Добавим файл в репозиторий git:

$ git add test.py

Что вы об этом думаете, мистер git?

$ git status

 

On branch master

 

Initial commit

 

Changes to be committed:

  (use "git rm --cached <file>..." to unstage)

 

    new file:   test.py

Это значит, что файл test.py стал частью локального репозитория, но изменения еще не были отправлены. Исправим данное обстоятельство:

$ git commit -m "simple print program"

 

[master (root-commit) 52d60d7] my first commit

  1 file changed, 1 insertion(+)

  create mode 100644 test.py

Строка -m"myfirstcommit" — ваш комментарий. Если вы опустите ее, то git выведет на экран редактор и тем самым предложит вам ввести сообщение. Оно становится частью истории изменений нашего файла.

Взглянем на текущий статус:

$ git status

 

On branch master

nothing to commit, working directory clean

О’кей, все текущие изменения были отправлены. Это значит, что мы можем менять содержимое файла и не беспокоиться о потере его оригинала. Внесем изменение в файл test.py — заменим Oops на Ops! и сохраним файл (пример 19.26).

Пример 19.26. Возвращаемся к test.py

print('Ops!')

Посмотрим, что теперь думает git:

$ git status

 

On branch master

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)

 

    modified:   test.py

 

no changes added to commit (use "git add" and/or "git commit -a")

Используйте команду gitdiff, чтобы увидеть, какие строки изменились с момента последней отправки:

$ git diff

 

diff --git a/test.py b/test.py

index 76b8c39..62782b2 100644

--- a/test.py

+++ b/test.py

@@ -1 +1 @@

-print('Oops')

+print('Ops!')

Если вы попробуете отправить это изменение сейчас, то git пожалуется:

$ git commit -m "change the print string"

 

On branch master

Changes not staged for commit:

    modified:   test.py

 

no changes added to commit

Фраза stagedforcommit означает, что вам нужно добавить файл; в примерном переводе она выглядит как «Эй, git, смотри сюда!»:

$ git add test.py

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

$ git commit -m "my first change"

 

[master e1e11ec] my first change

  1 file changed, 1 insertion(&plus;), 1 deletion(-)

Если вы хотите увидеть все те ужасные вещи, которые проделывали с файлом test.py, начиная с недавних, то используйте команду gitlog:

$ git log test.py

commit e1e11ecf802ae1a78debe6193c552dcd15ca160a

Author: William Lubanovic <>

Date:   Tue May 13 23:34:59 2014 -0500

 

     change the print string

 

commit 52d60d76594a62299f6fd561b2446c8b1227cfe1

Author: William Lubanovic <>

Date:   Tue May 13 23:26:14 2014 -0500

 

     simple print program

Распространение ваших программ

Вы знаете, что ваши файлы нужно установить в файлы и каталоги, а также то, что программу, написанную на Python, можно запустить с помощью интерпретатора.

Менее известен тот факт, что интерпретатор Python тоже может выполнять код, размещенный в архивах ZIP. Еще менее известно то, что особые архивы, которые называются pex-файлами (/), также могут быть скомпилированы.

Клонируйте эту книгу

Вы можете получить копию всех программ из данной книги. Посетите репозиторий Git () и следуйте инструкциям по их копированию на ваш локальный компьютер. Если у вас есть git, то запустите команду gitclone, чтобы создать репозиторий Git на вашем компьютере. Вы также можете загрузить файлы в формате ZIP.

Как узнать больше

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

Книги

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

Бизли Д., Джонс Б. Python. Книга рецептов. — М.: ДМК-Пресс, 2020.

• Рейтц К., Шлюссер Т. Автостопом по Python. — СПб.: Питер, 2017.

• Barry P. Head First Python (2nd Edition). — O’Reilly, 2016.

• Beazley D.M. Python Essential Reference. 5th ed. — Addison-Wesley, 2019.

• Gorelick M., Ozsvald I. High Performance Python. — O’Reilly, 2014.

• Maxwell A. Powerful Python. — Powerful Python Press, 2017.

• McKinney W. Python for Data Analysis: Data Wrangling with Pandas, NumPy and IPython. — O’Reilly, 2012.

• Ramalho L. Fluent Python. — O’Reilly, 2015.

• Slatkin B. Effective Python. — Addison-Wesley, 2015.

Summerfield M. Python in Practice: Create Better Programs Using Concurrency, Libraries and Patterns. — Addison-Wesley, 2013.

Конечно же, хороших книг гораздо больше ().

Сайты

Вот несколько сайтов, где вы можете найти полезные руководства:

Python for You and Me (/) — введение в Python, в котором также рассматриваются нюансы работы с ОС Windows;

• Real Python (/) — многие авторы внесли свой вклад в создание этих руководств;

• Learn Python the Hard Way (), автор Зед Шоу;

• Dive Into Python 3 (), автор Марк Пилгрим;

Mouse Vs. Python (/), автор Майкл Дрисколл.

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

comp.lang.python ();

• comp.lang.python.announce ();

• r/python subreddit ();

Planet Python (/).

Наконец, рассмотрим сайты, с которых можно скачать разнообразные пакеты:

The Python Package Index ();

• Awesome Python (/);

• Stack Overflow Python Questions ();

• ActiveState Python recipes ();

Python packages trending on GitHub ().

Группы

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

Конференции

Самые крупные из множества конференций (/) и совещаний по всему миру () проводятся в Северной Америке (/) и Европе ().

Вакансии, связанные с Python

Чтобы найти вакансии, связанные с Python, воспользуйтесь следующими сайтами:

Indeed (/);

• Stack Overflow ();

• ZipRecruiter ();

• Simply Hired (/);

• CareerBuilder (/);

• Google ();

LinkedIn ().

На большей части этих сайтов введите слово python в первой строке поиска и ваше местоположение во второй. Среди хороших локальных сайтов могу порекомендовать Craigslist, эта ссылка, например, работает для Сиэтла (). Просто измените seattle на sfbay, boston, nyc или другой префикс сайта Craigslist, чтобы выполнить поиск в других районах. Для поиска вакансий Python, подразумевающих удаленную работу (на базе дистанционного доступа или «работу из дома»), воспользуйтесь следующими сайтами:

Indeed ();

• Google ();

• LinkedIn ();

• Stack Overflow ();

• Remote Python ();

• We Work Remotely ();

• ZipRecruiter ();

• Glassdoor ();

• Remotely Awesome Jobs ();

• Working Nomads ();

GitHub ().

Читайте далее

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

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

Упражнения

Питонщикам сегодня ничего не задавали.

Вы как детектив: «Я знаю, что я здесь! И если не выйду с поднятыми руками, то приду за собой!»

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

Назад: Глава 18. Распутываем Всемирную паутину
Дальше: Глава 20. Пи-Арт

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