junior: база, с которой начинают
Расскажи, зачем вообще нужен numpy, если в Python уже есть списки. Почему векторизованные операции обгоняют цикл по списку?
как ответить numpy хранит данные не как список Python-объектов, а как непрерывный typed-буфер памяти одного dtype — без boxing и указателей на каждый элемент. Операции вроде a + b выполняются единым C-циклом (ufunc) поверх этого буфера, минуя байткод интерпретатора на каждой итерации, а данные лежат в кэше процессора последовательно. Отсюда кратный (обычно на порядки) выигрыш по скорости и меньше памяти на больших массивах.
разбор Список Python — это массив указателей на PyObject, каждый элемент — отдельный объект с накладными расходами (заголовок, счётчик ссылок), разбросанный по памяти. ndarray — непрерывный блок байт фиксированного dtype: размер элемента известен заранее, объекты не создаются.
Цикл for x in list на каждой итерации гоняет интерпретатор через диспетчеризацию байткода (LOAD, CALL, распаковку объекта). Векторизованная операция — это один вызов в скомпилированную C-функцию, которая крутит цикл на C-скорости, иногда с SIMD-инструкциями. Плюс контигуальная память даёт меньше промахов кэша.
Типичная ловушка на follow-up: «векторизация» — не магическое слово, а именно отсутствие Python-цикла внутри. df.apply(lambda x: ...) в pandas выглядит как numpy-стиль, но внутри всё равно интерпретируемый Python-цикл — выигрыша не будет.
Могут спросить про линейную алгебру отдельно — там ещё добавляется BLAS/LAPACK, и порядок памяти (C-order vs F-order) может дать разницу в разы для матричных операций.
чтобы прозвучать сильнее Для мидла-плюс проговори, что ufunc — это именно скомпилированный C-код с векторизованным циклом (иногда с SIMD), а не просто «numpy быстрее по built-in причине»; и сразу приведи контрпример, где вызов выглядит векторизованным, а на деле остаётся Python-циклом (pandas .apply с лямбдой).
python-data-001 · junior · high Что такое dtype у ndarray и зачем массив вообще его хранит? Что случится, если положить в np.array разнородные данные?
как ответить dtype задаёт единый тип и фиксированный размер элемента массива. У числового ndarray значения лежат в общем буфере без отдельного PyObject на элемент, а расположение описывают shape и strides — срез может быть непрерывным или strided view. Для разнородного ввода NumPy ищет общий dtype: числа и строки могут стать строками, произвольные объекты — dtype=object. Рваная вложенная последовательность без явного dtype=object с NumPy 1.24 вызывает ValueError.
разбор dtype фиксирует тип и разрядность: int64 занимает 8 байт, float32 — 4. Однородное представление позволяет выполнять операции в нативных циклах над общим буфером, но не обещает непрерывность любого массива: срезы и транспонирование часто создают view с другими strides.
При смешивании совместимых типов действует продвижение: int и float обычно дают вещественный dtype, а np.array([1, 2.5, "x"]) может привести всё к строкам. dtype=object нужен, когда элементы следует хранить как ссылки на Python-объекты, например для словарей или произвольных экземпляров. Рваный ввод np.array([[1, 2], [3]]) — отдельный случай: начиная с NumPy 1.24 он вызывает ValueError; чтобы сознательно создать объектный массив, указывают dtype=object. Такой массив теряет многие преимущества компактного числового представления и векторизованных циклов.
Целые NumPy имеют фиксированную разрядность, поэтому арифметика над массивом может переполняться без предупреждения; скалярная операция NumPy обычно выдаёт RuntimeWarning. Важное исключение: np.sum и np.prod для узких целых по умолчанию повышают результат до платформенного целого. Проверяют представление через arr.dtype, arr.shape, arr.strides и arr.nbytes.
чтобы прозвучать сильнее Упомяни strided views, явный dtype=object для ragged input и исключение из правила переполнения: sum/prod обычно повышают узкие целые до платформенного типа.
python-data-002 · junior · high Расскажи, что такое Series и DataFrame в pandas и как они связаны между собой.
как ответить Series — одномерный маркированный массив: по сути обёртка над numpy-массивом плюс объект Index, который даёт каждому элементу метку. DataFrame — двумерная таблица, и удобнее всего думать о нём как о dict, где каждое значение — это Series-столбец, и все столбцы разделяют один и тот же row-индекс. Именно общий Index отвечает за выравнивание данных при арифметике и джойнах: pandas сопоставляет элементы по меткам, а не по позиции.
полный разбор и проверка ответа арбитром — в приложении
python-data-003 · junior · high Чем .loc отличается от .iloc и в каких случаях ты берёшь один, а не другой?
как ответить loc — выборка по меткам (label-based): значения индекса, имена столбцов, булевы маски, и в срезах правая граница включается. iloc — чисто позиционная выборка (integer position-based), ведёт себя как срез обычного списка, где правая граница исключается. Если индекс DataFrame — это RangeIndex по умолчанию, loc и iloc на первый взгляд совпадают, но семантика разная: df.loc[0:5] вернёт строку с меткой 5, а df.iloc[0:5] — нет, если индекс не по порядку или содержит дубли или пропуски, разница становится критичной.
полный разбор и проверка ответа арбитром — в приложении
python-data-004 · junior · high Как устроена булева фильтрация в pandas — что реально происходит, когда пишешь df[df['col'] > 5]?
как ответить df['col'] > 5 — это поэлементное векторизованное сравнение (через numpy под капотом), результат — Series из True/False с тем же индексом, что у исходного DataFrame. Передача этой булевой маски в df[...] или df.loc[mask] отбирает строки, где значение True, без явного Python-цикла. Комбинировать условия нужно побитовыми операторами & | ~ с обязательными скобками вокруг каждого сравнения — and/or для pandas-объектов не работают и кидают ValueError про неоднозначность истинности массива.
полный разбор и проверка ответа арбитром — в приложении
python-data-005 · junior · high Расскажи, как устроен groupby в pandas: что происходит на этапах split-apply-combine, и как задать разные агрегации для разных колонок одним вызовом?
как ответить groupby разбивает DataFrame на группы по значениям ключа (split), к каждой группе применяет функцию — sum, mean, кастомную (apply) — и склеивает результаты обратно в один объект (combine). Чтобы агрегировать разные колонки по-разному, в agg передают словарь {колонка: функция или список функций}, а не одну функцию на весь DataFrame.
полный разбор и проверка ответа арбитром — в приложении
python-data-006 · junior · high У тебя две таблицы: пользователи и заказы, некоторые пользователи без заказов. Что вернёт такой LEFT JOIN и как это соотносится с merge(how='left') в pandas?
CREATE TABLE users(id INTEGER, name TEXT);
CREATE TABLE orders(id INTEGER, user_id INTEGER, amount INTEGER);
INSERT INTO users VALUES (1,'Alice'),(2,'Bob');
INSERT INTO orders VALUES (1,1,100),(2,3,50);
SELECT users.name, orders.amount
FROM users
LEFT JOIN orders ON users.id = orders.user_id;
как ответить LEFT JOIN сохраняет все строки левой таблицы (users), а к строкам без совпадения в orders подставляет NULL — Bob останется в результате с NULL в amount, потому что ни один заказ не ссылается на user_id=2. В pandas merge(how='left') работает так же: все строки left DataFrame сохраняются, непарные значения из right становятся NaN. Ключевое отличие от inner в том, что inner отбросил бы Bob целиком.
полный разбор и проверка ответа арбитром — в приложении
python-data-007 · junior · high Есть тяжёлый агрегатный отчёт по таблице в БД — тянешь всё в pandas и считаешь там, или агрегируешь прямо в SQL? Как ты вообще выбираешь, где что считать?
как ответить SQL ближе к данным: агрегация в самой БД использует индексы и не тащит сырые строки по сети, поэтому SUM/COUNT/GROUP BY/JOIN лучше делать там, вытягивая уже свёрнутый результат. Pandas — когда логика не ложится на SQL: сложные условные преобразования, pivot с произвольными функциями, соединение с внешними источниками, или когда данные уже маленькие и локальные. Общее правило — фильтровать и агрегировать как можно ближе к источнику, тянуть по сети минимум.
полный разбор и проверка ответа арбитром — в приложении
python-data-008 · junior · high Ниже — итоговый SQL-запрос, который прилетел в логи БД после того, как в поле username формы логина ввели ' OR '1'='1' -- , а backend собирал запрос f-строкой. Что вернёт этот запрос и почему форма логина внезапно пускает без пароля?
CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT);
INSERT INTO users VALUES (1, 'admin', 's3cr3t'), (2, 'ann', 'ann123');
SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = 'anything';
как ответить Запрос вернёт вообще всех пользователей таблицы, потому что условие '1'='1' истинно всегда, а -- комментирует остаток строки вместе с проверкой пароля — WHERE фактически превращается в «true». Backend, который просто проверяет «нашлась хоть одна строка», пускает как первого попавшегося юзера. Это классическая SQL-инъекция: пользовательский ввод стал частью структуры запроса, а не данными. Лечится параметризацией — execute с плейсхолдерами, где значение передаётся отдельно от текста запроса и не может изменить его логику.
полный разбор и проверка ответа арбитром — в приложении
python-data-009 · junior · high У тебя csv на 20 ГБ, а оперативки — 8. Как будешь читать и обрабатывать файл?
как ответить При большом файле грузишь его частями через pd.read_csv(..., chunksize=N) или построчным итератором csv.reader, обрабатываешь и агрегируешь каждый чанк независимо, не накапливая всё в памяти. Промежуточные результаты — партиционные суммы/счётчики, которые потом объединяешь. Дополнительно снижаешь память явным указанием dtype и категориальными типами для строк с малым числом уникальных значений.
полный разбор и проверка ответа арбитром — в приложении
python-data-010 · junior · high middle: где отделяют уверенных
Чем срез numpy-массива принципиально отличается от среза списка? Что выведет этот код?
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
sub = arr[1:3]
sub[0] = 999
print(arr)
как ответить Базовый срез (start:stop:step) у ndarray — это view: новый объект-обёртка над тем же буфером памяти, с другим offset/shape/strides, но без копирования данных. Поэтому изменение sub[0] реально меняет arr — результат [10, 999, 30, 40, 50]. Список ведёт себя иначе: lst[1:3] у list — это всегда новый список со скопированными ссылками, изменение среза оригинал не трогает. Если нужна независимая копия ndarray, её берут явно через .copy().
полный разбор и проверка ответа арбитром — в приложении
python-data-013 · middle · high Почему apply построчно в pandas обычно намного медленнее векторных операций, и когда apply всё-таки оправдан?
как ответить apply вызывает python-функцию отдельно для каждой строки — под капотом это чистый python-цикл без ускорения NumPy/Cython, плюс накладные расходы на создание Series для каждой строки. Векторные операции (df['a'] + df['b'], np.where, .str-методы) выполняются в скомпилированном C/Cython-коде над целым массивом сразу. apply оправдан, когда логику реально нельзя векторизовать: сложная ветвящаяся бизнес-логика, вызов внешней функции или API на каждую строку.
полный разбор и проверка ответа арбитром — в приложении
python-data-014 · middle · high Что вернёт этот LEFT JOIN и почему в результате появляются NULL? И чем поведение изменилось бы, поменяй LEFT JOIN на INNER JOIN?
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT);
INSERT INTO users VALUES (1, 'Ann'), (2, 'Bob');
CREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INTEGER, amount INTEGER);
INSERT INTO orders VALUES (10, 1, 100), (11, 3, 50);
SELECT o.id, u.name, o.amount FROM orders o LEFT JOIN users u ON o.user_id = u.id;
как ответить LEFT JOIN сохраняет обе строки заказов из orders, даже когда user_id=3 не находит пару в users — вместо полей юзера туда подставится NULL, и в результате получится две строки, одна с name = NULL. INNER JOIN, наоборот, требует совпадения в обеих таблицах: строку с заказом 11 он выбросит, и останется только одна строка — заказ Ann на 100. Выбор зависит от вопроса к данным: «покажи все заказы, даже сиротские» — LEFT JOIN, «покажи только заказы с валидным юзером» — INNER JOIN.
полный разбор и проверка ответа арбитром — в приложении
python-data-015 · middle · high Чем parquet отличается от csv, и когда ты выберешь один формат вместо другого?
как ответить CSV — текстовый построчный формат без схемы: универсален, но парсится медленно и не хранит типы. Parquet — колоночный бинарный формат со встроенной схемой и сжатием: читаешь только нужные колонки, статистика row group'ов даёт predicate pushdown, а объём на диске в разы меньше. Для аналитики и повторных чтений большого датасета берёшь parquet, для выгрузки/обмена с внешними системами и небольших объёмов — csv.
полный разбор и проверка ответа арбитром — в приложении
python-data-016 · middle · high