Peephole - это способ, которым Python оптимизирует определенные элементы вашей программы во время компиляции, либо предварительно вычисляя константные выражения, либо преобразовывая определенные структуры данных.
Постоянные выражения
Оптимизировать постоянные выражения действительно просто. Python в основном предварительно вычисляет константы. Предположим, что в вашей программе по какой-то причине используется следующее умножение:
secondsInADay = 60*60*24
Python предварительно вычислит это умножение и заменит его на 86400
. Вам может быть интересно, почему бы просто не написать 86400
прямо в коде, ответ - ясность. В приведенном выше выражении вы можете видеть, что для того, чтобы рассчитать, сколько секунд в день, вам нужно умножить 60 секунд, 60 минут на час, на 24 часа в сутки. Таким образом ваш код может выглядеть яснее. Python не будет производить это вычисление каждый раз, когда появляется умножение, он просто предварительно вычислит его и заменит на окончательное значение.
Короткие последовательности также предварительно рассчитываются. Представьте, что у вас есть этот код,
myTuple = (2, 4)*5 # -> (2, 4, 2, 4, 2, 4, 2, 4, 2, 4) myString = "qwerty "*2 # -> "qwerty qwerty "
Как вы можете видеть в приведенном выше коде, у нас есть две переменные, первая - это кортеж, умноженный на 5, а второй - короткая строка, умноженная на 2, эти короткие последовательности будут предварительно рассчитаны, и Python заменит исходное выражение на значение в комментариях. Стоит упомянуть, что Python должен балансировать между хранением и вычислением. Если он предварительно вычисляет длинные последовательности, программа может быть быстрее, но в конечном итоге будет использовать много памяти.
Чтобы увидеть, что это происходит, вы можете просто открыть консоль Python и написать следующий код:
def my_func(): a = 60*60*24 myString = ("querty ") * 2 myTupple = (2, 4) *5 myString = ("This is a sequence with a lot of characters") * 100
После объявления этой функции вы можете написать следующий код для доступа ко всем константам, объявленным в области видимости этой функции,
my_func.__code__.co_consts
Результат должен быть следующим:
>>> my_func.__code__.co_consts (None, 86400, 'querty querty ', (2, 4, 2, 4, 2, 4, 2, 4, 2, 4), 'This is a sequence with a lot of characters', 100)
Как вы можете видеть, в выходных данных выше Python уже предварительно рассчитал постоянные значения и короткие последовательности, вместо того, чтобы иметь 60*60*24
, функция уже имеет постоянное значение 86400
, то же самое происходит с кортежем и короткой строкой, но как вы можно увидеть, что длинная последовательность не была рассчитана заранее, и вместо этого мы получаем две разные константы, 'This is a sequence with a lot of characters'
и 100
. Как упоминалось выше, Python должен балансировать между хранением и вычислением.
Тесты на членство: замена изменяемых структур данных на неизменяемые структуры данных
Python в основном преобразует изменяемые структуры данных в неизменяемую версию. Списки преобразуются в кортежи, а наборы - в frozensets.
Например,
def my_func(element): if element in ["a", "b", "c"]: print(element)
Приведенный выше код будет преобразован в это,
def my_func(element): if element in ("a", "b", "c"): print(element)
Это сделано только потому, что доступ к неизменяемой версии структуры данных происходит быстрее, чем к изменяемой. Вы можете проверить это, выполнив то же самое, что и перед запуском следующего кода:
my_func.__code__.co_consts
Результат должен быть следующим:
>>> my_func.__code__.co_consts (None, ('a', 'b', 'c'))
Как видите, функция имеет постоянное значение, которое является неизменной версией (кортеж) объявленного списка.
Наконец, сделав то же самое, что и раньше, но с set, вы увидите, что он будет преобразован в frozenset,
def my_func(element): if element in {"a", "b", "c"}: print(element) >>> my_func.__code__.co_consts (None, frozenset({'a', 'b', 'c'}))
Если вас интересует оптимизация Python, вы можете прочитать мою статью Оптимизация Python (Intering).