Твой Docker-образ весит 1.5 ГБ, а сборка каждый раз тянет все зависимости с нуля? Пора это исправить. Правильный Dockerfile экономит время, трафик и нервы.
Главное правило: никогда не использовать
python:latest. Он огромный.# ❌ Плохо: 1 ГБ+
FROM python:3.11
# ✅ Хорошо: ~200 МБ
FROM python:3.11-slim
# ✅ Для фанатов минимализма: ~100 МБ (но могут быть проблемы с компиляцией)
FROM python:3.11-alpine
slim — урезанный Debian, alpine — минимальный Linux на musl libc. Для большинства проектов хватает slim.Зачем тащить в продакшен компиляторы и исходники?
# Этап 1: сборка
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
# Создаём один слой со всеми зависимостями
RUN pip install --user --no-warn-script-location -r requirements.txt
# Этап 2: финальный образ
FROM python:3.11-slim
WORKDIR /app
# Копируем только установленные пакеты из builder
COPY --from=builder /root/.local /root/.local
# Копируем код
COPY . .
# Важно: добавляем путь к пакетам
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "main.py"]
builder.🧪 Оптимизация кэширования зависимостей: почему Docker снова качает pandas?
Docker кэширует слои. Если требования не меняются — не переустанавливай.
FROM python:3.11-slim
WORKDIR /app
# 1. Копируем ТОЛЬКО requirements.txt
COPY requirements.txt . # Этот слой кэшируется отдельно!
# 2. Устанавливаем зависимости
RUN pip install --no-cache-dir -r requirements.txt
# 3. Копируем весь остальной код
COPY . . # Этот слой пересобирается при любом изменении кода
CMD ["python", "main.py"]
RUN pip install... до тех пор, пока requirements.txt не изменится. Изменил одну строчку в коде — зависимости не переустанавливаются.🔍 Убираем мусор: один RUN, меньше слоев
Каждая команда в Dockerfile создаёт слой. Объединяй!
# ❌ Плохо: 4 слоя, остаётся мусор
RUN apt-get update
RUN apt-get install -y gcc
RUN pip install pandas
RUN apt-get remove -y gcc
# ✅ Хорошо: 1 слой, чисто
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc && \
pip install pandas && \
apt-get remove -y gcc && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* # Чистим кэш apt
&&. Флаг --no-install-recommends ставит только необходимые пакеты. rm -rf /var/lib/apt/lists/* удаляет кэш пакетов.Собираем FastAPI-приложение с зависимостями, включая компилируемые.
# Этап сборки
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-warn-script-location -r requirements.txt
# Этап рантайма
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 # Отключаем .pyc файлы
# Не запускай от root!
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
--no-warn-script-location чтобы не было предупреждений. PYTHONDONTWRITEBYTECODE=1 ускоряет запуск и экономит место. Запуск от не-root пользователя повышает безопасность.slim-образы, многоступенчатую сборку и правильное кэширование зависимостей. Объединяй команды в один RUN и убирай за собой. Итоговый образ должен весить в 5-10 раз меньше первоначального.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤3
with open()`. Пишем свои для транзакций и таймеровКонтекстные менеджеры в Python — это магия
with, которая управляет жизненным циклом ресурсов. Все знают про файлы, но настоящая сила открывается, когда пишешь свои менеджеры для транзакций, замеров времени и автоматического отката.Любой контекстный менеджер — это класс с методами
__enter__ и __exit__.class MyManager:
def __enter__(self):
print("Входим в контекст")
return self # Этот объект попадёт в as
def __exit__(self, exc_type, exc_val, exc_tb):
print("Выходим из контекста")
return False # Если True — исключение будет подавлено
with MyManager() as mgr:
print("Внутри контекста")
__enter__ выполняется при входе в with, __exit__ — всегда при выходе, даже если было исключение.Представь, что нужно гарантировать откат изменений при ошибке:
import sqlite3
class Transaction:
def __init__(self, conn):
self.conn = conn
def __enter__(self):
self.conn.execute("BEGIN")
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
self.conn.commit()
print("✅ Коммит")
else:
self.conn.rollback()
print("❌ Откат из-за ошибки:", exc_val)
return False # Пробрасываем исключение дальше
# Использование
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")
try:
with Transaction(conn) as tx:
tx.execute("INSERT INTO users VALUES (1, 'Alice')")
raise ValueError("Искусственная ошибка!")
except:
pass
print(conn.execute("SELECT * FROM users").fetchall()) # []
with вылетает исключение — __exit__ делает rollback. Нет ошибки — commit.Идеально для быстрого профайлинга кусков кода:
import time
from contextlib import contextmanager
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, *args):
self.end = time.perf_counter()
self.elapsed = self.end - self.start
print(f"⏱️ Время выполнения: {self.elapsed:.4f} сек")
with Timer():
data = [x**2 for x in range(1_000_000)]
# ⏱️ Время выполнения: 0.0453 сек
perf_counter() даёт высокоточное время. Менеджер можно расширить, чтобы сохранять результаты в лог.Через
__init__ передаём настройки, а в __enter__ — применяем:class LogToFile:
def __init__(self, filename, mode="a"):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
print(f"📂 Файл {self.filename} открыт")
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
print(f"📂 Файл {self.filename} закрыт")
with LogToFile("app.log") as log:
log.write("Старт сессии\n")
Писать класс каждый раз долго. Есть синтаксический сахар:
from contextlib import contextmanager
@contextmanager
def temporary_file(content):
import tempfile
tmp = tempfile.NamedTemporaryFile(delete=False, mode="w")
try:
tmp.write(content)
tmp.close()
yield tmp.name # Внутри with доступно это значение
finally:
import os
os.unlink(tmp.name) # Удаляем файл после выхода
with temporary_file("секретные данные") as fpath:
print(f"Файл создан: {fpath}")
# Файл уже удалён
yield — это __enter__, всё после — __exit__. Идеально для одноразовых менеджеров.Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤5
assert_typeЗнаешь боль, когда Pydantic-модель прошла валидацию в рантайме, но mypy кричит на несоответствие типов? Решение есть — учим Pydantic и статические анализаторы понимать друг друга.
from pydantic import BaseModel, field_validator
from typing import assert_type
class User(BaseModel):
id: int
name: str
@field_validator('id')
@classmethod
def validate_id(cls, v: str) -> int: # Принимает str, возвращает int
return int(v)
# Данные проходят валидацию
user = User.model_validate({"id": "123", "name": "Ivan"})
print(user.id) # 123
# Но mypy не знает, что id точно int после валидации
assert_type(user.id, int) # ✅ mypy теперь понимает
assert_type() не выполняет проверку в рантайме — это подсказка для mypy. Если типы не совпадут, анализатор ругнётся ещё до запуска.Когда стандартных валидаторов недостаточно, создаём функцию с
TypeGuard:from typing import TypeGuard
from pydantic import BaseModel
class Item(BaseModel):
value: str
def is_valid_item(data: dict) -> TypeGuard[Item]:
"""Проверяет, можно ли создать Item из data, и сообщает mypy о типе."""
try:
Item.model_validate(data)
return True
except:
return False
# Пример использования
raw_data = {"value": "test"}
if is_valid_item(raw_data):
# Здесь mypy точно знает, что raw_data подходит для Item
item = Item.model_validate(raw_data) # ✅ Без ошибок типизации
print(item.value)
TypeGuard — это явное обещание mypy: "Если функция вернула True, то тип такой-то". Работает в связке с isinstance.Когда метод может возвращать разные модели в зависимости от входных данных:
from typing import overload, Union
from pydantic import BaseModel
class Success(BaseModel):
data: dict
status: str = "ok"
class Error(BaseModel):
error: str
status: str = "fail"
@overload
def create_response(data: dict) -> Success: ...
@overload
def create_response(data: None, error: str) -> Error: ...
def create_response(data: Union[dict, None] = None, error: str = "") -> Union[Success, Error]:
if data is not None:
return Success(data=data)
return Error(error=error)
# Теперь mypy понимает, какая модель вернётся
result1 = create_response({"user": "Ivan"})
assert_type(result1, Success) # ✅
result2 = create_response(error="Not found")
assert_type(result2, Error) # ✅
@overload декларативно описывает все возможные сигнатуры функции. Mypy использует это для точного вывода типов.Заставляем Pydantic быть строгим, а mypy — проверять:
from pydantic import BaseModel, ConfigDict
from typing import Literal
class StrictModel(BaseModel):
model_config = ConfigDict(strict=True)
id: int
status: Literal["active", "pending"] # Только два значения
# Эта строка вызовет ошибку валидации (строка вместо int)
# user = StrictModel.model_validate({"id": "100", "status": "active"}) # ❌ ValidationError
# А mypy поймает ошибку типа ещё раньше
from typing import TYPE_CHECKING
if TYPE_CHECKING:
data: dict[str, str] = {"id": "100", "status": "active"}
# mypy: error: Argument 1 to "model_validate" of "BaseModel"
# has incompatible type "dict[str, str]"; expected "dict[str, Any]" ❌
strict=True в конфиге, чтобы Pydantic не пытался преобразовывать типы. Mypy увидит несоответствие в TYPE_CHECKING блоке.assert_type и TypeGuard — это мосты между Pydantic и mypy. Первый говорит «поверь мне, здесь такой тип», второй — «я проверил данные и гарантирую тип». Используй их, чтобы ошибки типов ловились до запуска, а не в проде.Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🔥1
🤐 Склеиваем списки с помощью `zip()`
Частая задача: есть два связанных списка (например, имена и зарплаты), и нужно пройтись по ним одновременно.
⛔️ Плохой способ (через индекс):
🖥 Pythonic способ:
Функция
💡 Важно:
#python #bestpractices #codingtips
Частая задача: есть два связанных списка (например, имена и зарплаты), и нужно пройтись по ним одновременно.
⛔️ Плохой способ (через индекс):
names = ['Anna', 'Oleg']
salaries = [100, 200]
for i in range(len(names)):
print(names[i], salaries[i])
Функция
zip берет по одному элементу из каждого списка и отдает их парами.
names = ['Anna', 'Oleg', 'Max']
salaries = [100, 200, 300]
for name, salary in zip(names, salaries):
print(f"{name} получает {salary}k")
💡 Важно:
zip остановится, как только закончится самый короткий список. Если нужно сохранить все данные (заполнив пустоты None`), используйте `zip_longest из модуля itertools.#python #bestpractices #codingtips
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤2
Ты делаешь 1000 асинхронных запросов к API, а сервер отвечает 429-й ошибкой? Без ограничения одновременных запросов ты рискуешь получить бан или перегрузить удаленный сервис. Semaphore - это страж, который пропускает только N корутин одновременно.
import asyncio
import aiohttp
async def fetch(url, session, semaphore):
async with semaphore: # Ждем, пока не освободится слот
async with session.get(url) as response:
return await response.text()
async def main():
semaphore = asyncio.Semaphore(3) # Не более 3 одновременных запросов
urls = ['https://api.example.com/item/{}'.format(i) for i in range(20)]
async with aiohttp.ClientSession() as session:
tasks = [fetch(url, session, semaphore) for url in urls]
results = await asyncio.gather(*tasks)
print(f'Получено {len(results)} ответов')
asyncio.run(main())
async with semaphore блокирует выполнение, если все слоты заняты. Как только один запрос завершается, слот освобождается и пускает следующий.Иногда нужно ограничить не всю функцию целиком, а только критическую секцию:
async def bounded_fetch(url, session, semaphore):
# Тут можно делать предварительную обработку без семафора
async with semaphore: # только сам запрос ограничиваем
async with session.get(url) as response:
return await response.json()
Допустим, у тебя два API: одно разрешает 5 RPS, другое - 10. Создай отдельные семафоры:
class RateLimiter:
def __init__(self):
self.sem_a = asyncio.Semaphore(5)
self.sem_b = asyncio.Semaphore(10)
async def call_api_a(self, session):
async with self.sem_a:
return await session.get('https://api.a.com/')
async def call_api_b(self, session):
async with self.sem_b:
return await session.get('https://api.b.com/')
В реальном коде нужно быть готовым к ошибкам. Комбинируем семафор с повторными попытками:
import async_timeout
async def fetch_with_retry(url, session, semaphore, retries=3):
for attempt in range(retries):
try:
async with semaphore:
async with async_timeout.timeout(5): # таймаут 5 секунд
async with session.get(url) as response:
return await response.text()
except (asyncio.TimeoutError, aiohttp.ClientError) as e:
print(f'Попытка {attempt+1} для {url} не удалась: {e}')
if attempt == retries - 1:
raise
await asyncio.sleep(2 ** attempt) # экспоненциальная задержка
async with semaphore).Допустим, парсим сайт, который разрешает 2 запроса в секунду. Используем семафор + задержку между стартами:
async def crawl(urls):
sem = asyncio.Semaphore(2)
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
# запускаем корутину, но не ждем её сейчас
task = asyncio.create_task(bounded_fetch(url, session, sem))
tasks.append(task)
# делаем паузу между стартами, чтобы не превысить лимит даже на старте
await asyncio.sleep(0.5) # 2 запроса в секунду -> пауза 0.5с
results = await asyncio.gather(*tasks)
Semaphore - это твой щит от троттлинга. Оборачивай каждый поход в сеть в async with semaphore и подбирай лимиты под API. Для разных эндпоинтов - разные семафоры. И никогда не забывай про таймауты.Полезен совет по защите от бана? Жмите 🔥
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6❤2👍2
Ты когда-нибудь писал класс, который внутри создаёт объекты, а потом не мог его протестировать? Или менял базу данных, а переписывать приходилось полпроекта? Dependency Injection (DI) лечит эту боль. Всё, что нужно — передавать зависимости через конструктор.
Плохо:
class UserService:
def __init__(self):
self.db = PostgreSQLDatabase() # Сам создаёт зависимость
def get_user(self, user_id):
return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
Хорошо:
class UserService:
def __init__(self, database): # Получает готовую зависимость
self.db = database
def get_user(self, user_id):
return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
# Теперь можно подставить любую БД с методом query
service = UserService(PostgreSQLDatabase())
class MockDB:
def query(self, sql):
return {"id": 1, "name": "Тест"}
def test_get_user():
service = UserService(MockDB())
user = service.get_user(1)
assert user["name"] == "Тест"
Когда зависимостей много, таскать их вручную надоедает. Сделаем простой контейнер:
class Container:
def __init__(self):
self._services = {}
def register(self, name, builder):
self._services[name] = builder
def resolve(self, name):
builder = self._services.get(name)
if not builder:
raise ValueError(f"Service {name} not found")
return builder(self)
# Настраиваем контейнер
container = Container()
container.register('db', lambda c: PostgreSQLDatabase())
container.register('user_service', lambda c: UserService(c.resolve('db')))
# Получаем готовый сервис со всеми зависимостями
user_service = container.resolve('user_service')
Иногда объекту нужны не только сервисы, но и параметры (например, строка подключения). Контейнер легко расширить:
container.register('db', lambda c: PostgreSQLDatabase(c.resolve('config').db_url))
container.register('config', lambda c: Config.from_env())class UserRepo:
def __init__(self, db):
self.db = db
def get(self, user_id):
return self.db.query(...)
class Logger:
def log(self, msg):
print(f"[LOG] {msg}")
class UserService:
def __init__(self, repo, logger):
self.repo = repo
self.logger = logger
def get_user(self, user_id):
self.logger.log(f"Fetching user {user_id}")
return self.repo.get(user_id)
# Собираем через контейнер
container.register('db', lambda c: PostgreSQLDatabase())
container.register('repo', lambda c: UserRepo(c.resolve('db')))
container.register('logger', lambda c: Logger())
container.register('user_service', lambda c: UserService(c.resolve('repo'), c.resolve('logger')))
service = container.resolve('user_service')
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4❤2
В Python два способа описать контракт: абстрактные базовые классы (ABC) и протоколы. Оба говорят "у объекта должен быть метод X", но работают по-разному.
from abc import ABC, abstractmethod
class Drawer(ABC):
@abstractmethod
def draw(self):
pass
class Circle(Drawer):
def draw(self):
return "🔴"
def show(obj: Drawer):
print(obj.draw())
show(Circle()) # ✅
show("строка") # ❌ mypy ругнётся
Drawer. Жёстко, зато явно.
from typing import Protocol
class Drawer(Protocol):
def draw(self): ...
class Circle:
def draw(self):
return "🔴"
class Square:
def draw(self):
return "⬛️"
def show(obj: Drawer):
print(obj.draw())
show(Circle()) # 🔴
show(Square()) # ⬛️
draw - объект подходит.
# ABC - когда нужна общая логика
class Logger(ABC):
def log(self, msg):
print(f"[{self.prefix()}] {msg}")
@abstractmethod
def prefix(self): ...
# Protocol - когда нужна гибкость
class Fetcher(Protocol):
async def fetch(self, url): ...
# Любой класс с fetch подойдёт - aiohttp, httpx, мок для тестов
from typing import Protocol
class Handler(Protocol):
def handle(self, event: dict): ...
class UserHandler:
def handle(self, event):
print(f"Пользователь: {event}")
class OrderHandler:
def handle(self, event):
print(f"Заказ: {event}")
def process(events, handler: Handler):
for event in events:
handler.handle(event)
process([{"id": 1}], UserHandler())
process([{"order": 123}], OrderHandler()) # оба работают
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawer(Protocol):
def draw(self): ...
print(isinstance(Circle(), Drawer)) # True
isinstance работает, но проверяет только наличие метода, не типы аргументов.Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
Все пишут циклы с
range(), но на больших данных они тормозят как бабушкин компьютер. Для числодробилки есть два монстра: numpy (векторизация) и numba (JIT-компиляция). Разбираемся, как ускориться в десятки раз и не сойти с ума.import time
def sum_squares(n):
total = 0
for i in range(n):
total += i * i
return total
start = time.time()
result = sum_squares(10_000_000)
print(f"Python: {time.time() - start:.2f} сек")
# Python: 0.85 сек (примерно)
Numpy выполняет операции над целыми массивами на C, без питоновских циклов.
import numpy as np
def np_sum_squares(n):
arr = np.arange(n) # массив [0, 1, 2, ..., n-1]
return np.sum(arr * arr) # всё сразу
start = time.time()
result = np_sum_squares(10_000_000)
print(f"Numpy: {time.time() - start:.2f} сек")
# Numpy: 0.05 сек (в 17 раз быстрее)
А если логика не векторизуется (условия, рекурсия)? Numba компилирует питоновский код в машинный прямо перед выполнением.
from numba import njit
@njit
def nb_sum_squares(n):
total = 0
for i in range(n):
total += i * i
return total
start = time.time()
result = nb_sum_squares(10_000_000)
print(f"Numba: {time.time() - start:.2f} сек")
# Numba: 0.02 сек (в 42 раза быстрее)
@njit — и код полетел. Магия.# Numpy — массовые операции
arr = np.array([1, 2, 3, 4, 5])
print(arr.mean()) # 3.0
print(arr * 2) # [2 4 6 8 10]
# Numba — сложная логика с условиями
@njit
def conditional_sum(arr):
total = 0
for x in arr:
if x > 0:
total += x
return total
📦 Практический пример: обработка изображения
import numpy as np
from numba import njit
img = np.random.randint(0, 255, (1000, 1000), dtype=np.uint8)
@njit
def filter_nb(img):
h, w = img.shape
result = np.zeros((h, w), dtype=np.uint8)
for i in range(1, h-1):
for j in range(1, w-1):
val = (int(img[i-1][j]) + img[i+1][j] + img[i][j-1] + img[i][j+1]) // 4
result[i][j] = val
return result
# filter_nb(img) ~0.1 сек (в 50 раз быстрее чистого Python)
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3
Когда у тебя есть объект, который может находиться в разных состояниях и менять поведение в зависимости от них (заказ, документ, чат-бот), без машины состояний код превращается в спагетти из if-else. Python даёт два удобных способа построить чистый конечный автомат: классические классы с состояниями и новый
match/case.class Order:
def __init__(self):
self.state = "new"
def next(self):
if self.state == "new":
self.state = "paid"
print("Оплачено")
elif self.state == "paid":
self.state = "shipped"
print("Отправлено")
elif self.state == "shipped":
self.state = "delivered"
print("Доставлено")
else:
print("Уже доставлено")
from abc import ABC, abstractmethod
class OrderState(ABC):
@abstractmethod
def next(self, order):
pass
class NewState(OrderState):
def next(self, order):
print("Оплачено")
order.state = PaidState()
class PaidState(OrderState):
def next(self, order):
print("Отправлено")
order.state = ShippedState()
class ShippedState(OrderState):
def next(self, order):
print("Доставлено")
order.state = DeliveredState()
class DeliveredState(OrderState):
def next(self, order):
print("Заказ уже доставлен")
class Order:
def __init__(self):
self.state = NewState()
def next(self):
self.state.next(self)
class Order:
def __init__(self):
self.state = "new"
def next(self):
match self.state:
case "new":
self.state = "paid"
print("Оплачено")
case "paid":
self.state = "shipped"
print("Отправлено")
case "shipped":
self.state = "delivered"
print("Доставлено")
case "delivered":
print("Уже доставлено")
case _:
print("Неизвестное состояние")
Допустим, нужно не только менять статус, но и выполнять разные действия:
from datetime import datetime
class Order:
def __init__(self):
self.state = "new"
self.history = []
def next(self):
match self.state:
case "new":
self.state = "paid"
self.history.append(("paid", datetime.now()))
print("Оплата прошла успешно")
case "paid":
self.state = "shipped"
self.history.append(("shipped", datetime.now()))
print("Заказ отправлен")
case "shipped":
self.state = "delivered"
self.history.append(("delivered", datetime.now()))
print("Заказ доставлен клиенту")
case _:
print("Нельзя перейти из текущего состояния")
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍3
Все пишут
for x in list, но мало кто знает, что за этим скрывается протокол итератора. Разберём магию: как объекты становятся итерируемыми, как написать свой итератор и почему yield экономит память лучше любого оптимизатора.forКогда ты пишешь:
items = [1, 2, 3]
for item in items:
print(item)
Python делает примерно это:
iterator = iter(items) # получаем итератор
while True:
try:
item = next(iterator) # берём следующий элемент
print(item)
except StopIteration: # когда элементы кончились
break
for, если у него есть методы __iter__() или __getitem__(). Но настоящая магия — в протоколе итератора.Чтобы объект стал итерируемым, ему нужны:
-
__iter__() — возвращает итератор (обычно self)-
__next__() — возвращает следующий элемент или бросает StopIterationclass Counter:
def __init__(self, limit):
self.limit = limit
self.current = 0
def __iter__(self):
return self # возвращаем себя как итератор
def __next__(self):
if self.current >= self.limit:
raise StopIteration # сигнал, что всё
self.current += 1
return self.current - 1
for x in Counter(5):
print(x) # 0 1 2 3 4
for понимает, когда остановиться. Всё честно, без магии.Хочешь бесконечный счётчик? Легко:
class InfiniteCounter:
def __init__(self, start=0):
self.current = start
def __iter__(self):
return self
def __next__(self):
self.current += 1
return self.current - 1
counter = InfiniteCounter()
for i in counter:
print(i)
if i > 5:
break
# 0 1 2 3 4 5
break, иначе уйдёшь в космос.Согласись, тащить целый класс ради одного итератора — жирно. Тут на сцену выходят генераторы.
def counter_gen(limit):
for i in range(limit):
yield i
for x in counter_gen(5):
print(x) # 0 1 2 3 4
yield превращает функцию в генератор. При каждом вызове next() она выполняется до следующего yield и замораживает состояние.def infinite_gen(start=0):
while True:
yield start
start += 1
gen = infinite_gen()
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
import sys
# Список
numbers_list = [x for x in range(1_000_000)]
print(sys.getsizeof(numbers_list)) # ~8 MB
# Генератор
numbers_gen = (x for x in range(1_000_000))
print(sys.getsizeof(numbers_gen)) # ~112 bytes
def read_large_file(file_path):
with open(file_path, 'r') as f:
for line in f:
yield line.strip()
# Используем
for line in read_large_file("huge_log.txt"):
if "ERROR" in line:
print(line)
# Список (жадный)
squares_list = [x*x for x in range(1000)]
# Генератор (ленивый)
squares_gen = (x*x for x in range(1000))
print(sum(squares_gen)) # можно посчитать сумму без создания списка
for - это просто синтаксический сахар над iter() и next() с ловлей StopIteration. Генераторы с yield позволяют писать ленивые последовательности без классов.Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍3🔥2🤷♀1
Media is too big
VIEW IN TELEGRAM
В этом видео автор приступает к одной из важнейших тем в Python — объектно-ориентированному программированию. Вы узнаете теоретическую основу ООП и научитесь на практике создавать свои первые классы и объекты в языке Питон, закладывая фундамент для написания сложных и структурированных программ.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4🔥1
Media is too big
VIEW IN TELEGRAM
В этом видео автор раскрывает три основные концепции объектно-ориентированного программирования в Python. Вы научитесь применять наследование для повторного использования кода, реализовывать инкапсуляцию для защиты данных и использовать полиморфизм для создания гибких интерфейсов.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤🔥4❤3🔥1
Базовый, но супер-важный навык написания надежного кода.
🦺 Хватит ловить `KeyError` в словарях!
Представьте, вы получаете JSON от API или читаете конфиг. Вы пытаетесь достать значение по ключу, а его там нет. Программа падает с ошибкой
KeyError.Вместо того чтобы писать проверки
if key in data:, используйте метод `.get()`. Он безопасно вернет
None, если ключа нет. А еще лучше — ему можно передать значение по умолчанию вторым аргументом.
user_config = {"username": "admin", "language": "ru"}
# Опасно:
# theme = user_config["theme"] # ОШИБКА!
# Безопасно:
theme = user_config.get("theme", "dark")
# Если ключа "theme" нет, вернется "dark"
print(theme) # dark
Это правило хорошего тона в Python. Используйте
.get() везде, где не уверены на 100% в структуре словаря! 🛡#python #optimization #cleancode
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1