📦 Как сделать классный Python-пакет
Раньше я думал, что создание пакетов в питоне — жуткая головная боль. Никогда с этим не связывался.
Оказывается, ситуация давно изменилась, и делать библиотеки стало легко и приятно. Буквально так:
Попробуйте: https://antonz.ru/packaging/
P.S. Если у вас есть собственная библиотека, которой не стыдно поделиться — присылайте в личку. Про самые интересные напишу отдельно.
#код
Раньше я думал, что создание пакетов в питоне — жуткая головная боль. Никогда с этим не связывался.
Оказывается, ситуация давно изменилась, и делать библиотеки стало легко и приятно. Буквально так:
flit init
...
flit publish
Попробуйте: https://antonz.ru/packaging/
P.S. Если у вас есть собственная библиотека, которой не стыдно поделиться — присылайте в личку. Про самые интересные напишу отдельно.
#код
Python и Go
Странным образом многие питон-разработчики рано или поздно приходят к языку Go. Кто-то переключается на него полностью, кто-то использует для отдельных задач. Так или иначе, Go и Python образуют пару максимально непохожих, но часто упоминаемых вместе языков.
Go прост, можно даже сказать — примитивен. При этом код на нём читать заметно тяжелее, чем на питоне. Но только пока не начнёте писать асинхронщину — тут ситуация переворачивается.
Go беден библиотеками (как стандартной, так и third-party). Но это помогает использовать его только там, где уместно.
Go быстр. Правда быстр. И, благодаря компиляции в машинный код, очень компактен (как вам докер-образ размером в 10 Мб?).
А ещё Go мало кого оставляет равнодушным: его либо любят, либо ненавидят. Мы решили исследовать этот феномен, взяли по человеку от каждого лагеря и завели канал, где перемоем Go все косточки.
Подписывайтесь, если Go вам интересен: @thank_go
Странным образом многие питон-разработчики рано или поздно приходят к языку Go. Кто-то переключается на него полностью, кто-то использует для отдельных задач. Так или иначе, Go и Python образуют пару максимально непохожих, но часто упоминаемых вместе языков.
Go прост, можно даже сказать — примитивен. При этом код на нём читать заметно тяжелее, чем на питоне. Но только пока не начнёте писать асинхронщину — тут ситуация переворачивается.
Go беден библиотеками (как стандартной, так и third-party). Но это помогает использовать его только там, где уместно.
Go быстр. Правда быстр. И, благодаря компиляции в машинный код, очень компактен (как вам докер-образ размером в 10 Мб?).
А ещё Go мало кого оставляет равнодушным: его либо любят, либо ненавидят. Мы решили исследовать этот феномен, взяли по человеку от каждого лагеря и завели канал, где перемоем Go все косточки.
Подписывайтесь, если Go вам интересен: @thank_go
Отрезать строке голову и хвост
В Python 3.9 строке добавили методы, которые удаляют префикс и суффикс:
На стадии обсуждения PEP разгорелся нешуточный спор. Сначала автор предложил названия
Конечно, именование переменных и методов — первая неразрешимая проблема программирования (вторая, как вы знаете — устаревание кеша). Но решение странное, на мой взгляд.
До сих пор в языке
А в строках для обрезки части —
Да, само по себе
Так или иначе, строка обзавелась двумя новыми методами. Всего их теперь 47 (!), не считая дандеров.
#stdlib
В Python 3.9 строке добавили методы, которые удаляют префикс и суффикс:
>>> "Френк и семечки".removeprefix("Френк и ")
'семечки'
>>> "Френк и семечки".removesuffix(" и семечки")
'Френк'
На стадии обсуждения PEP разгорелся нешуточный спор. Сначала автор предложил названия
cutprefix()
и cutsuffix()
, но сообществу не понравился глагол cut
. Альтернативой предложили strip
, trim
и remove
, долго и мучительно обсуждали, наконец остановились на remove
.Конечно, именование переменных и методов — первая неразрешимая проблема программирования (вторая, как вы знаете — устаревание кеша). Но решение странное, на мой взгляд.
До сих пор в языке
remove
использовался в смысле «удалить элемент коллекции»:deque.remove()
array.remove()
os.remove()
А в строках для обрезки части —
strip
:str.strip()
str.lstrip()
str.rstrip()
Да, само по себе
strip
— не слишком удачное название (в других языках чаще используют trim
). Но оно давно прижилось, так что логично его и использовать дальше.Так или иначе, строка обзавелась двумя новыми методами. Всего их теперь 47 (!), не считая дандеров.
#stdlib
👍1
Прочитать произвольную строку из файла
Предположим, вы решили разработать продвинутого саппорт-бота. В нём будет машин лёнинга до самых краёв, так что человек почти не понадобится. К сожалению, неотложные дела отвлекли ваше внимание, и вы делегировали задачу Френку.
Прямо скажем, это было не лучшее решение. Тупая и ленивая скотина придумала, что достаточно заготовить файл с универсальными ответами на все случаи жизни, и на каждый вопрос отвечать случайной фразой:
Простой, надёжный алгоритм. Осталось воплотить его в питоне. Здесь Френку поможет
Ничего себе! Это едва ли не короче, чем hello world. К тому же, функция
Бот готов, Френк возвращается к своим семечкам.
#stdlib
Предположим, вы решили разработать продвинутого саппорт-бота. В нём будет машин лёнинга до самых краёв, так что человек почти не понадобится. К сожалению, неотложные дела отвлекли ваше внимание, и вы делегировали задачу Френку.
Прямо скажем, это было не лучшее решение. Тупая и ленивая скотина придумала, что достаточно заготовить файл с универсальными ответами на все случаи жизни, и на каждый вопрос отвечать случайной фразой:
# answers.txt
Перезагрузите ваше устройство, пожалуйста
Проверили, проблема на вашей стороне
Спасибо, займёмся этим позже
Наши технические возможности исчерпаны
Простой, надёжный алгоритм. Осталось воплотить его в питоне. Здесь Френку поможет
linecache.getline()
:import linecache
import random
def get_answer():
line_num = random.randint(1, 4)
answer = linecache.getline("answers.txt", line_num)
return answer.strip()
>>> get_answer()
'Проверили, проблема на вашей стороне'
Ничего себе! Это едва ли не короче, чем hello world. К тому же, функция
getline()
кеширует все строчки файла в списке, так что следующие вызовы get_answer()
отработают моментально.Бот готов, Френк возвращается к своим семечкам.
#stdlib
Зачем читать исходники
Я как-то писал, что в документацию питона добавили ссылки на исходники модулей. Читать их не только увлекательно, но и полезно.
Помните
Модуль не случайно называется
И есть в модуле функция
> Check the cache for validity. Use this function if files in the cache may have changed on disk, and you require the updated version.
Вроде понятно, проверяет и актуализирует кеш. А вот как выглядит исходник этой функции:
Оказывается,
Конкретно в случае с
В любой непонятной ситуации читай исходники, как говорил Урбан Мюллер, автор языка Brainfuck.
#код
Я как-то писал, что в документацию питона добавили ссылки на исходники модулей. Читать их не только увлекательно, но и полезно.
Помните
linecache.getline()
из прошлого поста, который выбирает строчку файла по номеру?>>> linecache.getline("answers.txt", 3)
'Проверили, проблема на вашей стороне'
Модуль не случайно называется
linecache
. При первом обращении к файлу linecache
записывает его содержимое в кеш (в глобальную переменную cache
). Именно из этого кеша getline()
и выбирает строку по номеру. Благодаря кешу второй и последующие вызовы уже не читают файл и отрабатывают моментально.# lines - список строк файла
cache[filename] = size, mtime, lines, fullname
И есть в модуле функция
linecache.checkcache()
. Вот её документация:> Check the cache for validity. Use this function if files in the cache may have changed on disk, and you require the updated version.
Вроде понятно, проверяет и актуализирует кеш. А вот как выглядит исходник этой функции:
def checkcache(filename=None):
# проверка, обновился ли файл
# по сравнению с кешем
# и если обновился, то:
cache.pop(filename)
Оказывается,
checkcache()
не актуализирует, а очищает кеш! Из-за этого следующий вызов getline()
отработает заметно медленнее: придётся заново начитывать весь файл.Конкретно в случае с
linecache
это вряд ли станет большой проблемой, но представьте, какой был бы неприятный сюрприз, если бы речь шла о продакшен-кеше вашего приложения ツВ любой непонятной ситуации читай исходники, как говорил Урбан Мюллер, автор языка Brainfuck.
#код
Ищем фильмы, книги и подкасты с помощью Python
У Apple есть API поиска по iTunes Store и другим каталогам. Очень простое, но мало кто за пределами экосистемы айос-разработчиков про него знает. Поэтому решил написать о нём — конечно, с примерами на питоне:
Статья на хабре:
https://habr.com/ru/post/509192/
#код
У Apple есть API поиска по iTunes Store и другим каталогам. Очень простое, но мало кто за пределами экосистемы айос-разработчиков про него знает. Поэтому решил написать о нём — конечно, с примерами на питоне:
import requests
def search(term, media):
url = "https://itunes.apple.com/search"
payload = {"term": term, "media": media}
response = requests.get(url, params=payload)
response.raise_for_status()
results = response.json().get("results", [])
return results
Статья на хабре:
https://habr.com/ru/post/509192/
#код
Какие заметки были бы вам интересны? (можно указать несколько)
Final Results
60%
Стандартная библиотека Python
49%
Другие клёвые маленькие библиотеки
36%
Django, Celery, всякий веб
44%
PostgreSQL, Redis, базы данных
35%
Pandas, Keras, машинное обучение
Проверить, входит ли элемент в коллекцию
Предположим, вы ведёте реестр монет. В нём записаны монетки всех времён, стран и достоинств. На вашем сайте любой может проверить, есть ли та или иная монета в реестре, и если нет — добавить её.
Как проверить, есть ли монета в реестре?
Можно так:
Конечно, так делать нехорошо. Операция
10 секунд на проверку тысячи элементов, пффф. Решение — использовать множества:
Операция
А что с памятью? Проверим:
Множество оказалось в 2 раза тяжелее списка. Ничего, для миллиона монеток хватит. Но что делать, если в коллекции один миллиард объектов, тоже всё в память запихивать? Есть и другие варианты, о них в следующий раз.
#stdlib
Предположим, вы ведёте реестр монет. В нём записаны монетки всех времён, стран и достоинств. На вашем сайте любой может проверить, есть ли та или иная монета в реестре, и если нет — добавить её.
Как проверить, есть ли монета в реестре?
Можно так:
coins = ["1 aud", "5 ars", "1 byn", "10 ghs"]
def has(coin):
return coin in coins
>>> has("1 byn")
True
>>> has("20 cny")
False
Конечно, так делать нехорошо. Операция
element in list
последовательно проверяет каждый элемент списка, то есть её сложность O(n)
. Незаметно на маленьких списках, но если у вас в реестре 1 млн монет, а с сайта приходит по тысяче запросов в секунду — начнёт тормозить:>>> import random
>>> import timeit
>>> list_ = [random.random() for _ in range(1_000_000)]
>>> num = random.random()
>>> timeit.timeit(lambda: num in list_, number=1000)
9.66
10 секунд на проверку тысячи элементов, пффф. Решение — использовать множества:
>>> set_ = set(random.random() for _ in range(1_000_000))
>>> num = random.random()
>>> timeit.timeit(lambda: num in set_, number=1000)
0.00018
Операция
element in set
выполняется за O(1)
. На множестве проверка отработала примерно в 50000 раз быстрее, чем на списке.А что с памятью? Проверим:
from pympler import asizeof
def size_mb(obj):
return round(asizeof.asizeof(obj) / 1024**2)
>>> size_mb(list_)
31
>>> size_mb(set_)
55
Множество оказалось в 2 раза тяжелее списка. Ничего, для миллиона монеток хватит. Но что делать, если в коллекции один миллиард объектов, тоже всё в память запихивать? Есть и другие варианты, о них в следующий раз.
#stdlib
👍1
Проверить, есть ли элемент в огромной коллекции
Как мы выяснили в прошлый раз, проверка на вхождение элемента в множество выполняется моментально, но занимает прилично места:
Для множества на 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 раз при сохранении приемлемой скорости иногда может вам пригодиться.
#пакетик
Грамотно работать с любым диапазоном
Все знают, что
Но не все знают, что
И даже так:
И так тоже:
При этом
А время выполнения операций при этом как в обычном списке:
Ну разве он не чудо?
#stdlib
Все знают, что
range()
в питоне используется, когда нужно что-то сделать сколько-то раз:>>> for i in range(3, 0, -1):
... print(i)
3
2
1
Но не все знают, что
range
— это коллекция (что? да!), вполне себе полноценная:>>> seq = range(10, 100)
>>> len(seq)
90
>>> 52 in seq
True
>>> seq[10]
20
И даже так:
>>> max(seq)
99
>>> seq.index(31)
21
>>> seq.count(42)
1
И так тоже:
>>> s1 = range(0, 10, 3)
>>> s2 = range(0, 11, 3)
>>> s1 == s2
True
При этом
range
, в отличие от всех прочих коллекций, занимает мизерное место в памяти (48 байт), вне зависимости от того, сколько элементов в него попадают. Это потому, что хранит он только 3 атрибута: start
, stop
, step
>>>from pympler import asizeof
>>> seq = range(0, 100)
>>> asizeof.asizeof(seq)
48
>>> seq = range(0, 100_000)
>>> asizeof.asizeof(seq)
48
>>> seq = range(0, 100_000_000)
>>> asizeof.asizeof(seq)
48
А время выполнения операций при этом как в обычном списке:
len()
, in
, [idx]
— за O(1).Ну разве он не чудо?
#stdlib
🔥1
Скорость работы оператора in range
После вчерашней заметки некоторые подписчики справедливо заметили, что сложность проверки «element in list» составляет O(n), а не O(1). А я пишу, что для range она O(1). Да, вы молодцы, так и есть ツ
Действительно: чтобы проверить, есть ли элемент в списке, придётся обойти все элементы списка, пока не найдём искомый — это сложность O(n). Но в случае с диапазоном мы точно знаем первый элемент, последний элемент и шаг. Поэтому разработчики стандартной библиотеки пошли на хитрость.
Допустим, есть выражение
Проверили границы, посчитали остаток от деления, бумс, готово. Для отрицательного step работает аналогично.
Так что
#stdlib
После вчерашней заметки некоторые подписчики справедливо заметили, что сложность проверки «element in list» составляет O(n), а не O(1). А я пишу, что для range она O(1). Да, вы молодцы, так и есть ツ
Действительно: чтобы проверить, есть ли элемент в списке, придётся обойти все элементы списка, пока не найдём искомый — это сложность O(n). Но в случае с диапазоном мы точно знаем первый элемент, последний элемент и шаг. Поэтому разработчики стандартной библиотеки пошли на хитрость.
Допустим, есть выражение
x in range(start, stop, step)
. Для положительного step можно обойтись без перебора всех элементов, вот так:def contains(range_, x):
if x < range_.start:
return False
if x >= range_.stop:
return False
return (x - range_.start) % range_.step == 0
>>> r = range(1000, 10000, 3)
>>> contains(r, 2068)
True
>>> contains(r, 2070)
False
Проверили границы, посчитали остаток от деления, бумс, готово. Для отрицательного step работает аналогично.
Так что
in range
действительно выполняется за O(1), в отличие от in list
.#stdlib
Задачка: сотрудникофикатор
Время для задачки! Допустим, вы основали модный HR-стартап, который подбирает идеальные коллективы сотрудников. Дело это нелёгкое, так что начали с простой эвристики:
> Любой коллектив идеален, пока в нём не появляется Френк
Подготовили интеллектуальный алгоритм, который предлагает сотрудника:
Остался последний шаг — разработать нечто под названием
Ваша задача — реализовать
Давайте я для затравки начну заведомо неудачным вариантом:
Ссылка на репл
Форкайте, реализуйте свой
Завтра вечером покажу лучшие варианты, а потом разберём плюсы и минусы каждого.
#задачка
Время для задачки! Допустим, вы основали модный HR-стартап, который подбирает идеальные коллективы сотрудников. Дело это нелёгкое, так что начали с простой эвристики:
> Любой коллектив идеален, пока в нём не появляется Френк
Подготовили интеллектуальный алгоритм, который предлагает сотрудника:
import random
names = ["Френк", "Клер", "Зоя", "Питер", "Лукас"]
def employee():
name = random.choice(names)
return name
Остался последний шаг — разработать нечто под названием
employeficator()
, что и будет подбирать дружный коллектив. Использоваться оно будет так:>>> [name for name in employeficator()]
['Зоя', 'Зоя', 'Питер']
>>> [name for name in employeficator()]
['Лукас', 'Зоя', 'Питер']
Ваша задача — реализовать
employeficator()
максимально идиоматично.Давайте я для затравки начну заведомо неудачным вариантом:
def employeficator():
employees = []
name = employee()
while name != "Френк":
employees.append(name)
name = employee()
return employees
Ссылка на репл
Форкайте, реализуйте свой
employeficator()
и присылайте ссылку на форк мне → @nalgeonЗавтра вечером покажу лучшие варианты, а потом разберём плюсы и минусы каждого.
#задачка
Решение: сотрудникофикатор
Разберём задачку о сотрудниках.
Для начала, что такое «идиоматично». Идиоматичный код использует «родные» конструкции языка и стандартной библиотеки, не нарушая при этом питонячий дзен (simple is better than complex, readability counts, вот это всё).
Месиво из вложенных циклов с break и continue вряд ли можно назвать идиоматичным. Точно также не будет идиоматичной «функциональная» колбаса из вызовов functools и itertools. Абсолютных критериев тут нет, но общий смысл, надеюсь, понятен.
Теперь к решению. Задача была с небольшим подвохом: искомый
Да, это функция
Но в варианте с двумя аргументами
Первый аргумент — функция или что-нибудь вызываемое (callable), второй — контрольное значение (sentinel). Каждое обращение к итератору вызывает
Это ровно то поведение, что требовалось в задаче — вызывать
Так что
P.S. Некоторые участники решили, что коллектив обязательно должен состоять из 3 сотрудников или не может включать нескольких сотрудников с одинаковыми именами. Но таких ограничений в условиях не было. Вы сами усложнили себе задачу 🤷♀️
#задачка
Разберём задачку о сотрудниках.
Для начала, что такое «идиоматично». Идиоматичный код использует «родные» конструкции языка и стандартной библиотеки, не нарушая при этом питонячий дзен (simple is better than complex, readability counts, вот это всё).
Месиво из вложенных циклов с break и continue вряд ли можно назвать идиоматичным. Точно также не будет идиоматичной «функциональная» колбаса из вызовов functools и itertools. Абсолютных критериев тут нет, но общий смысл, надеюсь, понятен.
Теперь к решению. Задача была с небольшим подвохом: искомый
employeficator()
уже есть в стандартной библиотеке. Больше того, не просто в стандартной библиотеке, а в самом её сердце, в built-in функциях! Вот он:[name for name in iter(employee, "Френк")]
Да, это функция
iter()
. Обычно её вызывают с одним аргументом — коллекцией:>>> seq = [1, 2, 3]
>>> it = iter(seq)
>>> next(it)
1
Но в варианте с двумя аргументами
iter()
работает иначе:iter(callable, sentinel)
Первый аргумент — функция или что-нибудь вызываемое (callable), второй — контрольное значение (sentinel). Каждое обращение к итератору вызывает
callable()
и возвращает результат его выполнения. А как только callable()
возвращает значение sentinel
, итератор прекращает работу.Это ровно то поведение, что требовалось в задаче — вызывать
employee()
, пока очередной вызов не вернёт "Френк"
. Так что
iter()
здесь — идеальное решение. Поздравляю всех, кто его предложил! Есть и другие хорошие варианты, разберём их в следующий раз.P.S. Некоторые участники решили, что коллектив обязательно должен состоять из 3 сотрудников или не может включать нескольких сотрудников с одинаковыми именами. Но таких ограничений в условиях не было. Вы сами усложнили себе задачу 🤷♀️
#задачка
Как вам задачка?
Final Results
65%
Супер, давай каждую неделю!
24%
Норм, иногда можно
11%
Не надо больше
А вот и полный разбор задачки о сотрудникофикаторе. Не обошлось без моржа: https://antonz.ru/iter-with-sentinel/
#задачка
#задачка
Антон Жиянов
Задачка об итераторе на Python
Как подобрать коллектив единомышленников с помощью random и iter
Как работать с данными без экселя и pandas
Должен признаться: я недолюбливаю пандас. Спору нет, штука мощная и вполне подходит для обработки датасетов. Но пользоваться им удобно, только если работаете с пандасом каждый день. Иначе запомнить эти десятки функций и сотни хаотичных параметров невозможно — так и будете каждый раз гуглить простейшие операции.
Авторы пандаса думали о чем угодно, только не об удобстве пользователя. Если не верите — почитайте документацию о джойне таблиц. Выглядит так, как будто космический корабль строим, хотя с точки зрения предметной области задача элементарная.
Возможно, я бы смирился и безропотно учил пандасовское API. Если бы задолго до появления pandas не придумали SQL — лаконичный, продуманный доменный язык, который идеально подходит для работы с данными. Да, для 5% задач пандас окажется лучше, но не вижу смысла поедать кактус в остальных 95%.
К чему это всё. Я запускаю курс «SQLite на практике» о том, как использовать SQLite для повседневной работы с данными:
— Быстро анализировать наборы данных.
— Строить сводные отчеты из нескольких источников.
— Загружать, трансформировать и выгружать данные в нужном формате.
— Удобно работать с JSON-документами, деревьями и графами.
Курс не по основам SQL (этого добра в интернете хватает). Вместо разжевывания синтаксиса и теории фокусируется на конкретных задачах — так участники сразу смогут применять знания в работе. Входные требования: базовое понимание SQL и любовь к командной строке.
Курс платный. Но пока он в разработке, есть места для 10 бета-тестеров — они смогут пройти всю программу бесплатно. Если вам интересно, записывайтесь.
#курс
Должен признаться: я недолюбливаю пандас. Спору нет, штука мощная и вполне подходит для обработки датасетов. Но пользоваться им удобно, только если работаете с пандасом каждый день. Иначе запомнить эти десятки функций и сотни хаотичных параметров невозможно — так и будете каждый раз гуглить простейшие операции.
Авторы пандаса думали о чем угодно, только не об удобстве пользователя. Если не верите — почитайте документацию о джойне таблиц. Выглядит так, как будто космический корабль строим, хотя с точки зрения предметной области задача элементарная.
Возможно, я бы смирился и безропотно учил пандасовское API. Если бы задолго до появления pandas не придумали SQL — лаконичный, продуманный доменный язык, который идеально подходит для работы с данными. Да, для 5% задач пандас окажется лучше, но не вижу смысла поедать кактус в остальных 95%.
К чему это всё. Я запускаю курс «SQLite на практике» о том, как использовать SQLite для повседневной работы с данными:
— Быстро анализировать наборы данных.
— Строить сводные отчеты из нескольких источников.
— Загружать, трансформировать и выгружать данные в нужном формате.
— Удобно работать с JSON-документами, деревьями и графами.
Курс не по основам SQL (этого добра в интернете хватает). Вместо разжевывания синтаксиса и теории фокусируется на конкретных задачах — так участники сразу смогут применять знания в работе. Входные требования: базовое понимание SQL и любовь к командной строке.
Курс платный. Но пока он в разработке, есть места для 10 бета-тестеров — они смогут пройти всю программу бесплатно. Если вам интересно, записывайтесь.
#курс
Спасибо всем, кто подал заявки! Желающих оказалось в несколько раз больше, чем мест, так что прием заявок я остановил 🤷В ближайшие дни напишу бета-тестерам.
Если вы оставили заявку, но не попали в тест — для вас бессрочная 50% скидка на полный курс, когда он выйдет.
Если вы оставили заявку, но не попали в тест — для вас бессрочная 50% скидка на полный курс, когда он выйдет.
Travis CI → GitHub Actions
В прошлом году я писал, как сделать классный Python-пакет. Там упоминаются полезные облачные сервисы: Travis CI для сборки, Coveralls для покрытия, Code Climate для качества кода.
Так вот, сдается мне, что Travis CI пора на покой. В 2020 году Гитхаб довел до ума свои Actions, и они просто бесподобны. Где еще вы настроите сборку и публикацию под Windows, Linux и macOS за десять минут?
Рекомендация этого года — GitHub Actions:
https://antonz.ru/github-actions/
#код
В прошлом году я писал, как сделать классный Python-пакет. Там упоминаются полезные облачные сервисы: Travis CI для сборки, Coveralls для покрытия, Code Climate для качества кода.
Так вот, сдается мне, что Travis CI пора на покой. В 2020 году Гитхаб довел до ума свои Actions, и они просто бесподобны. Где еще вы настроите сборку и публикацию под Windows, Linux и macOS за десять минут?
Рекомендация этого года — GitHub Actions:
https://antonz.ru/github-actions/
#код
Простое против легкого
9 лет назад в докладе «Simple Made Easy» Рич Хикки рассказал о разнице между простым (simple) и легким (easy) в разработке софта. Стремление к простым программам (в противоположность легким) — самый важный, наверное, принцип разработки. И при этом совершенно непопулярный.
Simple — это о внутреннем устройстве программы, ее архитектуре. У простых программ мало внутренних зависимостей, движущихся частей, настроек. Антипод простой программы — сложная. Простая программа или сложная — это объективная характеристика.
Easy — это о том, насколько человеку легко работать с программой. Это субъективная характеристика: что мне легко, другому сложно, и наоборот. Антипод легкой программы — тяжелая.
Например, SQLite — легкая, но не простая. Внутри там ад, особенно в системе типов и взаимовлиянии многочисленных параметров. А Redis — простой. Но для многих не такой легкий, как SQLite, потому что непривычный. Docker — «легкий», но сложный. Kubernetes — тяжелый и адово сложный.
JavaScript — легкий, но очень сложно устроен. Python — тоже легкий и сложный, хотя и попроще джаваскрипта. Go — простой.
Модули стандартной библиотеки bisect и heapq — простые. Но не легкие, если вы не знаете алгоритмов, которые они реализуют. dataclasses и namedtuple созданы, чтобы быть легкими, но при этом очень сложные.
Простые программы в долгой перспективе лучше легких. В простой программе оказывается легче разобраться, легче использовать на реальных сценариях, легче менять и дорабатывать. Легкую (но при этом сложную) программу можно быстро начать использовать, но дальше ждет стена.
Разработчики предпочитают писать «легкие» программы, а не простые — потому что простые делать тяжело. Придется продумывать архитектуру, работать с ограничениями, много раз переписывать. Намного легче слепить из палочек и веточек, а сверху приделать «легкий» интерфейс.
Я очень хочу, чтобы в мире софта появлялось больше простых, а не «легких» программ и библиотек. А у вас есть любимые простые штуки?
#код
9 лет назад в докладе «Simple Made Easy» Рич Хикки рассказал о разнице между простым (simple) и легким (easy) в разработке софта. Стремление к простым программам (в противоположность легким) — самый важный, наверное, принцип разработки. И при этом совершенно непопулярный.
Simple — это о внутреннем устройстве программы, ее архитектуре. У простых программ мало внутренних зависимостей, движущихся частей, настроек. Антипод простой программы — сложная. Простая программа или сложная — это объективная характеристика.
Easy — это о том, насколько человеку легко работать с программой. Это субъективная характеристика: что мне легко, другому сложно, и наоборот. Антипод легкой программы — тяжелая.
Например, SQLite — легкая, но не простая. Внутри там ад, особенно в системе типов и взаимовлиянии многочисленных параметров. А Redis — простой. Но для многих не такой легкий, как SQLite, потому что непривычный. Docker — «легкий», но сложный. Kubernetes — тяжелый и адово сложный.
JavaScript — легкий, но очень сложно устроен. Python — тоже легкий и сложный, хотя и попроще джаваскрипта. Go — простой.
Модули стандартной библиотеки bisect и heapq — простые. Но не легкие, если вы не знаете алгоритмов, которые они реализуют. dataclasses и namedtuple созданы, чтобы быть легкими, но при этом очень сложные.
Простые программы в долгой перспективе лучше легких. В простой программе оказывается легче разобраться, легче использовать на реальных сценариях, легче менять и дорабатывать. Легкую (но при этом сложную) программу можно быстро начать использовать, но дальше ждет стена.
Разработчики предпочитают писать «легкие» программы, а не простые — потому что простые делать тяжело. Придется продумывать архитектуру, работать с ограничениями, много раз переписывать. Намного легче слепить из палочек и веточек, а сверху приделать «легкий» интерфейс.
Я очень хочу, чтобы в мире софта появлялось больше простых, а не «легких» программ и библиотек. А у вас есть любимые простые штуки?
#код