Python Заметки
2.31K subscribers
58 photos
2 videos
2 files
212 links
Интересные заметки и обучающие материалы по Python

Контакт: @paulwinex

⚠️ Рекламу на канале не делаю!⚠️

Хештеги для поиска:
#tricks
#libs
#pep
#basic
#regex
#qt
#django
#2to3
#source
#offtop
Download Telegram
Небольшой трик с регулярными выражениями который редко вижу в чужом коде.

Допустим, вам нужно распарсить простой текст и вытащить оттуда пары имя+телефон. Вернуть всё это надо в виде списка словарей. Возьмем очень простой пример текста.

>>> text = '''
>>> Alex:8999123456
>>> Mike:+799987654
>>> Oleg:+344456789
>>> '''

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

(\w+):([\d+]+)

Как мы будем формировать словарь из найденных групп?

>>> import re
>>> results = []
>>> for match in re.finditer(r"(\w+):([\d+]+)", text):
>>> results.append({
>>> "name": match.group(1),
>>> "phone": match.group(2)
>>> })
>>> print(results)
[{'name': 'Alex', 'phone': '8999123456'}, ...]

Можно немного сократить запись используя zip

>>> results = []
>>> for match in re.finditer(r"(\w+):([\d+]+)", text):
>>> results.append(dict(zip(['name', 'phone'], match.groups())))

Но есть способ лучше! Это именованные группы в regex. Можно в паттерне указать имя группы и результат сразу забрать в виде словаря.

>>> for match in re.finditer(r"(?P<name>\w+):(?P<phone>[\d+]+)", text):
>>> results.append(match.groupdict())

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

(?P<group-name>...)

Теперь найденная группа имеет имя и можно обратиться к ней как к элементу списка

>>> name = match['name']

Либо забрать сразу весь словарь методом groupdict()

>>> match.groupdict()

#tricks #regex
Регулярные выражения иногда могут быть просто монструозными. Выглядеть это может крайне запутанно. Сами регэкспы и без того история непростая, а когда это длинный паттерн на несколько десятков знаков, разобрать там что-либо становится не просто.

Но на помощь приходит Python и его стремление сделать нашу жизнь проще!

В функциях регулярок можно после паттерна указывать флаги, один из которых позволяет писать паттерны более свободно. А именно, добавлять пробелы и переносы, которые будут игнорированы. В результате мы можем разбить паттерн на строки и добавить комментов.

Чтобы это сработало нужно добавить флаг re.VERBOSE. Пробелы в паттерне теперь следует указывать явно спец символами.
Согласитесь, что даже с именованными группами а таком виде регэкспа выглядит вполне сносно 😉.

#tricks #regex
Ранее я делал серию постов про битовые операторы.
Вот вам ещё один наглядный пример как это используется в Python в модуле re.

Чтобы указать флаг для компилятора нам надо указать его после передаваемой строки. Например, добавляем флаг для игнорирования переноса строки.

pattern = re.compile(r"(\w+)+")
words = pattern.search(text, re.DOTALL)

А как указать несколько флагов? Ведь явно будут ситуации когда нам потребуется больше одного. Кто читал посты по битовые операторы уже понял как.

pattern.search(text, re.DOTALL | re.VERBOSE)

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

>>> for n in [1, 2, 4, 8, 16, 32, 64, 128, 256]:
>>>
print(bin(n))

0b1
0b10
0b100
0b1000
0b10000
0b100000
0b1000000
0b10000000
0b100000000

Чтобы было понятней, давайте напишем тоже самое но иначе, добавим ведущие нули:

000000001
000000010
000000100
000001000
000010000
000100000
001000000
010000000
100000000

Не понятно что тут происходит? Читай три поста про битовые операторы начиная с этого ➡️ https://t.iss.one/pythonotes/45

В общем, это пример применения побитовых операций в самом Python.
Теперь вы знаете Python еще немного лучше)

#tricks #regex #libs
Функция sub в regex может принимать функцию в качестве аргумента repl.

📄 Из документации:
If repl is a function, it is called for every non-overlapping occurrence of pattern. The function takes a single match object argument, and returns the replacement string.

То есть для каждого совпадения будет вызвана функция для вычисления замены вместо замены на одну и ту же строку для всех совпадений.
Иными словами, для замены разных совпадений на разные строки не потребуется запускать re.sub() много раз для каждой строки замены. Достаточно определить функцию, которая вернёт строку для каждого из совпадений.

Описание слишком запутанное🤔, давайте лучше рассмотрим на простом примере:

Создаем карту замены. То есть какие строки на какие требуется менять.

remap = {
'раз': '1',
'два': '2',
'три': '3',
'четыре': '4',
'пять': '5',
}

Пишем функцию поиска строки для замены. Единственным аргументом будет объект re.Match.
Используя данные этого объекта мы вычисляем замену on-the-fly!

def get_str(match: re.Match):
word = match.group(1)
return remap.get(word.lower()) or word

Пример текста.

text = '''Раз Два Три Четыре Пять
Вместе будем мы считать
Пять Четыре Три Два Раз
Мы считать научим вас
'''

Теперь запускаем re.sub и вместо строки замены (repl) подаём имя функции.

(Данный паттерн ищет отдельные слова в тексте)

>>> print(re.sub(r'(\w+)', get_str, text))
1 2 3 4 5
Вместе будем мы считать
5 4 3 2 1
Мы считать научим вас

Думаю, достаточно наглядно 🤓

#libs #regex