Hello World
1.92K subscribers
68 photos
6 videos
3 files
161 links
Be so good that you cannot be ignored. And then, go one step beyond.
Download Telegram
че пацаны аниме?

я не покупал если че, привезли бесплатно с интела на пробу, по программе поддержки студентов и ученых.

upd: Потестил: довольно неплохая карточка так то.
Дрочим датаклассы 💅🏻

Долго мучился с вопросом, что делать с длинными постами. Не вижу смысла писать короткие примеры типа "почему плохо совать [] в дефолтный аргумент в функции". За годы тут я выработал отвращение к повторяющимся скучным постам в блогах, бесконечным вопросам в комментах вроде "бро, контент будет? или снова репост старого поста?" и т.д. Хорошо, что в тг теперь можно использовать нормальный inline code markdown. Поэтому здесь я заканчиваю предисловие, и мы начинаем.

Зачем нужны датаклассы?

Базовая фишка датаклассов - красивое представление объекта при выводе через print. Например, класс User:

class User:
def __init__(self, user_id: int, name: str) -> None:
self.user_id = user_id
self.name = name

user = User(user_id=1111, name="@hw_code")
print(user)

на экран вылезет что-то в духе:
<__main__.User object at 0x0000021492214BD0>

Таким образом, нет необходимости писать самостоятельно __repr__ или __str__ (ну, по сути __repr__, это легко проверить, если его перегрузить).

Создание датакласса.

При создании датакласса стоит отметить следующую вещь. Датаклассы в каком-то смысле абузят понятие классовой переменной (это которая обычно общая для всех инстансов), также добавляя необходимые нам классовые методы сравнения, и прочего.
from dataclasses import dataclass

@dataclass
class User:
user_id: int
name: str

Очевидно, что они потом добавляются в __init__, следовательно данная запись аналогична предыдущей. Но теперь при вызове print мы увидим что отрабатывает дефолтный __repr__ датакласса.

Основные моменты.

Теперь кратко обсудим основные моменты. Можно добавлять дефолтные значения:
@dataclass
class User:
user_id: int
name: str
is_active: bool = True

работает так только для простых типов. Для сложных вроде списков нам понадобится field и default_factory:
@dataclass
class User:
user_id: int
name: str
is_active: bool = True
email_addresses: list[str] = field(default_factory=list)

поскольку мы хотим каждый раз создавать новый список, мы указываем функцию, которая будет создавать нам этот список. Если например мы захотим чтобы у каждого нового юзера автоматически создавался айди, мы снова вернемся к default_factory, но укажем внутри функцию, которая создаст нам этот айди:
import random

def my_super_ultra_id_func() -> int:
return random.randint(0, 1000)

@dataclass
class User:
user_id: int = field(default_factory=my_super_ultra_id_func)
name: str = field(default="")
is_active: bool = True
email_addresses: list[str] = field(default_factory=list)

ну и к имени тоже заодно докинул. Теперь например можно исключить список адресов из __repr__, если мы не хотим показывать это пользователю (читай прикладному программисту, юзающему наш класс).
@dataclass
class User:
user_id: int = field(default_factory=my_super_ultra_id_func)
name: str = field(default="")
is_active: bool = True
email_addresses: list[str] = field(default_factory=list, repr=False)

Вывод с экрана:
User(user_id=498, name='hw_code', is_active=True)

Мы все еще можем использовать кастомные значения для параметров, указанных в аргументах:
user = User(user_id=1, name="hw_code", is_active=False, email_addresses=["hello@world.code"])
print(user)

Вывод:
User(user_id=1, name='hw_code', is_active=False)


Не буду сильно затягивать здесь. Если статьи не читают из-за их длины, то и длинные посты, возможно, читают с меньшим желанием. Предлагаю вам здесь навалить лойсов, и тогда ваши билды всегда будут успешными, 10 раз смерджитесь в мастер, а сеньор вам руку пожмет. Ну или сами себе руку пожмете, фигурально, как в том меме с Обамой. А я отчаливаю, но возможно, выложу и вторую версию. Посмотрим.

@hw_code
Channel photo updated
Please open Telegram to view this post
VIEW IN TELEGRAM
Дрочим датаклассы v 2.0 💅💅

Приветствую, дорогие читатели! Ваш любимый шизоид снова выходит на связь. В этом посте мы закончим разбирать датаклассы (надеюсь). В прошлом посте я не осветил такой интересный вопрос, как "зачем же все-таки они нужны?". Поясняю: вам необходимо создавать датаклассы в первую очередь для хранения и управления данными (как и следует из их названия). Либо, если вы хотите упростить себе задачу и писать меньше кода, воспользовавшись автоматической генерацией некоторых методов: __init__, __repr__, etc.

У нас в проектах это много где используется, в основном для упрощения управления и хранения различного рода данных.

Исключение аргументов из __init__

Рассмотрим пример, когда нам нужно убрать аргумент из инициализатора, например user_id, который нам должен генерироваться автоматически. Для этого в аргумент добавим init=False.


import random

def my_super_ultra_id_func() -> int:
return random.randint(0, 1000)

@dataclass
class User:
user_id: int = field(init=False, default_factory=my_super_ultra_id_func)
name: str = field(default="")
is_active: bool = True
email_addresses: list[str] = field(default_factory=list)


Используем __post_init__ для создания полей

Иногда нам необходимо например добавить переменную, которая будет являться комбинацией других. Для этого воспользуемся методом __post_init__.


@dataclass
class User:
user_id: int = field(default_factory=my_super_ultra_id_func)
name: str = field(default="")
is_active: bool = True
email_addresses: list[str] = field(default_factory=list, repr=False)
search_string: str = field(init=False)

def __post_init__(self) -> None:
self.search_string = f"{self.user_id}: {self.name}"


user = User(user_id=1, name="hw_code", is_active=False, email_addresses=["hello@world.code"])


Выведет:


User(user_id=1, name='hw_code', is_active=False, search_string='1: hw_code')


Private/protected

Абсолютно аналогично созданию private/protected переменной в обычном классе. Просто добавляем _ перед именем.


_search_string: str = field(init=False)


Read only поля датакласса

Допустим мы хотим сделать так, чтобы после инициализации значения полей нельзя было поменять. По умолчанию, когда мы используем декоратор @dataclass, значение аргумента frozen в нем равно False. Чтобы сделать "заморозить" поля датакласса, меняем это значение на True.


@dataclass(frozen=True)
class User:
user_id: int = field(default_factory=my_super_ultra_id_func)
name: str = field(default="")
is_active: bool = True
email_addresses: list[str] = field(default_factory=list, repr=False)
search_string: str = field(init=False)

def __post_init__(self) -> None:
self.search_string = f"{self.user_id}: {self.name}"


Теперь после инициализации мы не сможем изменить значения полей:

user = User(user_id=1, name="hw_code", is_active=False, email_addresses=["hello@world.code"])
user.name = "Big Dick" # ошибка


kw_only аргумент

Указывая этот аргумент мы можем вынудить программиста, использующего наш датакласс, использовать только именованые аргументы при инициализации.


@dataclass(kw_only=True)
class User:
user_id: int = field(default_factory=my_super_ultra_id_func)
name: str = field(default="")
is_active: bool = True
email_addresses: list[str] = field(default_factory=list, repr=False)
search_string: str = field(init=False)

def __post_init__(self) -> None:
self.search_string = f"{self.user_id}: {self.name}"


# сработает
user = User(user_id=1, name="hw_code", is_active=False, email_addresses=["hello@world.code"])

# не сработает
user = User(1, "hw_code", False, ["hello@world.code"])


Ну че, все готовы к началу рабочей недели? 🗿🗿🗿

@hw_code
Ну и напоследок еще один немаловажный момент, связанный с датаклассами. Иногда нам в таком классе нужна чисто классовая переменная (сори за масло масляное, cls var). Рассмотрим на примере с датаклассами и без.

Создание переменной класса

Без датакласса мы бы написали так


class User:
cvar: float = 0.5

def __init__(self, user_id: int, name: str) -> None:
self.user_id = user_id
self.name = name


Используя датакласс мы бы написали так


from typing import ClassVar

@dataclass
class User:
cvar: ClassVar[float] = 0.5

user_id: int = field(default_factory=my_super_ultra_id_func)
name: str = field(default="")

В последнем примере вернул поля и для user_id и для name, поскольку именованый cvar должен был бы по идее идти после них, но я оставил его в начале.

Здесь в принципе без детальных пояснений. С typing наверняка все знакомы, если на норм проектах пишете... Как я и говорил, датаклассы абузят понятие классовой переменной, и базовый синтаксис в классах, но зато взамен вы получаете множество уже реализованных "под капотом" методов для работы с данными. В целом, справедливый обмен. Если остались какие то вопросы, в доке все довольно хорошо объяснено, и всегда можно заглянуть туда.

@hw_code
Хотел чет еще вкинуть по логгированию или юнит тестам, но увы не успел. Итоги подводить не буду, сами для себя думаю уже все это сделали. Так что, без лишний предисловий, с нг. 🥳
🚀 Привет! Давненько не заглядывал к вам сюда, дел было много, как всегда. Недавно наткнулся в твиттере на обсуждение Groq. Парень там хвалит эту штуку, говорит, что у него время респонса офигенное, и в целом не уступает даже ChatGPT. А я решил спроектировать клиент для использования их API, и самое крутое – без необходимости закрытого ключа!

Хотя pylint, правда, решил подкинуть мне немного головной боли и отказывается работать. Видимо, я мог пропустить мелкий косяк где-то. Хотя вроде бы все работает без нареканий.

Если интересно, вот ссылка на мой проект: GitHub - Dartrisen/groq
ща из калифорнийской комы выползу...
Наконец то норм фронтэнд на питоне? А мне только недавно зашел Next.js...

https://x.com/jeremyphoward/status/1818036923304456492

https://fastht.ml

Надо попробовать на досуге.
Вот бывает такая фигня, работаешь в своем бранче, никого не трогаешь, но тут надо тесты писать. Написал, запускаешь nox, показывает что coverage 99% где то в совсем другой части проекта, никак не связанной с твоим кодом. Начинаешь вопрошать мол как оно попало в master? Стягиваешь master, запускаешь nox и показывает зеленый билд... Меняешь бранч, показывает что проблема все таки есть. Снова в master обратно, и уже и там показывает что все плохо. Но ведь это все уже в мастере, вот как так? А github actions который у нас там подвязан в CI/CD тоже показывает зеленый билд 🥲

Я эти тесты на одном месте крутил. Особенно если их писал не я. Это конечно не часто случается, но когда проект содержит десятки тыщ строк кода, может случиться и такое.
В каком то из давних интервью меня попросили предсказать вывод следующего Python-кода:


class A:
pass

class B:
value = 1

class C:
value = 3

class D(A, B, C):
def __str__(self):
return str(self.value)

print(D())


Эту проблему на тот момент я не мог решить, но я ее запомнил и вернулся к ней только недавно. Ключ к решению этой задачи лежит в понимании т.н. Method Resolution Order (MRO) в Python. MRO определяет порядок, в котором происходит поиск базовых классов при обращении к методу или атрибуту.

💡 В данном случае D наследуется от A, B и C. Но когда мы печатаем D(), атрибут value берётся из класса B! Почему?

Вот объяснение:
1️⃣ Python ищет value в классе D.
2️⃣ Затем он смотрит в A, но A не имеет value.
3️⃣ Далее он проверяет B — и находит value = 1.

Поскольку B стоит перед C в MRO, используется value из B, поэтому вывод будет 1! ⚙️

Вы можете увидеть MRO с помощью D.mro():

[<class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]


@hw_code
Я в последнее время часто сижу в Твиттере. Там собралась большая техническая тусовка: люди постоянно создают новые проекты и стартапы, зарабатывают деньги и делятся опытом. После выхода того самого интервью с Лексом Фридманом и Питером Левелсом (ну про Фридмана-то вы наверняка слышали?), там активно обсуждают, нужно ли действительно "вылизывать" свои приложения, делать тесты и писать код "как надо", а не как хочется. Питер высказывался о том, что все эти TDD, Vercel, крутые базы данных и прочее — не так уж и важны. Он буквально на коленке создал вирусные AI-проекты, которые приносят огромные деньги. Инди-хакеры тут же подхватили эту идею и начали постить, мол, да, надо пушить в продакшен сразу, тесты не нужны, вообще всё не нужно — хочу и пушу говнокод прямо в мастер. "Что вы мне сделаете?" Поэтому мне интересно, что вы думаете по этому поводу:
Мда, если даже здесь всё не так однозначно... Ладно, вот вам ещё один спорный тезис. Кто-то в обсуждениях заметил, что некоторые программерские инструменты будто бы специально сделаны сложными в использовании. Например, те же СУБД (почему-то не люблю это слово). Postgres — это, конечно, мощно, но если у вас тоже возникал вопрос: почему бы не использовать MySQL или даже sqlite, то вы не одиноки. На примере того же Левелса можно отметить, что его сервера выдерживают большой наплыв пользователей, несмотря на использование самых простых инструментов, таких как sqlite. Я, честно говоря, вообще не уверен знает ли он, что есть асинхронная обработка реквестов и т.д. (интересно, как оно на пыхе там?).

Можно долго спорить, но у каждого инструмента есть свои причины для использования. Если ваш проект используют всего трое человек (вы и ещё два фаундера), возможно, не стоит стремиться "сделать всё идеально с самого начала". Но это больше про предпринимательство. Пока нет чёткого видения product market fit, нет смысла усложнять процесс разработки. Если нужно будет, перепишите потом. Одно дело — работать в корпорации, где стабильно платят и цена ошибки не так велика (я знаю примеры, когда тулзы разрабатывали буквально годами, чтобы в итоге сказать "всё, мы решили забросить этот проект"), и совсем другое — когда денег нет и нет чёткого понимания рынка (нужно разработать быстро).
докер и микросервисы это залупа конченая, а фирмы которые это используют просто страдают “shiny new framework syndrome”.

Change my mind.