Oh My Py
2.47K subscribers
21 photos
55 links
Все о чистом коде на Python // antonz.ru
Download Telegram
Channel created
🖐 Знакомство

Привет! Я Антон Жиянов. Разрабатываю опенсорс, веду курсы, пишу про облачные сервисы, открытые данные и программирование (вот все проекты).

Oh My Py — канал про тайные возможности стандартной библиотеки Питона. Тайные не потому, что кто-то их скрывает, конечно ツ

Просто стандартная библиотека огромная! А разработчики часто не копают глубоко и изобретают велосипед вместо того, чтобы использовать готовое.

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

На канале не бывает новостей, копипасты, переводов и рекламы. Бывают анонсы моих проектов, в том числе платных.

Поехали!
👍7
Сделать превьюшку длинного текста

Допустим, мы хотим получить превьюшку длинной статьи. Можно обрезать механически:

article = "Около двух месяцев назад породистый голубь по имени Френк постучался в стеклянные двери омской ветеринарной клиники"


>>> article[:30]
'Около двух месяцев назад пород'


Фраза оборвана посреди слова — это неуважение к читателю и к Френку.

А можно воспользоваться функцией textwrap.shorten():

>>> import textwrap
>>> textwrap.shorten(article, 30, placeholder="...")
'Около двух месяцев назад...'


Намного лучше!

#stdlib
👍41
Отформатировать текст для консоли

Если любите делать 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

И ежедневно рассылаете подписчикам по всему миру.

Всё хорошо, но знакомый эксперт из МГИМО подсказывает: в английском принято каждое слово в заголовке начинать с заглавной буквы. А у вас-то не так!

Можно, конечно, бить заголовок по пробелам через .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.

Сравнить строку или список с шаблоном поможет модуль 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
👍1
Сравнить строки на похожесть

Помните ваш стартап с самыми актуальными новостями дня? Кажется, у него появился конкурент — он нагло крадёт ваши аутентичные новости, рерайтит их, и рассылает ничего не подозревающим клиентам, подрывая вашу репутацию.

Судите сами, вот ваши новости:

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
Напечатать развесистую структуру данных в кратком виде

Поговорим о функциях, которые пппечатают. Живут они в модуле 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 и его упоротые друзья

На днях мы использовали метод 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