Подборка @pythonetc, июль 2018

znizhvtddgxyegzwv-ctoh9ptei.png


Это вторая подборка советов про Python и программирование из моего авторского канала @pythonetc. Предыдущие подборки:

Регулярные языки


Регулярный язык (regular language) — это формальный язык, который можно представить в виде конечного автомата. Иными словами, для посимвольной обработки текста вам достаточно лишь помнить текущее состояние, причем количество таких состояний конечно.

Прекрасный пример: машина, которая проверяет, являются ли входные данные простым числом вроде –3, 2.2 или 001. В начале статьи показана схема конечного автомата. Двойные окружности обозначают конечные состояния, в них машина может остановиться.

Машина начинает работать с позиции ①. Возможно, находит минус, затем цифру, а затем на позиции ③ обрабатывает необходимое количество цифр. После этого может проверяться наличие десятичного разделителя (③ → ④), после которого идет одна цифра (④ → ⑤) или больше (⑤ → ⑤).

Классический пример нерегулярного языка — семейство строковых выражений вида:

a-b
aaa-bbb
aaaaa-bbbbb

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

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

Забавно, что многие современные движки регулярных выражений не являются регулярными. Например, модуль regex для Python поддерживает рекурсию (которая поможет в решении задачи с aaa-bbb).

Динамическая диспетчеризация


Когда Python исполняет вызов метода, допустим, a.f(b, c, d), он сначала должен выбрать правильную функцию f. В силу полиморфизма a определяет, что будет в итоге выбрано. Процесс выбора метода обычно называют динамической диспетчеризацией (dynamic dispatch).

Python поддерживает только полиморфизм с единичной диспетчеризацией (single-dispatch polymorphism). Это означает, что на выбор объекта влияет лишь сам объект (в нашем примере — a). В других языка могут учитываться типы b, c и d — такой механизм называется множественной диспетчеризацией (multiple dispatch). Ярким примером является язык C#.

Однако множественную диспетчеризацию можно эмулировать с помощью единичной. Именно для этого был создан шаблон проектирования «visitor»: в нем дважды используется единичная диспетчеризация для имитации двойной.

Помните, что перегрузка (overload) методов (как в Java и С++) — не аналог множественной диспетчеризации. Динамическая диспетчеризация работает в рантайме, а перегрузка выполняется только в ходе компиляции.

Эти примеры помогут вам лучше разобраться в теме:


Встроенные имена


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

>>> print = 42
>>> print(42)
Traceback (most recent call last):
File "", line 1, in 
TypeError: 'int' object is not callable


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

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

>>> import builtins
>>> print = 42
>>> builtins.print(1)
1


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

strace


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

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

В подобных случаях может быть полезен strace. Это Unix-утилита, отслеживающая системные вызовы. Можете запустить ее предварительно — strace python script.py —, но обычно удобнее подключаться к уже работающему приложению: strace -p PID.

$ cat test.py
with open('/tmp/test', 'w') as f:
f.write('test')
$ strace python test.py 2>&1 | grep open | tail -n 1
open("/tmp/test", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 3


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

В этом примере вывод остановлен, пока не завершится запись в STDIN:

$ strace python -c 'input()'
read(0,


Литералы кортежей


Одной из наиболее несогласованных частей синтаксиса Python являются литералы кортежей.

Для создания кортежа достаточно перечислить значения через запятую: 1, 2, 3. А что насчет кортежа, состоящего из одного элемента? Просто добавляем висячую запятую: 1,. Это выглядит некрасиво и нередко приводит к ошибкам, но вполне логично.

Как насчет пустого кортежа? Это одна запятая — ,? Нет, это (). А что, скобки создают кортеж, как и запятые? Нет, (4) — не кортеж, это просто 4.

In : a = [
...:     (1, 2, 3),
...:     (1, 2),
...:     (1),
...:     (),
...: ]

In : [type(x) for x in a]
Out: [tuple, tuple, int, tuple]


Чтобы запутать всё еще сильнее, для литералов кортежей часто требуются дополнительные скобки. Если вам нужно, чтобы кортеж был единственным аргументом функции, то очевидно, что f(1, 2, 3) работать не будет — придется написать f((1, 2, 3)).

© Habrahabr.ru