Vilnius,Lithunia
Выглядит отлично! Программа прошла один тест, поэтому отправим ее на производство обрабатывать столицы и страны со всего мира, пока она не ошибется на данном файле:
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 из командной строки, введя — m pdb, например, так:
$ python — m pdb capitals.py cities2.csv
> /Users/williamlubanovic/book/capitals.py(1)()
-> 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)()
-> def process_cities(filename):
Программа завершилась нормально, точно так же, как и раньше, когда мы запускали ее вне отладчика. Попробуем запустить ее снова, использовав специальные команды, чтобы сузить место поиска проблемы. Похоже, имеет место логическая ошибка, а не синтаксическая проблема или исключение (из-за них мы бы увидели сообщение об ошибке).
Введите s (step — «шаг»), чтобы пройти по отдельным строкам кода. Это позволит пройти по всем строкам — вашим, стандартной библиотеки и любых других используемых вами модулей. Когда вы применяете команду s, вы также входите во все функции и проходите каждую построчно. Введите n (next — «следующий»), чтобы идти по шагам, но не заходить внутрь функций: когда вы находитесь на строке, где вызывается функция, эта команда выполняет всю функцию и вы оказываетесь на следующей строке. Используйте s, если вы не уверены в том, где конкретно есть проблема, а n — если уверены, что некоторая функция проблем не вызывает, особенно если это длинная функция. Зачастую вы будете проходить построчно весь свой код и пропускать библиотечный, поскольку подразумевается, что он хорошо протестирован. Мы используем s, чтобы начать двигаться от начала программы к функции process_cities():
(Pdb) s
> /Users/williamlubanovic/book/capitals.py(12)()
-> if __name__ == '__main__':
(Pdb) s
> /Users/williamlubanovic/book/capitals.py(13)()
-> import sys
(Pdb) s
> /Users/williamlubanovic/book/capitals.py(14)()
-> 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 (for line in file:) будет считывать каждую строку входного файла, поэтому она выглядит невинно. Единственное место, где мы можем вернуться из функции до прочтения всех данных, — это строка 6 (return). Поставим точку останова на строке 6:
(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'
Что такого особенного… а, забудьте.
Серьезно? Столица называется *quit*o? Наш менеджер не ожидал, что строка 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 без аргументов выведет все строки, начиная с точки предыдущего вызова команды 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
Теперь модифицируем наш тест так, чтобы выполнялась проверка на полное совпадение со строкой quit, без всяких других символов: