Kodomo

Пользователь

Занятие №6

Если вы хотите получить пользу от этого конспекта, нужно брать из него каждый фрагмент кода, копировать его, исполнять, и разбираться, как он устроен.

... И задавать нам вопросы!

Словари

В списках в качестве индексов мы можем использовать только числа. Иногда этой возможности не хватает. Например, когда мы хотим хранить в питоне словарь: отображать слово на его перевод.

Для этого в питоне есть структура данных, которая называется – тадам – словари.

Создаются они так:

   1 translations = {'кошка': 'cat', 'собака': 'dog', 'чучело':'scarecrow'}

Дальше, по аналогии со списком, мы можем получить значение, ассоциированное со словом 'кошка':

   1 print(translations['кошка'])

Терминология: то, что в списках в питоне называется индексом, в словарях называется ключом.

Запись в словарь делается тоже аналогично спискам:

   1 translations['солнце'] = 'sun'

Отличие от списков: если такого ключа в словаре не было, то он создаётся (и это основной способ добавления данных в словарь), а если такой ключ в словаре был, то меняется то, что по нему в словаре лежит:

   1 translations['кошка'] = 'kitty'
   2 print(translations['кошка'])

Когда мы словарь подставляем туда, где питон ожидает список, питон воспринимает словарь как список его ключей:

   1 'кошка' in translations
   2 'зубр' in translations

В словаре данные хранятся в таком порядке, чтобы питон мог быстро их получать. Для нас этот порядок не имеет никакого смысла и выглядит совершенно случайным.

Однако мы очень легко можем получить список ключей словаря, упорядоченными по алфавиту:

   1 sorted(translations)

Как нам вывести упорядоченным весь словарь?

   1 for elem in translations:
   2         print(elem)

Это выводит просто все ключи словаря в случайном порядке... Совсем не то.

   1 for elem in sorted(translations):
   2         print(elem)

Так мы выводим их уже по порядку, но только ключи.

Осталось взять по ключу соответствующее ему значение:

   1 for elem in sorted(translations):
   2         print(elem, ':', translations[elem])

Мы уже выяснили, что по ключу хранится только одно значение. А если мы всё-таки очень хотим хранить несколько? Мы можем хранить в словаре любые другие контейнеры тоже:

   1 translations['кошка'] = ['cat', 'kitty']
   2 translations['больной'] = {'существительное': 'patient', 'прилагательное': 'ill'}

Ещё мы можем из словарей удалять элементы:

   1 elem = translations.pop('собака')
   2 print(translations)
   3 print(elem)

Очень часто требуется проверить, есть ли какое-то значение в словаре, если оно есть, то получить его в переменную, а если его нет, то получить в этой переменной какое-нибудь другое значение (например, None), которое символизирует тот факт, что ничего не нашлось.

Мы можем это сделать так:

   1 if 'зубр' in translations:
   2         result = translations['зубр']
   3 else:
   4         result = None

Но для этого в питоне есть и более короткая готовая запись:

   1 translations.get('зубр')
   2 translations.get('собака')

А если нам нужен не None, то мы можем вторым аргументом сказать get, что возвращать:

   1 translations.get('собакак', 'translation not found')
   2 translations.get('собака', 'translation not found')

Поэтому полезно иногда смотреть в 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 reg1 = 'а|б|в|г|д'
   2 reg2 = '[абвгд]'
   3 reg3 = '[а-д]'
   4 re.findall(reg1, text)
   5 re.findall(reg2, text)
   6 re.findall(reg3, text)

Таким образом мы можем, например, попросить питон найти нам все места в строке, где за словом город идёт одна маленькая буква:

   1 re.findall('город[а-я]', text)

[^ множество букв ] обозначает любую одну букву, кроме тех, которые заданы в множестве:

   1 re.findall('город[^ ]', text)

Оно находит все строк из слова город и буквы за ним, если сразу за ним идёт буква.

Если же мы хотим найти слово город во всех формах, то нам нужно, чтобы за ним шло сколько угодно букв.

В регулярных выражениях, если мы за буквой или скбокой ставим символ *, то это обозначает, что нужно эту букву или содержимое этой скобки повторить ноль или более раз:

a* <=> (|a|aa|aaa|aaaa|aaaaa|...)

Теперь мы можем найти слово город во всех формах:

   1 re.findall('город[а-я]*', text)
   2 re.findall('город[^, ]*', text)

Символ + вслед за буквой или скобкой обозначает повторение 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 symbol = '(?:[^.?!]|[.] *[^а-я])'
   2 re.findall('{symbol}*город{symbol}*[.?!]'.format(symbol=symbol))

Пожалуй, больше всего выразительная сила регулярных выражений (и её ограничение) чувствуется в примерах, когда вы состыковываете вместе несколько частей, каждая из которых является выбором из нескольких вариантов или неограниченной длины повтором.

Например, мы хотим перечислить несколько основ слов, и хотим для каждой из основ найти все продолжения:

   1 re.findall('(город|государств)[^,; ]*')

Оффтопик

В задаче про города может оказаться полезным сделать словарь, в котором ключи – названия городов, а значения – числа 0 или 1.

Такой словарь можно сделать очень просто, если немножко поискать, а какие ещё вкусности есть вокруг в стандартной библиотеке.

Во-первых, мы можем создать список из 0 такой же длины, как и наш список городов.

   1 cities = ["Москва", "Ереван", "Питер"]
   2 zeroes = [0] * len(cities)
   3 print(zeroes)

Дальше, мы можем обнаружить в питоне встроенную функцию zip, которая из двух списков делает список пар соответствующих элементов из них:

   1 tuples = list(zip(cities, zeroes))
   2 print(tuples)

(Одна тонкость: функция zip возвращает итератор – то есть ведёт её результат себя как нужный нам список, но вот чтобы посмотреть на него в человеческом виде нам нужно применять к ней list)

И ещё мы можем обнаружить функцию dict, (по аналогии с функциями int, float, str, list), которая из всего пытается делать словарь. Она умеет делать словарь либо из другого словаря, либо из списка пар ключ-значение.

   1 marked = dict(tuples)
   2 print(marked)

Употреблять это мы можем как есть, а можем и сложить всё вместе:

   1 marked = dict(zip(cities, [0] * len(cities)))

Полезные ссылки

Шпаргалка

RE

Synonyms

Description

Examples

Van[yj]a

В квадратных скобочках указываются символы, которые могут стоять в данной позиции. При этом можно использовать тире для того, чтобы указывать последовательность символов, например: [0-9] (все цифры), [a-z] все маленькие буквы, [a-zA-Z] (все буквы)


+ Vanya
+ Vanja
- Vania

Van[^ji]

Если первым символом после открывающей квадратной скобочки является ^, то в квадратных скобочках перечисляются символы, которые НЕ могут стоять в данной позиции, т.е. остальные все символы, кроме этих, могут.


+ Vanya
+ Vanna
- Vanja
- Vania

Van{3}ya

Vannnya

В фигурных скобочках указывается количество раз, которое повторяется непосредственно предшествующий символ


+ Vannnya
- Vanya

Van{1,3}ya

В фигурных скобочках через запятую указывается минимальное и максимальное количество раз, которое повторяется непосредственно предшествующий символ


+ Vannnya
+ Vanya
- Vaya
- Vannnnya

Van{2,}ya

Если максимальное количество раз пропущено, то это значит что количество повторений сверху не ограничено и может быть бесконечным


+ Vannya
+ Vannnnnya
- Vanya

Van?ya

Van{0,1}ya

Знак вопроса означает, что непосредственно предшествующий символ необязательный


+ Vaya
+ Vanya
- Vannya

Van*ya

Van{0,}ya

Звездочка означает 0 или большее количество повторений непосредственно предшествующего символа


+ Vaya
+ Vanya
+ Vannya

Van+ya

Van{1,}ya

Плюсик означает 1 или большее количество повторений непосредственно предшествующего символа


- Vaya
+ Vanya
+ Vannya

Va.ya

Точка означает любой символ, кроме символа конец строки (\n)


+ Va.ya
+ Vanya
- Vaya

Va\.ya

Backslash «защищает» символ, который следует за ним, т.е. делает символ обычным.


+ Va.ya
- Vanya
- Vaya

(Va)+nya

Скобочки используются для группировки


+ Vanya
+ VaVanya
- Vavanya

^Van

^ обозначает начало строки


+ Vanya
+ Vanzzz
- Ivan

ya$

$ обозначает конец строки


+ Kolya
- Roma

Vanya|Kolya

Выбор одного из нескольких вариантов


+ Vanya
+ Kolya
- Roma

Van(ya|e)

Выбор одного из нескольких вариантов и круглые скобки


+ Vanya
+ Vane
- Vani

\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

Граница слова


+ Vanya Petrov
- VanyaPupkin

\B

НЕ Граница слова