'BINARY_SUBSCR', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
'INPLACE_FLOOR_DIVIDE', 'INPLACE_TRUE_DIVIDE', 'SLICE+0', 'SLICE+1',
'SLICE+2', 'SLICE+3', 'STORE_SLICE+0', 'STORE_SLICE+1', 'STORE_SLICE+2',
'STORE_SLICE+3', 'DELETE_SLICE+0', 'DELETE_SLICE+1', 'DELETE_SLICE+2',
'DELETE_SLICE+3', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY',
'INPLACE_DIVIDE', 'INPLACE_MODULO', 'STORE_SUBSCR', 'DELETE_SUBSCR',
'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR', 'BINARY_OR',
'INPLACE_POWER', 'GET_ITER', 'PRINT_EXPR', 'PRINT_ITEM', 'PRINT_NEWLINE',
'PRINT_ITEM_TO', 'PRINT_NEWLINE_TO', 'INPLACE_LSHIFT', 'INPLACE_RSHIFT',
'INPLACE_AND', 'INPLACE_XOR', 'INPLACE_OR', 'BREAK_LOOP', 'LOAD_LOCALS',
'RETURN_VALUE', 'IMPORT_STAR', 'EXEC_STMT', 'YIELD_VALUE', 'POP_BLOCK',
'END_FINALLY', 'BUILD_CLASS', 'STORE_NAME', 'DELETE_NAME',
'UNPACK_SEQUENCE', 'FOR_ITER', 'STORE_ATTR', 'DELETE_ATTR', 'STORE_GLOBAL',
'DELETE_GLOBAL', 'DUP_TOPX', 'LOAD_CONST', 'LOAD_NAME', 'BUILD_TUPLE',
'BUILD_LIST', 'BUILD_MAP', 'LOAD_ATTR', 'COMPARE_OP', 'IMPORT_NAME',
'IMPORT_FROM', 'JUMP_FORWARD', 'JUMP_IF_FALSE', 'JUMP_IF_TRUE',
'JUMP_ABSOLUTE', 'LOAD_GLOBAL', 'CONTINUE_LOOP', 'SETUP_LOOP',
'SETUP_EXCEPT', 'SETUP_FINALLY', 'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST',
'RAISE_VARARGS', 'CALL_FUNCTION', 'MAKE_FUNCTION', 'BUILD_SLICE',
'MAKE_CLOSURE', 'LOAD_CLOSURE', 'LOAD_DEREF', 'STORE_DEREF',
'CALL_FUNCTION_VAR', 'CALL_FUNCTION_KW', 'CALL_FUNCTION_VAR_KW',
'EXTENDED_ARG']
Легко догадаться, что LOAD означает загрузку значения в стек, STORE — выгрузку, PRINT — печать, BINARY — бинарную операцию и т.п.
Отладка
В интерпретаторе языка Python заложены возможности отладки программ, а в стандартной поставке имеется простейший отладчик — pdb. Следующий пример показывает программу, которая подвергается отладке, и типичную сессию отладки:
Листинг
# File myfun.py
def fun(s):
lst = []
for i in s:
lst.append(ord(i))
return lst
Так может выглядеть типичный процесс отладки:
Листинг
>>> import pdb, myfun
>>> pdb.runcall(myfun.fun, «ABCDE»)
> /examples/myfun.py(4)fun()
— > lst = []
(Pdb) n
> /examples/myfun.py(5)fun()
— > for i in s:
(Pdb) n
> /examples/myfun.py(6)fun()
— > lst.append(ord(i))
(Pdb) l
1 #!/usr/bin/python
2 # File myfun.py
3 def fun(s):
4 lst = []
5 for i in s:
6 -> lst.append(ord(i))
7 return lst
[EOF]
(Pdb) p lst
[]
(Pdb) p vars()
{'i': 'A', 's': 'ABCDE', 'lst': []}
(Pdb) n
> /examples/myfun.py(5)fun()
— > for i in s:
(Pdb) p vars()
{'i': 'A', 's': 'ABCDE', 'lst': [65]}
(Pdb) n
> /examples/myfun.py(6)fun()
— > lst.append(ord(i))
(Pdb) n
> /examples/myfun.py(5)fun()
— > for i in s:
(Pdb) p vars()
{'i': 'B', 's': 'ABCDE', 'lst': [65, 66]}
(Pdb) r
— Return - > /examples/myfun.py(7)fun() — >[65, 66, 67, 68, 69]
— > return lst
(Pdb) n
[65, 66, 67, 68, 69]
>>>
Интерактивный отладчик вызывается функцией pdb.runcall() и на его приглашение (Pdb) следует вводить команды. В данном примере сессии отладки были использованы некоторые из следующих команд: l (печать фрагмент трассируемого кода), n (выполнить все до следующей строки), s (сделать следующий шаг, возможно, углубившись в вызов метода или функции), p (печать значения), r (выполнить все до возврата из текущей функции).
Разумеется, некоторые интерактивные оболочки разработчика для Python предоставляют функции отладчика. Кроме того, отладку достаточно легко организовать, поставив в ключевых местах программы, операторы print для вывода интересующих параметров. Обычно этого достаточно, чтобы локализовать проблему. В CGI–сценариях можно использовать модуль cgitb, о котором говорилось в одной из предыдущих лекций.
Профайлер
Для определения мест в программе, на выполнение которых уходит значительная часть времени, обычно применяется профайлер.
Модуль profile
Этот модуль позволяет проанализировать работу функции и выдать статистику использования процессорного времени на выполнение той или иной части алгоритма.
В качестве примера можно рассмотреть профилирование функции для поиска строк из списка, наиболее похожих на данную. Для того чтобы качественно профилировать функцию difflib.get_close_matches(), нужен большой объем данных. В файле russian.txt собрано 160 тысяч слов русского языка. Следующая программа поможет профилировать функцию difflib.get_close_matches():
Листинг
import difflib, profile
def print_close_matches(word):
print "\n».join(difflib.get_close_matches(word + "\n», open(«russian.txt»)))
profile.run(r'print_close_matches(«профайлер»)')
При запуске этой программы будет выдано примерно следующее:
Листинг
провайдер
трайлер
бройлер
899769 function calls (877642 primitive calls) in 23.620 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 23.610 23.610 <string>:1(?)
1 0.000 0.000 23.610 23.610 T.py:6(print_close_matches)
1 0.000 0.000 0.000 0.000 difflib.py:147(__init__)
1 0.000 0.000 0.000 0.000 difflib.py:210(set_seqs)
159443 1.420 0.000 1.420 0.000 difflib.py:222(set_seq1)
2 0.000 0.000 0.000 0.000 difflib.py:248(set_seq2)
2 0.000 0.000 0.000 0.000 difflib.py:293(__chain_b)
324261 2.240 0.000 2.240 0.000 difflib.py:32(_calculate_ratio)
28317 1.590 0.000 1.590 0.000 difflib.py:344(find_longest_match)
6474 0.100 0.000 2.690 0.000 difflib.py:454(get_matching_blocks)
28317/6190 1.000 0.000 2.590 0.000 difflib.py:480(__helper)
6474 0.450 0.000 3.480 0.001 difflib.py:595(ratio)
28686 0.240 0.000 0.240 0.000 difflib.py:617(<lambda>)
158345 8.690 0.000 9.760 0.000 difflib.py:621(quick_ratio)
159442 2.950 0.000 4.020 0.000 difflib.py:650(real_quick_ratio)
1 4.930 4.930 23.610 23.610 difflib.py:662(get_close_matches)
1 0.010 0.010 23.620 23.620 profile:0(print_close_matches(«профайлер»))
0 0.000 0.000 profile:0(profiler)
Здесь колонки таблицы показывают следующие значения: ncalls — количество вызовов (функции), tottime — время выполнения кода функции (не включая времени выполнения вызываемых из нее функций), percall — то же время, в пересчете на один вызов, cumtime — суммарное время выполнения функции (и всех вызываемых из нее функций), filename — имя файла, lineno — номер строки в файле, function — имя функции (если эти параметры известны).
Из приведенной статистики следует, что наибольшие усилия по оптимизации кода необходимо приложить в функциях quick_ratio() (на нее потрачено 8,69 секунд), get_close_matches() (4,93 секунд), затем можно заняться real_quick_ratio() (2,95 секунд) и _calculate_ratio() (секунд).
Это лишь самый простой вариант использования профайлера: модуль profile (и связанный с ним pstats) позволяет получать и обрабатывать статистику: их применение описано в документации.
Модуль timeit
Предположим, что проводится оптимизация небольшого участка кода. Необходимо определить, какой из вариантов кода является наиболее быстрым. Это можно сделать с помощью модуля timeit.
В следующей программе используется метод timeit() для измерения времени, необходимого для вычисления небольшого фрагмента кода. Измерения проводятся для трех вариантов кода, делающих одно и то же: конкатенирующих десять тысяч строк в одну строку. В первом случае используется наиболее естественный, «лобовой» прием инкрементной конкатенации, во втором — накопление строк в списке с последующим объединением в одну строку, в третьем применяется списковое включение, а затем объединение элементов списка в одну строку: