Python Flashcards
(26 cards)
Типы данных
dict, словарь – пары ключ-значение
Ключом могут быть неизменяемые объекты, у которых реализован магический метод __hash__()
Значениями могут быть любые объекты
Типичные операции:
d[key], get(), keys(), values(), items().
Set это множество, во многом ведёт себя как словарь без значений.
Это изменяемый набор уникальных и неупорядоченных элементов.
Нет порядка. Нет повторов.
Хранит:
Только неизменяемые объекты: int, float, string, tuple (tifs)
Можно вложить tuple (если его элементы хэшируемые).
Типичные операции:
add(), remove(), union(), intersection().
Список, list – это упорядоченная коллекция объектов. Списки могут иметь сколько угодно уровней вложенности и хранить неограниченное количество объектов. Кроме того, в одном списке могут одновременно храниться объекты разных типов.
Типичные операции:
append(), remove(), pop(), срезы lst[1:3].
Tuple (тапл), или кортеж, это неизменяемый объект, это те же списки, только неизменяемые.
Обращаться к элементам нужно по индексам, как в списках.
Типичные операции:
Только чтение: индексация, срезы, перебор.
Чем отличается генератор от итератора. Что экономит память, а что быстрее?
Итератор это класс, в котором реализованы два магических методы __iter__() и __next__()
__iter__() — возвращает сам объект (итератор),
__next__() — возвращает следующий элемент.
По объектам этого класса можно будет пробегаться циклом или запрашивать next(iter(myObject)).
Вычисляет и хранит всю последовательность в памяти, ей ограничен.
Можно создать из любой итерируемой коллекции (список, кортеж, строки, словари).
После окончания итерации объект становится “пустым” — нельзя пройтись по нему второй раз без пересоздания.
Генератор это функция, использующая ключевое слово yield
Также генератор можно задать как generator expression (генераторное выражение).
Хранит только текущее значение и то, как вычислить следующее. Может работать с бесконечно большими последовательностями.
Где его используют?
Обработка больших файлов
Чтение построчно без загрузки всего файла в память
Потоковая обработка данных (data pipeline)
Что экономит память — итератор или генератор?
Оба экономят память, потому что не хранят все элементы в памяти сразу, а отдают по одному.
Но генератор чаще используется именно для экономии памяти, так как его проще и быстрее написать. Он идеален для потоковой обработки данных, например, построчного чтения файлов или обработки больших объёмов.
Что быстрее — итератор или генератор?
В среднем генератор немного быстрее, потому что его реализация компактнее и оптимизирована Python’ом под капотом.
Ручной итератор через класс даёт больше гибкости, но из-за лишнего кода и вызовов может быть чуть медленнее.
На практике разница в скорости минимальна, но генераторы выигрывают по удобству и читаемости.
У нас есть объект итератор. Мы закинули его в for цикл
Как работает инициализация оператора и как он понимает когда остановиться?
Вызывается iter(obj).
Вызывает метод __iter__() объекта.
Он должен вернуть итератор — объект, у которого есть метод __next__()
Каждый вызов __next__() возвращает следующий элемент.
Если элементы закончились - выбрасывается исключение StopIteration
for-цикл это ловит и завершается без ошибки
В чем отличие кортежа от списка?
Cписки являются изменяемыми, а кортежи неизменяемыми.
Изменяемые объекты всегда лежат в одной и той же ячейке памяти(id() от объекта вернёт всегда один результат, а неизменяемые - нет.
При попытке изменить неизменяемый объект, в памяти просто создастся новый объект, который будет уже с другим id() (адресом в памяти) и своим hash() (если он хешируемый) и переменная начинает ссылаться на этот новый объект
Список и кортеж реализованы как лист из указателей (ссылок) на Python объекты, т.е. физически они не хранят объекты рядом с друг другом. Когда вы удаляете объект из списка происходит удаление ссылки на этот объект. Если на объект ещё кто-то ссылается, то он продолжит находиться в памяти.
Как реализованы словари и множества в Python?
Типы dict и set в Python построены на основе хэш-таблиц.
Что происходит когда ключ добавляется? Когда значение добавляет значение в set?
Вызывается hash(key) — получаем хеш объекта.
По хешу вычисляется индекс в хеш-таблице.
Если слот свободен — кладём туда пару (dict) или ключ (set).
Если занят — Python проверяет == и, при необходимости, ищет следующую подходящую ячейку (разрешение коллизий). Если == это True - значение обновляется
Почему нельзя использовать изменяемые типы данных в качестве ключей?
Потому что изменяемые объекты могут менять свой хеш во времени.
Если ключ после вставки в dict изменится, мы потеряем доступ к значению.
Поэтому такие типы, как list, dict, set, не имеют __hash__() и не могут быть ключами.
Почему tuple можно использовать как ключ, а list — нет, хотя они похожи?
tuple неизменяемый, а значит безопасен как ключ.
tuple реализует __hash__(), если все его элементы тоже хешируемые.
list — изменяемый и не реализует __hash__(), чтобы предотвратить логические ошибки при использовании в хеш-таблицах.
Зачем нужен hash если можно сравнить посимвольно?
hash нужен для быстрого сравнения объектов. Это нужно например для словарей, именно за счёт хэша доступ по ключу в словарях так быстро работает. Полное сравнение объектов может работать долго, поэтому сначала сравниваются хэши объектов и только при их совпадении производится полное сравнение через eq.
В идеале хэш должен быть с одной стороны небольшой по размеру, а с другой стороны разный для разных объектов. Это не всегда возможно и оправданно, поэтому в реальности хэш это некий компромисс и коллизии всё же бывают, но их немного, поэтому поиск по ключу в словаре и проверка наличия элемента в set работает моментально (за время порядка O(1)).
Хеш всегда уникален?
Есть два объекта с одинаковыми хешами. Это значит что объекты равны?
Нет. Хеш-функция может возвращать одинаковые значения для разных объектов — это называется коллизия.
Поэтому Python дополнительно сравнивает объекты через ==.
Разные хеши означает, что объекты точно разные.
Python даже не вызывает ==, если хеши отличаются.
Что происходит при коллизии хешей в словаре?
В Python есть механизм, который заключается в том что
если мы получаем такой же хеш, как у уже существующего ключа,
то сначала проверяется __eq__ — равны ли сами объекты.
Если объекты разные — это коллизия. В этом случае Python ищет новое место
в хеш-таблице по стратегии открытой адресации (обычно квадратичный пробинг),
и размещает новый ключ в следующей подходящей ячейке.
Открытая адресация это когда мы ищем другую “адресную” ячейку внутри таблицы. Это случается тогда, когда ключи оказываются не равны и происходит поиск следующего свободного слота
линейный пробинг: i+1, i+2, i+3…
квадратичный пробинг (как в Python): i+1², i+2², i+3²…
Какие типы данных нельзя использовать в качестве ключей словаря?
Все изменяемые типы, у которых нет __hash__():
list, dict, set, tuple, если внутри есть хотя бы один не-хешируемый объект
Можно ли изменять элементы списка внутри кортежа?
Да, можно.
Если внутри кортежа лежит изменяемый объект (например, список), то сам объект менять можно, даже если кортеж “внешне” считается неизменяемым.
Почему кортеж считается неизменяемым, если внутри него есть изменяемые объекты?
Кортеж считается неизменяемым, потому что его структура — фиксированная:
количество элементов, порядок, тпозиции ссылок на объекты
Ты не можешь: добавить или удалить элемент, заменить один элемент на другой (t[0] = 10 — ошибка)
Но сам кортеж содержит ссылки на объекты, а не значения.
Если внутри лежит изменяемый объект (например, список), то его содержимое можно менять — но ссылка остаётся той же, а значит структура кортежа формально не нарушена.
Как хранятся ссылки на объекты внутри кортежа?
Кортеж хранит массив ссылок на объекты, как указатели:
не значения напрямую, а именно адреса в памяти,
это означает, что кортеж сам по себе не владеет содержимым — он просто ссылается.
Как сравниваются объекты (сравниваем объект1 и объект2)? Как происходит сравнение? Как можно понять что они одинаковые? Через какой метод они сравниваются?
Методом __eq__
Когда ты сравниваешь два объекта в Python, например:
if obj1 == obj2:
происходит вызов магического метода __eq__().
Механизм сравнения:
obj1 == obj2 → Python вызывает obj1.__eq__(obj2)
Если __eq__() не переопределён в классе — используется реализация по умолчанию (сравнение по id, то есть по адресу в памяти).
Если __eq__() возвращает True, значит объекты считаются равными.
Логика такая что у каждого класса можно реализовать метод eq который будет сравнивать экземпляры двух классов. Если __eq__ не реализовать — Python сравнивает объекты по адресу в памяти (то есть obj1 is obj2).
В переменную a кладем словарь. Хочу, чтобы в переменной b лежал такой же словарь, как и в переменной a
Через метод copy.copy()
Есть еще метод copy.deepcopy()
Разница:
copy.copy() — поверхностная (shallow) копия
Копирует только сам объект, но не копирует вложенные объекты (они останутся ссылками на те же объекты).
copy.deepcopy() — глубокая копия
Копирует всё: и сам объект, и все вложенные объекты (рекурсивно).
Пользуешься типизацией? mypy
Типизируешь методы? Что они возвращают
Пользуюсь типизацией точечно
Airflow-операторах и Spark-джобах тоже использую типизацию для __init__, execute()
Явно указать типы аргументов (например, int, str, dict[str, Any])
Сделать код читаемее и понятнее для IDE и mypy
Защититься от случайных ошибок
source_path: str
Это путь к исходным данным, например в HDFS или S3.
batch_size: int
Размер порции данных, которые обрабатываются за один шаг.
Типизирую ключевые аргументы и возвращаемые значения: пути (str), порции данных (int), списки значений (List[str]), Spark-таблицы (DataFrame) и контекст выполнения Airflow (Context)
Что такое декоратор и зачем он нужен? Писала декораторы?Как передавать параметры в декоратор? Приводил ли примеры написания декораторов?
Работала с декораторами
Декоратор — это функция, которая оборачивает другую функцию или метод, чтобы добавить к ней дополнительную логику без изменения её кода.
Используется, чтобы переиспользовать функциональность
Не писала сама, не было такой необходимости
Но знаю как реализовать.
— Мы пишем функцию, которая принимает другую функцию как аргумент
— Внутри неё определяем вложенную функцию, которая оборачивает поведение оригинальной
— В конце возвращаем эту вложенную функцию
— И используем синтаксис @имя_декоратора над функцией, которую хотим обернуть
Можно передавать параметры в декоратор?
Да, в декоратор можно передавать параметры, но тогда он становится функцией, которая возвращает декоратор — то есть ещё один уровень обёртки.
Как устроена реализация декоратора с параметрами?
1. Внешняя функция принимает параметры декоратора.
2. Она возвращает сам декоратор, который принимает целевую функцию.
3. Внутри декоратора — обёртка, которая добавляет нужную логику и вызывает оригинальную функцию.
Как переопределить метод? Нужен ли там декоратор?
Переопределение метода (override) — это когда в подклассе ты пишешь свою версию метода, который уже есть в родительском классе.
Это делается без декоратора, просто через объявление метода с тем же именем.
Декоратор не обязателен, но в новых версиях Python можно использовать @override из typing для явного указания IDE и проверки через mypy.
Пользовалась ли ООП python?
Я использовала ООП, когда это было нужно — например, при написании кастомных Airflow операторов. Там я наследовалась от базового класса BaseOperator или SparkSubmitOperator, переопределяла метод execute, задавала параметры через __init__ — классический ООП-подход.
Но в большинстве рабочих задач я больше использовала функциональный или процедурный стиль: например, написание Spark-джоб, скриптов трансформации, ETL-логики, где нет необходимости создавать классы.
Если бы в проекте требовалась сложная логика с переиспользованием, я бы, конечно, активно применяла ООП.
Как инициализировать класс? Как будет выглядеть конструкция?
Чтобы создать (инициализировать) экземпляр класса в Python, нужно:
Определить конструктор в самом классе через метод __init__, указав там необходимые параметры.
Вызвать класс как функцию, передав аргументы в скобках — это и создаст новый объект.
Есть метод super и мы перенимаем все что есть в родительском классе
Можем поменять методы в родительском классе, можем задать новые или переопределить
Будут использоваться методы которые мы предопределили
Использовала магические методы (функции)? Что такое магические методы?
Магические методы - это специальные методы в Python, которые начинаются и заканчиваются двумя подчёркиваниями (dunder-методы), например __init__, __str__, __eq__. Они позволяют переопределить поведение встроенных операций языка.
__init__ вызывается при создании объекта. используется для инциализации. это метод конструктора. мы в инит сдаем базово переменные все, инициализируем класс
__new__ это когда нужно управлять процессом создания
__eq__ — отвечает за сравнение ==
__hash__ отвечает за вычисление хеш-значения объекта (например, для ключей в словарях и множеств).
Методов много
__getattribute__(self, name)
Вызывается при любом доступе к атрибуту (obj.attr).
Очень мощный и опасный метод — легко сделать бесконечную рекурсию.
__setattr__(self, name, value)
Вызывается при установке значения атрибуту (obj.attr = value).
Полезен, если хочешь контролировать или валидировать установку атрибутов.
С магическими методами я сталкивалась в основном при изучении языка и написании классов, но в продакшене использовала их нечасто — в проектах с данными чаще важна читаемая логика, чем переопределение поведения объектов.
Но при этом я понимаю, как и зачем они используются.
Если бы пришлось разрабатывать полноценные библиотеки, я бы активно это использовала.
Чем отличаются @staticmethod, @classmethod и @property?
Это встроенные (built-in) декораторы Python, которые изменяют поведение методов класса.
staticmethod это декоратор, который связан с классом, а не с его экземплярами. Чтобы быть вызванным, он не требует создания экземпляра класса и не имеет доступа к экземпляру.
Он не может модифицировать состояние класса или его экземпляров.
Это просто функция, логически связанная с классом.
classmethod это декоратор, который превращает обычный метод в метод класса.
Он привязан к самому классу, а не к экземпляру.
Первым аргументом такого метода всегда идёт класс (cls), а не self.
С помощью @classmethod можно обращаться к атрибутам класса и создавать новые экземпляры.
property превращает метод в атрибут.
Позволяет добавить логику при обращении к полям объекта — например, при чтении, записи или удалении.
Может использоваться с @getter, @setter и @deleter для управления чтением, записью и удалением значений. Подходит для инкапсуляции и валидации.
@getter, @setter и @deleter — это части свойства @property, которые позволяют управлять логикой чтения, записи и удаления атрибута, сохраняя доступ как к обычному полю.
Это удобно для инкапсуляции, валидации и скрытия внутренней реализации.
Private? Protected методы как-то реализованы? А сама пользуешься этим? Есть ли в питоне приватные функции?
Приватные функции не реализованы. Просто принято, что есть:
Protected методы (защищённые):
Обозначаются через один подчёркивание: _method_name
Служит для обращения внутри класса и во всех его дочерних классах
Private методы (приватные):
Обозначаются двумя подчёркиваниями: __method_name
Служит для обращения только внутри класса
Name mangling — это автоматическое переименование имён с двумя подчёркиваниями (), чтобы их нельзя было случайно переопределить или вызвать извне.
В питоне нет строгой приватности, но я использую _ для внутренних методов (например, _fetch_data, _validate_input) — чтобы отделить служебную логику.
__ применяю редко, когда важно избежать конфликтов имён. В основном придерживаюсь соглашений, особенно в больших пайплайнах и классах.
_fetch_data, _validate_input как внутренние вспомогательные методы в кастомных классах (например, в Airflow-операторах). Это помогает структурировать код и избегать лишнего доступа извне.
_log, _context — стандартные protected-поля Airflow, которые активно использовала для логирования и получения параметров DAG.
Что такое MRO (Method Resolution Order)?
MRO (Method Resolution Order) — это порядок, в котором Python ищет методы и атрибуты в случае наследования, особенно при множественном наследовании.
Он определяется через алгоритм С3 - линеаризации
Принципы C3-линеаризации:
Дочерний класс всегда идёт перед родителями
Если один и тот же класс упоминается несколько раз, используется только первое появление
Сохраняется порядок, указанный в class X(A, B, C)
Он гарантирует, что порядок будет устойчивым, без конфликтов, и super() будет работать предсказуемо.
Проблема ромбовидного наследования (Diamond Problem)
Проблема ромбовидного наследования возникает, когда класс наследуется от двух (или более) предков, которые, в свою очередь, имеют общего предка. В результате получается «ромб»
Как Python решает эту проблему
Method Resolution Order (MRO)
Python вычисляет упорядоченный список классов, по которому будет искать методы.
Алгоритм C3-линеаризации
Он строит MRO так, чтобы:
все базовые классы шли после дочерних,
сохранялся порядок, в котором указаны родительские классы в объявлении,
ни один класс не появлялся в списке более одного раза.
super()
При использовании super() внутри методов Python следует этому же списку MRO, что позволяет корректно цеплять вызовы по «ромбовидной» иерархии без дублирования.
Итог: благодаря MRO и C3-линеаризации Python однозначно и предсказуемо определяет, чью версию метода использовать, и избегает двукратного вызова одного и того же класса при многократном наследовании.