Все специализации

Python-разработчик: вопросы на собеседовании

59 вопросов с разбором ответов — те формулировки, которые действительно встречаются на интервью.

MRO2 (классический порядок разрешения методов) используется в старых, "классических" классах, которые явно не наследуются от object. Он основан на глубине поиска по иерархии наследования.

MRO3 (C3 linearization algorithm) используется в классах, наследующихся от object (что является стандартом в Python 3, и в Python 2.2+ при использовании __ будущие__ import division). Он гарантирует создание монотонного порядка, предотвращая некоторые проблемы многократного наследования, которые могли возникнуть с MRO2 (например, Diamond Problem).

Основные отличия:

Алгоритм: MRO2 - простой DFS; MRO3 - C3 linearization.

Наследование от object: MRO2 - нет или не требуется; MRO3 - используется для всех новых стилей классов.

Гарантии: MRO2 не всегда гарантирует монотонность и может привести к непредсказуемому поведению при многократном наследовании; MRO3 гарантирует монотонность и предсказуемый порядок.

Можно посмотреть MRO с помощью атрибута __mro__:

Если функция не содержит оператор return, она все равно будет возвращать значение. По умолчанию Python возвращает None из функций, которые завершаются без явного return.

Даже пустая функция без какого-либо кода внутри будет возвращать None:

Это важное поведение для понимания потока выполнения и значений в функциях Python.

Дендер-методы (также известные как "магические" или "специальные" методы) — это методы в классах Python, имена которых начинаются и заканчиваются двойным подчеркиванием (например, __init__, __str__). Они используются для реализации протоколов или переопределения поведения операторов и встроенных функций.

Примеры использования:

Инициализация объекта (__init__)

Представление объекта в виде строки (__str__, __repr__)

Перегрузка операторов (__add__ для сложения, __mul__ для умножения)

Управление контекстом (с использованием with) (__enter__, __exit__)

Создание итераторов (__iter__, __next__)

Работа с атрибутами (__getattr__, __setattr__)

Использование дандер-методов позволяет классам взаимодействовать с механизмом языка Python на более низком уровне, делая пользовательские объекты согласованными со встроенными типами данных.

Функция run() — это стандартный метод в Python, используемый для запуска различных сущностей, которые могут быть выполнены независимо, часто в контексте многопоточности или многопроцессорности.

Основное применение run() связано с классами, которые наследуются от threading.Thread или multiprocessing.Process. Когда создается экземпляр одного из этих классов, код, предназначенный для выполнения в отдельном потоке или процессе, размещается именно в методе run().

При создании экземпляра потока или процесса, например:

Метод run() не вызывается напрямую для запуска потока или процесса. Вместо этого используется метод start():

Метод start() отвечает за создание нового потока или процесса и вызывает метод run() внутри этого нового контекста. Не следует вызывать run() напрямую, так как это просто выполнит код в текущем потоке или процессе, а не создаст новый.

В контексте multiprocessing.Process аналогично:

Таким образом, run() служит контейнером для исполняемого кода в изолированной среде (поток или процесс), а start() является механизмом для инициирования этой изоляции и запуска run().

Помимо threading и multiprocessing, метод run() может встречаться и в других библиотеках или фреймворках, где требуется инкапсулировать исполняемую логику для последующего запуска, например, в some event loops or task schedulers, although less common than in the standard threading/multiprocessing modules.

Домен - это область в иерархической системе доменных имён (DNS), которая идентифицируется уникальным доменным именем. Он представляет собой логическую группу ресурсов (например, веб-сайтов, серверов), объединённых под одним административным контролем.

Доменное имя - это уникальный адрес в Интернете, используемый для доступа к веб-сайтам и другим онлайн-ресурсам. Это легко запоминаемое буквенно-цифровое обозначение, которое преобразуется системой DNS в IP-адрес.

Пример:

В доменном имени google.com, "google.com" является доменным именем.

".com" - это домен верхнего уровня (TLD).

"google" - это домен второго уровня.

Вложенные транзакции — это транзакции, начатые внутри другой активной транзакции. Они позволяют управлять атомарностью и изоляцией отдельных логических блоков в рамках более крупной операции. Результат вложенной транзакции (успех или откат) зависит от исхода родительской транзакции: если родительская транзакция откатывается, то все вложенные транзакции, независимо от их собственного статуса, также откатываются.

Реализация в разных СУБД может отличаться. В некоторых системах (например, PostgreSQL) они реализуются с помощью сохранения точек (SAVEPOINT).

Ключевые особенности:

Атомарность: Вложенная транзакция либо полностью фиксируется, либо полностью откатывается в рамках родительской транзакции.

Изоляция: Изменения внутри вложенной транзакции могут быть не видны снаружи до фиксации родительской транзакции (зависит от уровня изоляции).

Зависимость: Окончательный статус вложенной транзакции определяется родительской. Откат родительской транзакции отменяет все изменения всех вложенных транзакций.

Сравнение с обычными транзакциями:

Применяются для обеспечения частичного отката в сложных операциях, когда не весь процесс должен прерваться из-за ошибки в одной из частей. Например, при пакетном обновлении данных, где сбой в обработке одного элемента может не требовать отката всего пакета.

Python-фреймворки, такие как Django или SQLAlchemy, предоставляют абстракции для работы с транзакциями, включая возможность использования точек сохранения или симуляции вложенности через контекстные менеджеры.

Метаклассы используются для управления процессом создания классов. Они позволяют изменять или расширять поведение создания класса, например, автоматически добавлять методы, атрибуты или проводить проверки при его определении. Это мощный инструмент для создания фреймворков, ORM или для реализации специфических шаблонов проектирования.

Автоматизация: Добавление стандартного поведения или атрибутов всем классам, использующим метакласс.

Проверки: Валидация определения класса.

Регистрация: Запись информации о классах при их создании.

API: Создание специфичных DSL или API.

Пример:

Использование метаклассов является продвинутой техникой и часто встречается во фреймворках, где требуется контролировать и изменять поведение большого количества классов. Для большинства повседневных задач они не требуются, и их чрезмерное использование может усложнить код.

WebSocket основан на TCP, как и стандартный сокет, но обеспечивает полнодуплексную связь поверх HTTP-соединения. Обычный сокет (например, TCP-сокет в Python) — это низкоуровневый интерфейс для сетевого взаимодействия, требующий ручной обработки протокола прикладного уровня. WebSocket предоставляет стандартизированный протокол, который начинается как HTTP-соединение, а затем переходит (апгрейдится) на WebSocket-протокол, позволяя серверу и клиенту обмениваться сообщениями в любое время без необходимости постоянных запросов (polling).

Основные отличия:

Протокол: WebSocket — протокол прикладного уровня для двусторонней связи, обычный сокет — интерфейс для работы с транспортным уровнем (TCP/UDP).

Установление соединения: WebSocket использует HTTP for handshake, затем переходит на свой протокол. Обычный сокет устанавливает соединение напрямую (например, с помощью connect()).

Двунаправленность: WebSocket изначально полнодуплексный. С обычным сокетом полнодуплексность достигается чтением и записью в один и тот же ресурс.

Накладные расходы (overhead): После установления соединения WebSocket имеет меньшие накладные расходы на передачу данных по сравнению с многократными HTTP-запросами или poll-методами обычных сокетов.

Пример установления WebSocket-соединения:

Пример использования обычного TCP-сокета в Python:

CGI (Common Gateway Interface) — это стандарт протокола, который определяет, как веб-сервер взаимодействует с внешними программами (скриптами), генерирующими веб-страницы динамически. Веб-сервер передает запрос клиента скрипту через стандартные потоки ввода/вывода и переменные окружения, а скрипт возвращает результат (HTML, изображения и т.д.) через стандартный поток вывода.

Плюсы CGI:

Простота: Легко начать разрабатывать, используя практически любой язык программирования, поддерживающий стандартные потоки ввода/вывода.

Независимость: Скрипт CGI не зависит от конкретного веб-сервера, пока тот поддерживает стандарт CGI.

Минусы CGI:

Неэффективность: Для каждого входящего запроса веб-серверу необходимо запускать новый процесс для CGI-скрипта. Это приводит к значительным накладным расходам на создание процессов, что особенно заметно при высокой нагрузке.

Ограниченная производительность: Каждый процесс скрипта работает изолированно, без возможности разделения ресурсов или состояния между запросами, что снижает производительность.

Сложность управления состоянием: Создание и поддержание состояния между запросами для пользователя или приложения требует дополнительных механизмов (например, сессий).

__len__ возвращает длину объекта (количество элементов). Используется для реализации встроенной функции len(). Должен возвращать неотрицательное целое число.

__abs__ возвращает абсолютное значение объекта. Используется для реализации встроенной функции abs(). Может возвращать значение того же типа, что и объект, или другого числового типа.

Пример использования в классе:

Асинхронный контекстный менеджер используется в асинхронных функциях (async def) и позволяет управлять асинхронными ресурсами. Он предоставляет методы __aenter__ и __aexit__, которые являются сопрограммами и могут использовать await.

Синхронный контекстный менеджер используется в обычных функциях (def) и управляет синхронными ресурсами. Он предоставляет методы __enter__ и __exit__, которые являются обычными функциями и не могут использовать await.

Основное отличие — возможность выполнения асинхронных операций (await) в методах __aenter__ и __aexit__, что недоступно в синхронных __enter__ и __exit__.

Пример синхронного:

Пример асинхронного:

Оптимизация запросов важна по нескольким причинам:

Повышение производительности: Быстрые запросы уменьшают время отклика приложений и обеспечивают лучший пользовательский опыт.

Снижение нагрузки на базу данных: Оптимизированные запросы требуют меньше ресурсов (CPU, памяти, диска), что позволяет базе данных обслуживать больше пользователей и операций.

Масштабируемость: Эффективные запросы помогают при увеличении объема данных или числа пользователей, предотвращая деградацию производительности.

Снижениеコスト: Меньшая нагрузка на базу данных может означать меньшие затраты на инфраструктуру.

Стабильность системы: Неоптимизированные запросы могут вызывать блокировки, тайм-ауты и сбои в работе всей системы.

Нереляционные (NoSQL) базы данных отличаются от реляционных гибкой схемой, горизонтальной масштабируемостью и оптимизацией под специфические типы данных и нагрузки.

Основные типы NoSQL БД:

Key-Value: Простейшая структура, где данные хранятся как пары ключ-значение. Быстрый доступ по ключу. Примеры: Redis, DynamoDB.

Document: Данные хранятся в документах (например, JSON, XML), имеющих произвольную структуру. Удобны для иерархических данных. Примеры: MongoDB, Couchbase.

Column-Family: Данные организованы в столбцы, объединённые в семейства. Оптимизированы для агрегации и анализа больших объемов данных. Примеры: Cassandra, HBase.

Graph: Данные представлены в виде узлов (вершин) и связей (ребер). Эффективны для моделирования сложных зависимостей. Примеры: Neo4j, ArangoDB.

Преимущества NoSQL:

Гибкость схемы: Легко адаптироваться к меняющимся требованиям к данным.

Горизонтальная масштабируемость: Проще распределить данные по нескольким серверам для увеличения производительности и надежности.

Высокая производительность: Для специфических задач могут быть намного быстрее реляционных БД благодаря оптимизированной структуре.

Недостатки NoSQL:

Зависимость от типа данных: Не все типы NoSQL подходят для всех задач.

Сложность запросов: SQL часто более выразителен для сложных join-операций.

Меньшая зрелость экосистемы: По сравнению с реляционными БД (хотя разрыв сокращается).

Отсутствие строгих гарантий ACID (у многих): Могут жертвовать частью свойств ACID в пользу производительности и доступности.

Я имею опыт работы с MongoDB для хранения данных с переменной структурой и Redis для кеширования и управления сессиями.

Выбор между реляционной и нереляционной БД зависит от специфики проекта, типа данных, требуемой масштабируемости и структуры запросов. Часто в проектах используется комбинация обеих типов (полиглотное хранение).

Функция round(number, ndigits) округляет число number до ndigits знаков после запятой. Если ndigits опущен, округляет до ближайшего целого. Важно помнить, что round() может возвращать результат с "плавающей точкой", что может привести к небольшим неточностям из-за особенностей представления чисел с плавающей запятой.

Форматирование строк (с использованием f-строк или метода format()) также позволяет округлить число до определенного количества знаков после запятой. Преимуществом этого подхода является то, что он всегда возвращает строку, которая может быть полезна для вывода.

Тип данных считается изменяемым (mutable), если его состояние может быть изменено после создания. Неизменяемым (immutable) - если не может.

Проверить это можно несколькими способами:

Проверка по умолчанию: Знание стандартных типов данных Python.

Изменяемые: list, dict, set, bytearray.

Неизменяемые: int, float, str, tuple, frozenset, bytes.

Попытка изменения: Попробовать изменить объект. Если это возможно без создания нового объекта, тип изменяемый.

# Изменяемый пример: list

my_list = [1, 2, 3]

print(id(my_list)) # Адрес в памяти до изменения

my_list.append(4)

print(id(my_list)) # Адрес в памяти после изменения (тот же)

# Неизменяемый пример: str

my_str = "hello"

print(id(my_str)) # Адрес в памяти до изменения

# my_str[0] = 'H' # Это вызовет TypeError

my_str = my_str.upper()

print(id(my_str)) # Адрес в памяти после "изменения" (новый адрес)

Использование id(): Сравнить идентификатор объекта до и после попытки изменения. Если id() не меняется, тип, вероятно, изменяемый (при условии, что операция не создает новый объект). Если id() меняется, это подтверждает, что для "изменения" был создан новый объект, и тип неизменяемый.

# Использование id() с изменяемым типом (list)

a = [1, 2]

print(id(a))

a.append(3) # Изменяем объект in-place

print(id(a)) # id() остается тем же

# Использование id() с неизменяемым типом (tuple)

b = (1, 2)

print(id(b))

# b = b + (3,) # Эта операция создает новый кортеж

# print(id(b)) # id() будет отличаться

Проверка наличия методов изменения: Изменяемые типы данных часто имеют методы, которые модифицируют объект на месте (например, append, extend, pop, sort для списков; update, pop, clear для словарей).

# Проверка наличия метода append для списка

if hasattr([1, 2], 'append'):

print("Объект, похоже, изменяемый (список)")

# Проверка наличия метода upper для строки

if hasattr("hello", 'upper') and not hasattr("hello", 'append'):

print("Объект, похоже, неизменяемый (строка)")

Наиболее надежный и фундаментальный подход — это id(), демонстрирующий, изменился ли сам объект или был создан новый.

Упорядоченные коллекции сохраняют порядок добавления элементов, обеспечивая доступ к элементам по индексу. Неупорядоченные коллекции не гарантируют сохранение порядка элементов.

Пример с упорядоченным списком:

Пример с неупорядоченным множеством:

В Python итератор — это объект, который реализует методы __iter__() и __next__(). Метод __iter__() возвращает сам объект-итератор, а __next__() возвращает следующий элемент последовательности. Когда элементы заканчиваются, __next__() вызывает исключение StopIteration.

Из встроенных типов данных и функций, которые возвращают итераторы, можно выделить:

Списки (list)

Кортежи (tuple)

Строки (str)

Множества (set)

Словари (dict) (по умолчанию итерируются по ключам)

Итераторы, создаваемые генераторными выражениями или функциями-генераторами.

Функции range(), enumerate(), zip(), map(), filter().

Примеры:

Можно создать свои собственные итераторы, реализовав соответствующие методы.

Паттерн "Наблюдатель" (Observer) — это поведенческий паттерн проектирования, который определяет отношение "один ко многим" между объектами. Когда один объект (издатель) изменяет состояние, все его зависимые объекты (подписчики) автоматически уведомляются и обновляются.

Основные участники паттерна:

Издатель (Subject): Объект, за состоянием которого наблюдают. Предоставляет интерфейс для регистрации, отмены регистрации и уведомления наблюдателей.

Наблюдатель (Observer): Объект, который хочет получать обновления от издателя. Предоставляет интерфейс для получения уведомлений.

Принцип работы:

Наблюдатели подписываются на уведомления издателя.

При изменении состояния издателя, он перебирает список зарегистрированных наблюдателей и вызывает их методы обновления.

Наблюдатели получают уведомление и выполняют соответствующие действия, часто запрашивая у издателя обновленные данные.

Преимущества:

Низкая связность: Издатель не знает конкретные классы наблюдателей, взаимодействуя только через общий интерфейс.

Динамическое изменение зависимостей: Наблюдатели могут добавляться или удаляться во время выполнения.

Повторное использование: Издатели и наблюдатели могут быть повторно использованы независимо друг от друга.

Недостатки:

Сложность: Может быть сложно понять логику взаимодействия при большом количестве наблюдателей и издателей.

Порядок уведомления: Нет гарантии порядка, в котором будут уведомлены наблюдатели, если это не реализовано специально.

Пример реализации в Python:

Где применяется:

Системы событий и уведомлений.

Графические пользовательские интерфейсы (GUI) для обновления элементов при изменении данных.

Разработка реактивных систем.

Системы публикации/подписки.

Прототип адаптивного сайта начнем с мобильного подхода (mobile-first).

Определение основных брейкпойнтов: Начнем с минимального размера экрана (например, 320px) и определим ключевые размеры, на которых верстка должна меняться.

Создание базового макета для мобильных: Разработаем HTML-структуру и CSS-стили для самой маленькой версии сайта. Уделим внимание читаемости текста, размерам элементов управления и использованию пространства.

Применение медиазапросов: Используем @media правила для изменения стилей при достижении определенных брейкпойнтов. Сначала адаптируем под планшеты, затем под десктопы.

Гибкие элементы: Используем относительные единицы измерения (%, em, rem, vw, vh) и гибкие контейнеры (flexbox, grid) для создания резиновой верстки.

Оптимизация изображений: Применим атрибуты srcset и sizes для элемента <img> или используем элемент <picture> для загрузки изображений, оптимизированных под разные разрешения.

Тестирование: Регулярно тестируем прототип на различных устройствах и в разных браузерах. Используем инструменты разработчика для эмуляции разных разрешений.

Пример структуры с медиазапросами:

Важные аспекты:

Использование мета-тега viewport: <meta name="viewport" content="width=device-width, initial-scale=1.0"> в <head>.

Прогрессивное улучшение: Сначала создаем рабочую версию для всех, затем добавляем более сложные элементы для устройств с расширенными возможностями.

Прототипирование на основе дизайн-системы: Использование готовых компонентов и стилей поможет ускорить разработку.

Использование CSS-фреймворков (Bootstrap, Tailwind CSS), если уместно для проекта.

Итоговая структура HTML будет включать основные разделы (хедер, навигация, контент, футер) с классами, которые будут реагировать на медиазапросы. Пример:

Курсор (Cursor) в контексте баз данных и их взаимодействия с Python (например, с библиотеками вроде psycopg2 или sqlite3) представляет собой объект, который позволяет выполнять команды SQL и получать результаты.

Он является точкой взаимодействия между приложением на Python и базой данных.

Для чего он необходим:

Выполнение SQL-запросов: Через объект курсора вызываются методы (например, execute()), которые отправляют SQL-команды в базу данных.

Обработка результатов: Курсор предоставляет методы для получения данных, возвращаемых запросами (например, fetchone(), fetchall(), fetchmany()).

Управление транзакциями (иногда): Хотя основные методы управления транзакциями (commit(), rollback()) обычно находятся на объекте соединения (connection), курсор работает в рамках текущей транзакции, связанной с соединением.

Итерация по результатам: Курсор может быть итерируемым объектом, позволяя легко перебирать строки результата запроса.

Простым языком, если соединение с базой данных - это "трубопровод", то курсор - это "кран" на этом трубопроводе, через который проходят запросы и данные.

Пример использования с sqlite3:

json.dumps (то s в конце означает string) преобразует Python-объект в строку в формате JSON.

json.dump преобразует Python-объект в строку в формате JSON и записывает ее в файлоподобный объект (например, файл) по указанному пути.

json.loads (опять же, s означает string) преобразует строку в формате JSON обратно в Python-объект.

json.load считывает данные из файлоподобного объекта (например, файла) в формате JSON и преобразует их обратно в Python-объект.

Сводная таблица различий:

WebSocket — протокол для двусторонней связи поверх TCP/IP, который устанавливает постоянное соединение между клиентом и сервером, позволяя им обмениваться данными в реальном времени.

"Обычные сокеты" (чаще всего имеются в виду классические TCP/IP сокеты) представляют собой низкоуровневый программный интерфейс (API) для сетевого обмена данными. WebSocket использует TCP/IP-сокеты как транспортный уровень, но добавляет поверх них логику протокола для установления и поддержания постоянного соединения в вебе.

Ключевые отличия:

Протокол уровня приложения: WebSocket — это протокол уровня приложения (Application Layer), разработанный специально для веб-приложений, позволяющий избежать накладных расходов HTTP-соединений (например, постоянного переустановления соединения). "Обычные сокеты" — это API для взаимодействия на транспортном уровне (Transport Layer), не имеющий собственного протокола уровня приложения.

Установление соединения: WebSocket устанавливает соединение через рукопожатие (handshake) HTTP, которое затем "апгрейдится" до WebSocket-соединения. После этого соединение остается открытым для двусторонней передачи данных. Классические HTTP-соединения, как правило, являются кратковременными (request-response) или используют механизмы типа long polling для имитации постоянства.

Эффективность: WebSocket значительно снижает накладные расходы (header overhead) по сравнению с HTTP-запросами, так как после установления соединения передаются только сами данные, обернутые в небольшие фреймы WebSocket. Это делает их более эффективными для приложений, требующих частой передачи небольших объемов данных (например, чаты, онлайн-игры).

Двусторонняя связь: WebSocket обеспечивает полноценную дуплексную связь, позволяя серверу отправлять данные клиенту в любое время без предварительного запроса со стороны клиента. В классическом HTTP взаимодействие инициируется клиентом.

Интеграция с веб: WebSocket разработан с учетом работы через стандартные веб-порты (80/443) и совместим с существующей веб-инфраструктурой (прокси, фаерволы), что упрощает его развертывание в веб-окружении.

Сравнение в таблице:

Пример использования "обычных сокетов" на Python:

WebSocket поверх "обычных сокетов" добавляет слой протокола для управления кадрами данных, рукопожатием и поддержанием состояния соединения, специфичного для веб-окружения. Реализация WebSocket-сервера в Python обычно полагается на библиотеки типа websockets или Flask-SocketIO.

Model View Teamplay (MVT) — это архитектурный шаблон, используемый в Django.

Model: Взаимодействует с базой данных. Определяет структуру данных и бизнес-логику.# models.py

class Product:

def __init__(self, name, price):

self.name = name

self.price = price

View: Обрабатывает HTTP-запросы и возвращает HTTP-ответы. Получает данные из Model, управляет логикой представления и передает данные в Template.# views.py

def product_detail(request, product_id):

# Получение данных из Model

product = get_product_from_db(product_id)

# Передача данных в Template

return render(request, 'product_detail.html', {'product': product})

Teamplay (Template): Определяет, как данные будут представлены пользователю. В Django это HTML-шаблоны с использованием специфичного синтаксиса.<!-- product_detail.html -->

<h1>{{ product.name }}</h1>

<p>Цена: {{ product.price }}</p>

В MVT:

Template и View тесно связаны. View рендерит Template.

View является посредником между Model и Template.

Model полностью отделена от остальной части приложения.

Сравнение с MVC:

В Python нет строгих модификаторов доступа, как в C++ или Java (public, private, protected). Вместо этого используется концепция "сильной рекомендации" и "соглашения об именовании".

Видимость атрибутов и методов контролируется через префиксы в их именах:

Публичные (public): Атрибуты и методы без префиксов. Доступны извне класса.

class MyClass:

def public_method(self):

print("Это публичный метод")

obj = MyClass()

obj.public_method()

Защищенные (protected): Атрибуты и методы с префиксом _. Это соглашение, указывающее, что они предназначены для использования внутри класса и его подклассов. Доступ извне возможен, но не рекомендуется.

class MyClass:

def _protected_method(self):

print("Это защищенный метод")

obj = MyClass()

obj._protected_method() # Доступ возможен, но не рекомендуется

Приватные (private): Атрибуты и методы с префиксом __ (два нижних подчеркивания). Python выполняет "сокрытие имен" (name mangling), изменяя имя атрибута/метода, чтобы сделать прямой доступ извне сложнее.

class MyClass:

def __private_method(self):

print("Это приватный метод")

obj = MyClass()

# obj.__private_method() # Вызовет AttributeError

# Доступ через искаженное имя возможен, но не рекомендуется:

# obj._MyClass__private_method()

Влияние на видимость:

public: Видимы и доступны везде.

protected: Видимы и доступны везде, но по соглашению предназначены для внутреннего использования.

private: Видимы и доступны только внутри класса, прямое имя искажается, что затрудняет доступ извне.

Таблица сравнения:

Количество текущих соединений к базе данных можно узнать несколькими способами, зависящими от используемой СУБД.

PostgreSQL:

MySQL:

SQL Server:

Python с использованием psycopg2 (для PostgreSQL):

Python с использованием mysql.connector (для MySQL):

Python с использованием pyodbc (для SQL Server):

Также можно использовать инструменты мониторинга СУБД или операционной системы.

Методы экземпляра (instance method) работают с данными конкретного объекта, методы класса (class method) — с данными самого класса, а статические методы (static method) никак не связаны ни с экземпляром, ни с классом.

Методы экземпляра:

Принимают первым аргументом self (ссылку на экземпляр).

Имеют доступ к атрибутам экземпляра (self.attribute) и другим методам экземпляра.

Являются наиболее распространенным типом методов.

Методы класса:

Декорируются @classmethod.

Принимают первым аргументом cls (ссылку на сам класс).

Имеют доступ к атрибутам класса (cls.class_attribute) и другим методам класса.

Часто используются для создания альтернативных конструкторов или работы с данными, общими для всех экземпляров.

Статические методы:

Декорируются @staticmethod.

Не принимают ни self, ни cls.

Не имеют доступа к атрибутам экземпляра или класса.

По сути, являются обычными функциями, помещенными внутрь класса для логической группировки.

Различия в таблице:

Python традиционно считается интерпретируемым языком. Исходный код не компилируется в машинный код напрямую до выполнения. Вместо этого, он сначала компилируется в байт-код, который затем исполняется виртуальной машиной Python (CPython, PyPy и т.д.).

Этот код компилируется в байт-код.

Затем виртуальная машина Python выполняет этот байт-код.

Хотя существует этап компиляции в байт-код, именно выполнение байт-кода интерпретатором является ключевым аспектом, определяющим его как интерпретируемый язык в общепринятом смысле.

Существуют также альтернативные реализации Python, такие как PyPy, использующие JIT (Just-In-Time) компиляцию, что может размывать это различие, но стандартная реализация CPython работает по интерпретируемому принципу.

Сравнивая с полностью компилируемыми языками, такими как C или C++, где исходный код компилируется непосредственно в машинный код, Python требует этапа выполнения в виртуальной машине.

Middleware в Django - это пошагово выполняемые промежуточные функции, которые обрабатывают входящие запросы и исходящие ответы на разных стадиях их жизненного цикла. Они позволяют модифицировать или дополнять стандартное поведение обработки HTTP-запросов/ответов.

Основные стандартные middleware включают:

django.contrib.sessions.middleware.SessionMiddleware: Обеспечивает поддержку сессий, позволяя хранить данные между запросами пользователя.

django.contrib.auth.middleware.AuthenticationMiddleware: Ассоциирует пользователя с текущим запросом, используя сессии или другие методы аутентификации.

django.contrib.messages.middleware.MessageMiddleware: Предоставляет систему сообщений для отображения одноразовых уведомлений пользователю (например, после успешного сохранения данных).

django.middleware.csrf.CsrfViewMiddleware: Защищает от CSRF-атак, проверяя наличие и корректность CSRF-токена в POST-запросах.

django.middleware.security.SecurityMiddleware: Добавляет различные механизмы безопасности, такие как перенаправление HTTP на HTTPS, установка заголовков безопасности (X-XSS-Protection, HSTS и др.).

django.middleware.common.CommonMiddleware: Выполняет общие задачи, такие как добавление слэша к URL при необходимости или обработка ETag.

Middleware позволяют:

Перехватывать и модифицировать входящие запросы перед их обработкой представлением (view).

Модифицировать исходящие ответы перед их отправкой клиенту.

Выполнять действия до или после вызова представления.

Обрабатывать исключения, возникающие в процессе обработки запроса.

Централизованно управлять логикой, которая применяется ко всем или определенным запросам (например, аутентификация, логирование, проверка прав).

Пример пользовательского middleware:

PEP 8 — это официальный стиль кодирования для Python, который описывает рекомендации по оформлению кода. Его цель — сделать код более читаемым и единообразным, чтобы облегчить совместную работу и поддержку.

Основные моменты PEP 8:

Отступы 4 пробела.

Максимальная длина строки — 79 символов.

Именование переменных, функций и классов (snake_case для функций и переменных, CamelCase для классов).

Использование пробелов вокруг операторов и после запятых.

Разделение функций и классов двумя пустыми строками.

Соблюдение PEP 8 помогает избежать неоднозначностей и улучшает качество кода. Для автоматической проверки часто используют инструменты типа flake8 или pylint.

Небольшие фрагменты данных, которые веб-сайт отправляет в браузер пользователя и сохраняет на его устройстве. Используются для запоминания информации о пользователе, такой как предпочтения, состояние авторизации или содержимое корзины покупок, между сеансами или посещениями сайта.

Основные типы:

Сессионные (Session Cookies): Временно хранятся в памяти и удаляются при закрытии браузера.

Постоянные (Persistent Cookies): Хранятся на диске пользователя в течение заданного срока или до ручного удаления.

Основные атрибуты:

Name: Имя cookie.

Value: Значение cookie.

Domain: Домен, для которого действуют cookie.

Path: Путь внутри домена, для которого действуют cookie.

Expires / Max-Age: Срок действия cookie.

Secure: cookie отправляются только по HTTPS.

HttpOnly: cookie недоступны через JavaScript.

Передача cookie происходит между сервером и браузером посредством заголовков HTTP:

Сервер отправляет cookie в заголовке Set-Cookie.

Браузер отправляет сохраненные cookie в заголовке Cookie при последующих запросах к тому же домену.

Пример работы в Flask:

Влияние на приватность:

Cookie могут использоваться для отслеживания активности пользователя по различным сайтам, что вызывает вопросы о приватности. Потому существуют механизмы для контроля и управления cookie в браузерах.

Паттерн "Адаптер" (Adapter) преобразует интерфейс одного класса в другой, который ожидает клиент. Он полезен, когда нужно использовать существующий класс, но его интерфейс не соответствует требуемому. Создается класс-обертка (адаптер), который содержит экземпляр адаптируемого класса и реализует требуемый интерфейс, делегируя вызовы соответствующим методам адаптируемого объекта.

Типы адаптеров:

Объектный адаптер: Использует композицию. Адаптер содержит ссылку на адаптируемый объект.

Классовый адаптер: Использует множественное наследование. Адаптер наследуется как от требуемого интерфейса, так и от адаптируемого класса (менее распространен в Python, так как множественное наследование может быть сложным).

Пример объектного адаптера:

Предположим, у нас есть класс OldSystem, который мы хотим использовать, но его метод называется old_request, а ожидается метод new_request.

Применяется для интеграции старых систем с новыми, использования сторонних библиотек с несовместимыми интерфейсами.

Плюсы:

Позволяет классам с несовместимыми интерфейсами работать вместе.

Улучшает повторное использование кода.

Отделяет интерфейс от реализации.

Минусы:

Может увеличить количество классов в проекте.

Добавляет дополнительный уровень косвенности.

Функция wait() встречается в контексте многопроцессности, многопоточности и сетевого взаимодействия. В стандартной библиотеке Python она чаще всего относится к:

Модуль subprocess: ожидание завершения дочернего процесса.

Модуль threading: ожидание завершения выполнения потока.

Модуль socket: ожидание событий на сокете (при использовании низкоуровневых функций).

В subprocess:

Метод wait() объекта Popen блокирует выполнение текущей программы до тех пор, пока дочерний процесс не завершится. Возвращает код завершения дочернего процесса.

В threading:

Метод join() объекта Thread (по сути, аналог wait() для потоков) блокирует выполнение текущего потока до тех пор, пока поток, на котором вызван join(), не завершится.

В socket:

В низкоуровневых сокетах, при использовании select или poll, концепция ожидания реализуется через блокирование вызова этих функций до тех пор, пока не произойдет событие ввода-вывода на каком-либо из отслеживаемых сокетов. Хотя прямого метода wait() нет, блокирующий характер этих функций эквивалентен ожиданию.

Важно понимать, что wait() (или его аналоги) приводит к блокировке. В асинхронном программировании (с использованием asyncio) используются другие механизмы для ожидания, такие как await.

Компилируемый язык переводится в машинный код компилятором до выполнения программы. Результат этого процесса - исполняемый файл. Интерпретируемый язык выполняется построчно интерпретатором во время выполнения.

Python относится к интерпретируемым языкам, хотя его выполнение включает байт-код и виртуальную машину (CPython), что добавляет слой абстракции.

Литералы - это фиксированные значения, представляющие собой прямые текстовые представления основных типов данных. Они используются для создания объектов этих типов без вызова конструкторов или функций.

Основные типы литералов:

Строковые литералы: Последовательность символов в одинарных, двойных, тройных одинарных или тройных двойных кавычках.'одинарные кавычки'

"двойные кавычки"

'''тройные одинарные

для многострочных

строк'''

"""тройные двойные

для многострочных

строк"""

Числовые литералы:

Целочисленные (int): Представляют целые числа. Могут быть в десятичной, двоичной (префикс 0b), восьмеричной (0o) или шестнадцатеричной (0x) системах счисления.123

0b1010 # двоичное 10

0o17 # восьмеричное 15

0xFF # шестнадцатеричное 255

С плавающей точкой (float): Представляют вещественные числа с десятичной точкой или в экспоненциальной форме.3.14

1.0e-5 # 0.00001

Комплексные (complex): Представляют числа с мнимой частью.1 + 2j

1.0e-5 # 0.00001

Логические литералы: Представляют булевы значения.True

False

Специальный литерал None: Представляет отсутствие значения.None

Коллекции (частично литералы): Синтаксис для создания списков, кортежей и словарей часто рассматривается как литеральная форма.

Списки (list): Элементы в квадратных скобках.[1, 'a', True]

Кортежи (tuple): Элементы в круглых скобках (скобки обязательны только для пустого кортежа или кортежа из одного элемента).(1, 2, 'b')

() # пустой кортеж

(5,) # кортеж из одного элемента

Множества (set): Элементы в фигурных скобках (кроме пустого множества).{1, 2, 3}

# Для пустого множества используется set()

Словари (dict): Пары ключ-значение в фигурных скобках.{'имя': 'Иван', 'возраст': 30}

{} # пустой словарь

() # пустой кортеж

{} # пустой словарь

"Правильный код" для меня — это код, который соответствует следующим критериям, по приоритету:

Функциональность и корректность: Код решает поставленную задачу, работает без ошибок и соответствует требованиям.

Читаемость и ясность: Код легко понять другим разработчикам (и мне в будущем). Это достигается за счет:

Осмысленных имен переменных, функций и классов.

Последовательного стиля оформления (PEP 8 в Python).

Разбиения на логические блоки.

Минимального использования "магии", если это не оправдано.

Поддерживаемость: Код легко модифицировать, дополнять и отлаживать. Это связано с читаемостью, но также включает:

Слабую связанность компонентов.

Модульность.

Покрытие тестами.

Модульность.

Покрытие тестами.

Производительность (при необходимости): Код эффективен с точки зрения использования ресурсов (времени и памяти), когда это критично для задачи. Преждевременная оптимизация не приветствуется.

Тестируемость: Код спроектирован таким образом, чтобы его можно было легко покрыть автоматизированными тестами.

Пример простого, но "правильного" кода, иллюстрирующего читаемость и ясность:

Пример менее "правильного" кода (менее читаемого):

В итоге, "правильный код" — это баланс между работоспособностью, понятностью и возможностью дальнейшего развития.

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

Буду использовать аргументацию, основанную на снижении рисков:

Предложу внедрение инструментов Continuous Integration, которые будут автоматически запускать написанные тесты при каждом коммите, что позволит оперативно выявлять проблемы.

В случае категорического отказа заказчика от инвестиций в тесты, я буду фиксировать риски, связанные с их отсутствием, в документации, чтобы снизить будущую ответственность команды в случае возникновения проблем. Однако, основной упор буду делать на постепенное внедрение и демонстрацию ценности тестов.

__new__ - это метод класса, отвечающий за создание нового экземпляра класса. Он вызывается до __init__ и определяет, какой объект будет возвращен. Чаще всего используется для создания уникальных объектов (Singleton) или объектов неизменяемого типа (вроде tuple или str).

Сравнение __new__ и __init__:

__new__:

Метод статический (cls - первый аргумент).

Вызывается ДО создания экземпляра.

Возвращает созданный экземпляр объекта.

__new__:

__init__:

Метод экземпляра (self - первый аргумент).

Вызывается ПОСЛЕ создания экземпляра.

Не возвращает значений (неявно возвращает None).

Используется для инициализации атрибутов созданного объекта.

__init__:

Poetry — это инструмент для управления зависимостями и сборки пакетов в проектах на Python.

Преимущества Poetry:

Детерминированное управление зависимостями: Poetry использует файл poetry.lock, который фиксирует точные версии всех установленных зависимостей, обеспечивая воспроизводимость рабочего окружения.

Изоляция зависимостей: Каждый проект имеет свое виртуальное окружение, изолируя зависимости от других проектов и системы.

Упрощенное управление зависимостями: Команды для добавления, удаления и обновления зависимостей интуитивно понятны и просты.

Встроенный менеджер виртуальных окружений: Poetry сам создает и управляет виртуальными окружениями, избавляя от необходимости использовать venv или virtualenv отдельно.

Управление публикацией пакетов: Poetry упрощает процесс сборки и публикации пакетов на PyPI.

Файл pyproject.toml: Все настройки проекта (зависимости, метаданные и т.д.) хранятся в едином файле pyproject.toml, который соответствует стандарту PEP 518.

Пример добавления зависимости:

Пример удаления зависимости:

Пример установки зависимостей из lock-файла:

Пример структуры pyproject.toml:

Оператор ** в Python выполняет операцию возведения в степень. Он вычисляет результат возведения первого операнда в степень, указанную вторым операндом.

Приоритет оператора ** выше, чем у унарных операторов (- и +) и бинарных арифметических операторов (*, /, %, //, +, -).

Таблица приоритетов (от высшего к низшему, упрощенно):

** является правоассоциативной операцией, что означает, что выражения с несколькими операторами ** вычисляются справа налево.

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

Первичный ключ (Primary Key, PK) — это столбец или набор столбцов в таблице, который однозначно идентифицирует каждую уникальную запись (строку).

Свойства:

Каждая таблица должна иметь первичный ключ.

Значения в первичном ключе должны быть уникальными.

Значения в первичном ключе не могут содержать NULL.

Внешний ключ (Foreign Key, FK) — это столбец или набор столбцов в одной таблице (таблице-потомке), который ссылается на первичный ключ другой таблицы (родительской таблицы).

Назначение:

Создает связь между двумя таблицами.

Обеспечивает целостность ссылочных данных, гарантируя, что значения во внешнем ключе существуют в связанном первичном ключе.

Динамическая типизация: тип переменной определяется во время выполнения программы. Одна и та же переменная может хранить данные разных типов.

Статическая типизация: тип переменной определяется во время компиляции (или интерпретации до выполнения). После объявления переменная может хранить данные только определенного типа.

Основные различия:

Пример на Python (динамическая):

Пример на Java (статическая):

SQLite - это легковесная, автономная, бессерверная, самодостаточная транзакционная база данных. Данные хранятся в одном файле.

SQL Server Express - бесплатная редакция Microsoft SQL Server. Серверная база данных, требующая установки и управления сервером.

Основные различия:

В Python типы данных делятся на изменяемые (mutable) и неизменяемые (immutable).

Изменяемые типы данных могут быть изменены после создания. При изменении объекта изменяемого типа, сам объект остается тем же, но его содержимое меняется.

Неизменяемые типы данных не могут быть изменены после создания. Любая операция, которая, казалось бы, изменяет объект неизменяемого типа, на самом деле создает новый объект.

Примеры:

Примеры кода:

Неизменяемый int:

a = 5

b = a

print(id(a)) # Выводим id объекта

a += 1

print(id(a)) # Выводим id того же объекта после "изменения" - он другой

print(b) # b осталось прежним

Неизменяемый int:

Изменяемый list:

list1 = [1, 2, 3]

list2 = list1

print(id(list1)) # Выводим id списка

list1.append(4)

print(id(list1)) # Выводим id списка после добавления элемента - он тот же

print(list2) # list2 также изменился

Изменяемый list:

Различие между ними имеет значение для поведения переменных и передачи аргументов в функции. При передаче изменяемого объекта в функцию, изменения внутри функции будут видны вне ее. При передаче неизменяемого объекта, изменения внутри функции будут создавать новый локальный объект.

C++ — компилируемый, статически типизированный язык низкого уровня. Python — интерпретируемый, динамически типизированный язык высокого уровня.

Основные отличия:

Типизация: В C++ типы переменных определяются при компиляции и не могут изменяться (статическая). В Python типы переменных определяются во время выполнения и могут меняться (динамическая).

Скорость выполнения: Код C++ компилируется в машинный код, что обеспечивает высокую скорость выполнения. Python интерпретируется, что делает его медленнее C++ для вычислительно сложных задач.

Управление памятью: C++ требует ручного управления памятью (выделение/освобождение). Python имеет автоматическое управление памятью (сборщик мусора).

Синтаксис: Синтаксис Python более лаконичный и читаемый, чем у C++.

Область применения: C++ часто используется для системного программирования, разработки операционных систем, игр, высокопроизводительных приложений. Python — для веб-разработки, анализа данных, машинного обучения, автоматизации, скриптов.

Кроссплатформенность: Python более кроссплатформенный "из коробки". Код C++ может требовать изменений при компиляции под разные платформы.

Пример отличий в синтаксисе:

Фабрика декораторов – это функция, которая возвращает декоратор. Она принимает аргументы для настройки поведения возвращаемого декоратора. Это позволяет создавать многократно используемые декораторы с различным поведением в зависимости от переданных параметров.

Основные шаги:

Определяется внешняя функция (фабрика) с параметрами.

Внутри фабрики определяется функция-декоратор, которая принимает декорируемую функцию.

Внутри функции-декоратора определяется функция-обертка, которая содержит логику декоратора и может использовать параметры из фабрики и аргументы декорируемой функции.

Функция-декоратор возвращает функцию-обертку.

Фабрика возвращает функцию-декоратор.

Это полезно для создания гибких и настраиваемых декораторов.

Используются встроенные строковые методы ``.upper()и.lower()`.

Таблица сравнения:

Асинхронность и многопоточность — подходы к выполнению параллельных задач.

Многопоточность (multithreading) подразумевает создание и выполнение нескольких потоков в рамках одного процесса. Потоки выполняются параллельно (на многоядерных системах) или псевдопараллельно (на одноядерных системах через переключение контекста). Подходит для задач, интенсивно использующих CPU.

import threading

import time

def task(name):

# Имитация работы

time.sleep(1)

print(f"Поток {name} завершен")

threads = []

for i in range(3):

thread = threading.Thread(target=task, args=(i,))

threads.append(thread)

thread.start() # Запуск потока

for thread in threads:

thread.join() # Ожидание завершения потока

print("Все потоки завершены")

Асинхронность (asynchronous) основана на использовании одного потока выполнения (или небольшого количества потоков) с событийно-ориентированной архитектурой. Вместо ожидания завершения блокирующей операции (например, ввод-вывод), задача "отдает" управление, позволяя другим задачам выполняться, а затем "возобновляется", когда операция завершена. Подходит для I/O-bound задач.

import asyncio

import time

async def async_task(name):

# Имитация асинхронной блокирующей операции (например, сетевого запроса)

await asyncio.sleep(1)

print(f"Асинхронная задача {name} завершена")

async def main():

tasks = []

for i in range(3):

task = asyncio.create_task(async_task(i))

tasks.append(task)

await asyncio.gather(*tasks) # Ожидание завершения асинхронных задач

if __name__ == "__main__":

asyncio.run(main())

print("Все асинхронные задачи завершены")

Основные отличия:

Для реализации связи "многие ко многим" в SQL используется промежуточная (связующая) таблица. Эта таблица содержит два внешних ключа, которые ссылаются на первичные ключи двух таблиц, между которыми устанавливается связь.

Предположим, у нас есть таблицы authors (авторы) и books (книги), и мы хотим реализовать связь, где один автор может написать много книг, и одна книга может быть написана многими авторами.

Создание таблиц сущностей: Создаем основные таблицы authors и books.

-- Таблица авторов

CREATE TABLE authors (

author_id INT PRIMARY KEY,

name VARCHAR(255) NOT NULL

);

-- Таблица книг

CREATE TABLE books (

book_id INT PRIMARY KEY,

title VARCHAR(255) NOT NULL

);

Создание связующей таблицы: Создаем таблицу author_book, которая будет связывать авторов и книги. Эта таблица будет содержать внешние ключи, ссылающиеся на author_id и book_id.

-- Связующая таблица для связи Many-to-Many

CREATE TABLE author_book (

author_id INT,

book_id INT,

PRIMARY KEY (author_id, book_id), -- Составной первичный ключ для уникальности пары

FOREIGN KEY (author_id) REFERENCES authors(author_id),

FOREIGN KEY (book_id) REFERENCES books(book_id)

);

PRIMARY KEY (author_id, book_id) гарантирует, что каждая пара (автор, книга) уникальна в связующей таблице, предотвращая дублирование связей.

FOREIGN KEY ограничения обеспечивают ссылочную целостность, гарантируя, что author_id и book_id в таблице author_book соответствуют существующим записям в таблицах authors и books соответственно.

Вставка данных: Вставляем данные в таблицы.

-- Вставка данных в таблицу авторов

INSERT INTO authors (author_id, name) VALUES

(1, 'Author A'),

(2, 'Author B'),

(3, 'Author C');

-- Вставка данных в таблицу книг

INSERT INTO books (book_id, title) VALUES

(101, 'Book 1'),

(102, 'Book 2'),

(103, 'Book 3');

-- Вставка данных в связующую таблицу (связи между авторами и книгами)

INSERT INTO author_book (author_id, book_id) VALUES

(1, 101), -- Author A написал Book 1

(1, 102), -- Author A написал Book 2

(2, 101), -- Author B написал Book 1

(3, 103), -- Author C написал Book 3

(1, 103); -- Author A написал Book 3

Запрос данных: Для получения связанных данных используются операции JOIN.

Получить книги, написанные определенным автором:

SELECT b.title

FROM books b

JOIN author_book ab ON b.book_id = ab.book_id

WHERE ab.author_id = 1; -- Книги, написанные автором с ID = 1

Получить авторов определенной книги:

SELECT a.name

FROM authors a

JOIN author_book ab ON a.author_id = ab.author_id

WHERE ab.book_id = 101; -- Авторы книги с ID = 101

Получить все книги с их авторами:

SELECT b.title, a.name

FROM author_book ab

JOIN books b ON ab.book_id = b.book_id

JOIN authors a ON ab.author_id = a.author_id;

SELECT b.title

FROM books b

SELECT a.name

FROM authors a

FROM author_book ab

Таблица author_book является ключом к реализации M2M, поскольку она хранит все пары связанных сущностей.

EXPLAIN (или EXPLAIN PLAN в зависимости от СУБД) — это команда SQL, которая предоставляет информацию о том, как планировщик запросов базы данных намеревается выполнить SQL-запрос. Она не выполняет сам запрос, а анализирует его и выдает пошаговый план выполнения.

План выполнения включает:

Порядок таблиц: В каком порядке будут сканироваться или объединяться таблицы.

Методы доступа: Какие индексы будут использоваться (или будет выполнено полное сканирование таблицы).

Методы соединения: Как будут соединяться таблицы (например, nested loop, hash join, merge join).

Фильтрация: Когда и как будут применяться предикаты (условия WHERE).

Сортировка и группировка: Как будут выполняться операции ORDER BY и GROUP BY.

Информация, предоставляемая EXPLAIN, помогает:

Понять, почему запрос работает медленно.

Определить отсутствующие или неэффективные индексы.

Оптимизировать структуру запроса или схемы базы данных.

Синтаксис варьируется между СУБД. Например, в PostgreSQL и MySQL:

Результат EXPLAIN обычно представляет собой набор строк, описывающих каждый шаг выполнения запроса. Интерпретация вывода требует знания специфики конкретной СУБД.

Теорема CAP (Consistency, Availability, Partition Tolerance) утверждает, что в распределенной системе одновременно может быть обеспечено только два из трех свойств:

Согласованность (Consistency): Все узлы видят одни и те же данные в одно и то же время. Если данные изменены на одном узле, это изменение немедленно отражается на всех остальных.

Доступность (Availability): Система гарантирует, что каждый запрос получает ответ (неошибочный). Любой рабочий узел может обрабатывать запросы.

Устойчивость к разделению (Partition Tolerance): Система продолжает работать, даже если из-за сетевых проблем происходит разделение на несколько изолированных кластеров, и узлы не могут общаться друг с другом.

При сетевом разделении (Partition Tolerance), система вынуждена выбирать между согласованностью и доступностью:

CP система: Предпочитает Consistent (Согласованность) перед Available (Доступность). Во время разделения система может стать недоступной, чтобы гарантировать, что все узлы, которые доступны, видят согласованные данные. Запросы на запись могут быть отклонены.

AP система: Предпочитает Available (Доступность) перед Consistent (Согласованность). Во время разделения система остается доступной, но данные могут быть несогласованными между разделами. После разрешения разделения данные должны быть синхронизированы.

CA система: Теоретически существует, но не может существовать в условиях реальной сети, где разделения неизбежны. В распределенной системе всегда приходится учитывать Partition Tolerance.

Теорема CAP не утверждает, что систему нельзя сделать одновременно согласованной и доступной в нормальных условиях без разделений. Она применима именно в момент сетевого разделения.

При выборе между Consistent и Available в условиях Partition Tolerance, решение зависит от требований к системе:

Банковские системы, где важна точность данных, часто выбирают CP.

Системы социальных сетей или интернет-магазинов, где важна непрерывная доступность, даже при некоторой задержке в синхронизации данных, часто выбирают AP.

В asyncio есть три основных вида табличных (или контейнерных) объектов для управления асинхронными операциями:

Tasks: Объекты, инкапсулирующие сопрограммы (coroutines) и позволяющие планировать их выполнение в цикле событий. Tasks представляют собой более высокоуровневую абстракцию по сравнению с сопрограммами, позволяя их отменять, проверять статус и получать результаты.

# Создание задачи из сопрограммы

async def my_coro():

await asyncio.sleep(1)

return "done"

async def main():

task = asyncio.create_task(my_coro())

result = await task

print(result)

# asyncio.run(main())

Futures: Низкоуровневые объекты, представляющие собой результат асинхронной операции, который будет доступен в будущем. Futures могут быть разрешены (resolved) с результатом или исключением. Tasks являются подклассами Futures.

# Пример работы с Future (обычно используется через Tasks)

async def set_future_result(future):

future.set_result("future done")

async def main_future():

loop = asyncio.get_running_loop()

future = loop.create_future() # Создание Future

loop.create_task(set_future_result(future))

result = await future # Ожидание результата

print(result)

# asyncio.run(main_future())

Queues: Асинхронные очереди, предназначенные для безопасного обмена данными между несколькими сопрограммами. Они реализуют асинхронные методы put() и get(), которые блокируются до тех пор, пока операция не может быть выполнена (например, очередь полна при put() или пуста при get()).

# Использование асинхронной очереди

async def producer(queue):

for i in range(5):

await queue.put(f"item {i}")

print(f"Produced item {i}")

await asyncio.sleep(0.1)

async def consumer(queue):

while True:

item = await queue.get()

print(f"Consumed {item}")

queue.task_done() # Сообщаем, что элемент обработан

if item == "item 4": # Условие выхода

break

await asyncio.sleep(0.2)

async def main_queue():

queue = asyncio.Queue() # Создание очереди

producer_task = asyncio.create_task(producer(queue))

consumer_task = asyncio.create_task(consumer(queue))

await producer_task

await queue.join() # Ожидание обработки всех элементов

consumer_task.cancel() # Отмена потребителя после обработки

try:

await consumer_task

except asyncio.CancelledError:

print("Consumer cancelled")

# asyncio.run(main_queue())

Таблица, обобщающая основные различия:

Middleware в web-разработке - это промежуточное программное обеспечение или компонент, который обрабатывает HTTP-запросы и ответы. Middleware располагается между веб-сервером и основным приложением, перехватывая запросы перед тем, как они достигнут приложения, и ответы перед тем, как они будут отправлены клиенту.

Основные функции middleware:

Обработка запросов:

Валидация данных

Аутентификация и авторизация

Логирование

Управление сессиями

Изменение заголовков запроса

Обработка запросов:

Валидация данных

Логирование

Управление сессиями

Обработка ответов:

Сжатие данных

Кэширование

Изменение заголовков ответа

Добавление информации (например, версия сервера)

Обработка ответов:

Сжатие данных

Кэширование

Принцип работы заключается в цепочке вызовов. Каждый middleware выполняет свою функцию, а затем передает запрос или ответ следующему middleware в цепочке. Последний middleware в цепочке передает запрос приложению, а ответ от приложения проходит через middleware в обратном порядке.

Примерами использования middleware в Python фреймворках являются:

Django: MIDDLEWARE настройка в файле settings.py.

Flask: Использование декораторов или подключение через расширения.

Middleware помогает в построении модульных и переиспользуемых веб-приложений, отделяя кросс-функциональную логику от основной бизнес-логики приложения.

Uvicorn: Высокопроизводительный, асинхронный ASGI-сервер, построенный на uvloop и h11. Часто используется с FastAPI и Starlette.

Hypercorn: ASGI-сервер, совместимый с HTTP/1, HTTP/2 и Quart. Поддерживает множество бэкендов, включая asyncio, uvloop и trio.

Daphne: Первый ASGI-сервер, разработанный для Django Channels. Менее производительный по сравнению с Uvicorn и Hypercorn в общих случаях, но хорошо интегрирован с экосистемой Django.

Существует несколько основных способов:

Включение логирования медленных запросов в конфигурации PostgreSQL.

В файле postgresql.conf можно настроить следующие параметры:

log_min_duration_statement: Записывает в лог запросы, выполняющиеся дольше указанного времени (в миллисекундах).

log_statement: Определяет, какие запросы логировать ('none', 'ddl', 'mod', 'all'). Для анализа медленных запросов обычно используется в сочетании с log_min_duration_statement.

После изменения postgresql.conf необходимо перезагрузить конфигурацию или сам сервер PostgreSQL. Журналы будут располагаться в каталоге данных PostgreSQL, путь к которому можно узнать из параметра log_directory.

Анализировать логи можно вручную или с помощью утилит, таких как pgBadger, которая парсит логи и генерирует читабельные отчеты.

Использование команды EXPLAIN и EXPLAIN ANALYZE.

EXPLAIN: Показывает план выполнения запроса без его фактического выполнения. Это позволяет понять, как база данных планирует получить данные, какие индексы будут использоваться, какие операции будут выполнены (сканирование таблиц, соединения и т.д.).

EXPLAIN ANALYZE: Выполняет запрос и показывает фактический план выполнения, включая время, затраченное на каждый этап, количество строк, пройденных каждым узлом плана, и использование памяти. Это дает более точное представление о производительности запроса.

При анализе вывода EXPLAIN ANALYZE следует обращать внимание на:

Типы сканирования (Seq Scan часто указывает на отсутствие нужного индекса).

Операции соединения (Nested Loop, Hash Join, Merge Join) и их эффективность.

Время, затраченное на каждый узел плана.

Использование буферов Shared Buffers.

Использование временных файлов.

Использование расширения pg_stat_statements.

Это расширение, поставляемое с PostgreSQL, собирает статистику по выполненным запросам, включая их общее время выполнения, количество вызовов, среднее время выполнения и т.д.

Чтобы использовать pg_stat_statements, его нужно включить в параметры shared_preload_libraries в postgresql.conf и перезагрузить сервер, а затем выполнить CREATE EXTENSION pg_stat_statements; в каждой базе данных, где необходимо собирать статистику.

Данные статистики доступны через представление pg_stat_statements. Самый простой способ получить список самых медленных запросов:

-- Выбрать 10 самых медленных запросов по среднему времени выполнения

SELECT query, mean_exec_time, calls, total_exec_time

FROM pg_stat_statements

ORDER BY mean_exec_time DESC

LIMIT 10;

-- Выбрать 10 запросов с наибольшим общим временем выполнения

SELECT query, total_exec_time, calls, mean_exec_time

ORDER BY total_exec_time DESC

LIMIT 10;

pg_stat_statements позволяет выявить запросы, которые чаще всего или дольше всего нагружают систему. Важно учитывать как среднее время выполнения, так и общее время выполнения (зависит от количества вызовов).

Комбинирование этих методов дает наиболее полное представление о производительности запросов в PostgreSQL и помогает выявить узкие места.

asyncio.gather() — это функция из модуля asyncio, которая позволяет выполнить несколько awaitable-объектов (например, корутины) параллельно и собрать их результаты.

Основные особенности:

Параллельное выполнение: Заданные корутины запускаются одновременно.

Ожидание завершения: gather() ждет завершения всех переданных корутин.

Сбор результатов: Результаты выполнения корутин возвращаются в виде списка в том же порядке, в котором они были переданы в gather().

Обработка исключений: По умолчанию, если одна из корутин вызывает исключение, gather() отменяет остальные незавершенные корутины и перевыбрасывает исключение. Поведение можно изменить с помощью параметра return_exceptions=True.

Пример использования:

В данном примере task_sleep("A", 2) и task_sleep("B", 1) запускаются практически одновременно. gather() ожидает их завершения и собирает их возвращаемые значения ("Result from A", "Result from B") в список.

Открыть терминал или командную строку.

Перейти в каталог, который нужно сделать репозиторием, или создать новый.# Перейти в существующий каталог

cd /путь/к/папке

# Или создать новый каталог и перейти в него

mkdir новый_репозиторий

cd новый_репозиторий

Инициализировать Git репозиторий в текущем каталоге.# Инициализация пустого Git-репозитория

git init

В каталоге появится скрытая папка .git, содержащая необходимые файлы для работы Git.

Для прерывания выполнения цикла используются операторы break и return, а также обработка исключений.

break прекращает выполнение ближайшего включающего цикла (for, while) и передает управление оператору, следующему за циклом.

return прекращает выполнение функции или метода и возвращает значение. Если цикл находится внутри функции, return прервет и цикл, и выполнение функции.

Исключения могут быть использованы для прерывания выполнения цикла, особенно если условие прерывания возникает из-за ошибки или особого состояния.

Сводная таблица методов:

В Docker при установке создаются три стандартные сети:

bridge: Сеть по умолчанию, к которой подключаются новые контейнеры, если не указано иное. Контейнеры в этой сети могут общаться друг с другом по IP-адресу, но не доступны извне без проброса портов.

host: Контейнер использует сетевой стек хоста. Нет изоляции сети между контейнером и хостом.

none: Контейнер не подключен ни к какой сети.

Пользовательские сети можно создавать дополнительно с помощью команды docker network create.

Паттерн проектирования 'Посетитель' (Visitor) позволяет добавлять новые операции (посетители) к существующей иерархии объектов (элементов) без изменения их структуры.

Преимущества:

Легкость добавления новых операций: Новая операция реализуется как новый класс посетителя.

Разделение ответственности: Логика операций отделена от структуры объектов, над которыми они выполняются.

Открыт для расширения, закрыт для модификации: Можно добавлять новые посетители без изменения классов элементов.

Недостатки:

Сложно добавить новые элементы: Добавление нового класса элемента требует модификации всех существующих классов посетителей.

Нарушение инкапсуляции: Посетитель часто нуждается в доступе к внутреннему состоянию элементов.

Применимость:

Когда имеется сложная структура объектов, и нужно выполнять разнообразные несвязанные операции над ними.

Когда часто добавляются новые операции, но структура объектов остается относительно стабильной.

Когда нужно избежать загрязнения классов элементов множеством операций.

А если вопрос прозвучит не так, как вы готовились?

Так бывает чаще всего. ИзиСобес слышит вопрос интервьюера и подсказывает ответ прямо во время разговора — его не видно ни в Zoom, ни при демонстрации экрана.

Посмотреть ИзиСобес