Forwarded from GitHub Сообщество
Neural-Doodle – нейронка, которая дорисовывает красивую картинку на основе вашего эскиза
Сценарий doodle. py генерирует новое изображение, используя одно, два, три или четыре изображения в качестве входных данных в зависимости от того, что вы пытаетесь сделать: оригинальный стиль и его аннотация, а также изображение целевого контента (необязательно) с его аннотацией (он же ваш рисунок)
#Python #AI #Interesting
👉 @Githublib
Сценарий doodle. py генерирует новое изображение, используя одно, два, три или четыре изображения в качестве входных данных в зависимости от того, что вы пытаетесь сделать: оригинальный стиль и его аннотация, а также изображение целевого контента (необязательно) с его аннотацией (он же ваш рисунок)
#Python #AI #Interesting
👉 @Githublib
👍2
Forwarded from Python академия
Дизассемблирование
При запуске программы на python, написанный код преобразуется в байт-код, который затем может быть запущен в интерпретаторе Python. Встроенный модуль
На картинке показана работа этого модуля на примере функции, но такое можно повторить и с классами – в таком случае все его функции будут дизассемблированы.
Подписывайтесь на канал 👉@pythonofff
При запуске программы на python, написанный код преобразуется в байт-код, который затем может быть запущен в интерпретаторе Python. Встроенный модуль
dis позволяет дизассемблировать байт-код в удобное представление для просмотра его инструкций. Полный спсиок инструкций байт-кода с описанием можно посмотреть здесь.На картинке показана работа этого модуля на примере функции, но такое можно повторить и с классами – в таком случае все его функции будут дизассемблированы.
Подписывайтесь на канал 👉@pythonofff
👍6
Media is too big
VIEW IN TELEGRAM
Python Full Course for free 🐍
#python #tutorial #beginners
⭐️Time Stamps⭐️
#1 (00:00:00) Python tutorial for beginners 🐍
#2 (00:05:57) variables ✘
#3 (00;17;38) multiple assignment 🔠
#4 (00:20:27) string methods 〰️
#5 (00:25:13) type cast 💱
#6 (00:30:14) user input ⌨️
#7 (00:36:50) math functions 🧮
#8 (00:40:58) string slicing ✂️
#9 (00:51:52) if statements 🤔
#10 (00:58:19) logical operators 🔣
#11 (01:04:03) while loops 🔄
#12 (01:07:31) for loops ➰
#13 (01:13:04) nested loops ➿
......
#82 (08:21:30) new windows 🗔
#83 (08:25:32) window tabs 📑
#84 (08:30:52) grid 🏢
#85 (08:39:52) progress bar 📊
#86 (08:49:48) canvas 🖍️
#87 (09:01:18) keyboard events ⌨️
#88 (09:05:54) mouse events 🖱️
#89 (09:11:00) drag & drop 👈
#90 (09:18:18) move images w/ keys 🏎️
#91 (09:29:13) animations 🛸
#92 (09:41:31) multiple animations 🎞️
#93 (09:53:04) clock program 🕒
#94 (10:01:03) send an email 📧
#95 (10:07:37) run with command prompt 👨💻
#96 (10:09:53) pip 🏗️
#97 (10:13:30) py to exe 🏃
#98 (10:17:13) calculator program 🖩
#99 (10:31:38) text editor program ✏️
100 (11:05:51) tic tac toe game ⭕
101 (11:26:25) snake game 🐍
источник
👉@BookPython
#python #tutorial #beginners
⭐️Time Stamps⭐️
#1 (00:00:00) Python tutorial for beginners 🐍
#2 (00:05:57) variables ✘
#3 (00;17;38) multiple assignment 🔠
#4 (00:20:27) string methods 〰️
#5 (00:25:13) type cast 💱
#6 (00:30:14) user input ⌨️
#7 (00:36:50) math functions 🧮
#8 (00:40:58) string slicing ✂️
#9 (00:51:52) if statements 🤔
#10 (00:58:19) logical operators 🔣
#11 (01:04:03) while loops 🔄
#12 (01:07:31) for loops ➰
#13 (01:13:04) nested loops ➿
......
#82 (08:21:30) new windows 🗔
#83 (08:25:32) window tabs 📑
#84 (08:30:52) grid 🏢
#85 (08:39:52) progress bar 📊
#86 (08:49:48) canvas 🖍️
#87 (09:01:18) keyboard events ⌨️
#88 (09:05:54) mouse events 🖱️
#89 (09:11:00) drag & drop 👈
#90 (09:18:18) move images w/ keys 🏎️
#91 (09:29:13) animations 🛸
#92 (09:41:31) multiple animations 🎞️
#93 (09:53:04) clock program 🕒
#94 (10:01:03) send an email 📧
#95 (10:07:37) run with command prompt 👨💻
#96 (10:09:53) pip 🏗️
#97 (10:13:30) py to exe 🏃
#98 (10:17:13) calculator program 🖩
#99 (10:31:38) text editor program ✏️
100 (11:05:51) tic tac toe game ⭕
101 (11:26:25) snake game 🐍
источник
👉@BookPython
👍7
Python Full Course 🐍
00:00 - Intro
00:57 - Quick Word
01:59 - Installing Python
04:40 - Pycharm
05:43 - Installing Pycharm
08:41 - Your first Python program
12:21 - Variables
14:55 - Creating Variables
20:03 - Naming Variables
22:34 - Data Types
26:06 - Dynamically Type Language
31:30 - Comments
34:05 - Strings
39:34 - Multiline and Formatting Strings
44:28 - Indentation
51:28 - Arithmetic Operators
55:53 - Comparison Operators
58:54 - Logical Operators
01:04:26 - Assignment Operators
01:08:13 - If Statements
01:14:06 - Quick Word About If Statements
01:16:33 - Ternary If Statements
01:19:26 - Lists
01:24:58 - Useful List Methods
01:28:33 Deleting Items from Lists
01:33:06 - Sets
01:37:32 - Set Union Intersection & Difference
01:43:53 - Dictionaries
01:50:16 - For Loops
01:53:26 - Loop Through Dictionaries
01:57:18 - Exercise
01:58:07 - Exercise Solution
02:00:04 - While Loop
02:03:18 - Break and Continue
02:08:31 - Functions
02:11:03 - Parameters and Arguments
02:15:12 - Return Values From Functions
02:21:16 - Built in Functions and Import Statement
02:25:45 - Creating Modules
02:31:17 - Classes and Objects
02:35:03 - Creating Classes and Objects
02:43:18 - Printing Objects
02:47:40 - Working With Dates
02:51:43 - Formatting Dates
02:56:15 - Creating Files
03:01:48 - Reading From Files
03:04:30 - A Better Way To Work With Files
03:08:58 - Fetching Data From Internet
03:11:48 - Fetching Jokes From Internet
03:21:47 - Pip & Modules
03:25:17 - Request Module
03:28:23 - Text To Speech
03:33:40 - Lets Wrap Up
https://www.youtube.com/watch?v=LzYNWme1W6Q
👉@BookPython
00:00 - Intro
00:57 - Quick Word
01:59 - Installing Python
04:40 - Pycharm
05:43 - Installing Pycharm
08:41 - Your first Python program
12:21 - Variables
14:55 - Creating Variables
20:03 - Naming Variables
22:34 - Data Types
26:06 - Dynamically Type Language
31:30 - Comments
34:05 - Strings
39:34 - Multiline and Formatting Strings
44:28 - Indentation
51:28 - Arithmetic Operators
55:53 - Comparison Operators
58:54 - Logical Operators
01:04:26 - Assignment Operators
01:08:13 - If Statements
01:14:06 - Quick Word About If Statements
01:16:33 - Ternary If Statements
01:19:26 - Lists
01:24:58 - Useful List Methods
01:28:33 Deleting Items from Lists
01:33:06 - Sets
01:37:32 - Set Union Intersection & Difference
01:43:53 - Dictionaries
01:50:16 - For Loops
01:53:26 - Loop Through Dictionaries
01:57:18 - Exercise
01:58:07 - Exercise Solution
02:00:04 - While Loop
02:03:18 - Break and Continue
02:08:31 - Functions
02:11:03 - Parameters and Arguments
02:15:12 - Return Values From Functions
02:21:16 - Built in Functions and Import Statement
02:25:45 - Creating Modules
02:31:17 - Classes and Objects
02:35:03 - Creating Classes and Objects
02:43:18 - Printing Objects
02:47:40 - Working With Dates
02:51:43 - Formatting Dates
02:56:15 - Creating Files
03:01:48 - Reading From Files
03:04:30 - A Better Way To Work With Files
03:08:58 - Fetching Data From Internet
03:11:48 - Fetching Jokes From Internet
03:21:47 - Pip & Modules
03:25:17 - Request Module
03:28:23 - Text To Speech
03:33:40 - Lets Wrap Up
https://www.youtube.com/watch?v=LzYNWme1W6Q
👉@BookPython
YouTube
Python Full Course 🐍
Python tutorial for beginners full course
#python #tutorial #beginners
Welcome to this full course where you will learn python for beginners. In this python for beginners course I will learn you python and give you a clear understanding of the core concepts…
#python #tutorial #beginners
Welcome to this full course where you will learn python for beginners. In this python for beginners course I will learn you python and give you a clear understanding of the core concepts…
👍8👏1
Совет: разделите уровень доступа к данным, используя паттерн "Репозиторий" 🚀.
Этот паттерн позволяет нам легко менять бэкэнд (БД, текстовый файл, CSV и т.д.), уменьшая жесткую связь и повышая гибкость. 💡 📈
#Python пример ниже с использованием sqlmodel + csv:
👉@BookPython
Этот паттерн позволяет нам легко менять бэкэнд (БД, текстовый файл, CSV и т.д.), уменьшая жесткую связь и повышая гибкость. 💡 📈
#Python пример ниже с использованием sqlmodel + csv:
👉@BookPython
👍1
Pydantic V2: Забываем
Переход на Pydantic V2, это не только ускорение за счет ядра на Rust, но и переосмысление валидации. Самая частая боль при миграции, проверка зависимостей между несколькими полями.
В V1 мы использовали
В чем соль?
В режиме
Пример (валидация периода дат):
Нюансы для профи:
1.
2. Производительность: Валидаторы на Python, это узкое горлышко. Если у вас HighLoad, старайтесь выразить ограничения через
Используйте возможности типизации на 100%.
#pydantic #fastapi #bestpractices #python
📲 Мы в MAX
👉@BookPython
root_validator, используем model_validator правильноПереход на Pydantic V2, это не только ускорение за счет ядра на Rust, но и переосмысление валидации. Самая частая боль при миграции, проверка зависимостей между несколькими полями.
В V1 мы использовали
root_validator и работали со словарем values (прощай, автодополнение IDE). В V2 правильный путь - model_validator в режиме after.В чем соль?
В режиме
mode='after' валидация запускается после того, как поля были распаршены и приведены к типам. Вы работаете с экземпляром класса (self), а не с сырым словарем.Пример (валидация периода дат):
from pydantic import BaseModel, model_validator
from datetime import datetime
class DateRange(BaseModel):
start_dt: datetime
end_dt: datetime
@model_validator(mode='after')
def check_dates_order(self):
# Обращаемся через self — IDE видит поля и их типы!
if self.end_dt <= self.start_dt:
raise ValueError("Дата окончания должна быть позже начала")
return self
# Тест
try:
DateRange(
start_dt="2024-01-01T12:00:00",
end_dt="2023-01-01T12:00:00"
)
except ValueError as e:
print(e)
Нюансы для профи:
1.
mode='before': Используйте только если вам нужно модифицировать сырые входные данные (например, JSON) до того, как Pydantic начнет их парсить. Это аналог pre=True из V1.2. Производительность: Валидаторы на Python, это узкое горлышко. Если у вас HighLoad, старайтесь выразить ограничения через
Field (например, ge, le), так как они отрабатывают на Rust-уровне, что значительно быстрее вызова python-функции.Используйте возможности типизации на 100%.
#pydantic #fastapi #bestpractices #python
👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Профилируем Python в продакшене: почему cProfile не подходит, и чем хорош py-spy
Когда на проде начинает течь память или скачет CPU, первая мысль, подключить профайлер. Стандартный
📉 Цена: Оверхед может замедлить приложение в 2-5 раз. На нагруженном проде это означает добить сервис окончательно.
Для Live-систем нужен Sampling Profiler (сэмплирующий профайлер). Золотой стандарт сейчас - py-spy.
Как это работает?
✅ Результат: Оверхед стремится к нулю. Код инструментализировать не нужно. Рестарт сервиса не нужен.
Два главных режима работы:
1. Live View (как htop, но для функций)
Посмотреть в реальном времени, в каких функциях процесс проводит больше всего времени.
Вы увидите список функций, отсортированный по
2. Flame Graph (Огненный граф)
Для глубокого анализа лучше записать работу сервиса за период и визуализировать стек.
Вы получите SVG-файл. Чем шире полоска, тем больше времени занимает функция. Вертикаль - это глубина стека. Сразу видно, кто «съел» процессорное время.
Нюансы для Middle+:
- GIL:
- Docker/K8s: Так как
#profiling #optimization #pyspy #debugging #python
📲 Мы в MAX
👉@BookPython
Когда на проде начинает течь память или скачет CPU, первая мысль, подключить профайлер. Стандартный
cProfile, это детерминированный профайлер. Он хукает каждый вызов функции.📉 Цена: Оверхед может замедлить приложение в 2-5 раз. На нагруженном проде это означает добить сервис окончательно.
Для Live-систем нужен Sampling Profiler (сэмплирующий профайлер). Золотой стандарт сейчас - py-spy.
Как это работает?
py-spy написан на Rust. Он работает как внешний процесс, который читает память вашего Python-процесса (через системные вызовы, аналогично gdb). Он делает «снимки» стека вызовов с высокой частотой (по дефолту 100 раз в секунду).✅ Результат: Оверхед стремится к нулю. Код инструментализировать не нужно. Рестарт сервиса не нужен.
Два главных режима работы:
1. Live View (как htop, но для функций)
Посмотреть в реальном времени, в каких функциях процесс проводит больше всего времени.
# Нужно только знать PID процесса
py-spy top --pid 12345
Вы увидите список функций, отсортированный по
OwnTime (время внутри функции) и TotalTime (время с учетом дочерних вызовов).2. Flame Graph (Огненный граф)
Для глубокого анализа лучше записать работу сервиса за период и визуализировать стек.
py-spy record -o profile.svg --pid 12345 --duration 60
Вы получите SVG-файл. Чем шире полоска, тем больше времени занимает функция. Вертикаль - это глубина стека. Сразу видно, кто «съел» процессорное время.
Нюансы для Middle+:
- GIL:
py-spy умеет показывать, держит ли функция GIL. Добавьте флаг --gil.- Docker/K8s: Так как
py-spy использует системный вызов ptrace, контейнеру нужны привилегии. В Kubernetes часто нужно добавить securityContext: capabilities: add: ["SYS_PTRACE"] подам, чтобы иметь возможность профилировать их на лету.#profiling #optimization #pyspy #debugging #python
👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤1
Ловушка замыканий: Почему ваши лямбды в цикле сломаны (Late Binding)
Вы пишете код, который генерирует список функций (например, колбэки для кнопок в UI или динамические фильтры). Кажется, что все логично, но на выходе получаете сюрприз.
Проблемный код:
Почему так происходит?
Это называется Late Binding (позднее связывание).
В Python замыкания (closures) захватывают переменные по ссылке, а не по значению.
Когда вы объявляете
К моменту, когда вы начинаете вызывать функции из списка
Как лечить?
Есть два каноничных способа заставить Python запомнить значение «здесь и сейчас».
1. Аргумент по умолчанию (Hack way)
Значения аргументов по умолчанию вычисляются в момент определения функции.
Это работает быстро, но выглядит немного грязно и может сбить с толку линтеры или коллег.
2.
Более чистый и явный способ.
Где это стреляет в реальной жизни?
- Генерация
- Динамическое создание
- Патчинг тестов в циклах.
Не дайте переменным пережить свое время.
#python #internals #functionalprogramming #gotchas
📲 Мы в MAX
👉@BookPython
Вы пишете код, который генерирует список функций (например, колбэки для кнопок в UI или динамические фильтры). Кажется, что все логично, но на выходе получаете сюрприз.
Проблемный код:
# Хотим создать 3 функции, которые возвращают 0, 1 и 2 соответственно
funcs = []
for i in range(3):
funcs.append(lambda: i)
# Проверяем
results = [f() for f in funcs]
print(results)
# Ожидание: [0, 1, 2]
# Реальность: [2, 2, 2]
Почему так происходит?
Это называется Late Binding (позднее связывание).
В Python замыкания (closures) захватывают переменные по ссылке, а не по значению.
Когда вы объявляете
lambda: i, Python не сохраняет текущее число 0, 1 или 2. Он сохраняет инструкцию: «когда меня вызовут, пойди в локальную область видимости, найди переменную с именем i и возьми ее значение».К моменту, когда вы начинаете вызывать функции из списка
results, цикл for уже завершился. Переменная i в этой области видимости навсегда осталась равной 2. Все три лямбды смотрят на одну и ту же переменную i.Как лечить?
Есть два каноничных способа заставить Python запомнить значение «здесь и сейчас».
1. Аргумент по умолчанию (Hack way)
Значения аргументов по умолчанию вычисляются в момент определения функции.
funcs = []
for i in range(3):
# i=i создает локальную переменную i внутри функции
# и присваивает ей текущее значение i из цикла
funcs.append(lambda i=i: i)
Это работает быстро, но выглядит немного грязно и может сбить с толку линтеры или коллег.
2.
functools.partial (Enterprise way)Более чистый и явный способ.
partial создает новый callable-объект, «замораживая» переданные аргументы.
from functools import partial
funcs = []
for i in range(3):
# Здесь значение i фиксируется жестко
funcs.append(partial(lambda x: x, i))
Где это стреляет в реальной жизни?
- Генерация
command для кнопок в Tkinter/PyQt.- Динамическое создание
task в asyncio циклах.- Патчинг тестов в циклах.
Не дайте переменным пережить свое время.
#python #internals #functionalprogramming #gotchas
👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤2
Pytest Patterns: Элегантный Teardown через
Если вы все еще пишете
Фикстуры (fixtures) - это не просто способ передать данные. Это полноценный механизм управления жизненным циклом зависимостей (DI).
1.
В Pytest фикстура может "замереть", отдать управление тесту, а потом продолжить выполнение. Это реализуется через генератор
Код до
Код после
Пример (временная база данных):
Это гарантирует, что ресурсы будут освобождены, и вам не нужны
2. Scopes: Не создавайте мир заново
По умолчанию фикстура имеет
Используйте
Паттерн "Изоляция при общем ресурсе":
Частая задача Middle+: иметь одну БД на весь прогон тестов (быстро), но чистые таблицы для каждого теста (изолированно).
Решение: комбинируем скоупы.
Итог:
🟢 Используйте
🟢 Тяжелые объекты (Engine, Client, Container) - в
🟢 Легкие объекты с состоянием (Session, User) - в
#pytest #testing #qa #bestpractices #python
📲 Мы в MAX
👉@BookPython
yield и оптимизация скоуповЕсли вы все еще пишете
def teardown_method(self): в классах тестов, вы не используете мощь Pytest на 100%.Фикстуры (fixtures) - это не просто способ передать данные. Это полноценный механизм управления жизненным циклом зависимостей (DI).
1.
yield вместо return: Встроенный TeardownВ Pytest фикстура может "замереть", отдать управление тесту, а потом продолжить выполнение. Это реализуется через генератор
yield.Код до
yield - это setUp.Код после
yield - это tearDown.Пример (временная база данных):
import pytest
from sqlalchemy import create_engine
@pytest.fixture
def db_engine():
# Setup: Поднимаем соединение
engine = create_engine("sqlite:///:memory:")
# Передаем объект в тест
yield engine
# Teardown: Этот код выполнится ПОСЛЕ завершения теста
# (даже если тест упал с ошибкой!)
engine.dispose()
Это гарантирует, что ресурсы будут освобождены, и вам не нужны
try/finally блоки внутри самих тестов.2. Scopes: Не создавайте мир заново
По умолчанию фикстура имеет
scope='function'. Она создается и умирает для каждого теста. Это безопасно, но медленно, если мы говорим о поднятии Docker-контейнера или коннекта к БД.Используйте
scope='session' для тяжелых ресурсов, которые можно переиспользовать.Паттерн "Изоляция при общем ресурсе":
Частая задача Middle+: иметь одну БД на весь прогон тестов (быстро), но чистые таблицы для каждого теста (изолированно).
Решение: комбинируем скоупы.
# Живет весь прогон тестов (создается 1 раз)
@pytest.fixture(scope="session")
def db_engine():
engine = create_engine(...)
yield engine
engine.dispose()
# Живет 1 тест (создается N раз)
@pytest.fixture(scope="function")
def db_session(db_engine):
# Берем engine из сессионной фикстуры
connection = db_engine.connect()
transaction = connection.begin() # Начали транзакцию
session = Session(bind=connection)
yield session
session.close()
# ROLLBACK транзакции после теста вернет базу в исходное состояние!
transaction.rollback()
connection.close()
Итог:
yield для очистки ресурсов.scope='session'.scope='function', наследуясь от тяжелых.#pytest #testing #qa #bestpractices #python
👉@BookPython
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
Ранее мы затронули типизацию в фикстурах (косвенно), поэтому сегодня поговорим про:
Protocol vs ABC: Утиная типизация на стероидах (Static Duck Typing)
В классическом ООП (Java, C#) и при использовании
Это создает жесткую связность (coupling): ваша реализация должна знать об интерфейсе и импортировать его.
С приходом
В чем суть?
Если класс имеет метод
Сравним код:
❌ Старый путь (ABC):
✅ Новый путь (Protocol):
Киллер-фича: Retroactive Abstraction (Ретроактивная абстракция)
Представьте, что вы используете стороннюю библиотеку (например,
С помощью
Нюансы для Middle+:
1. Runtime: По умолчанию
2. Свойства: В протоколе можно описывать не только методы, но и поля через
Используйте Протоколы, чтобы развязать зависимости между модулями. Это основа принципа Dependency Inversion в Python.
#python #typing #mypy #architecture #clean_code
📲 Мы в MAX
👉@BookPython
Protocol vs ABC: Утиная типизация на стероидах (Static Duck Typing)
В классическом ООП (Java, C#) и при использовании
abc.ABC в Python мы привыкли к Nominal Subtyping (Именная подтипизация). Чтобы объект считался Bird, он должен явно наследоваться от Bird.Это создает жесткую связность (coupling): ваша реализация должна знать об интерфейсе и импортировать его.
С приходом
typing.Protocol (Python 3.8+) мы получили Structural Subtyping (Структурная подтипизация).В чем суть?
Если класс имеет метод
quack(), то это Утка. Неважно, от чего он наследуется. Это и есть та самая «утиная типизация», но теперь поддерживаемая статическим анализатором (mypy, pyright, IDE).Сравним код:
❌ Старый путь (ABC):
from abc import ABC, abstractmethod
# 1. Жестко определяем интерфейс
class SenderABC(ABC):
@abstractmethod
def send(self, msg: str) -> None: pass
# 2. Обязаны наследоваться!
class EmailService(SenderABC):
def send(self, msg: str) -> None:
print(f"Email: {msg}")
def alert(sender: SenderABC):
sender.send("Alert!")
✅ Новый путь (Protocol):
from typing import Protocol
# 1. Описываем, "что мы ждем от объекта"
class SenderProto(Protocol):
def send(self, msg: str) -> None: ...
# 2. Реализация НИЧЕГО не знает про Protocol
# Никаких импортов и наследования!
class SmsService:
def send(self, msg: str) -> None:
print(f"SMS: {msg}")
# Mypy счастлив: SmsService имеет нужную структуру (метод send)
def alert(sender: SenderProto):
sender.send("Alert!")
alert(SmsService())
Киллер-фича: Retroactive Abstraction (Ретроактивная абстракция)
Представьте, что вы используете стороннюю библиотеку (например,
boto3 или клиент Redis). Вы не можете заставить их классы наследоваться от ваших ABC.С помощью
Protocol вы можете создать интерфейс для уже существующего чужого кода, не меняя его, и типизировать свои функции.Нюансы для Middle+:
1. Runtime: По умолчанию
isinstance(obj, MyProtocol) выбросит ошибку. Протоколы - это compile-time фича. Если нужна проверка в рантайме, декорируйте протокол @runtime_checkable.2. Свойства: В протоколе можно описывать не только методы, но и поля через
@property или просто аннотации типов.Используйте Протоколы, чтобы развязать зависимости между модулями. Это основа принципа Dependency Inversion в Python.
#python #typing #mypy #architecture #clean_code
📲 Мы в MAX
👉@BookPython
👍6