Занятие №6
Если вы хотите получить пользу от этого конспекта, нужно брать из него каждый фрагмент кода, копировать его, исполнять, и разбираться, как он устроен.
... И задавать нам вопросы!
Словари
В списках в качестве индексов мы можем использовать только числа. Иногда этой возможности не хватает. Например, когда мы хотим хранить в питоне словарь: отображать слово на его перевод.
Для этого в питоне есть структура данных, которая называется – тадам – словари.
Создаются они так:
1 translations = {'кошка': 'cat', 'собака': 'dog', 'чучело':'scarecrow'}
Дальше, по аналогии со списком, мы можем получить значение, ассоциированное со словом 'кошка':
1 print(translations['кошка'])
Терминология: то, что в списках в питоне называется индексом, в словарях называется ключом.
Запись в словарь делается тоже аналогично спискам:
1 translations['солнце'] = 'sun'
Отличие от списков: если такого ключа в словаре не было, то он создаётся (и это основной способ добавления данных в словарь), а если такой ключ в словаре был, то меняется то, что по нему в словаре лежит:
Когда мы словарь подставляем туда, где питон ожидает список, питон воспринимает словарь как список его ключей:
В словаре данные хранятся в таком порядке, чтобы питон мог быстро их получать. Для нас этот порядок не имеет никакого смысла и выглядит совершенно случайным.
Однако мы очень легко можем получить список ключей словаря, упорядоченными по алфавиту:
1 sorted(translations)
Как нам вывести упорядоченным весь словарь?
Это выводит просто все ключи словаря в случайном порядке... Совсем не то.
Так мы выводим их уже по порядку, но только ключи.
Осталось взять по ключу соответствующее ему значение:
Мы уже выяснили, что по ключу хранится только одно значение. А если мы всё-таки очень хотим хранить несколько? Мы можем хранить в словаре любые другие контейнеры тоже:
Ещё мы можем из словарей удалять элементы:
Очень часто требуется проверить, есть ли какое-то значение в словаре, если оно есть, то получить его в переменную, а если его нет, то получить в этой переменной какое-нибудь другое значение (например, None), которое символизирует тот факт, что ничего не нашлось.
Мы можем это сделать так:
Но для этого в питоне есть и более короткая готовая запись:
А если нам нужен не None, то мы можем вторым аргументом сказать get, что возвращать:
Поэтому полезно иногда смотреть в help(dict) или на [[|документацию на сайте питона]] и смотреть, не появилось ли там какой-нибудь функции, потребность в которой вы обнаружили, которая вдруг стала вам понятна.
Строки
Кроме одинарных и двойных кавычек в питоне есть ещё пара возможностей задавть строки.
Во-первых, если нам нужно скопипастить в питон длинный кусок текста, то есть все шансы, что в нём окажутся и одинарные, и двойные кавычки (и с ними нужно будет возиться втыкать перед ними \), да и вдобавок что-то делать с переносами строк было бы долго и мучительно.
Поэтому в питоне есть ещё два вида кавычек ''' ... ''' и
""" ... """" – случайно встретить в тексте их почти нет возможностей, и вдобавок, питон
разрешает между ними делать переносы строк сколько угодно:
1 text = '''Москва (произношение (инф.)) — столица Российской Федерации, город
2 федерального значения, административный центр Центрального федерального округа
3 и центр Московской области[7], в состав которой не входит. Крупнейший по
4 численности населения город России и её субъект — 12 108 257[3] чел. (2014),
5 самый населённый из городов, полностью расположенных в Европе, входит в первую
6 десятку городов мира по численности населения[8]. Центр Московской городской
7 агломерации.
8
9 Историческая столица Великого княжества Московского, Русского царства,
10 Российской империи (в 1728—1730 годах), Советской России и СССР. Город-герой.
11 В Москве находятся федеральные органы государственной власти Российской
12 Федерации (за исключением Конституционного суда), посольства иностранных
13 государств, штаб-квартиры большинства крупнейших российских коммерческих
14 организаций и общественных объединений. В городе организована система местного
15 самоуправления.
16
17 Расположена на реке Москве в центре Восточно-Европейской равнины, в междуречье
18 Оки и Волги. Как субъект федерации, Москва граничит с Московской и Калужской
19 областями.
20
21 Москва — важный туристический центр России; Московский Кремль, Красная
22 площадь, Новодевичий монастырь и Церковь Вознесения в Коломенском входят в
23 список Всемирного наследия ЮНЕСКО[9]. Она является также важнейшим
24 транспортным узлом. Город обслуживают 5 аэропортов, 9 железнодорожных
25 вокзалов, 3 речных порта (имеется речное сообщение с морями бассейнов
26 Атлантического и Северного Ледовитого океанов). С 1935 года в Москве работает
27 метрополитен.'''
Во-вторых, когда мы пишем пути файлов, питон иногда может \ воспринять не как символ, который мы хотели ввести, а как часть специального сочетания (в духе \n, \t и т.п.). Конечно, мы можем взять и каждый такой случай заэкранировать ещё одним символом \:
1 'C:\\Users\\new...'
Но это муторно, делает программу менее читаемой, и чревато ошибкой, если мы где-то продублировать \ забудем. Поэтому в питоне есть специальный тип строк, в которых \ ничего не значит:
1 r'C:\Users\new...'
Регулярные выражения
Регулярные выражения – это способ задавать множества строк. Чаще всего они используются для поиска в текстовых редакторах (типа notepad++ или pycharm) и в языках программирования.
В питоне всё для работы с регулярными выражениями обитает в модуле re:
1 import re
Если мы просто пишем строку, то это регулярное выражение, которое находит эту строку:
1 re.findall('подстрока', text)
Если мы пишем несколько строк через вертикальную черту |, то это регулярное выражение, которое ищет любую из этих строк:
1 re.findall('построка1|подстрока2', text)
Мы можем ограничивать область действия вертикальной черты с помощью скобок. Таким образом мы можем писать выражения вида сначала общая часть, потом несколько вариантов, потом снова общая часть:
1 re.findall('(город(ов|ами))', text)
Скобки несут для питона два смысла: во-первых, ограничить область действия вертикальной черты, а во-вторых, сказать, что вот эта часть регулярного выражения и есть то, что нам интересно, а остальное не интересно.
Если нам от скобок действительно нужно только ограничить действие вертикальной черты, то для этого есть другой тип скобок: (?: ... )
1 re.findall('город(?:ов|ами)', text)
Так-то лучше.
Есть ещё одно удобство. Если мы хотим описать выражение, которое находит только ровно одну букву из заданного множества, то мы это можем сделать тремя абсолютно эквивалентными способами:
Таким образом мы можем, например, попросить питон найти нам все места в строке, где за словом город идёт одна маленькая буква:
1 re.findall('город[а-я]', text)
[^ множество букв ] обозначает любую одну букву, кроме тех, которые заданы в множестве:
1 re.findall('город[^ ]', text)
Оно находит все строк из слова город и буквы за ним, если сразу за ним идёт буква.
Если же мы хотим найти слово город во всех формах, то нам нужно, чтобы за ним шло сколько угодно букв.
В регулярных выражениях, если мы за буквой или скбокой ставим символ *, то это обозначает, что нужно эту букву или содержимое этой скобки повторить ноль или более раз:
a* <=> (|a|aa|aaa|aaaa|aaaaa|...)
Теперь мы можем найти слово город во всех формах:
Символ + вслед за буквой или скобкой обозначает повторение 1 или более раз:
1 re.findall('город[^ ]+', text)
Такое выражение не будет находить само слово "город", но будет находить все остальные слова, начинающиеся с этих пяти букв (включая городничего и горожанина – но где же вы такие слова в современных текстах встретите?)
a+ <=> (a|aa|aaa|aaaa|aaaaa|...)
По существу, это (и даже немного меньше) и есть всё, что умеют регулярные выражения. В них есть ещё несколько сокращений и удобств, но они не меняют выразительной силы этого языка, да и на удобстве сказываются не радикально.
Попробуем найти все предложения, содержащие слово город. Дадим такое (заведомо сильно упрощённое) понимание предложения: это последовательность символов, среди которых нет точки, вопросительного или восклицательного знака, которая им заканчивается.
Такое пожелание переводится на питон таким образом:
1 re.findall('[^.?!]*город[^.?!]*[.?!]')
Для бесстрашных
Попробуем добиться того, чтобы наше регулярное выражение не останавливалось на сокращениях.
Для этого нам нужно несколько усложнить понятие символа, который может быть в предложении. Пока что мы использовали такое определение:
1 re.findall('[^.?!]')
Добавим туда усложнение: предложение состоит из символов, не являющихся точкой, вопросительным знаком и восклицательным знаком, либо из точки, за которой (возможно после пробела) следует не прописная буква. (Как раз так проявляют себя многие сокращения). Тогда наш символ выглядит так:
1 re.findall('[^.?!]|[.] *[^а-я]')
Осталось подставить этот кусочек выражения в исходное:
1 re.findall('(?:[^.?!]|[.] *[^а-я])*город(?:[^.?!]|[.] *[^а-я])*[.?!]')
Ужасно нечитаемо. По аналогии с "read-only" устройствами (которые на аппаратном уровне не допускают в них записи), программисты между собой называют такой код "write-only": его вполне возможно написать, но потом уже никто не сможет его прочитать и понять. Лучше бы оставить в программе следы того, как мы о ней думаем:
Пожалуй, больше всего выразительная сила регулярных выражений (и её ограничение) чувствуется в примерах, когда вы состыковываете вместе несколько частей, каждая из которых является выбором из нескольких вариантов или неограниченной длины повтором.
Например, мы хотим перечислить несколько основ слов, и хотим для каждой из основ найти все продолжения:
1 re.findall('(город|государств)[^,; ]*')
Оффтопик
В задаче про города может оказаться полезным сделать словарь, в котором ключи – названия городов, а значения – числа 0 или 1.
Такой словарь можно сделать очень просто, если немножко поискать, а какие ещё вкусности есть вокруг в стандартной библиотеке.
Во-первых, мы можем создать список из 0 такой же длины, как и наш список городов.
Дальше, мы можем обнаружить в питоне встроенную функцию zip, которая из двух списков делает список пар соответствующих элементов из них:
(Одна тонкость: функция zip возвращает итератор – то есть ведёт её результат себя как нужный нам список, но вот чтобы посмотреть на него в человеческом виде нам нужно применять к ней list)
И ещё мы можем обнаружить функцию dict, (по аналогии с функциями int, float, str, list), которая из всего пытается делать словарь. Она умеет делать словарь либо из другого словаря, либо из списка пар ключ-значение.
Употреблять это мы можем как есть, а можем и сложить всё вместе:
1 marked = dict(zip(cities, [0] * len(cities)))
Полезные ссылки
Шпаргалка
RE |
Synonyms |
Description |
Examples |
Van[yj]a |
|
В квадратных скобочках указываются символы, которые могут стоять в данной позиции. При этом можно использовать тире для того, чтобы указывать последовательность символов, например: [0-9] (все цифры), [a-z] все маленькие буквы, [a-zA-Z] (все буквы) |
|
Van[^ji] |
|
Если первым символом после открывающей квадратной скобочки является ^, то в квадратных скобочках перечисляются символы, которые НЕ могут стоять в данной позиции, т.е. остальные все символы, кроме этих, могут. |
|
Van{3}ya |
Vannnya |
В фигурных скобочках указывается количество раз, которое повторяется непосредственно предшествующий символ |
|
Van{1,3}ya |
|
В фигурных скобочках через запятую указывается минимальное и максимальное количество раз, которое повторяется непосредственно предшествующий символ |
|
Van{2,}ya |
|
Если максимальное количество раз пропущено, то это значит что количество повторений сверху не ограничено и может быть бесконечным |
|
Van?ya |
Van{0,1}ya |
Знак вопроса означает, что непосредственно предшествующий символ необязательный |
|
Van*ya |
Van{0,}ya |
Звездочка означает 0 или большее количество повторений непосредственно предшествующего символа |
|
Van+ya |
Van{1,}ya |
Плюсик означает 1 или большее количество повторений непосредственно предшествующего символа |
|
Va.ya |
|
Точка означает любой символ, кроме символа конец строки (\n) |
|
Va\.ya |
|
Backslash «защищает» символ, который следует за ним, т.е. делает символ обычным. |
|
(Va)+nya |
|
Скобочки используются для группировки |
|
^Van |
|
^ обозначает начало строки |
|
ya$ |
|
$ обозначает конец строки |
|
Vanya|Kolya |
|
Выбор одного из нескольких вариантов |
|
Van(ya|e) |
|
Выбор одного из нескольких вариантов и круглые скобки |
|
\s |
[ \t\n\r\f] |
Space char – символ пробела |
|
\S |
[^ \t\n\r\f] |
! Space char – не символ пробела |
|
\d |
[0-9] |
Digit |
|
\D |
[^0-9] |
! Digit |
|
\w |
[a-zA-Z0-9] |
Word char – буква |
|
\W |
[^a-zA-Z0-9] |
! Word char – не буква |
|
Vanya\b |
|
Граница слова |
|
\B |
|
НЕ Граница слова |
|