💣 Автоматизация задач в Python-проекте
Когда разрабатываешь библиотеку или приложение, всегда найдутся задачи, которые выполняешь изо дня в день:
— проверить код линтерами,
— прогнать тесты с замером покрытия,
— запустить в докере,
...
JS-разработчикам повезло: у них в package.json есть специальная секция scripts для таких штук. Для Питона ничего подобного не предусмотрено. Но есть отличное решение:
https://antonz.ru/makefile/
Когда разрабатываешь библиотеку или приложение, всегда найдутся задачи, которые выполняешь изо дня в день:
— проверить код линтерами,
— прогнать тесты с замером покрытия,
— запустить в докере,
...
JS-разработчикам повезло: у них в package.json есть специальная секция scripts для таких штук. Для Питона ничего подобного не предусмотрено. Но есть отличное решение:
https://antonz.ru/makefile/
📦 Как сделать классный 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 (!), не считая дандеров.
В 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 (!), не считая дандеров.
Прочитать произвольную строку из файла
Предположим, вы решили разработать продвинутого саппорт-бота. В нём будет машин лёнинга до самых краёв, так что человек почти не понадобится. К сожалению, неотложные дела отвлекли ваше внимание, и вы делегировали задачу Френку.
Прямо скажем, это было не лучшее решение. Тупая и ленивая скотина придумала, что достаточно заготовить файл с универсальными ответами на все случаи жизни, и на каждый вопрос отвечать случайной фразой:
Простой, надёжный алгоритм. Осталось воплотить его в питоне. Здесь Френку поможет
Ничего себе! Это едва ли не короче, чем hello world. К тому же, функция
Бот готов, Френк возвращается к своим семечкам.
Предположим, вы решили разработать продвинутого саппорт-бота. В нём будет машин лёнинга до самых краёв, так что человек почти не понадобится. К сожалению, неотложные дела отвлекли ваше внимание, и вы делегировали задачу Френку.
Прямо скажем, это было не лучшее решение. Тупая и ленивая скотина придумала, что достаточно заготовить файл с универсальными ответами на все случаи жизни, и на каждый вопрос отвечать случайной фразой:
# 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()
отработают моментально.Бот готов, Френк возвращается к своим семечкам.
Зачем читать исходники
Я как-то писал, что в документацию питона добавили ссылки на исходники модулей. Читать их не только увлекательно, но и полезно.
Помните
Модуль не случайно называется
И есть в модуле функция
> 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/
Сделал такую картинку в стиле Джулии Эванс, про модуль textwrap, с которого когда-то начался Oh My Py. Что думаете?
Какие заметки были бы вам интересны? (можно указать несколько)
Final Results
60%
Стандартная библиотека Python
49%
Другие клёвые маленькие библиотеки
36%
Django, Celery, всякий веб
44%
PostgreSQL, Redis, базы данных
35%
Pandas, Keras, машинное обучение
Проверить, входит ли элемент в коллекцию
Предположим, вы ведёте реестр монет. В нём записаны монетки всех времён, стран и достоинств. На вашем сайте любой может проверить, есть ли та или иная монета в реестре, и если нет — добавить её.
Как проверить, есть ли монета в реестре?
Можно так:
Конечно, так делать нехорошо. Операция
10 секунд на проверку тысячи элементов, пффф. Решение — использовать множества:
Операция
А что с памятью? Проверим:
Множество оказалось в 2 раза тяжелее списка. Ничего, для миллиона монеток хватит. Но что делать, если в коллекции один миллиард объектов, тоже всё в память запихивать? Есть и другие варианты, о них в следующий раз.
Предположим, вы ведёте реестр монет. В нём записаны монетки всех времён, стран и достоинств. На вашем сайте любой может проверить, есть ли та или иная монета в реестре, и если нет — добавить её.
Как проверить, есть ли монета в реестре?
Можно так:
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 раза тяжелее списка. Ничего, для миллиона монеток хватит. Но что делать, если в коллекции один миллиард объектов, тоже всё в память запихивать? Есть и другие варианты, о них в следующий раз.
Проверить, есть ли элемент в огромной коллекции
Как мы выяснили в прошлый раз, проверка на вхождение элемента в множество выполняется моментально, но занимает прилично места:
Для множества на 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 раз при сохранении приемлемой скорости иногда может вам пригодиться.
Грамотно работать с любым диапазоном
Все знают, что
Но не все знают, что
И даже так:
И так тоже:
При этом
А время выполнения операций при этом как в обычном списке:
Ну разве он не чудо?
Все знают, что
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).Ну разве он не чудо?
Скорость работы оператора in range
После вчерашней заметки некоторые подписчики справедливо заметили, что сложность проверки «element in list» составляет O(n), а не O(1). А я пишу, что для range она O(1). Да, вы молодцы, так и есть ツ
Действительно: чтобы проверить, есть ли элемент в списке, придётся обойти все элементы списка, пока не найдём искомый — это сложность O(n). Но в случае с диапазоном мы точно знаем первый элемент, последний элемент и шаг. Поэтому разработчики стандартной библиотеки пошли на хитрость.
Допустим, есть выражение
Проверили границы, посчитали остаток от деления, бумс, готово. Для отрицательного step работает аналогично.
Так что
После вчерашней заметки некоторые подписчики справедливо заметили, что сложность проверки «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
.Задачка: сотрудникофикатор
Время для задачки! Допустим, вы основали модный 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% скидка на полный курс, когда он выйдет.