Python Flashcards

1
Q

Что это простыми словами

A

Язык программирования с большим количеством написанных библиотек под разные задачи – на python (пайтон) можно писать игры, вебсайты, модели машинного обучения, загрузку хранилища данных.

У дата инженеров популярен, потому что задачи для оркестратора Airflow пишут на python, и у инструмента обработки данных Spark есть интерфейс PySpark.

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

Также в DE популярны Java и Scala, редко – Rust или Go:
* Java из-за того что хадуп написан на джаве и раньше под него писали более “низкоуровневый” код. Ближе к тому что работает под капотом – меньше “переводчиков” нужно использовать
* Scala это язык, на котором можно писать “нативно” под Spark, аналогично джаве и хадупу
* Rust или Go используются для написания производительных приложений, например в высокочастотной торговле, когда важна каждая микросекунда

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Итератор vs генератор

A

Итератор это класс, для которого реализованы магические методы __iter__() и __next__(). По объектам этого класса можно будет пробегаться циклом или запрашивать next(iter(myObject)).
Вычисляет и хранит всю последовательность в памяти, ей ограничен.

class MyNumbers:
  def \_\_iter\_\_(self):
    self.a = 1
    return self

  def \_\_next\_\_(self):
     x = self.a
    self.a += 1
    return x

Генератор это функция, в которой вместо return итерируемое значение выводится в yield. Хранит только текущее значение и то, как вычислить следующее. Может работать с бесконечно большими последовательностями.

def csv_reader(file_name):
    for row in open(file_name, "r"):
        yield row

for row in csv_reader(file_name):
    print(row)

Также генератор можно задать как generator expression (генераторное выражение).

csv_gen = (row for row in open(file_name))

for row in csv_gen:
    print(row)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Основные коллекции: dict, list, set, tuple
+Изменяемые vs неизменяемые типы

A

dict, словарь – пары ключ-значение, совместим с JSON
db_conf = {“db”: “shop”, “schema”: “sales”, “table”: “orders”, “host”: “my.dbname.private.domain.com”, “port”: 5432}

Значениями могут быть число, строка, список, другой словарь, в принципе почти любой объект. Про ключи смотри “Что можно и нельзя использовать как ключ словаря”.
Поиск в словаре по ключу занимает O(1), т.к. нужно посчитать за константу хэш от ключа и обратиться напрямую по нему. В случае коллизии (когда хэш от двух разных ключей совпадает) занимает O(n).
Пробежаться по ключам словаря можно через
for key in d:
Также через in можно проверить, входит ли ключ в словарь.
Пробежаться сразу по ключам и значениям:
for key, val in d.items():

Получить значение словаря: d[“key”], добавить или перезаписать значение: d[“key”] = “value”.
Если значения нет, выдаст KeyError. Безопасно можно обратиться через d.get(“key”, <default_return_value>). Без указания второго аргумента вернёт None, если ключа нет.

Set это множество, во многом ведёт себя как словарь без значений. Есть только хэшируемые ключи, которые запоминают порядок вставки, но зато через O(1) можно проверить вхождение в сет. Сет хранит только уникальные значения (например, можно проверить, сколько уникальных значений в строке/списке, если преобразовать их в сет). Есть много уникальных методов из реляционной алгебры (union, intersect, except и пр.).
unique_labels = {‘low’, ‘mid’, ‘high’}

Список, list – одномерный массив значений, не обязательно одного типа, но обычно одного. Значениями может быть любой объект. Значения не отсортированы, но хранятся в порядке добавления.
ports = [5432, 8080, 80, 5050]

Добавить значение в конец: l.append(value).
Извлекаются или переназначаются значения по индексу, то есть порядковому номеру, который начинается с нуля и идёт до n-1: ports[1] = 8080
Также можно обращаться к значениям по индексам “с правого конца”, от -1 до -n.
Частая ошибка во время исполнения кода IndexError – обратиться по индексу, который не существует в списке.

Пробежаться сразу по индексу и значению:
for i, elem in enumerate(l):
Не используй подход ниже, это не pythonic way

for i in range(len(l)):
    print(l[i])

Лучше обращайся сразу по for elem in l: или через enumerate.

Мы не узнаем, есть ли искомое значение в массиве, пока не проверим все по порядку, поэтому поиск в списке занимает O(n).

Сортировка занимает в среднем O(n*logn).

Tuple (тапл), или кортеж, это неизменяемый объект, который хранит значения разных типов. Может использоваться для возвращения строки из БД или передачи разнородных данных в одном объекте. Хранит порядок.
row = (‘Alice’, ‘Smith’, 25, ‘2B’, [‘Ru’, ‘Ge’], False)
Для создания кортежа с одним элементом добавь запятую в конец: my_tuple = (‘elem’,)

Обращаться к элементам нужно по индексам, как в списках.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Try except else finally

A

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

Аналог try-catch из других языков программирования.

  • try: попробовать выполнить участок кода
  • except <Exception>: если ошибка, выполни этот блок кода
  • else: если исключение не вызвано, выполни код
  • finally: вне зависимости от того, вызвано исключение или нет, выполни код
d = {"key": "value"}
try:
    print(d["another_key"])
except KeyError:
    print(f"there’s no 'another_key' in d keys: {d.keys()}")
except Exception as e:
    raise e("some text I want to print")
else:
    print("do something here")
finally:
    print("почти никто не использует else и finally")

Можно вручную вызвать исключение любого типа, например:

raise TypeError("Only integers are allowed")
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

+Что можно и нельзя использовать как ключ словаря

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Context manager (with)

A

Используется при работе с контекстами – соединениями к БД или API, с файлами на диске или в объектном хранилище.
Очень важное свойство – вне зависимости от причины выхода из контекста (нормальное завершение работы или ошибка) закрывается коннект. Может пригодиться для работы с соединениями с какими-то внутренними сервисами (обычно уже написан и используется в хуке/операторе airflow и методах работы с Х в пакетах).

Выглядит так:
with open("test.txt") as f:  
    data = f.read()

with MongoDBConnectionManager('localhost', '27017') as mongo:
    collection = mongo.connection.SampleDb.test

Для написания своего контекстного менеджера определи магические методы _enter__ и __exit__
Сами детали с этими исключениями exc_type, exc_value обычно не спрашивают.

class ContextManager():
    def \_\_init\_\_(self):
        print('init method called')

    def \_\_enter\_\_(self):
        print('enter method called')
        return self
     
    def \_\_exit\_\_(self, exc_type, exc_value, exc_traceback):
        print('exit method called')

with ContextManager() as manager:
    print('with statement block')
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Распаковка списков и словарей *args kwargs

A

Некоторые функции могут работать с неограниченным количеством аргументов. Или мы хотим передать все, но использовать только некоторые из них. Тогда можно “распаковать” список или словарь через * и соответственно. Можешь встретить в операторах airflow, например.

*args представляет позиционные аргументы вида “значение”
kwargs – именованные аргументы вида “ключ-значение”

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Слайсы в списках и строках

A

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

l = [0,1,2,3,4]
l[1:]    #[1,2,3,4]
l[:1]    #[0]
l[1:3]   #[1, 2]
l[1:5:2] #[1, 3]
l[1::2]  #[1, 3]
l[1::-2] #[1]
l[::-1]  #[4, 3, 2, 1, 0]
l[-1:]   #[4]
l[-2:]   #[3, 4]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Type hints и doc-strings

A

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

При наведении на название функции IDE подтягивает это описание и предлагает автодополнение

Выглядят примерно так:

def get_columns(table:str, schema:str) -> list[str]:
    	""" Returns list of "column datatype" definitions. ""”

Также можно указывать тип для переменной или класса:

db_name: str
db_name = get_db_name(context)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

List and dict comprehensions

A

Короткая запись цикла “for” для создания и/или обработки списков и словарей: фильтрация, какая-то трансформация.

[elem for elem in list]
сolumns_to_ignore = [“col1”, “col2”]
quoted_columns = [“‘“ + col + “’” for col in columns if col not in columns_to_ignore]
{key: val for key, val in d.values()}

Устойчивая конструкция – X for X in object, и только в самом левом месте можно какие-то трансформации над этим Х проводить (ну или фильтровать после object).

Вложенный вариант пишется так:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

длинный вариант
odd_numbers = []
for row in matrix:
    for element in row:
        if element % 2 != 0:
            odd_numbers.append(element)

 # короткий вариант
odd_numbers = [
    element for row in matrix for element in row if element % 2 != 0]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

GIL + Async await, асинхронка, сборщик мусора и всё связанное

A

Чаще всего здесь тебя ждут рассказ про сборщик мусора на Python, принцип его устройства, про библиотеку Asyncio и Global Interpreter Lock.

Сборщик мусора на Python реализован через счётчик ссылок. Python считает количество ссылок на объект, и как только все ссылки на объект заканчиваются — программа вычищает объект из памяти. Это необходимо, чтобы удалять из памяти ненужные объекты и оптимизировать использование ресурсов. Помимо подсчёта ссылок, Python также использует механизм выявления циклических ссылок, которые не могут быть удалены стандартным подсчётом. Корректную работу такого сборщика мусора обеспечивает GIL.

GIL — Global Interpreter Lock, особенность языка Python, запрещающая выполнение нескольких потоков параллельно в рамках одного интерпретатора. Это ограничение необходимо для обеспечения безопасности работы с памятью в многопоточных программах. Для обхода GIL можно использовать модуль multiprocessing, который создаёт отдельные процессы и позволяет задействовать несколько ядер процессора одновременно(для ускорения тяжёлых расчётов, например), но это не является полноценным решением проблемы отсутствия многопоточности.

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

Основные компоненты asyncio включают в себя:

Event Loop (Цикл событий) — основной механизм, который управляет выполнением асинхронных задач.

Coroutines (Корутины) — специальные функции, которые могут приостанавливать своё выполнение с помощью ключевых слов async и await.

Tasks (Задачи) — обёртка над корутинами, позволяющая планировать их выполнение в цикле событий.

Futures (Будущие объекты) — представляют собой результат асинхронной операции, который станет доступным в будущем.

Можно указать на сомнительную производительность и высокую сложность Asyncio и сказать, что лучше пользоваться другими DE-инструментами, а AsyncIo использовать только в случае если необходимо работать с каким-нибудь Web API и для этого нет более подходящих инструментов, чем обычно занимается Back-end разраб.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

ООП, основные принципы

A

Объектно-Ориентированное Программирование - важно рассказать про 4 основных принципа (иногда три, исключают абстракцию, не холиварьте по этому поводу на собесе)

Инкапсуляция

Скрытие внутренней реализации объекта и предоставление доступа к данным только через методы.

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

Де-факто в Python не существует способа сделать метод или объект приватным, но в среде разработчиков принято такие методы всё равно обозначать двумя подчёркиваниями с каждой стороны ( __method__ ) и не менять их вне класса, чтобы соблюдать инкапсуляцию

Наследование

Механизм, позволяющий одному классу (потомку) использовать и расширять функциональность другого класса (родителя).

Обеспечивает повторное использование кода и создание иерархии классов

Тут важно упомянуть про функцию .super() - которая позволяет обращаться к родительскому классу

Полиморфизм - это про взаимодействие функции или класса с разными типами данных и в разных условиях +- одинаково. Например функция len() может возвращать длину строки, или кол-во элементов в списке, получая на вход разные форматы данных(строку, список или ещё что-нибудь). В случае с классами, приведу пример

class Cat:
        def make_sound(self):
            print("Meow")
class Dog:
        def make_sound(self):
            print("Bark")

cat1 = Cat()
dog1 = Dog()

for animal in (cat1, dog1):
    animal.make_sound()

При выполнении код выведет: 
Meow
Bark

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

Абстракция

Это когда мы переходим от конкретных свойств каждого класса к общим, которые объединяют несколько классов
Пример:
Есть Data Vault модель, таблицы которой описаны классами в Python. В ней есть три сущности Hub, Link, Satellite. У них есть названия - Seller_Hub, Sell_Receipt_Link, Seller_Satellite и какие-то другие общие характеристики, например наличие бизнес-ключа. Когда мы понимаем, что у всех классов есть общие характеристики логическая связь, мы можем создать новый класс-абстракцию, Entity, которая в себе будет содержать все общие характеристики сущностей модели Data Vault(В нашем случае - название и бизнес-ключ). Такой же абстракцией может быть сущность Hub над классами Seller_Hub, Buyer_Hub, Car_Hub.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

immutable vs mutable

A

Как вариант можно перечислить: set, dict, list - изменяемые
bool, float, int, str, frozenset, tuple - неизменяемые

Будет круто добавить, что изменяемые всегда лежат в одной и той же ячейке памяти(id() от объекта вернёт всегда один результат, а изменяемые - нет.

Неизменяемые не всегда на 100% не меняются, например:

t = ('holberton', [1, 2, 3])
t[1].append(4)
print(t)

Выведет: (‘holberton’, [1, 2, 3, 4])
Cписок внутри кортежа изменился - потому что он изменяемый. Но сам кортеж продолжает ссылаться на тот же список и таким образом он “не изменился”.

Также можно добавить, что неизменяемые объекты могут быть ключом в словаре, а изменяемые нет и на неизменяемые можно вешать магический метод __hash__ и по ним быстрее идёт поиск

How well did you know this?
1
Not at all
2
3
4
5
Perfectly