🖐 Знакомство
Привет! Я Антон Жиянов. Разрабатываю опенсорс, веду курсы, пишу про облачные сервисы, открытые данные и программирование (вот все проекты).
Oh My Py — канал про тайные возможности стандартной библиотеки Питона. Тайные не потому, что кто-то их скрывает, конечно ツ
Просто стандартная библиотека огромная! А разработчики часто не копают глубоко и изобретают велосипед вместо того, чтобы использовать готовое.
Заодно обсудим полезные и не самые известные приёмы в работе с языком и структурами данных. А ещё особенности дизайна, плохой и хороший код, и порешаем задачки.
На канале не бывает новостей, копипасты, переводов и рекламы. Бывают анонсы моих проектов, в том числе платных.
Поехали!
Привет! Я Антон Жиянов. Разрабатываю опенсорс, веду курсы, пишу про облачные сервисы, открытые данные и программирование (вот все проекты).
Oh My Py — канал про тайные возможности стандартной библиотеки Питона. Тайные не потому, что кто-то их скрывает, конечно ツ
Просто стандартная библиотека огромная! А разработчики часто не копают глубоко и изобретают велосипед вместо того, чтобы использовать готовое.
Заодно обсудим полезные и не самые известные приёмы в работе с языком и структурами данных. А ещё особенности дизайна, плохой и хороший код, и порешаем задачки.
На канале не бывает новостей, копипасты, переводов и рекламы. Бывают анонсы моих проектов, в том числе платных.
Поехали!
👍7
Сделать превьюшку длинного текста
Допустим, мы хотим получить превьюшку длинной статьи. Можно обрезать механически:
Фраза оборвана посреди слова — это неуважение к читателю и к Френку.
А можно воспользоваться функцией
Намного лучше!
#stdlib
Допустим, мы хотим получить превьюшку длинной статьи. Можно обрезать механически:
article = "Около двух месяцев назад породистый голубь по имени Френк постучался в стеклянные двери омской ветеринарной клиники"
>>> article[:30]
'Около двух месяцев назад пород'
Фраза оборвана посреди слова — это неуважение к читателю и к Френку.
А можно воспользоваться функцией
textwrap.shorten():>>> import textwrap
>>> textwrap.shorten(article, 30, placeholder="...")
'Около двух месяцев назад...'
Намного лучше!
#stdlib
👍4❤1
Отформатировать текст для консоли
Если любите делать CLI-утилиты, модуль
Он умеет перформатировать многострочный текст, чтобы длина строки не превышала N символов:
Или добавить отступ, например для цитаты:
Френк одобряет.
#stdlib
Если любите делать CLI-утилиты, модуль
textwrap наверняка вам понравится.Он умеет перформатировать многострочный текст, чтобы длина строки не превышала N символов:
text = "Около двух месяцев назад породистый голубь по имени Френк постучался в стеклянные двери омской ветеринарной клиники"
import textwrap
s = textwrap.fill(text, width=20)
print(s)
Около двух месяцев
назад породистый
голубь по имени
Френк постучался в
стеклянные двери
Или добавить отступ, например для цитаты:
inspirational = "Цитаты простых людей:"
quote = "Откройте окно вообще дышать невозможно"
quote = textwrap.indent(quote, prefix="> ")
print(inspirational, quote, sep="\n")
Цитаты простых людей:
> Откройте окно вообще дышать невозможно
Френк одобряет.
#stdlib
👍1
Все слова с прописной буквы
Допустим, запустили вы стартап. В автоматическом режиме собираете самые упоротые новости русскоязычных СМИ, вот такие:
Кот из Новокузнецка признан виновным в потопе
Автоматически же переводите их на английский, вот так:
Cat from Novokuznetsk found guilty in the flood
И ежедневно рассылаете подписчикам по всему миру.
Всё хорошо, но знакомый эксперт из МГИМО подсказывает: в английском принято каждое слово в заголовке начинать с заглавной буквы. А у вас-то не так!
Можно, конечно, бить заголовок по пробелам через .
Соу мач беттер.
#stdlib
Допустим, запустили вы стартап. В автоматическом режиме собираете самые упоротые новости русскоязычных СМИ, вот такие:
Кот из Новокузнецка признан виновным в потопе
Автоматически же переводите их на английский, вот так:
Cat from Novokuznetsk found guilty in the flood
И ежедневно рассылаете подписчикам по всему миру.
Всё хорошо, но знакомый эксперт из МГИМО подсказывает: в английском принято каждое слово в заголовке начинать с заглавной буквы. А у вас-то не так!
Можно, конечно, бить заголовок по пробелам через .
split(), исправлять регистр через .capitalize() и склеивать обратно через .join(). Но есть способ лучше:>>> import string
>>> header = "Cat from Novokuznetsk found guilty in the flood"
>>> string.capwords(header)
'Cat From Novokuznetsk Found Guilty In The Flood'
Соу мач беттер.
#stdlib
👍1
Простое сравнение с шаблоном
Для проверки строки по шаблону обычно используют регулярные выражения и модуль re. Но иногда хочется что-нибудь попроще, пусть и не такое мощное — вроде like в SQL.
Сравнить строку или список с шаблоном поможет модуль
Под капотом используются регулярки, так что всегда можно конвертировать шаблон в регулярное выражение:
Курлык.
#stdlib
Для проверки строки по шаблону обычно используют регулярные выражения и модуль re. Но иногда хочется что-нибудь попроще, пусть и не такое мощное — вроде like в SQL.
Сравнить строку или список с шаблоном поможет модуль
fnmatch:import fnmatch
journal = [
"10:00 Начался обычный день в омской ветклинике",
"10:30 Голубь Френк постучался в стеклянные двери",
"10:50 Лисица Клер поскреблась в окно",
"11:10 Попугай Питер проник через вентиляцию",
"11:11 Клер попыталась сожрать Френка и Питера",
"11:25 Осьминог Пауль всплыл в мужском туалете",
]
>>> fnmatch.filter(journal, "*Френк*")
[ '10:30 Голубь Френк постучался в стеклянные двери',
'11:11 Клер попыталась сожрать Френка и Питера' ]
>>> fnmatch.fnmatch("frank", "f???k")
TrueПод капотом используются регулярки, так что всегда можно конвертировать шаблон в регулярное выражение:
>>> fnmatch.translate("*Френк*")
'(?s:.*Френк.*)\\Z'Курлык.
#stdlib
👍2
Сравнить строки на похожесть
Помните ваш стартап с самыми актуальными новостями дня? Кажется, у него появился конкурент — он нагло крадёт ваши аутентичные новости, рерайтит их, и рассылает ничего не подозревающим клиентам, подрывая вашу репутацию.
Судите сами, вот ваши новости:
А вот новости жалкого подражателя:
Нужны какие-то основания для судебного иска, и нужны быстро. Хорошо, что в стандартной библиотеке Питона есть модуль
И сравним:
АГА! 51%, 69% и 55% похожести! Всё ясно, какие ещё нужны доказательства.
#stdlib
Помните ваш стартап с самыми актуальными новостями дня? Кажется, у него появился конкурент — он нагло крадёт ваши аутентичные новости, рерайтит их, и рассылает ничего не подозревающим клиентам, подрывая вашу репутацию.
Судите сами, вот ваши новости:
genuine = [
"«Братец-хлеб» из Китая носит плащ и корону из булочек, чтобы кормить чаек",
"Мясо гигантских тараканов станет вкусной и недорогой альтернативой говядине",
"Скандал в ботаническом саду: 10 миллионов рублей ушло на зарплату кактусам",
]
А вот новости жалкого подражателя:
plagiary = [
"Китайский хлебный братец кормит чаек плащом и короной из булочек",
"Гигантское мясо тараканов станет говядине недорогой и вкусной альтернативой",
"Зарплата кактусов в ботаническом саду составила 10 скандальных миллионов рублей",
]
Нужны какие-то основания для судебного иска, и нужны быстро. Хорошо, что в стандартной библиотеке Питона есть модуль
difflib. Сделаем на нём функцию сравнения:import difflib
def similarity(s1, s2):
normalized1 = s1.lower()
normalized2 = s2.lower()
matcher = difflib.SequenceMatcher(None, normalized1, normalized2)
return matcher.ratio()
И сравним:
>>> similarity(genuine[0], plagiary[0])
0.51
>>> similarity(genuine[1], plagiary[1])
0.69
>>> similarity(genuine[2], plagiary[2])
0.55
АГА! 51%, 69% и 55% похожести! Всё ясно, какие ещё нужны доказательства.
#stdlib
👍1😁1
Напечатать развесистую структуру данных в кратком виде
Поговорим о функциях, которые пппечатают. Живут они в модуле
У функций есть замечательный опциональный параметр depth, который ограничивает уровень вложенности при форматировании. Он здорово помогает, если хочется получить общее представление о данных, не сильно вникая в детали.
Например, запросили вы апишечку и получили в ответ развесистый словарь:
Заглянем в него, не погружаясь в детали:
Ненужные подробности автоматически скрыты за «...», и мы видим самую суть. Френк, я в тебе не сомневался.
#stdlib
Поговорим о функциях, которые пппечатают. Живут они в модуле
pprint и умеют красиво форматировать разные коллекции и словари.У функций есть замечательный опциональный параметр depth, который ограничивает уровень вложенности при форматировании. Он здорово помогает, если хочется получить общее представление о данных, не сильно вникая в детали.
Например, запросили вы апишечку и получили в ответ развесистый словарь:
rating = requests.get("https://www.cia.gov/the-world-factbook/top-dumbest-animals").json()Заглянем в него, не погружаясь в детали:
import pprint
pprint.pprint(rating, depth=3)
{'leaderbord': [
{'details': {...}, 'name': 'Голубь Френк', 'position': 1},
{'details': {...}, 'name': 'Лисица Клер', 'position': 2},
{'details': {...}, 'name': 'Попугай Питер', 'position': 3},
{'details': {...}, 'name': 'Свинка Зои', 'position': 4},
{'details': {...}, 'name': 'Макака Лукас', 'position': 5}],
'name': 'Самые тупые животные'}
Ненужные подробности автоматически скрыты за «...», и мы видим самую суть. Френк, я в тебе не сомневался.
#stdlib
👍1
Чистый код: ratio и его упоротые друзья
На днях мы использовали метод
А что бы вы сказали, если узнали, что у того же
Я бы сказал, что это плохой код. Если бы коллега принёс такое на ревью, я бы предложил подумать ещё ツ Либо ты нормально называешь эти методы, чтобы понятно было, когда какой использовать. Либо прячешь их в глубине модуля и не делаешь частью публичного API.
Конкретно в данном случае я бы сделал «быстрый» и «очень быстрый» методы приватными, потому что они нужны только для оптимизации работы других публичных методов difflib. Используются примерно так:
Как вспомогательные методы — ладно. Но точно не в публичный интерфейс.
#код
На днях мы использовали метод
SequenceMatcher.ratio() из модуля difflib, чтобы оценить сходство двух строк.А что бы вы сказали, если узнали, что у того же
SequenceMatcher есть ещё методы quick_ratio() и real_quick_ratio()? С описанием «возвращает верхнюю границу ratio() довольно быстро» и «возвращает верхнюю границу ratio() очень быстро»?Я бы сказал, что это плохой код. Если бы коллега принёс такое на ревью, я бы предложил подумать ещё ツ Либо ты нормально называешь эти методы, чтобы понятно было, когда какой использовать. Либо прячешь их в глубине модуля и не делаешь частью публичного API.
Конкретно в данном случае я бы сделал «быстрый» и «очень быстрый» методы приватными, потому что они нужны только для оптимизации работы других публичных методов difflib. Используются примерно так:
if matcher.real_quick_ratio() >= cutoff and matcher.quick_ratio() >= cutoff and matcher.ratio() >= cutoff:
...
Как вспомогательные методы — ладно. Но точно не в публичный интерфейс.
#код
👍1
Разбить строку на слова с учётом кавычек
Предположим, вы собираете архив статей, и хотите для каждой автоматически определять теги — по ним можно будет моментально найти статью в архиве. В качестве тегов решили брать топ-3 слова из текста.
Например, такая статья:
Вы чистите текст от пунктуации, бьёте по пробелам и считаете слова. Вот топ-3:
Но погодите, разве правильно считать «четыре» и «сезона» разными тегами? Это ведь название отеля, лучше учитывать их как одно словосочетание. Тут-то и пригодится функция
Вот теперь теги что надо!
P.S. Вообще, shlex предназначен для разбора shell-подобных строк, так что если злая судьба заставит вас парсить bash-скрипты — вы знаете, куда смотреть.
#stdlib
Предположим, вы собираете архив статей, и хотите для каждой автоматически определять теги — по ним можно будет моментально найти статью в архиве. В качестве тегов решили брать топ-3 слова из текста.
Например, такая статья:
text = """Голубь Френк прибыл в отель "Четыре сезона" с дружеским визитом. По сообщениям очевидцев, он сожрал в ресторане киноа прямо из тарелки гостя, а затем клюнул в глаз прибежавшего на шум официанта.
Френк прилетает в "Четыре сезона" каждый год. В прошлый раз мерзкая птица нагадила в ванну с шампанским в королевском люксе, лишив кого-то романтического вечера."""
Вы чистите текст от пунктуации, бьёте по пробелам и считаете слова. Вот топ-3:
[(‘френк', 2),
('четыре', 2),
('сезона', 2)]
Но погодите, разве правильно считать «четыре» и «сезона» разными тегами? Это ведь название отеля, лучше учитывать их как одно словосочетание. Тут-то и пригодится функция
shlex.split() — она трактует словосочетания в кавычках как один токен:import shlex
from collections import Counter
# слегка чистим text, для краткости опускаю
words = shlex.split(text)
words = [word for word in words if len(word) > 3]
common = Counter(words).most_common(3)
print(common)
[('френк', 2),
('четыре сезона', 2),
('голубь', 1)]
Вот теперь теги что надо!
P.S. Вообще, shlex предназначен для разбора shell-подобных строк, так что если злая судьба заставит вас парсить bash-скрипты — вы знаете, куда смотреть.
#stdlib
👍3
Шаблонизатор для бедных
Мантра «There should be one — and preferably only one — obvious way to do it» из Zen of Python далека от реальности.
Все мы знаем, что в Питоне за долгие годы собрали аж три способа подстановки переменных в строку:
Но не все знают, что есть ещё и четвёртый способ —
Например, если вам не нужны расширенные возможности вроде форматирования чисел или обращения к атрибутам внутри шаблона, а нужно тупо заменять строковые переменные на их значения. Да ещё и синтаксис подстановки отличается от стандартного:
Тут и пригодится string.Template:
Если нужен ещё более извращённый синтаксис — достаточно перекрыть атрибут класса
#stdlib
Мантра «There should be one — and preferably only one — obvious way to do it» из Zen of Python далека от реальности.
Все мы знаем, что в Питоне за долгие годы собрали аж три способа подстановки переменных в строку:
who = "Голубь Френк"
"%s постучался в стеклянные двери" % who
"{} постучался в стеклянные двери".format(who)
f"{who} постучался в стеклянные двери"
Но не все знают, что есть ещё и четвёртый способ —
string.Template. Больше того, он ещё и может быть полезен иногда.Например, если вам не нужны расширенные возможности вроде форматирования чисел или обращения к атрибутам внутри шаблона, а нужно тупо заменять строковые переменные на их значения. Да ещё и синтаксис подстановки отличается от стандартного:
CHANGEME:who постучался в стеклянные двери
Тут и пригодится string.Template:
import string
class OmskTemplate(string.Template):
delimiter = "CHANGEME:"
>>> template = OmskTemplate("CHANGEME:who постучался в стеклянные двери")
>>> template.substitute({ "who": "Кот Джарвис"})
'Кот Джарвис постучался в стеклянные двери'
Если нужен ещё более извращённый синтаксис — достаточно перекрыть атрибут класса
pattern, указав в нём подходящее регулярное выражение.#stdlib
👍2
Чистый код: единообразие в именах
Всякая книга про хороший код начинается с главы об именах переменных и функций. Но каждый, кто работал с большим проектом, знает — хороших имён недостаточно. Важно ещё, чтобы они были единообразными во всём проекте.
Посмотрим на питоновский модуль difflib, который помогал нам сравнивать строки:
find_longest_match() находит самый длинный совпадающий кусок между двуми последовательностями и возвращает match — объект с совпадением и дополнительной информацией.
get_matching_blocks() находит все совпадения между двумя последовательностями и возвращает список из match.
get_close_matches() находит слова, сильнее всего похожие на переданное слово, возвращает список строк.
По отдельности вроде все названия хороши и понятны. Но я утверждаю, что это — плохой код:
1️⃣ find_longest_match вовращает объект-match, как и следует из названия; и get_matching_blocks возвращает такие же объекты, хотя название намекает, что должны возвращаться какие-то blocks
2️⃣ get_close_matches, судя по названию, должен возвращать match, как find_longest_match — но возвращает строки
3️⃣ одна и та же по сути операция (поиск совпадений) в одном случае называется find, а в двух других — get
В результате уже на следующий день не вспомнить, кто как называется, без обращения к документации.
Уж на уровне одного модуля можно напрячься и сохранить единообразие? Я предложил бы такие имена:
✓
✓
✓
#код
Всякая книга про хороший код начинается с главы об именах переменных и функций. Но каждый, кто работал с большим проектом, знает — хороших имён недостаточно. Важно ещё, чтобы они были единообразными во всём проекте.
Посмотрим на питоновский модуль difflib, который помогал нам сравнивать строки:
find_longest_match() находит самый длинный совпадающий кусок между двуми последовательностями и возвращает match — объект с совпадением и дополнительной информацией.
get_matching_blocks() находит все совпадения между двумя последовательностями и возвращает список из match.
get_close_matches() находит слова, сильнее всего похожие на переданное слово, возвращает список строк.
По отдельности вроде все названия хороши и понятны. Но я утверждаю, что это — плохой код:
1️⃣ find_longest_match вовращает объект-match, как и следует из названия; и get_matching_blocks возвращает такие же объекты, хотя название намекает, что должны возвращаться какие-то blocks
2️⃣ get_close_matches, судя по названию, должен возвращать match, как find_longest_match — но возвращает строки
3️⃣ одна и та же по сути операция (поиск совпадений) в одном случае называется find, а в двух других — get
В результате уже на следующий день не вспомнить, кто как называется, без обращения к документации.
Уж на уровне одного модуля можно напрячься и сохранить единообразие? Я предложил бы такие имена:
✓
find_longest_match()✓
find_all_matches()✓
find_similar_words()#код
👍3
Исходники стандартной библиотеки
У большинства питонячих модулей хорошее описание: написано доходчиво, часто есть примеры. Но ничто не заменит исходного кода, если что-то непонятно или хочется понять, как та или иная штука реализована.
Core-разработчик Питона Реймонд Хеттингер тоже это заметил, и поэтому в документации к каждому модулю стандартной библиотеки первым делом идёт ссылка на исходники этого самого модуля на гитхабе.
Если вы прочитали описание функции или класса, а вопросы остались — не стесняйтесь пойти в исходный код и посмотреть, как оно там устроено. Большинство модулей отлично написаны, код понятный, в меру откомментирован.
У большинства питонячих модулей хорошее описание: написано доходчиво, часто есть примеры. Но ничто не заменит исходного кода, если что-то непонятно или хочется понять, как та или иная штука реализована.
Core-разработчик Питона Реймонд Хеттингер тоже это заметил, и поэтому в документации к каждому модулю стандартной библиотеки первым делом идёт ссылка на исходники этого самого модуля на гитхабе.
Если вы прочитали описание функции или класса, а вопросы остались — не стесняйтесь пойти в исходный код и посмотреть, как оно там устроено. Большинство модулей отлично написаны, код понятный, в меру откомментирован.
👍1
Enum здорового человека
Если программист привык писать код, как это делали наши пращуры со времён аграрной революции, то перечисления у него выглядят как-то так:
Конечно, у наших современников есть способ получше —
Это не просто более многословный способ сделать то же самое. У енумов есть вагон плюшек, недоступных староверам. Например, можно делать синонимы состояний:
Или добавлять свои атрибуты:
А ещё можно: сортировать, сравнивать по is вместо ==, итерировать по значениям и создавать динамически. В общем, енумы — однозначное добро.
#stdlib
Если программист привык писать код, как это делали наши пращуры со времён аграрной революции, то перечисления у него выглядят как-то так:
class PigeonState:
eating = 0
sleeping = 1
flying = 2
>>> PigeonState.sleeping
1
Конечно, у наших современников есть способ получше —
enum:import enum
class PigeonState(enum.Enum):
eating = 0
sleeping = 1
flying = 2
>>> PigeonState.sleeping.value
1
Это не просто более многословный способ сделать то же самое. У енумов есть вагон плюшек, недоступных староверам. Например, можно делать синонимы состояний:
class PigeonState(enum.Enum):
eating = 0
sleeping = 1
flying = 2
# There is no way Frank
# is really doing that
thinking = 1
>>> PigeonState.thinking
<PigeonState.sleeping: 1>
Или добавлять свои атрибуты:
class PigeonState(enum.Enum):
eating = (0, "Ест")
sleeping = (1, "Спит")
flying = (2, "Парит в небесах")
def __init__(self, id, noscript):
self.id = id
self.noscript = noscript
>>> PigeonState.flying.id
2
>>> PigeonState.flying.noscript
'Парит в небесах'
А ещё можно: сортировать, сравнивать по is вместо ==, итерировать по значениям и создавать динамически. В общем, енумы — однозначное добро.
#stdlib
👍2