sorted() vs .sort(): когда применять тот или иной вариант ↕️
Python предоставляет два способа упорядочить данные: встроенную функцию sorted() и метод списка .sort(). Ниже — краткое сравнение и рекомендации по использованию.
Основные различия
1️⃣ sorted(iterable)
• Не изменяет исходный объект
• Возвращает новый отсортированный объект
• Принимает любой итерируемый (списки, строки, кортежи, генераторы)
2️⃣ list.sort()
• Сортирует список in-place (данные изменяются)
• Возвращает None
• Работает только со списками и экономит память, поскольку не создаёт копию
Пример
Общие параметры
🔹 key= — функция, определяющая порядок сортировки (напр. sorted(words, key=len))
🔹 reverse=True — сортировка по убыванию
🔹 Обе операции стабильные: элементы с одинаковым key сохраняют первоначальный порядок
Критерии выбора
🔹Требуется сохранить исходные данные без изменений → sorted()
🔹Нужно упорядочить не-list (строку, tuple, генератор) → sorted()
🔹Работа ведётся с крупным списком, важна экономия памяти → .sort()
🔹Планируется многократная пересортировка одного и того же списка → .sort()
🔹Сортировка встроена в цепочку выражений или генераторов → sorted()
⚠️ Особенности
🔹Метод .sort() всегда возвращает None; использование его результата в присваивании вызовет ошибку.
🔹При применении sorted() к очень большим коллекциям объём используемой памяти увеличивается, поскольку создаётся копия.
🔹Обе реализации основаны на алгоритме Timsort со средней сложностью O(N log N).
📌 Вывод
sorted() обеспечивает неизменность исходных данных и работает с любыми итерируемыми объектами. .sort() выполняет сортировку быстрее и экономнее по памяти, но только для списков и за счёт изменения объекта на месте. Используйте тот вариант, который соответствует конкретным требованиям задачи.
Python предоставляет два способа упорядочить данные: встроенную функцию sorted() и метод списка .sort(). Ниже — краткое сравнение и рекомендации по использованию.
Основные различия
1️⃣ sorted(iterable)
• Не изменяет исходный объект
• Возвращает новый отсортированный объект
• Принимает любой итерируемый (списки, строки, кортежи, генераторы)
2️⃣ list.sort()
• Сортирует список in-place (данные изменяются)
• Возвращает None
• Работает только со списками и экономит память, поскольку не создаёт копию
Пример
nums = [3, 1, 4, 1, 5]
sorted_nums = sorted(nums)
# → [1, 1, 3, 4, 5]
print(nums)
# исходный остаётся: [3, 1, 4, 1, 5]
nums.sort()
# сортировка на месте
print(nums)
# → [1, 1, 3, 4, 5]
Общие параметры
🔹 key= — функция, определяющая порядок сортировки (напр. sorted(words, key=len))
🔹 reverse=True — сортировка по убыванию
🔹 Обе операции стабильные: элементы с одинаковым key сохраняют первоначальный порядок
Критерии выбора
🔹Требуется сохранить исходные данные без изменений → sorted()
🔹Нужно упорядочить не-list (строку, tuple, генератор) → sorted()
🔹Работа ведётся с крупным списком, важна экономия памяти → .sort()
🔹Планируется многократная пересортировка одного и того же списка → .sort()
🔹Сортировка встроена в цепочку выражений или генераторов → sorted()
⚠️ Особенности
🔹Метод .sort() всегда возвращает None; использование его результата в присваивании вызовет ошибку.
🔹При применении sorted() к очень большим коллекциям объём используемой памяти увеличивается, поскольку создаётся копия.
🔹Обе реализации основаны на алгоритме Timsort со средней сложностью O(N log N).
📌 Вывод
sorted() обеспечивает неизменность исходных данных и работает с любыми итерируемыми объектами. .sort() выполняет сортировку быстрее и экономнее по памяти, но только для списков и за счёт изменения объекта на месте. Используйте тот вариант, который соответствует конкретным требованиям задачи.
👍6❤3🔥3
@property: геттеры-сеттеры «по-питоновски»
@property превращает обычные методы в «прозрачные» атрибуты. Класс продолжает выглядеть как работа с полями, но внутри можно валидировать входные данные, кэшировать результаты или менять реализацию — без изменения внешнего интерфейса.
🔹 Что делает @property
1️⃣ Позволяет объявить геттер — метод, который вызывается при чтении атрибута.
2️⃣ Через @<имя>.setter добавляется сеттер для записи.
3️⃣ При необходимости можно добавить @<имя>.deleter для удаления атрибута.
🔸 Мини-пример
Клиенту класса всё равно, что внутри есть логика: доступ идёт как к «обычному» полю.
Преимущества
🔹Инкапсуляция без шума. Проверки и вычисления скрыты, интерфейс лаконичный.
🔹Совместимость. Можно заменить публичное поле на property, не меняя вызовов в коде.
🔹Ленивая инициализация и кэш. Первый вызов рассчитывает значение, а setter/делитер управляют кэшем.
⚠️ Нюансы
🔹Логика в property должна оставаться быстрой; тяжёлые операции лучше вызывать явно.
🔹Сеттер обязателен, если атрибут нужно менять; без него объект станет «только для чтения».
✅ Когда использовать
🔹Необходимо валидировать входные данные при присвоении.
🔹Значение должно вычисляться на лету (площадь, кэшированное представление).
🔹Планируется изменить внутреннее хранилище данных, сохранив старый API.
@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
5 вопросов, которые почти наверняка зададут на Python-собеседовании 🧩
Ниже — короткая шпаргалка в стиле «вопрос → суть ответа → мини-пример». Сохраните пост, чтобы повторить перед интервью.
🔹 list vs tuple
Что спросят: «чем отличаются и зачем нужен кортеж?»
list изменяем, поэтому занимает больше памяти и не может быть ключом словаря. tuple неизменяем, легче и хэшируем — hash((1,2,3)) работает.
🔹 *args и **kwargs
Что спросят: «Как работает распаковка и порядок параметров?»
*args собирает все дополнительные позиционные аргументы в кортеж, а **kwargs — именованные в словарь.
В сигнатуре сначала идут обычные параметры, затем *args, потом именованные с значениями по умолчанию и в конце **kwargs.
🔹 yield vs return
Что спросят: «Чем генератор лучше обычной функции?»
yield приостанавливает функцию, сохраняя её состояние, поэтому следующая итерация продолжается с того же места.
Генератор выдаёт элементы по запросу, экономя память и позволяя обрабатывать большие потоки данных.
🔹 Почему += со строками медленный?
Что спросят: «Где здесь проблема неизменяемости?»
str неизменяем, поэтому при каждом += создаётся новая копия и суммарная сложность растёт до O(n²).
Накопите части в списке и соедините их одним ''.join() — это линейно по времени и памяти.
🔹 GIL и многопоточность
Что спросят: «Почему потоки не ускоряют CPU-bound код в CPython?»
GIL (Global Interpreter Lock) позволяет выполнять байт-код
только одному потоку CPython в любой момент времени.
Поэтому CPU-bound задачи не параллелятся потоками, а для параллели
используют multiprocessing, C-расширения или I/O-ориентированный asyncio.
Ниже — короткая шпаргалка в стиле «вопрос → суть ответа → мини-пример». Сохраните пост, чтобы повторить перед интервью.
🔹 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 — «старый надёжный»
Берёт любое количество частей и склеивает безопасно.
🔹 Pathlib — «современный» и удобный
⚠️ Подводные камни
🔹 Не склеивайте пути через + или f-строки — это ломается на другой ОС.
🔹 В os.path.join если одна часть начинается с ведущего «/» или диска (C:\), все предыдущие игнорируются.
🔹 В Path оператор / работает только между объектами Path и/или строками, но не с числами — приведите к str.
✅ Когда применять
🔹 Пишете скрипт, который будут запускать коллеги на разных ОС.
🔹 Строите сложные вложённые пути: base / 'raw' / date / 'file.parquet'.
Когда скрипт должен работать и в 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
Опубликовали разбор 2 задач из собеседования на аналитика данных в Яндекс 🔥
Статья:
https://vc.ru/hr/2069090-analiticheskie-zadachi-yandeksa
Статья:
https://vc.ru/hr/2069090-analiticheskie-zadachi-yandeksa
Please open Telegram to view this post
VIEW IN TELEGRAM
vc.ru
Две аналитические задачи с интервью «Яндекса»: разбор и практика на Python Academy
На позициях аналитиков данных (Data Analyst / Product Analyst) в «Яндексе» часто проверяют умение формализовать устную задачу, подобрать правильный математический инструмент и обосновать ответ. Разберём две реальные задачи: «Конфетный алгоритм» на подсчёт…
🔥4❤2👍2
math.isclose: как правильно сравнивать числа с плавающей точкой 🔬
У бэкенда и аналитики часто «падают» проверки: суммы и агрегаты расходятся на сотые/тысячные. Причина — погрешность представления float (двоичная дробь не всегда точно хранит десятичное число). Поэтому выражение 0.1 + 0.2 == 0.3 может дать False.
1️⃣ Проблема на пальцах
Точное равенство работать не будет. Нужно сравнивать «с запасом».
2️⃣ Решение — math.isclose
Функция считает числа «достаточно близкими», если их разница меньше допуска:
По умолчанию: rel_tol=1e-9, abs_tol=0.0.
3️⃣ Абсолютный vs относительный допуск
🔹 abs_tol — фиксированное окно, важно возле нуля.
🔹rel_tol — доля от величины, для крупных чисел.
На практике задавайте оба допуска.
4️⃣ Важно
🔹NaN никогда не «близок»; inf — только к inf того же знака.
🔹Допуски — часть бизнес-логики, фиксируйте их явно.
Итог: math.isclose делает проверки надёжными в мире погрешностей. Подберите разумные rel_tol и abs_tol — и тесты перестанут сыпаться из-за микроскопических расхождений.
У бэкенда и аналитики часто «падают» проверки: суммы и агрегаты расходятся на сотые/тысячные. Причина — погрешность представления 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() пытаются загрузить всё в память.
Решение — читать файл построчно: память остаётся стабильной, обработка начинается сразу.
🔹 зачем
— Память не растёт с размером файла.
— Скрипт начинает работать сразу, а не после полной загрузки.
🔹 базовый приём
Файловый объект буферизует чтение; в цикл попадает одна строка за раз. enumerate(..., 1) даёт номер строки, rstrip("\n") при необходимости убирает перевод строки.
🔹 практика: считаем ошибки в логе
Один проход по файлу, без хранения всего лога в памяти.
🔹 csv: правильно и экономно
DictReader отдаёт строки как словари по именам колонок. newline="" нужен, чтобы модуль csv корректно обрабатывал переводы строк на всех ОС (особенно Windows).
⚠️ важно помнить
🔹 read() и readlines() загружают весь файл — избегайте на больших данных.
🔹 Одна очень длинная строка всё равно занимает много памяти: по возможности используйте разумные длины строк (CSV/JSONL).
🔹 Явно указывайте encoding (обычно utf-8), чтобы избежать ошибок декодирования.
✅ когда применять
🔹 Логи, телеметрия, большие CSV/JSONL, выгрузки из БД.
🔹 ETL-скрипты и бэкенд-воркеры, где важны стабильность и предсказуемая память.
Большие файлы (сотни МБ и больше) роняют скрипты: наивные 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 — и получите быстрые ответы, разгруженную базу и более предсказуемую работу сервиса.
Медленные запросы к БД, внешним 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 генерирует правдоподобные имена, адреса, почты и даты за секунды, причём воспроизводимо.
🔹 установка
🔹 пример
Локаль ru_RU даёт русские имена/адреса/форматы.
🔹 воспроизводимость (фиксируем «случайность»)
С тем же seed вы получите те же значения — удобно для тестов и ревью.
🔹 уникальные значения, когда нужны
unique помогает избежать дублей (например, при генерации логинов/почт).
🔹 мини-датасет в одну строку (без «простынь» кода)
Дальше можно быстро выгрузить в CSV/БД любым удобным способом.
🔹 что генерировать чаще всего
1️⃣ пользователи: имя, e-mail, телефон, город, дата регистрации;
2️⃣ заказы/платежи: сумма, валюта, статус, дата-время;
3️⃣ web-данные: домены, URL, user-agent.
📌 итог
Faker экономит часы подготовки данных и делает сценарии воспроизводимыми. Укажите локаль, при необходимости зафиксируйте seed — и за пару строк получите реалистичный набор под вашу задачу.
Когда нужно наполнить тестовую БД/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/max/any, простой цикл.
🔹Потоки и большие объёмы: чтение файла построчно, «страницы» из API.
🔹Пайплайн «filter → map → reduce» без промежуточных списков.
Когда список уместнее
🔹Нужен доступ по индексу или случайное обращение.
🔹Нужны многократные проходы по тем же данным.
🔹Важно знать len() заранее, хранить срезы или кэшировать результат.
📌 Итог
Берите генераторы, когда результат не нужно хранить и вы проходите данные один раз: так код потребляет меньше памяти и работает стабильнее. Списки оставляйте для задач с индексами, повторным обходом и известной длиной.
Если нужно просто «пройтись по данным и посчитать», создавать целый список — лишняя трата памяти и времени. Генераторы отдают элементы по одному «на лету», поэтому код стартует быстрее и не раздувает 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, связанную с аргументами функций.
Посмотрите на код ниже:
Каким будет вывод второй строки?
1️⃣ ['банан']
2️⃣ ['яблоко', 'банан']
3️⃣ Error
4️⃣ None
Правильный ответ и объяснение:
Ответ:
2️⃣ ['яблоко', 'банан']
Почему так происходит?
В Python значения по умолчанию для аргументов вычисляются всего один раз — в момент определения функции (когда интерпретатор видит строку def ...), а не при каждом её вызове.
Если в качестве значения по умолчанию используется изменяемый объект (mutable), такой как список [] или словарь {}, то он становится «общим» для всех последующих вызовов функции.
🔹При первом вызове в «общую» корзину попало «яблоко».
🔹При втором вызове функция обратилась к тому же самому объекту списка и добавила туда «банан».
Как этого избежать?
Всегда используйте None в качестве значения по умолчанию:
Теперь при каждом вызове без аргумента будет создаваться новый пустой список.
Сегодня разберем одну из самых коварных особенностей 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). Сохраните и используйте, чтобы быстро освежить знания перед важным собесом.
📥 офлайн-версия — в файле к этому посту
удачи на собеседованиях! 💪
Мы собрали самые популярные вопросы и варианты ответов — читайте по ссылке:
https://python-academy.org/ru/interview-questions
Чтобы было удобнее, подготовили офлайн-версию (PDF). Сохраните и используйте, чтобы быстро освежить знания перед важным собесом.
📥 офлайн-версия — в файле к этому посту
удачи на собеседованиях! 💪
🔥22❤5👍2
✂️ Иллюзия .strip(): почему 'mac.com'.strip('.com') вернёт 'a'
Хочешь очистить данные перед работой и отрезать домен с помощью
⚠️ В чём подвох?
Многие думают, что
Метод агрессивно отщипывает эти буквы с обоих концов строки до тех пор, пока не встретит чужой символ.
Символы
🚀 Безопасное решение
Для удаления точных окончаний и приставок в Python 3.9 добавили специальные методы. Они ищут строго целую подстроку:
💡 Оставь
Хочешь очистить данные перед работой и отрезать домен с помощью
.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 возвращает одни двойки. Это классический баг, на котором часто ошибаются новички.
⚠️ Как выглядит проблема
⚙️ Почему так происходит
Вся суть в механизме позднего связывания (late binding).
🔹 Функция
🔹 Она запоминает лишь ссылку на саму переменную в окружающем коде.
🔹 Когда цикл завершается, переменная
🚀 Как починить
Нужно заставить Python зафиксировать значение сразу. Самый элегантный способ — передать его через аргумент по умолчанию:
🔸 Аргументы по умолчанию вычисляются ровно в момент создания функции, поэтому каждая
💡 Значения по умолчанию фиксируются при создании функции, а код внутри нее выполняется только при вызове.
Создаешь список функций, ожидаешь получить 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 склеиваются в словаре
Попробуй создать словарь с ключами
⚠️ Что произойдёт на практике
⚙️ Почему так работает
🔹 В Python логический тип
🔹 Математически
🔹 Словари ищут элементы по хэшу. Так как
📉 Как происходит перезапись
🔸 Сначала создаётся пара, где ключ —
🔸 Затем Python доходит до ключа
🔸 Сам ключ не меняется (в словаре остаётся
💡 Будь осторожен при группировке данных: ключи разных типов могут внезапно оказаться близнецами под капотом.
Попробуй создать словарь с ключами
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