Проверить, есть ли элемент в огромной коллекции
Как мы выяснили в прошлый раз, проверка на вхождение элемента в множество выполняется моментально, но занимает прилично места:
Для множества на 1 млн элементов получилось 160 микросекунд на 1000 проверок, 101 Мб в памяти.
Что если элементов будет 1 млрд? Это уже около 100 Гб, не хотелось бы держать их в памяти. Устроил бы компромиссный вариант, который работает медленнее, но занимает меньше места.
И он существует! Это фильтр Блума — специальная вероятностная структура данных. Она отвечает на вопрос «есть ли элемент в коллекции?» одним из двух вариантов:
— точно нет;
— возможно есть.
Вот как это работает:
Фильтр Блума на 1 млн элементов с вероятностью ложно-положительного ответа 0.1% занимает всего 3 Мб (вместо 100 Мб «честного» множества). А что со скоростью?
15 миллисекунд — это в 100 раз медленнее, чем проверка по множеству, но всё ещё достаточно быстро (например, в 600 раз быстрее проверки по списку).
Проверим на 1 млрд:
Три с лишним гигабайта, рост линейный. Чудес не бывает, но выигрыш по памяти в 30 раз при сохранении приемлемой скорости иногда может вам пригодиться.
#пакетик
Как мы выяснили в прошлый раз, проверка на вхождение элемента в множество выполняется моментально, но занимает прилично места:
>>> set_ = set(str(random.random()) for _ in range(1_000_000))
>>> num = str(random.random())
>>> timeit.timeit(lambda: num in set_, number=1000)
0.000160
>>> size_mb(set_)
101
Для множества на 1 млн элементов получилось 160 микросекунд на 1000 проверок, 101 Мб в памяти.
Что если элементов будет 1 млрд? Это уже около 100 Гб, не хотелось бы держать их в памяти. Устроил бы компромиссный вариант, который работает медленнее, но занимает меньше места.
И он существует! Это фильтр Блума — специальная вероятностная структура данных. Она отвечает на вопрос «есть ли элемент в коллекции?» одним из двух вариантов:
— точно нет;
— возможно есть.
Вот как это работает:
>>> from bloom_filter import BloomFilter
>>> bloom = BloomFilter(max_elements=1_000_000, error_rate=0.001)
>>> for el in set_:
... bloom.add(el)
>>> size_mb(bloom)
3
Фильтр Блума на 1 млн элементов с вероятностью ложно-положительного ответа 0.1% занимает всего 3 Мб (вместо 100 Мб «честного» множества). А что со скоростью?
>>> timeit.timeit(lambda: num in bloom, number=1000)
0.015
15 миллисекунд — это в 100 раз медленнее, чем проверка по множеству, но всё ещё достаточно быстро (например, в 600 раз быстрее проверки по списку).
Проверим на 1 млрд:
>>> bloom = BloomFilter(max_elements=1_000_000_000, error_rate=0.001)
>>> size_mb(bloom)
3428
Три с лишним гигабайта, рост линейный. Чудес не бывает, но выигрыш по памяти в 30 раз при сохранении приемлемой скорости иногда может вам пригодиться.
#пакетик
Субботний пакет
Репозиторий питонячих пакетов (PyPI) включает аж 300 тысяч проектов. Среди них есть прикладные (requests) и инфраструктурные (pip), полезные (redis) и не очень (insultgenerator). Есть большие и маленькие, надежные и бажные, набирающие обороты и давно заброшенные. Всякие есть.
Я подумал, что было бы неплохо писать об одном из них раз в неделю, по субботам — в рубрике #пакетик. Так что если вы автор какого-нибудь классного пакета — дайте знать в личку (@nalgeon). Может, одна из суббот станет вашей ツ
Репозиторий питонячих пакетов (PyPI) включает аж 300 тысяч проектов. Среди них есть прикладные (requests) и инфраструктурные (pip), полезные (redis) и не очень (insultgenerator). Есть большие и маленькие, надежные и бажные, набирающие обороты и давно заброшенные. Всякие есть.
Я подумал, что было бы неплохо писать об одном из них раз в неделю, по субботам — в рубрике #пакетик. Так что если вы автор какого-нибудь классного пакета — дайте знать в личку (@nalgeon). Может, одна из суббот станет вашей ツ
👍1
Естественная сортировка
Мой сегодняшний выбор — пакет Сета Мортона natsort, который сортирует строки привычным для человека образом.
Допустим, у нас есть список важных гостей. Он в легком беспорядке:
Отсортируем:
Порядка не прибавилось ツ А вот как будет с
Другое дело!
#пакетик
Мой сегодняшний выбор — пакет Сета Мортона natsort, который сортирует строки привычным для человека образом.
Допустим, у нас есть список важных гостей. Он в легком беспорядке:
data = [
"4 - Дуглас",
"2 - Клер",
"11 - Зоя",
"1 - Френк",
"31 - Питер",
]
Отсортируем:
>>> sorted(data)
['1 - Френк', '11 - Зоя', '2 - Клер', '31 - Питер', '4 - Дуглас']
Порядка не прибавилось ツ А вот как будет с
natsort
:>>> import natsort
>>> natsort.natsorted(data)
['1 - Френк', '2 - Клер', '4 - Дуглас', '11 - Зоя', '31 - Питер']
Другое дело!
#пакетик
Планировщик задач
В стандартной библотеке есть встроенный планировщик задач (а чего вообще в ней нет?). Подробно расскажу в другой раз, но в целом он, скажем так, не слишком юзер-френдли.
Поэтому Дэн Бэйдер сделал schedule — «планировщик для людей». Смотрите, какой милый:
Ноль зависимостей, чистый и великолепно документированный код, примеры на все случаи жизни.
#пакетик
В стандартной библотеке есть встроенный планировщик задач (а чего вообще в ней нет?). Подробно расскажу в другой раз, но в целом он, скажем так, не слишком юзер-френдли.
Поэтому Дэн Бэйдер сделал schedule — «планировщик для людей». Смотрите, какой милый:
import schedule
import time
def job():
print("I'm working...")
schedule.every().hour.do(job)
schedule.every(5).to(10).minutes.do(job)
schedule.every().day.at("10:30").do(job)
while True:
schedule.run_pending()
time.sleep(1)
Ноль зависимостей, чистый и великолепно документированный код, примеры на все случаи жизни.
#пакетик
Счетчик для огромных коллекций
В стандартной библиотеке есть класс Counter. Он отлично подходит, чтобы считать количество объектов разных типов. Но что делать, если объектов миллиарды, и счетчик просто не помещается в оперативную память?
Поможет bounter — это счетчик, который предоставляет схожий интерфейс, но внутри построен на вероятностных структурах данных. За счет этого он занимает в 30–250 раз меньше памяти, но может (слегка) привирать.
Ноль зависимостей, питон 3.3+
#пакетик
В стандартной библиотеке есть класс Counter. Он отлично подходит, чтобы считать количество объектов разных типов. Но что делать, если объектов миллиарды, и счетчик просто не помещается в оперативную память?
Поможет bounter — это счетчик, который предоставляет схожий интерфейс, но внутри построен на вероятностных структурах данных. За счет этого он занимает в 30–250 раз меньше памяти, но может (слегка) привирать.
from bounter import bounter
counts = bounter(size_mb=128)
counts.update(["a", "b", "c", "a", "b"])
>>> counts.total()
5
>>> counts["a"]
2
Ноль зависимостей, питон 3.3+
#пакетик
Универсальные оповещения
Есть куча способов отправлять уведомления — от проверенного SMTP и удобного Telegram до смс и специальных приложений для мобилок вроде Pushover.
Обычно для этого используют 3rd-party библиотеку соответствующего провайдера. Но есть более удобный способ — пакет notifiers от Ора Карми. Он предоставляет простой универсальный интерфейс для отправки сообщений через любой сервис.
Например, через телеграм:
Поддерживается аж 16 провайдеров, а интерфейс один — метод
Питон 3.6+
#пакетик
Есть куча способов отправлять уведомления — от проверенного SMTP и удобного Telegram до смс и специальных приложений для мобилок вроде Pushover.
Обычно для этого используют 3rd-party библиотеку соответствующего провайдера. Но есть более удобный способ — пакет notifiers от Ора Карми. Он предоставляет простой универсальный интерфейс для отправки сообщений через любой сервис.
Например, через телеграм:
import notifiers
token = "bot_token"
chat_id = 1234
tg = notifiers.get_notifier("telegram")
tg.notify(message="Привет!", token=token, chat_id=chat_id)
Поддерживается аж 16 провайдеров, а интерфейс один — метод
.notify()
. И никаких дополнительных 3rd-party библиотек. Удобно!Питон 3.6+
#пакетик
Современный HTTP-клиент
Мало у какого языка такая нажористая стандартная библиотека, как у питона. Но все равно для работы с HTTP люди пользуются сторонним пакетом requests.
А я вот отказался от него в пользу замечательного httpx от Тома Кристи. Синхронный и асинхронный интерфейсы, поддержка wsgi/asgi, плюс все фичи requests — и совместимость с ним!
Можно заменить requests → httpx, и все продолжит работать:
Питон 3.6+
#пакетик
Мало у какого языка такая нажористая стандартная библиотека, как у питона. Но все равно для работы с HTTP люди пользуются сторонним пакетом requests.
А я вот отказался от него в пользу замечательного httpx от Тома Кристи. Синхронный и асинхронный интерфейсы, поддержка wsgi/asgi, плюс все фичи requests — и совместимость с ним!
Можно заменить requests → httpx, и все продолжит работать:
>>> import httpx
>>> r = httpx.get("http://httpbingo.org/json")
>>> r.status_code
200
>>> r.headers["content-type"]
'application/json; encoding=utf-8'
>>> r.json()["slideshow"]["title"]
'Sample Slide Show'
Питон 3.6+
#пакетик
Разбор текста по шаблону
Все знают, как в питоне форматировать текст по шаблону:
А благодаря библиотеке parse от Ричарда Джонса, с такой же легкостью можно разбирать текст обратно по переменным:
parse по большей части поддерживает стандартный питонячий мини-язык форматирования, так что новый синтаксис учить не придется.
Внутри работает на регулярках. Ноль зависимостей, питон 2 и 3
#пакетик
Все знают, как в питоне форматировать текст по шаблону:
import datetime as dt
date = dt.date(2020, 11, 20)
who = "Френк"
count = 42
tmpl = "{:%Y-%m-%d}: {} и его {:d} друга вылетели в Копенгаген"
>>> tmpl.format(date, who, count)
'2020-11-20: Френк и его 42 друга вылетели в Копенгаген'
А благодаря библиотеке parse от Ричарда Джонса, с такой же легкостью можно разбирать текст обратно по переменным:
import parse
tmpl = "{:ti}: {} и его {:d} друга вылетели в Копенгаген"
txt = "2020-11-20: Френк и его 42 друга вылетели в Копенгаген"
>>> date, who, count = parse.parse(tmpl, txt)
>>> date
datetime.datetime(2020, 11, 20, 0, 0)
>>> who
'Френк'
>>> count
42
parse по большей части поддерживает стандартный питонячий мини-язык форматирования, так что новый синтаксис учить не придется.
Внутри работает на регулярках. Ноль зависимостей, питон 2 и 3
#пакетик
👍2