Python Academy
1.07K subscribers
23 photos
1 file
3 links
По всем вопросам и коммерческим предложениям писать @LadanovNick

Чат студентов Python Academy
https://t.me/pythonacademychat
Download Telegram
This media is not supported in the widget
VIEW IN TELEGRAM
👍7🦄4🔥3
@property: геттеры-сеттеры «по-питоновски»
@property превращает обычные методы в «прозрачные» атрибуты. Класс продолжает выглядеть как работа с полями, но внутри можно валидировать входные данные, кэшировать результаты или менять реализацию — без изменения внешнего интерфейса.

🔹 Что делает @property
1️⃣ Позволяет объявить геттер — метод, который вызывается при чтении атрибута.
2️⃣ Через @<имя>.setter добавляется сеттер для записи.
3️⃣ При необходимости можно добавить @<имя>.deleter для удаления атрибута.

🔸 Мини-пример

class Rectangle:
def __init__(self, w, h):
# вызовет setter
self.width = w
self.height = h

# ---- width ----
@property
def width(self):
return self._w

@width.setter
def width(self, v):
if v <= 0:
raise ValueError('> 0')
self._w = v

# ---- area (read-only) ----
@property
def area(self):
return self.width * self.height

Клиенту класса всё равно, что внутри есть логика: доступ идёт как к «обычному» полю.

Преимущества
🔹Инкапсуляция без шума. Проверки и вычисления скрыты, интерфейс лаконичный.
🔹Совместимость. Можно заменить публичное поле на property, не меняя вызовов в коде.
🔹Ленивая инициализация и кэш. Первый вызов рассчитывает значение, а setter/делитер управляют кэшем.

⚠️ Нюансы
🔹Логика в property должна оставаться быстрой; тяжёлые операции лучше вызывать явно.
🔹Сеттер обязателен, если атрибут нужно менять; без него объект станет «только для чтения».

✅ Когда использовать
🔹Необходимо валидировать входные данные при присвоении.
🔹Значение должно вычисляться на лету (площадь, кэшированное представление).
🔹Планируется изменить внутреннее хранилище данных, сохранив старый API.
👍4🔥2❤1
Python Academy
This media is not supported in the widget
VIEW IN TELEGRAM
❤5😢3🔥1
5 вопросов, которые почти наверняка зададут на Python-собеседовании 🧩
Ниже — короткая шпаргалка в стиле «вопрос → суть ответа → мини-пример». Сохраните пост, чтобы повторить перед интервью.

🔹 list vs tuple
Что спросят: «чем отличаются и зачем нужен кортеж?»

# list — изменяем
a = [1, 2]; a.append(3)

# tuple — неизменяем
b = (1, 2, 3)

# ✅ может быть ключом dict
hash(b)

list изменяем, поэтому занимает больше памяти и не может быть ключом словаря. tuple неизменяем, легче и хэшируем — hash((1,2,3)) работает.

🔹 *args и **kwargs
Что спросят: «Как работает распаковка и порядок параметров?»

def demo(a, *args, z=0, **kw):
print(a, args, z, kw)

demo(1, 2, 3, z=4, x=5)
# → 1 | (2, 3) | 4 | {'x': 5}

*args собирает все дополнительные позиционные аргументы в кортеж, а **kwargs — именованные в словарь.
В сигнатуре сначала идут обычные параметры, затем *args, потом именованные с значениями по умолчанию и в конце **kwargs.

🔹 yield vs return
Что спросят: «Чем генератор лучше обычной функции?»

def squares(n):
for i in range(n):
yield i * i

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

🔹 Почему += со строками медленный?
Что спросят: «Где здесь проблема неизменяемости?»

# неэффективно
s = ''
for w in words:
# каждая конкатенация создаёт новую строку
s += w

# эффективно
s = ''.join(words)

str неизменяем, поэтому при каждом += создаётся новая копия и суммарная сложность растёт до O(n²).
Накопите части в списке и соедините их одним ''.join() — это линейно по времени и памяти.

🔹 GIL и многопоточность
Что спросят: «Почему потоки не ускоряют CPU-bound код в CPython?»
GIL (Global Interpreter Lock) позволяет выполнять байт-код
только одному потоку CPython в любой момент времени.
Поэтому CPU-bound задачи не параллелятся потоками, а для параллели
используют multiprocessing, C-расширения или I/O-ориентированный asyncio.
🔥5👍4❤1
os.path.join и Pathlib: кросс-платформенные пути 🛣️

Когда скрипт должен работать и в Windows, и в Linux, ручная склейка "/" или "\\" быстро приводит к багам. Используйте встроенные инструменты, которые сами подставят нужный разделитель.

Почему это важно ❗️
Пути к файлам отличаются:
🔹Windows → C:\data\file.txt
🔹Linux/Mac → /home/user/file.txt
os.path.join() и pathlib.Path автоматически подставляют правильный разделитель и избегают двойных «//» или «\».

🔸 os.path.join — «старый надёжный»

import os

folder = 'data'
name = 'report.csv'

path = os.path.join('C:\\tmp', folder, name)
print(path)
# Windows: C:\tmp\data\report.csv

Берёт любое количество частей и склеивает безопасно.

🔹 Pathlib — «современный» и удобный

from pathlib import Path

# /home/<user> или C:\Users\<user>
base = Path.home()
path = base / 'data' / 'report.csv'
print(path)


⚠️ Подводные камни
🔹 Не склеивайте пути через + или f-строки — это ломается на другой ОС.
🔹 В os.path.join если одна часть начинается с ведущего «/» или диска (C:\), все предыдущие игнорируются.
🔹 В Path оператор / работает только между объектами Path и/или строками, но не с числами — приведите к str.

✅ Когда применять
🔹 Пишете скрипт, который будут запускать коллеги на разных ОС.
🔹 Строите сложные вложённые пути: base / 'raw' / date / 'file.parquet'.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍3🔥3
math.isclose: как правильно сравнивать числа с плавающей точкой 🔬
У бэкенда и аналитики часто «падают» проверки: суммы и агрегаты расходятся на сотые/тысячные. Причина — погрешность представления float (двоичная дробь не всегда точно хранит десятичное число). Поэтому выражение 0.1 + 0.2 == 0.3 может дать False.

1️⃣ Проблема на пальцах

0.1 + 0.2 == 0.3
# False
0.1 + 0.2
# 0.30000000000000004

Точное равенство работать не будет. Нужно сравнивать «с запасом».

2️⃣ Решение — math.isclose

import math

math.isclose(0.1 + 0.2, 0.3)
# True

Функция считает числа «достаточно близкими», если их разница меньше допуска:

abs(a - b) ≤ max(rel_tol * max(|a|, |b|), abs_tol)

По умолчанию: rel_tol=1e-9, abs_tol=0.0.

3️⃣ Абсолютный vs относительный допуск

🔹 abs_tol — фиксированное окно, важно возле нуля.

# Н, очень малая сила
force = 3e-10

math.isclose(force, 0.0, abs_tol=1e-8)
# True


🔹rel_tol — доля от величины, для крупных чисел.

math.isclose(1_000_000, 1_000_001, rel_tol=1e-6)
# True

На практике задавайте оба допуска.

4️⃣ Важно
🔹NaN никогда не «близок»; inf — только к inf того же знака.
🔹Допуски — часть бизнес-логики, фиксируйте их явно.

Итог: math.isclose делает проверки надёжными в мире погрешностей. Подберите разумные rel_tol и abs_tol — и тесты перестанут сыпаться из-за микроскопических расхождений.
❤5🔥4👍2
Чтение больших файлов построчно без «пожара в памяти» 📄

Большие файлы (сотни МБ и больше) роняют скрипты: наивные read()/readlines() пытаются загрузить всё в память.
Решение — читать файл построчно: память остаётся стабильной, обработка начинается сразу.

🔹 зачем
— Память не растёт с размером файла.
— Скрипт начинает работать сразу, а не после полной загрузки.

🔹 базовый приём

with open("big.txt", encoding="utf-8") as f:
for i, line in enumerate(f, 1):
line = line.rstrip("\n")
...

Файловый объект буферизует чтение; в цикл попадает одна строка за раз. enumerate(..., 1) даёт номер строки, rstrip("\n") при необходимости убирает перевод строки.

🔹 практика: считаем ошибки в логе

errors = 0
with open("access.log", encoding="utf-8") as f:
for line in f:
if "ERROR" in line:
errors += 1
print(errors)

Один проход по файлу, без хранения всего лога в памяти.

🔹 csv: правильно и экономно

import csv

with open("data.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
...

DictReader отдаёт строки как словари по именам колонок. newline="" нужен, чтобы модуль csv корректно обрабатывал переводы строк на всех ОС (особенно Windows).

⚠️ важно помнить
🔹 read() и readlines() загружают весь файл — избегайте на больших данных.
🔹 Одна очень длинная строка всё равно занимает много памяти: по возможности используйте разумные длины строк (CSV/JSONL).
🔹 Явно указывайте encoding (обычно utf-8), чтобы избежать ошибок декодирования.

✅ когда применять
🔹 Логи, телеметрия, большие CSV/JSONL, выгрузки из БД.
🔹 ETL-скрипты и бэкенд-воркеры, где важны стабильность и предсказуемая память.
👍7🔥3👏2❤1
Кеширование для начинающих: когда помогает redis 🧠

Медленные запросы к БД, внешним API и «тяжёлые» расчёты делают сервис медленным и дорогим. Redis хранит уже посчитанные ответы в памяти: следующее обращение получает результат на порядок быстрее, а база отдыхает.

🔹 когда это реально помогает
Повторяющиеся чтения одного и того же (карточка товара, список категорий), агрегаты и статистика, которые меняются не каждую минуту, публичные эндпоинты с большим трафиком, ответы от внешних API с лимитами.

🔹 как это работает (простыми словами)
1️⃣ Формируем ключ (например, product:{id} или report:{params_hash}).
2️⃣ Сначала смотрим в Redis: если значение нашлось — сразу отдаём его пользователю.
3️⃣ Если в Redis ключа нет, берём из источника (БД/API), кладём в Redis с временем жизни (TTL) и отдаём ответ.
Это называется cache-aside («ленивое» кеширование): кеш пополняется по мере запросов.

🔹 что класть в кеш, а что — нет
Кладём: небольшие JSON/строки (десятки–сотни КБ), «дорогие» результаты, справочники.
Не кладём: очень персональные и редко повторяющиеся ответы, секретные данные без шифрования/ограничений, гигантские объекты (для них — файловое хранилище).

🔹 ttl: свежесть данных — это бизнес-выбор
Выбирайте TTL так, как нужна «свежесть»:
минуты — курсы валют; часы — каталог; секунды–минуты — фича-флаги.
Добавьте небольшой джиттер (±10–20%), чтобы ключи не протухали одновременно и не создавали всп

🔹 как начать сегодня
1️⃣ Найдите самые медленные/частые запросы по метрикам.
2️⃣ Определите ключ и TTL исходя из ценности «свежести».
3️⃣ Включите cache-aside на одном-двух эндпоинтах.

📌 итог
Redis даёт максимум пользы там, где много одинаковых дорогих чтений. Начните с простого сценария cache-aside + осмысленный TTL — и получите быстрые ответы, разгруженную базу и более предсказуемую работу сервиса.
❤7👍4🔥3🤔1
Faker: быстрый генератор тестовых данных 🎭
Когда нужно наполнить тестовую БД/Excel «живыми» данными или показать демо — руками это долго. Faker генерирует правдоподобные имена, адреса, почты и даты за секунды, причём воспроизводимо.

🔹 установка

pip install Faker


🔹 пример

from faker import Faker
fake = Faker("ru_RU")

fake.name() # 'Илья Козлов'
fake.email() # 'maria.petrova@example.com'
fake.date() # '2025-06-14'

Локаль ru_RU даёт русские имена/адреса/форматы.

🔹 воспроизводимость (фиксируем «случайность»)

from faker import Faker
Faker.seed(42); fake = Faker("ru_RU")

fake.city() # 'Новокузнецк'

С тем же seed вы получите те же значения — удобно для тестов и ревью.

🔹 уникальные значения, когда нужны

fake.unique.email() # 'anna.smirnova@example.org'

unique помогает избежать дублей (например, при генерации логинов/почт).

🔹 мини-датасет в одну строку (без «простынь» кода)

rows = [{"id": i, "email": fake.unique.email()} for i in range(1, 4)]
# [{‘id': 1, 'email': '...'}, {'id': 2, 'email': '...'}, {'id': 3, 'email': '...'}]

Дальше можно быстро выгрузить в CSV/БД любым удобным способом.

🔹 что генерировать чаще всего
1️⃣ пользователи: имя, e-mail, телефон, город, дата регистрации;
2️⃣ заказы/платежи: сумма, валюта, статус, дата-время;
3️⃣ web-данные: домены, URL, user-agent.

📌 итог
Faker экономит часы подготовки данных и делает сценарии воспроизводимыми. Укажите локаль, при необходимости зафиксируйте seed — и за пару строк получите реалистичный набор под вашу задачу.
👍12❤7⚡4💘1
Генераторы вместо списков: экономим память 📉
Если нужно просто «пройтись по данным и посчитать», создавать целый список — лишняя трата памяти и времени. Генераторы отдают элементы по одному «на лету», поэтому код стартует быстрее и не раздувает RAM.

Что такое генераторы
Это «ленивые» источники данных. Они не хранят всю последовательность, а вычисляют следующий элемент по запросу.
Есть два простых способа получить генератор:
🔹Генераторное выражение: круглые скобки ( … )
🔹Функция с yield: возвращает элементы по одному

Зачем это нужно
🔹Экономия памяти: не создаём промежуточные списки.
🔹Быстрый старт: обработка начинается сразу, даже для крупных диапазонов/файлов.
🔹Удобные «конвейеры»: фильтры и преобразования без временных структур.

Мини-пример

sum([x*x for x in range(10_000_000)])
# ❌ создаёт огромный список в памяти

sum(x*x for x in range(10_000_000))
# ✅ генератор: элементы не хранятся → меньше памяти



def squares(n):
for i in range(n):
yield i*i

list(squares(5)) # → [0, 1, 4, 9, 16]

Памятка:
🔹квадратные скобки [...] → список,
🔹круглые (...) → генератор.

Когда использовать
🔹Один проход по данным: sum/max/any, простой цикл.
🔹Потоки и большие объёмы: чтение файла построчно, «страницы» из API.
🔹Пайплайн «filter → map → reduce» без промежуточных списков.

Когда список уместнее
🔹Нужен доступ по индексу или случайное обращение.
🔹Нужны многократные проходы по тем же данным.
🔹Важно знать len() заранее, хранить срезы или кэшировать результат.

📌 Итог
Берите генераторы, когда результат не нужно хранить и вы проходите данные один раз: так код потребляет меньше памяти и работает стабильнее. Списки оставляйте для задач с индексами, повторным обходом и известной длиной.
👍10❤4🔥2💘1
🧩 Что выведет этот код?

Сегодня разберем одну из самых коварных особенностей Python, связанную с аргументами функций.

Посмотрите на код ниже:
def add_item(item, basket=[]):
basket.append(item)
return basket

print(add_item("яблоко"))
print(add_item("банан"))

Каким будет вывод второй строки?
1️⃣ ['банан']
2️⃣ ['яблоко', 'банан']
3️⃣ Error
4️⃣ None

Правильный ответ и объяснение:
Ответ:
2️⃣ ['яблоко', 'банан']

Почему так происходит?
В Python значения по умолчанию для аргументов вычисляются всего один раз — в момент определения функции (когда интерпретатор видит строку def ...), а не при каждом её вызове.

Если в качестве значения по умолчанию используется изменяемый объект (mutable), такой как список [] или словарь {}, то он становится «общим» для всех последующих вызовов функции.

🔹При первом вызове в «общую» корзину попало «яблоко».
🔹При втором вызове функция обратилась к тому же самому объекту списка и добавила туда «банан».

Как этого избежать?
Всегда используйте None в качестве значения по умолчанию:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket

Теперь при каждом вызове без аргумента будет создаваться новый пустой список.
👍17❤4🔥4💘2
Python-Academy-Interview-Questions-ru.pdf
1.3 MB
Топ-вопросы с собеседований по Python + ответы 📝
Мы собрали самые популярные вопросы и варианты ответов — читайте по ссылке:
https://python-academy.org/ru/interview-questions

Чтобы было удобнее, подготовили офлайн-версию (PDF). Сохраните и используйте, чтобы быстро освежить знания перед важным собесом.
📥 офлайн-версия — в файле к этому посту

удачи на собеседованиях! 💪
🔥22❤5👍2
✂️ Иллюзия .strip(): почему 'mac.com'.strip('.com') вернёт 'a'

Хочешь очистить данные перед работой и отрезать домен с помощью .strip('.com')? Осторожно: этот метод часто молча «откусывает» часть нужной строки.

⚠️ В чём подвох?
Многие думают, что .strip() удаляет переданную подстроку целиком. На деле он воспринимает аргумент как набор независимых символов: 'c', 'o', 'm', '.'.

Метод агрессивно отщипывает эти буквы с обоих концов строки до тех пор, пока не встретит чужой символ.

domain = 'mac.com'
print(domain.strip('.com'))
# Вывод: 'a'


Символы m, c и точка были удалены не только из окончания, но и из самого слова «mac»!

🚀 Безопасное решение
Для удаления точных окончаний и приставок в Python 3.9 добавили специальные методы. Они ищут строго целую подстроку:

domain = 'mac.com'
print(domain.removesuffix('.com')) # 'mac'
print('anti-virus'.removeprefix('anti-')) # 'virus'


💡 Оставь .strip() исключительно для зачистки невидимых пробелов и переносов строк, а для суффиксов бери .removesuffix().
❤6👍6🔥2
🪤 Загадка с собеса: ловушка с lambda в цикле

Создаешь список функций, ожидаешь получить 0, 1 и 2, а Python возвращает одни двойки. Это классический баг, на котором часто ошибаются новички.

⚠️ Как выглядит проблема
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]


⚙️ Почему так происходит
Вся суть в механизме позднего связывания (late binding).
🔹 Функция lambda не сохраняет значение i в момент своего создания.
🔹 Она запоминает лишь ссылку на саму переменную в окружающем коде.
🔹 Когда цикл завершается, переменная i останавливается на значении 2. При вызове все три функции обращаются к этой финальной двойке.

🚀 Как починить
Нужно заставить Python зафиксировать значение сразу. Самый элегантный способ — передать его через аргумент по умолчанию:
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]

🔸 Аргументы по умолчанию вычисляются ровно в момент создания функции, поэтому каждая lambda получает свою личную независимую копию числа.

💡 Значения по умолчанию фиксируются при создании функции, а код внутри нее выполняется только при вызове.
👍5
🎭 Загадка с собеса: почему True и 1 склеиваются в словаре

Попробуй создать словарь с ключами True и 1. Интуитивно мы ждём два разных элемента, но Python тихо оставит только один.

⚠️ Что произойдёт на практике
my_dict = {True: 'да', 1: 'нет'}
print(my_dict)
# Вывод: {True: 'нет'}


⚙️ Почему так работает
🔹 В Python логический тип bool — это прямой подкласс целых чисел (int).
🔹 Математически True абсолютно равно 1, а False равно 0.
🔹 Словари ищут элементы по хэшу. Так как hash(True) == hash(1), Python считает их одним и тем же ключом.

📉 Как происходит перезапись
🔸 Сначала создаётся пара, где ключ — True, а значение — 'да'.
🔸 Затем Python доходит до ключа 1. Он видит совпадение и решает, что такой элемент «уже есть».
🔸 Сам ключ не меняется (в словаре остаётся True), но его значение молча обновляется на 'нет'.

💡 Будь осторожен при группировке данных: ключи разных типов могут внезапно оказаться близнецами под капотом.
👍4🔥4❤1
🏦 Магия round(): почему 2.5 превращается в 2

Забудь школьные правила математики. Если в Python написать round(2.5), ответом будет 2, а не 3. Эта неочевидная деталь часто становится ловушкой для новичков.

⚙️ Как это работает
Python использует «банковское округление» (к ближайшему чётному числу), если дробная часть равна ровно половине.
🔹 round(1.5) # Вернёт 2
🔹 round(2.5) # Тоже вернёт 2
🔹 round(3.5) # Вернёт 4

📉 Зачем ломать математику?
Это спасает аналитиков от накопления статистической ошибки.
🔸 При школьном округлении (всегда вверх) итоговые суммы в больших датасетах постепенно завышаются.
🔸 Банковское правило идеально балансирует погрешность: половина «половинок» уходит вниз, а половина — вверх. Среднее значение остаётся честным.

🚀 Как вернуть классику
Если тебе нужно строгое математическое округление, встроенная функция не подойдёт. Используй модуль decimal:

from decimal import Decimal, ROUND_HALF_UP

num = Decimal('2.5')
print(num.quantize(Decimal('1'), rounding=ROUND_HALF_UP)) # Вернёт 3


💡 Для аналитики и работы с данными встроенный round() идеален, а для точных финансовых расчётов всегда бери decimal.
👍4❤3👌2