7
Об ошибках
Никогда не бывает такого, что пишешь большую программу, а она сразу работает как хочешь1 Обычно запускаешь первые разы – и сразу видишь какие-то ошибки. Питон старается выдавать дружественные человекочитаемые сообщения об ошибках максимально понятным и при этом довольно подробным образом, но всё-таки, расшифровка этих сообщений иногда требует некоторой привычки. Давайте посмотрим на несколько примеров довольно типичных ошибок и попробуем в них разобраться.
1 >>> file = open("Молоко — Википедия.html")
2 >>> text = file.read()
3 Traceback (most recent call last):
4 File "<pyshell#1>", line 1, in <module>
5 text = file.read()
6 File "C:\Python34\lib\encodings\cp1251.py", line 23, in decode
7 return codecs.charmap_decode(input,self.errors,decoding_table)[0]
8 UnicodeDecodeError: 'charmap' codec can't decode byte 0x98 in position 34254: character maps to <undefined>
Здесь главное посмотреть на слово UnicodeDecodeError. Два полезных кусочка знаний. Во-первых, у питона в памяти любые тексты хранятся в формате, который питон называет словом unicode (спорный вопрос, насколько это корректное название, нам главное понимать, что так это представление текстов называет питон). Во-вторых, питон называет переведение текста из любой кодировки во внутренний формат словом decode, а из внутреннего представление в любую кодировку словом encode. Итого, UnicodeDecodeError обозначает ошибку перевода текста из какой-то кодировки во внутреннее представление. А дальше питон вдаётся в дебри того, какой это был байт по номеру, и какой у него был код, и почему он не смог его расшифровать – пожалуй, такая подробность может быть полезна только людям, которые разрабатывают очередной перекодировщик.
Попросту: мы сказали питону, что файл в одной кодировке, а на самом деле он в другой кодировке. В данном случае мы не сказали питону вообще ни про какую кодировку, и это просто значит, что питон пытался угадать, но угадал неправильно.
Надеюсь, такая ошибка только у меня возникает. Разумеется, если мы хотим вызвать функцию, то после имени функции нужно ставить скобки. parentheses, собственно, и переводится как скобки. (Притом только круглые. Фигурные скобки называются braces, а квадратные brackets).
Сначала читаем сообщение об ошибке: TypeError не очень информативно – очень много чего в питоне называется TypeError. Читаем дальше: строковый объект не вызываемый. Вообще, слово callable в питоне вообще используется почти как существительное, и значит он такую штуку, после которой можно поставить скобочки, чтобы её вызвать. То есть попросту он говорит, что строка – не функция! (А мужики-то не знали)
Из этого мы делаем вывод, что он пытался строку вызвать как функцию. А смотрим мы на строку open(). Значит нужно пристальнее посмотреть, что у нас известно под именем open, и мы обнаруживаем, что вместо того, чтобы вызвать open мы присвоили в переменную open строку.
Перезапустим шелл, чтобы вернуть себе родной open.
TypeError неинтересно, встроенная_функция_или_метод не subscriptable. Слово subscript обозначает нижний индекс, как в a1, a2, ... an. В питоне предполагается, что запись a[n] является аллегорией к такой математической записи. (Или наоборот. В любом случае слово "аллегория" – клёвое, особенно в контексте языков программирования).
То есть мы попытались взять элемент по индексу или по ключу из чего-то, что не является ни списком, ни словарём.
И даже если результат класть в переменную, мы всё равно делаем ту же ошибку.
Глядим пристально: мы же не говорили ни о каких списках и словарях, мы хотели функцию вызвать! Тут нужны круглые скобки, а не квадратные!
SyntaxError – синтаксическая ошибка. То есть питон ещё даже исполнять ничего не успел начать, он пока что вообще не может разобрать, что здесь написано. Ошибка в закорючках.
EOL расшифровывается как end of line. (А EOF – как end of file).
string literal обозначает задание строки в кавычках. Я на русский несколько некорректно перевожу это словосочетание как строковая константа.
Итого имеем телеграмму от питона: "ошибка в закорючках: наткнулся на конец строки, пока разбирал строку в кавычках" ТЧК2
Смотрим внимательно. Первая одинарная кавычка в строке питоном воспринимается не как апостроф, а как знак, что строка заканчивается. Следом за ней есть двойная кавычка – питон считает, что оттуда начинается следующая строка, и вот она-то и не заканчивается.
Здесь нам помогает странноватое свойство питона:
1 >>> file = open('Молоко — ''Википедия.html')
На удивление, такая команда работает и открывает файл "Молоко — Википедия.html". См. ниже про implicit string literal concatenation.
Здесь всё просто и в типе ошикби "файл не найден", и в комментариях "нет такого файла или папки", да питона нам ещё и имя говорит.
Суть ошибки та же, но название совсем другое: OSError, то есть ошибка операционной системы (OS). Да ещё и некорректный аргумент.
Операционная система – в нашем случае Windows – решила, что файлов с кавычкой в имене не может быть, и по той простой причине, что таких файлов быть не может никогда, поэтому она даже не проверяла, есть ли такой файл, а сразу выругалась благим матом.
У разных операционных систем разные понятия о том, что допустимо, а что недопустимо в имени файла. Хуже того, у них разные понятия о том, в каких кодировках может имя файла храниться.
Поэтому я вам настоятельно рекомендую: если ваши файлы имеют какие-то шансы оказаться в другой ОС (например, вы отсылаете их в качестве домашней работы мне, перебрасываете с компьютера на планшет или смартфон, или просто перекидываете с винды на мак или обратно), называйте файлы так, чтобы в их именах были только латинские буквы, цифры и символ подчёркивания. Это самое надёжное ограничение. В разных условиях с разными другими символами (даже с пробелами и скобками) могут быть разные сложности.
Читаем внимательно. AttributeError: ошибка атрибута? Вспоминаем: атрибут – это переменная внутри объекта. Если у нас есть объект y, то y.x – это атрибут x у объекта y.
Ошибка атрибута почти всегда говорит о том, что мы хотим взять из объекта что-то, чего в нём нет. (Изредка она говорит, что мы в него хотим положить то, что в него класть нельзя).
Ок, читаем дальше. _io.TextIOWrapper – приходится выучить, что таким ужасным образом питон называет файлы. (А во втором питоне он ещё называл файлы словом file, было такое время, и фонтаны были голубые).
Итак, у объекта файла нет атрибута open.
Так и не должно быть! Файлы же открывают так: file = open(...).
Питон не выругался, но сделал нечто, что может нас удивить. Мы бы ожидали, что раз мы join дали один аргумент, то он его же нам и вернёт?
Ан-нет, мы ему дали не список из одного элемента, а одну строку. А join только и надо, чтобы по тому, что мы ему дали, можно было пройти циклом. Если идти циклом по строке, то на каждом шагу мы будем брать очередной элемент. Вот join и воспринял нашу строку как список её букв, и склеил их через запятую.
1 >>> ','.join([1,2,3])
2 Traceback (most recent call last):
3 File "<pyshell#19>", line 1, in <module>
4 ','.join([1,2,3])
5 TypeError: sequence item 0: expected str instance, int found
6 >>> ','.join(['1','2',3])
7 Traceback (most recent call last):
8 File "<pyshell#20>", line 1, in <module>
9 ','.join(['1','2',3])
10 TypeError: sequence item 2: expected str instance, int found
Снова TypeError.
sequence item 0 – то есть речь идёт про элемент номер 0 из последовательности (которую мы дали на вход join).
str instance обозначает любую строку. Слово instance обозначает представителя класса или типа (в третьем питоне это почти синонимы). Например, 'hello' – представитель типа str, а 42 – представитель типа int. В питоне есть даже такая проверка: isinstance(42, int) – правда ли, что 42 – представитель типа int?
Итого питон говорит нам: в элементе последовательности с номером 0 мы ожидали бы увидеть строку, а нашли число.
Действительно, у join есть такое неприятное свойство, что ему подавай на вход обязательно список только строк. Сам преобразовать что угодно другое в строки он не умеет. (Не понимаю, почему. В большинстве случаев я в этом случае добавляю к вызову необходимый минимум кода, чтобы применить к каждому элементу списка функцию str).
На всякий случай вспомним, что является прямым назначением функции join:
Дальше.
1 >>> '+'.join(['1','2','3'], 2)
2 Traceback (most recent call last):
3 File "<pyshell#23>", line 1, in <module>
4 '+'.join(['1','2','3'], 2)
5 TypeError: join() takes exactly one argument (2 given)
6 >>> '+'.join(['1','2','3'], ['3'])
7 Traceback (most recent call last):
8 File "<pyshell#24>", line 1, in <module>
9 '+'.join(['1','2','3'], ['3'])
10 TypeError: join() takes exactly one argument (2 given)
11 >>> x = ['1','2']
12 >>> y = ['3']
13 >>> '+'.join(x, y)
14 Traceback (most recent call last):
15 File "<pyshell#29>", line 1, in <module>
16 '+'.join(x, y)
17 TypeError: join() takes exactly one argument (2 given)
18 >>> '+'.join('1', '2')
19 Traceback (most recent call last):
20 File "<pyshell#30>", line 1, in <module>
21 '+'.join('1', '2')
22 TypeError: join() takes exactly one argument (2 given)
Снова TypeError. Дословно: функция join принимает РОВНО один аргумент, а мы ему даём два.
И упорно пытаемся давать, надо заметить. А он с не меньшим упорством не понимает, чего мы от него хотим. Зачем ему лишние аргументы-то?
Это мы уже видели: забыли кавычку закрыть.
Есть в питоне встроенная функция sum, которая умеет складывать числа. (Вообще, в питоне немного встроенных функций. Чуть больше полусотни, а полезных всего штук 20).
Ситуация простая, а сообщение об ошибке неожиданное.
Снова TypeError. неподдерживаемые типы операндов для +: int и str.
Операнд – это аргумент операции.
То есть у нас почему-то полуается, что мы складываем число со строкой? Откуда число?
Ведь здесь он не ругается!
Оказывается, у функции sum есть необязательный второй аргумент: значение, которое нужно прибавить к сумме. И если его не указать, то оно по умолчанию (default) будет 0.
Вот и получается, что первое действие, которое пытался выполнить питон, было 0 + '1'. Отсюда и такое сообщение об ошибке.
Может, мы сможем это победить, если в start положим строку?
1 >>> sum('123', '4')
2 Traceback (most recent call last):
3 File "<pyshell#35>", line 1, in <module>
4 sum('123', '4')
5 TypeError: sum() can't sum strings [use ''.join(seq) instead]
6 >>> sum(['1','2','3'], '4')
7 Traceback (most recent call last):
8 File "<pyshell#36>", line 1, in <module>
9 sum(['1','2','3'], '4')
10 TypeError: sum() can't sum strings [use ''.join(seq) instead]
Снова TypeError. sum не может складывать строки, и вообще, шли бы вы все пользоваться joinом – как бы говорит нам питон.
В общем, он и прав. (Хотя не могу понять, зачем они эту специальную проверку тут добавили. Учитывая, что в прежних версиях питона её не было).
Дальше.
Снова TypeError. Число не iterable.
Словом iterable в питоне называется всё, по чему можно пройтись циклом: строки, списки, словари, итераторы3.
Число не список? Спасибо, кэп!
Нужно вычислить, кто же это сказал, ибо наваяли мы в этой строке много конструкций. Самый правильный способ разобраться – разделить её на несколько строк:
Ага! Всё просто: в z у нас число, а sum хочет на вход список.
Та же история.
Мы хотели 6!
Но нет, join сделал строку '123', а int из неё сделал число 123. Мы из этого числа сделали список [123], а сумма элементов этого списка равна 123.
Снова помогло бы деление строки на несколько с помощью переменных.
Снова TypeError (неужто их бесконечно много?). Аргумент int() должен быть строкой или числом, но не списком.
Вот тут нам просто самым прямым текстом объяснили, что мы делаем не так. (Правда, заодно тут как раз и мы сами не можем понять, чего же мы хотели).
Словари:
У словаря штука в квадратных скобках называется и не subscript, и не index, а key. (Вот ведь любители плодить термины!)
Лаконичненько питон сообщил нам, что ключа '1' нет в словаре.
Присматриваемся пристально. Ведь и вправду нет, есть ключи 1 и 3, но не '1'! Такую ошибку иногда можно очень долго и очень мучительно вылавливать, однако...
Тут всё очевидно: нет такого модуля. Для питона регистр букв играет большую роль. Tkinter и tkinter – разные штуки!
1 >>> import tkinter
А так всё работает.
Строка не поддерживает item assignment, то есть присвоения части по индексу.
В питоне строки принципиально сделаны неизменяемыми объектами. В отличие от списков, все операции со строками всегда не меняют исходную строку, но возвращают нам изменённую строку в качестве результата:
Конечно, мы вольны положить результат обратно в ту же переменную, но (и тут тонкое отличие переменных от объектов, про которое мы с вами ещё поговорим) сама исходная строка при этом не меняется, просто её теперь нет в этой переменной:
Тут думать не надо: нет такой переменной.
1 >>> оpen('hello')
2 Traceback (most recent call last):
3 File "<pyshell#55>", line 1, in <module>
4 оpen('hello')
5 NameError: name 'оpen' is not defined
6 >>> Open('hello')
7 Traceback (most recent call last):
8 File "<pyshell#55>", line 1, in <module>
9 Open('hello')
10 NameError: name 'Open' is not defined
А вот если вы напечатали русскую букву 'о' или написали название функции не в том регистре, то вы сами себе злобные Буратины.
Ровно по этой причине в питоне настоятельнейшим образом рекомендуется давать переменным однобуквенные имена: эл-малое, ай-большое, о-малое и о-большое. Их и между собой легко перепутать (в некоторых шрифтах), и с единицей и нулём спутать. А потом долго и мучительно разбираться, почему же одна единичка работает, а другая не работает.
Лично моё пожелание: чтобы имена переменных всегда были словами английского языка или словосочетаниями английского языка, записанными маленькими буквами через подчёркивание. ВСЕГДА. (Ну, кроме упражнений с питоном, когда мы пытаемся найти разыне эпичные способы питон сломать и прочих одноразовых экзерсисов, следов которых никто никогда не увидит.)
Тут всё понятно: мы дали функции недостаточно аргументы. replace нужно сказать, что на что мы меняем, а мы ей дали только какую-то непонятную единицу.
1 >>> tkinter.Tk(1,2)
2 Traceback (most recent call last):
3 File "<pyshell#58>", line 1, in <module>
4 tkinter.Tk(1,2)
5 File "C:\Python34\lib\tkinter\__init__.py", line 1851, in __init__
6 self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
7 TypeError: must be str or None, not int
Тоже всё понятно: от нас хотят строку или число... Непонятно только, зачем функции Tk() аргументы – ну да это вопрос к документации...
Не просто недостаточно аргументов, а недостаточно required positional arguments, да ещё питон и говорит нам, каких именно. Осталось только в хелпы подглядеть, что мы напутали.
positional argument – это аргументы, которые мы передаём просто через запятую. Есть ещё keyword argument – это аргументы, которые мы передаём по имени, как encoding в open('filename', encoding='utf-8').
Мы пока больше смотрели случаев, когда питон ругается на то, что нам кажется почти правильным. А вот питон не ругается, но зато делает безобразнейше не то, что от него требовалось:
Должен вам доложить, что именно из-за ровно такой опечатки – замены точки на запятую – (только в совсем другом языке) был потерян один из спускаемых аппаратов на Марс (кажется, Фобос-1, если не путаю). Ошибочка ценой в какие-то безумные сотни миллионов долларов, однако.
Печально, что до сих пор почти ни в одном языке программирования не искорены случаи, где замена точки на запятую приводит к программе, которая продолжает работать, но делает это по-другому. Будьте бдительны!
Синтаксическая ошибка. Внимательно смотрим на все закорючки. Вот двоеточие на точку с запятой питон заменить не даст.
Если мы всё же хотим просуммировать числа, которые лежат в виде списка строк (по-русски это и не скажешь нормальн), то делаем мы это просто ручками:
Хм, где-то мы такую ошибку видели. 0 + '1'...
Вот, теперь получилось.
Все слова по отдельности уже знакомы, но что ж ему не нравится: файл нельзя использовать с квадратными собками? Но мы же его прочитали... Прочитали? Точно? Да нет же, мы его только открыли! Нужно добавить text = file.read(), и работать вообще с текстом.
Файл нельзя использовать в качестве функции. Ок, тут всё ясно.
1 >>> re.findall(')', text)
2 Traceback (most recent call last):
3 File "<pyshell#78>", line 1, in <module>
4 re.findall(')', text)
5 File "C:\Python34\lib\re.py", line 206, in findall
6 return _compile(pattern, flags).findall(string)
7 File "C:\Python34\lib\re.py", line 288, in _compile
8 p = sre_compile.compile(pattern, flags)
9 File "C:\Python34\lib\sre_compile.py", line 465, in compile
10 p = sre_parse.parse(p, flags)
11 File "C:\Python34\lib\sre_parse.py", line 753, in parse
12 raise error("unbalanced parenthesis")
13 sre_constants.error: unbalanced parenthesis
sre_constants.error мало чего говорит. Если закопаться в детали реализации, то мы обнаружим, что модуль re использует для грязной работы модуль sre – собственно реализация регулярных выражений.
unbalanced = несбалансированная
parenthesis = скобка
Ага! В регулярных выражениях те скобки, которые для регулярных выражений имеют смысл, должны быть в балансе и гармонии открывающих и закрывающих скобок.
А вот это неожиданное.
indent = отступ, то есть число пробелов от начала строки.
unexpected = неожиданный.
Дело вот в чём. В питоне отступ играет существенную роль. Когда питон читает очередную команду, он должен понять, в какую составную команду она вложена – по отступу. И если у нас в теле if отступ был 4 пробела, снаружи от if отступ был 0 пробелов, а после тела if мы написали команду с отступом 2 пробела, то у питона большие проблемы, он не знает, что мы имели в виду, и, собственно, так нам об этом и скажет. А для пущего порядка и для защиты от ошибок он требует, чтобы и вообще всегда и везде строки на одном уровне вложенности имели одинаковый отступ.
Соответственно, строки вне всяческих составных конструкций должны идти с отступом ноль пробелов, и никаким другим отступом.
Вот на это питон и ругается.
Не можем сконвертировать файл в строку неявно (implicitly).
Действительно, всё та же ошибка, что в переменной file у нас не текст файла, а сам файл.
1 >>> re.sub('a', 'b', file)
2 Traceback (most recent call last):
3 File "<pyshell#86>", line 1, in <module>
4 re.sub('a', 'b', file)
5 File "C:\Python34\lib\re.py", line 175, in sub
6 return _compile(pattern, flags).sub(repl, string, count)
7 TypeError: expected string or buffer
8 >>> file
9 <_io.TextIOWrapper name='Молоко — Википедия.html' mode='r' encoding='cp1251'>
Ошибка: ожидаю строку или буфер. (Буфером в питоне называется одна крайне экзотическая разновидность строк).
Всё то же: сам файл (а не его текст) мы не можем скормить в регулярные выражения.
>>> file.strip() Traceback (most recent call last): File "<pyshell#88>", line 1, in <module> file.strip() AttributeError: '_io.TextIOWrapper' object has no attribute 'strip'
У файлов нет атрибута strip. Потому, что strip есть только у строк. Чтобы из файла сделать строку, его нужно сначала прочитать.
ValueError – самое непонятное мне название типа ошибки: у вас ошибка с данными. (Хорошо хоть не GeneticCodeError)
I/O или IO очень много где обозначает ввод-вывод. Часто оно значит либо взаимодействие программы с окружающим миром (например, интерфейс), либо работу с файлом: чтение и/или записиь.
Итак: операция ввода-вывода на закрытом файле.
Действительно, после того, как мы закрыли файл, мы с ним ничего не можем делать. Разве что заново открыть.
IndexError. Ошибка... индексирования? Да, его. То есть взятия чего-то по индексу или записывания чего-то по индексу.
Индекс списка вне диапазона... Диапазаона чего? Допустимых значений.
В переводе на русский: мы задали номер элемента слишком большой или слишком маленький.
io.UnsupportedOperation: неподдерживаемая операция ввода-вывода...
"not writable": то есть сюда нельзя писать.
Дело вот в чём. Когда мы открываем файл на чтение или на запись, мы питону говорим о своих намерениях. И во-первых, он сам готовится к такому режиму общения с файлом, а во-вторых, и нам от греха подальше запрещает делать то, на что мы вначале не намеревались.
(Если бы, например, питон нам разрешал читать из файла, который мы открыли на запись, было бы смешно: сколько бы мы ни читали, всегда бы выходило пусто. Копаться в том, почему бы это было так, было бы тяжко. Вот нас питон от этого и защищает).
1 >>> file = open('Молоко — Википедия.html', encoding='cp1251')
2 >>> text = file.read()
3 Traceback (most recent call last):
4 File "<pyshell#95>", line 1, in <module>
5 text = file.read()
6 File "C:\Python34\lib\encodings\cp1251.py", line 23, in decode
7 return codecs.charmap_decode(input,self.errors,decoding_table)[0]
8 UnicodeDecodeError: 'charmap' codec can't decode byte 0x98 in position 34254: character maps to <undefined>
Знакомое: ошибка кодировки. Нужно открывать файл с другой кодировкой.
Implicit literal concatenation
В питоне есть смешное правило: если две строки в кавычках идут подряд только через пробелы, то питон считает, что это одна строка в кавычках, которую мы удобства ради разделили.
Очень экзотическая штука, нужна в странных ситуациях, когда мы хотим длинную строку как-то посимпатичнее изобразить, но не хотим возитьтся с плюсиками или избавляться потом от лишних переносов строк в ней:
Надо заметить, что:
Это правило работает только для строк в кавычках.
В общем, проще считать, что такого правила и нет. Но если вдруг вы его где встретите, не удивляйтесь.
Регулярки: re.sub, re.split
Скачаем из википедии страничку про молоко.
Откроем и обнаружим, что там сплошь странные закорючки. Это язык html. В нём есть всего два понятия:
штука от & до ; называется "entity" и является способом кодировать какой-нибудь символ, которого нет на клавиатуре (например, длинное тире, неразрывный пробел, какой-нибудь греческо-математический значок или китайский иероглиф)
штука от <слово...> до </слово> называется "tag" или "тэг", и она говорит, что у куска текста от открывающего тэга до закрывающего тэга есть какие-то интересные свойства; напримерб <b>Молоко</b> обозначает, что Молоко нужно написать жирным шрифтом.
Кунг-фу html состоит в том, чтобы выучить, какие бывают тэги, и как ими правильно пользоваться – но это сейчас за пределами наших ближайших интересов.
Нам главное, что кроме вот этих (вполне читаемых) дополнений html – текстовый файл.
Давайте попробуем из него все тэги выкинуть, и получить текст!
В этом нам поможет re.sub(регулярка, на-что-заменить-находку, текст)
1 >>> file = open("Молоко — Википедия.html", encoding='utf-8')
2 >>> text = file.read()
3 >>> import re
4 >>> print(text[:100])
5 <!DOCTYPE html>
6 <!-- saved from url=(0066)https://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BB%D0%BE%D0%
7
8 >>> smile = re.sub('[&][^;]*;', ':)', text)
9 >>> len(smile)
10 191437
Для начала избавились от смайликов.
Смотреть на такой большой текст в консоли не удобно, сохраним в файл.
Для этого функции open мы можем вторым аргументом сказать одно из:
'r' – мы будем из файла только читать. Если файла не существует, то это ошибка.
'w' – мы будем в файл только писать. Если файла не было, мы его создадим. А если был – ВНИМАНИЕ – то мы его предварительно сотрём.
'a' – мы будем в файл только дописывать в самый конец. Если файла не было, мы его создадим. Если был, допишем в конец. Добрая традиция гласит, что при дописывании в конец файла нужно оставлять за собой перенос строки в конце, чтобы когда кто-нибудь ещё будет дописывать в файл что-нибудь ещё, оно началось с новой строки.
После того, как мы открыли файл на запись, мы можем пользоваться у него методом write(). Он очень тупой и ест только строки. Нужно не забывать закрыват файл или ползоваться with – только в тот момент, когда файл закрывается, изменения отправляются на жёсткий диск, а до этого они болтаются в оперативной памяти.
Смотрим на текст. Всё ок.
Питон в файле хранит курсор, который указывает на то место в файле, где мы находимся. Если мы прочитали файл целиком, то курсор находится в конце файла, и от него дальше читать некуда, поэтому повторное чтение файла бессмысленно. Проще всего в этом случае открыть файл заново.
Убить тэги просто: открывающая скобка, всё, кроме закрывающей скобки, закрывающая скобка – заменяем это на пустую строку, и voila!
Только есть сколько-то мусора непонятного. Этот мусор почти весь обитает либо в тэге <script...>...</script>, либо в тэге <style...>...</style>. Уберём их! (Только это нужно делать _до_ того, как мы убрали все тэги, иначе там уже никаких <script> не осталось).
Ага, хорошо только много пустых строк.
Что-то некоторые строки склеились. И вправду, если у нас между строками a и b была одна пустая строка, то это было a\n\nb, а мы его превратили в ab. Ошибочка, заменять два переноса строки нужно на один:
Так-то лучше.
Из приятностей регулярных выражений. Мы можем говорить:
a* – повтори от 0 до бесконечности раз
a+ – повтори от 1 до бесконечности
a? – повтори 0 или 1 раз
a{n,m} – повтори от n до m раз
a{,m} – повтори от 0 до m раз
a{n,} – повтори от n раз до бесконечности
Теперь попробуем разбить текст на предложения.
Для этого в питоне есть функция re.split()
Мы можем здесь добиваться более качественных результатов разными способами. Либо, как в прошлый раз, вернуться к паарадигме, что мы описываем не то, что делит предложение, а то, что может в предложение входить, и тогда пользуемся findall. Либо мы можем освоить ещё пару хитростей из регулярных выражений:
(?= ... ) проверяет, что следом за текущей позицией есть то, что в скобках, но при этом частью итоговой находки регулярного выражения эта штука не станет
(?<= ... ) проверяет, что перед текущей позицией есть то, что в скобках, но снова не включает эту часть в находку.
1 re.split(r'(?<=[а-яa-z](?:[.?!]|[.]{3})\s+(?=[A-ZА-Я])', cleaner)
Запуск программ
Кусок теории. У каждой программы есть понятие папки, где она находится. Эта папка называется рабочей директорией или current working directory. Мы можем её узнавать (os.getcwd) или менять (os.chdir):
Кусок теории: абсолютные и относительные пути.
До каждого файла мы можем дотянуться по его абсолютному пути – начиная от C: (или D: и т.п.). Но это почти никогда не удобно (например, если вы пересылаете программу кому-то, то у него всё почти наверняка лежит в других папках).
Второй способ указать имя файла – относительный путь. Относительный путь – это та часть пути, которая, если её присоединить к рабочей директории, станет абсолютным путём.
Действительно, мы сейчас не в learn-python, а файл там.
Работает!
NB. 'C:\\Users\\dendik\\Desktop' + '\\' + 'learn-python\\clean.txt' == 'C:\\Users\\dendik\\Desktop\\learn-python\\clean.txt'
То есть действительно, если мы этот путь прибавим к текущей рабочей директории, то мы получим тот же самый полный путь, который мы можем посмотреть в свойствах файла.
Это работает и с большим числом уровней вложенности.
1 >>> open(r'learn-python\Молоко — Википедия_files\90px-Milk_glass.jpg')
2 <_io.TextIOWrapper name='learn-python\\Молоко — Википедия_files\\90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
3 >>> open(r'learn-python/Молоко — Википедия_files/90px-Milk_glass.jpg')
4 <_io.TextIOWrapper name='learn-python/Молоко — Википедия_files/90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
5 >>> open('learn-python/Молоко — Википедия_files/90px-Milk_glass.jpg')
6 <_io.TextIOWrapper name='learn-python/Молоко — Википедия_files/90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
7 >>> open('learn-python/Молоко — Википедия_files\90px-Milk_glass.jpg')
8 <_io.TextIOWrapper name='learn-python/Молоко — Википедия_files\\90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
9 >>> open(r'learn-python/Молоко — Википедия_files\90px-Milk_glass.jpg')
10 <_io.TextIOWrapper name='learn-python/Молоко — Википедия_files\\90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
11 >>> open(r'learn-python/Молоко — Википедия_files\/90px-Milk_glass.jpg')
12 <_io.TextIOWrapper name='learn-python/Молоко — Википедия_files\\/90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
13 >>> open(r'learn-python/Молоко — Википедия_files\//////90px-Milk_glass.jpg')
14 <_io.TextIOWrapper name='learn-python/Молоко — Википедия_files\\//////90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
15 >>> open(r'learn-python/Молоко — Википедия_files\90px-Milk_glass.jpg')
16 <_io.TextIOWrapper name='learn-python/Молоко — Википедия_files\\90px-Milk_glass.jpg' mode='r' encoding='cp1251'>
На самом деле, писать пути через '\\' – это самый идиотский способ. Во-первых, мы можем использовать r'' строки, чтобы не дублировать слэши. Во-вторых, и это самый правильный способ, мы можем использовать '/' вместо '\\' – windows признаёт оба способа. Что характерно, UNIX и MacOS признают только '/', так что если вы используете его, то ваша программа работает в наибольшем числе систем.
Есть ещё одно специальное имя поддриректории: .. обозначает "уровень выше".
Соответственно ../.. обозначает двумя уровнями выше. ../learn-python обозначает подняться на уровень выше, найти там learn-python и зайти в него.
Скачаем программу mystem и распакуем mystem.exe там же, где лежат наши питонские программы. Так нам будет проще с ним работать (теперь должно быть понятно, почему).
Вызвать программу из питона очень просто.
Только вот mystem запускается, показывает хелпы и закрывается.
У каждой текстовой программы (на самом деле, совсем у каждой, но остальные не подают виду) есть три способа общения с пользователем:
stdin – читать текст от пользователя с экрана; это ровно то, откуда читает текст функция input() в питоне
stdout – писать текст на экран
stderr – писать на экран сообщения об ошибках (или хелпы какие-нибудь – в общем, не содержательные данные)
Мы можем попросить питон сохранить в файл содержимое, которое программа пишет в один из этих потоков.
Очень просто:
Теперь в файле helps.txt оказались наши хелпы.
Почитав их, расшифровываем, что:
- программе можно давать много разных параметров, которые меняют режим работы программы, они выглядят -буква, и называются флагами
- программе можно давать несколько параметров, которые берут аргументы
- программе можно давать два имени файла: входной и выходной. Если кого-то из них нет, то вместо них мы будем писать на stdout и читать с stdin соответственно
Попробуем запустить:
Когда call возвращает 0, это значит, что программа завершилась успешно. Если он возвращает что угодно, кроме 0, скорее всего, произошла какая-то ошибка.
И вправду, мы написали senteces вместо sentences!
Ура, работает! Но подробнее о том, что делает mystem, и кто такой морфологический разбор – на лингвистике.
У меня тоже не бывает. Просто у меня из-за кучи опыта понятие "большая программа" чуть-чуть другое. (1)
Даже на мой век уже не досталось ни отправить ни получить ни одной телеграммы. Но какая-то романтика вокруг этого слова всё равно витает. (2)
Напоминаю: итератор -- штука, которая от всей функциональности списков умеет только быть подставленной в for или быть скормленной аргументом в list -- но нет у неё ни доступа по индексу, ни полезных методов. Нунжы итераторы как правило для того, чтобы не дублировать большие списки в памяти компьютера (3)