Главная / Блог / QA automation INTERVIEW PART 2

Собеседование на должность
QA Automation Engineer.
Основы программирования.

[ Часть 2 ]
Собеседование на должность
QA Automation Engineer.
Основы программирования.

Smartiqa Article
  • Дата: 1 сентября 2023
  • Автор: Евгений Поваров

1. Проектирование

  1. Почему глобальные переменные это плохо? 1. Нарушают инкапсуляцию (к ним открыт доступ из любой части программы), добавляют лишние зависимости между компонентами. 2. Ухудшают масштабируемость 3. Способствуют возникновению трудноуловимых ошибок.
  2. Что такое инверсия управления? Явление, при котором роль главной программы в координации и последовательности действий приложения выполняет фреймворк (а не код пользователя). В этом основное отличие фреймворка и библиотеки. Библиотека - это набор функций, которые вызываются кодом пользователя, а после окончания выполнения возвращают управление пользователю. В случае с фреймворком он сам координирует и вызывает код пользователя.
  3. Закон Деметры. Каждый модуль должен обладать минимальной информированностью о других модулях. "Не разговаривай с незнакомцами!"
  4. Принцип подстановки Барбары Лисков. Наследующий класс должен дополнять, а не замещать поведение базового класса. Если класс Б унаследован от А, то мы можем заменить в программе все использования класса А на Б и при этом в работе программы ничего не изменится.
  5. Dependency hell (Ад зависимостей). Разрастание графа зависимостей библиотек. Чем опасно? Например, несколько или даже один программный продукт может косвенно потребовать разные версии одной и той же библиотеки.
  6. Хорошо спроектированное ПО должно обладать сильным сцеплением и слабой связностью. Что это значит? Сцепление - сила зависимостей внутри модуля. Связность - сила зависимостей между разными модулями. Итог: внутри модуля должны быть сильные зависимости, а снаружи - нет.
  7. Нужны ли комментарии в коде? Нужно стараться писать код так, чтобы комментарии были не нужны.
  8. Шаблоны. Singleton (Одиночка). Гарантирует существование только одного объекта класса.
Python. Singleton pattern example.

class Singleton:
    __instance = None

    # Single call check
    def __init__(self):
        print('Constructor called!')

    @staticmethod
    def instance():
        if Singleton.__instance is None:
            Singleton.__instance = Singleton()
        return Singleton.__instance
6. Шаблоны. Observer (Наблюдатель). Наблюдаем за списком объектов. При возникновении события оповещаем каждый их них.
Python. Observer pattern example.

class CameraSystemManager:

    def __init__(self):
        self.__observers = list()

    def attach(self, observer):
        self.__observers.append(observer)

    def detach(self, observer):
        self.__observers.remove(observer)

    def notify(self):
        for observer in self.__observers:
            observer.take_photo()


class AbstractObserver(ABC):
    @abstractmethod
    def take_photo(self):
        pass


class Camera(AbstractObserver):

    def __init__(self, name):
        self.name = name

    def take_photo(self):
        print(f'{self.name}: Photo is done')


camera1 = Camera('Camera 1')
camera2 = Camera('Camera 2')

manager = CameraSystemManager()
manager.attach(camera1)
manager.attach(camera2)

manager.notify()
7. Шаблоны. Abstract Factory (Абстрактная фабрика). В приведенном примере метод create_form_with_buttons(factory) создает объекты классов Form и Button на основе переданной фабрики (LinuxFactory или WindowsFactory). Класс AbstractFactory определяет фабричные методы create_form() и create_button() и передает их по наследству классам LinuxFactory и WindowsFactory.
Python. Abstract Factory pattern example.

class AbstractFactory:

    @classmethod
    def create_form(cls, name):
        return cls.Form(name)

    @classmethod
    def create_button(cls, name):
        return cls.Button(name)


class LinuxFactory(AbstractFactory):
    class Form:
        def __init__(self, name):
            self.name = f'LinuxFactory: {name}'
            self.button = []

        def add_button(self, btn):
            self.button.append(btn)

    class Button:
        def __init__(self, name):
            self.name = f'LinuxFactory: {name}'


class WindowsFactory(AbstractFactory):
    class Form:
        def __init__(self, name):
            self.name = f'WindowsFactory: {name}'
            self.button = []

        def add_button(self, btn):
            self.button.append(btn)

    class Button:
        def __init__(self, name):
            self.name = f'WindowsFactory: {name}'


def create_form_with_buttons(factory):
    form = factory.create_form('Form 1')
    button = factory.create_button('Button 1')
    form.add_button(button)
    return form


linux_form = create_form_with_buttons(LinuxFactory)
windows_form = create_form_with_buttons(WindowsFactory)
8. Принцип YAGNI. Расшифровывается как You Aren't Gonna Need It (Вам это не понадобится). Пишем код, только если уверены, что он нужен. При рефакторинге не боимся удалять ненужные методы.
9. Принцип DRY. Расшифровывается как Don't Repeat Yourself (Не повторяйтесь). Нужно избегать дублирования кода.
10. Принцип KISS. Расшифровывается как Keep It Simple, Stupid (Будь проще). Чем проще, тем надежнее.
11. Принцип Big Design Up Front. Переводится как "Глобальное проектирование прежде всего". Сначала проектируем, и только потом приступаем к реализации.
12. Принцип SOLID. Делится на 5 принципов. 1) S (Single-responsibility principle): каждый класс/метод должен отвечать только за что-то одно. 2) O (Open–closed principle): Программные объекты должны быть открыты для расширения, но закрыты для модификации. Пример - наследование. 3) L (Liskov substitution principle): Можно заменить использование класса-родителя его потомком и в работе программы ничего не изменится. 4) I (Interface segregation principle): Объекты не должны зависеть от интерфейсов, которые они используют. То есть если мы создаем интерфейс, то не нужно делать обязательными методы, которые пригодятся только некоторым объектам, использующим этот интерфейс. Например интерфейс Animal с методами eat() и fly() - не все животные могут летать. 5) D (Dependency inversion principle): Объекты программы должны иметь слабую связность с другими объектами. Нужно полагаться на абстракции, а не на конкретные реализации. Например, можно сделать абстракцию над Datetime() классом. И тогда при смене поставщика даты, нужно будет внести изменения только внутри этой абстракции.
13. Бритва Оккама. Не нужно множить сущности без необходимости.
14. Окно Овертона. Это методика последовательного воздействия на разум общества, разделенная на 6 стадий: «Немыслимо», «Радикально», «Возможно», «Разумно», «Популярно», «Норма». Применительно к IT: когда приносим новую идею/фичу/подход на реализацию, то можем столкнуться с активным сопротивлением. Но если постепенно приходить с этой идей снова и снова (то есть двигать окно), то с большой вероятностью удастся ее популяризовать.

2. Языки программирования

  1. Императивные (процедурные и объектно-ориентированные) ЯП. При таком подходе программа представляет собой совокупность инструкций, которые изменяют состояние данных. Примеры: С++, Java, Ruby, Python.
  2. Функциональные ЯП. Обходимся вычислением результатов функций от исходных данных и результатов других функций, и не предполагаем явное хранение состояния. Примеры: Haksell, Erlang.
  3. Компилятор. Транслирует программу на языке высокого уровня в программу на низкоуровневом языке, близком машинному коду. На выходе - исполняемый файл. Пример: C++
  4. Интерпретатор. Построчно выполняет инструкции кода на высокоуровневом языке. Пример: Python
  5. Высокоуровневые ЯП. Легко читаются людьми. Не нужно знать, на каком оборудовании будет запускаться программа. Пример: Java, Python.
  6. Низкоуровневые ЯП. Учитывают требования архитектуры железа. Более быстрые и эффективные, но сложные для работы.
  7. Статическая / динамическая типизация. Статическая - типы данных выясняются на этапе компиляции (С++, Java). Динамическая - на этапе выполнения программы (Python, Ruby).
  8. Явная / неявная типизация. Явная - тип данных задает программист в коде (C++). Неявная - тип данных определяется компилятором / интерпретатором (Python).
  9. Структуры данных. Массив, Стек, Очередь, Связный список, Дерево, Граф, Хэш-таблица.
  10. Стек. Последний вошел (push), первый вышел (pop).
  11. Очередь. Первый вошел(append), первый вышел(pop).
  12. Связный список. Каждый узел списка - данные + указатель на следующий узел.
  13. Граф. Множество узлов, соединенных ребрами. Ребро может иметь вес.
  14. Дерево. Это граф, в котором нет циклов.
  15. Бинарное дерево поиска. Каждый узел может иметь 0, 1 или 2 потомка. Значение кладется в дерево так: последовательно идем от вершины дерева и сравниваем каждый узел в новым значением: если оно меньше узла, но кладем слева, если больше - то справа.
  16. Хэш-таблица. Можно представить как массив, в котором индекс элемента вычисляет как хэш-функция(свертка).
  17. Замыкание. Это функция, которая «запоминает» окружение, в котором она была создана.
Python. Closure example.

def counter():
    cnt = 0

    def current():
        nonlocal cnt
        cnt += 1
        print(cnt)

    return current


closure_function = counter()
closure_function()  # 1
closure_function()  # 2
18. Лямбда функция. Функция, которая 1) не имеет имени 2) возвращает значение одного выражения и 3) используется в коде единожды.
Python. Lambda function example.

def add(value):
    return lambda param: param + value


add_to_100_function = add(100)

a = add_to_100_function(5)  # 105
b = add_to_100_function(50)
19. Функция высокого порядка. Это функция, которая принимает или возвращает другие функции.
20. Циклы. for, while, do while.
21. Что такое линтер (linter)? Программа, которая проверяет код на соответствие набору правил по написанию кода. Сюда относятся: отступы, скобки, математические операции, длина строк и т д. Главная задача - сделать код единообразным. Популярные линтеры в Python: flake8, pylint

3. ООП

  1. Определение ООП. Методология, в которой программа - совокупность объектов, каждый из которых - экземпляр класса, а классы образуют иерархию наследования.
  2. Класс. Шаблон для создания объектов, обеспечивающий начальные значения состояний (инициализация полей и реализация методов).
  3. Объект. Экземпляр класса, имеющий определенные поля (атрибуты) и операции над ними (методы).
  4. Поля, методы. Поле - свойство объекта, метод - функция.
  5. Абстрактный класс. Класс, для которого не реализован ОДИН или БОЛЬШЕ методов. Особенности: 1. Это класс, для которого нельзя создать объект. 2. Может содержать как обычные, так и абстрактные поля и методы. 3. Не допускает множественное наследование.
  6. Абстрактный метод (виртуальный метод). Метод класса, реализация для которого отсутствует.
  7. Интерфейс. Это абстрактный класс, у которого НИ ОДИН метод не реализован, все они публичные и нет переменных класса. Любой интерфейс - это абстрактный класс, но не наоборот.
  8. Абстракция. 1. Выделяет главные свойства предмета. 2. Отбрасывает второстепенные характеристики.
  9. Инкапсуляция. Прячет внутреннюю реализацию объекта, все взаимодействия - через интерфейс.
  10. Наследование. Создаем класс на основе существующего. Потомок наследует поля и методы родителя + добавляет свои. Выражает отношение "Является" (например, Mercedes является машиной).
Python. Inheritance example.

class Human:
    def __init__(self, name):
        self.name = name

    def speak(self, phrase):
        print(f'{self.name} сказал: \'{phrase}\'')


class Doctor(Human):
    def __init__(self, name, specialization):
        super().__init__(name)
        self.specialization = specialization

    def diagnose(self):
        super().speak(f'Ваш диагноз - ОРВИ')


if __name__ == "__main__":
    doctor_alexander = Doctor('Александр', 'Терапевт')
    doctor_alexander.diagnose()
11. Композиция. Класс, известный как составной, содержит объект другого класса, известный как компонент. Выражает отношение "Имеет" (Например, машина имеет двигатель).
Python. Composition example.

class Hobby:
    def __init__(self, title):
        self.title = title

class Human:
    def __init__(self, name, hobby_title):
        self.name = name
        self.hobby = Hobby(hobby_title)

if __name__ == '__main__':
    human = Human('Павел', 'Шахматы')
OOP. Inheritance and Composition.
ООП. Композиция и наследование.
12. Чем отличаются Наследование и Композиция? Общее: позволяют повторно использовать существующий код. Отличия:
  1. Наследование требует расширения наследуемого класса.
  2. Во многих языках запрещено множественное Наследование. А значит нельзя переиспользовать функционал нескольких разных классов. В Композиции- можно.
  3. При Композиции легче писать юнит тесты - делаем заглушки. При Наследовании это сделать сложнее - не получится заменить заглушкой родительский класс.
  4. При наследовании класс-потомок зависит от функционала класса-родителя. Ломается родитель - ломается и потомок.
Итог: когда нужно использовать класс как таковой без каких-либо изменений, рекомендуется Композиция, а когда нужно изменить поведение метода в другом классе, рекомендуется Наследование.
OOP. Inheritance and Composition. Code.
ООП. Наследование и Композиция. Код.
13. Полиморфизм. Поддержка нескольких реализаций на основе общего интерфейса. Т.е. позволяет перегружать одноименные методы родительского класса в классах-потомках. Пример: метод len() позволяет получать длину последовательности для разных классов (int, str, list и т д).

Читайте также

4. Алгоритмы, задачи

1. Сформируйте последовательность Фибоначчи.
Python. Fibonacci sequence.

LENGTH = 7

def create_fib_sequence(length = LENGTH):
    lst = [0, 1]
    for i in range(1, length-1):
        lst.append(lst[i-1] + lst[i])
    return lst


res = create_fib_sequence()
2. Определить, является ли строка палиндромом.
Python. Palindrome function.

def is_palindrome(str):
    reversed_str = str[::-1]
    return str == reversed_str
3. Сортировка. Пузырьком. Проходимся по элементам массива и попарно сравниваем. Если левый больше правого - меняем местами.
Python. Bubble Sort.

for i in range(n-1):
    for j in range(n-i-1):
        if a[j] > a[j+1]:
            a[j], a[j+1] = a[j+1], a[j]
4. Сортировка. Вставка. Делим массив на две части (левую и правую). Левую часть считаем отсортированной. Изначально первый элемент массива оставляем в левой части, все остальное относим к правой (не отсортированной). Начинаем перемещаться по не отсортированной части. Берем первый элемент, и попарно сравнивая с соседними, ищем ему место в отсортированной части. Например, имеем массив [ 4 6 2 1 ]. Выполняем сортировку:
  1. Делим на 2 части: [ 4 | 6 2 1 ].
  2. Берем элемент 6 и ставим его на подходящее место в отсортированной части: [ 4 6 | 2 1 ].
  3. Ставим на свое место элемент 2: [ 2 4 6 | 1 ].
  4. Ставим на свое место элемент 1: [ 1 2 4 6 ].
5. Сортировка. QuickSort. Основывается на выборе опорного элемента и дальнейшей сортировке элементов на группы: меньше / равны / большего опорного. В качестве опорного элемента эффективно выбирать медианное значение. Медианное значение - значение, которое находится в середине отсортированного списка. Алгоритм:
  1. Выбираем опорный элемент.
  2. Перераспределяем элементы относительно опорного - слева меньше, справа больше.
  3. Рекурсивно выполняем п 1 и п 2 на полученных подмассивах.
  4. Рекурсия не применяется, если в подмаслила остался 1 элемент или вообще ни одного.

5. Python

  1. Какая типизация используется в Python? Динамическая
  2. Какие типы данных вы знаете? None, bool, int, float, complex, list, tuple, str, bytes, bytearray, memoryview, set, frozenset, dict.
  3. В чем разница между list, tuple, set? list - упорядоченный, изменяемый; tuple - упорядоченный, неизменяемый; set - НЕупорядоченный, изменяемый, НЕ допускает одинаковый элементов.
  4. Чем отличаются изменяемые и неизменяемые данные? Изменяемый тип — сложный тип данных в объектно-ориентированном программировании, значения которого (как правило — объекты) после своего создания допускают изменение своих свойств. К неизменяемым относятся целые числа (int), числа с плавающей запятой (float), булевы значения (bool), строки (str), кортежи (tuple). К изменяемым — списки (list), множества (set), байтовые массивы (byte arrays) и словари (dict).
  5. Дан кортеж: tpl = (1, 2, 3, [1,2,3], 5). Как все знают, кортеж - это неизменяемый тип данных. Изменится ли его содержимое после выполнения команды tpl[3].append(4)? Да, изменится, так как кортеж содержит только ссылки на объекты и поэтому не может защитить объект внутри себя от изменения.
  6. Чем отличаются генераторы от итераторов? Итератор — механизм поэлементного обхода данных, а генератор позволяет отложено создавать результат при итерации. Генератор может создавать результат на основе алгоритма или брать элементы из источника данных и изменять их. Получаем разделение ответственности: клиенты имеют возможность работать с разными коллекциями унифицированным образом, а коллекции становятся проще за счет того, что делегируют перебор элементов другой сущности. Любой генератор в Python - итератор, но не наоборот. Примеры генераторов: range() (генерирует арифметическую прогрессию), enumerate() (генерирует двухэлементные кортежи: индекс + элемент итерируемого объекта)
  7. Как пройтись по всем парам ключ-значение в словаре? for key, value in dct.items():
  8. Что такое lambda в Python? Анонимная функция, то есть функция, которая имеет функционал (в одну строку), но не имеет имени. Как правило, используется как параметр функции (например filter(), sorted()) или как возвращаемое значение функции. Примеры:
Python. Lambda.

# Пример 1
def multiply(num):
    return lambda x: x + num

multiply_by_10 = multiply(10)
res = multiply_by_10(5)


# Пример 2
filtered_lst = list(filter(lambda x: x % 2 == 0, lst))
filtered_dict = dict(filter(lambda item: 'O' in item[1], dct.items()))

# Пример 3
generated_dict = {item[0]: item[1] * 2 for item in dct.items()}

# Пример 4
mapped_lst = list(map(lambda x: x * 10, lst))
9. Что такое декоратор? Декоратор - это обёртка над функцией (или другим объектом), которая изменяет ее поведение. Это удобно, т.к. не нужно менять исходный код, который не обязательно будет вашим. Использование: оценка времени работы функции, кеширование, запуск только в определенное время, задание параметров подключения к базе данных и т.д.
Python. Decorator.

# Простой декоратор
def my_decorator(my_func):

    def inner(value):
        value *= 2
        my_func(value)

    return inner


# Декоратор для функции с параметрами
def my_decorator_with_params(my_func):

    def inner(*args, **kwargs):
        print('Decorator starts...')
        my_func(*args, **kwargs)
        print('Decorator finishes...')

    return inner


@my_decorator
def print_hi():
    print('Hi!')


@my_decorator_with_params
def adder(**nums):
    print(sum(nums.values()))

# Вызов декорированных функций
print_hi()
adder(a=1, b=2)
10. Знаете ли какие-нибудь встроенные в Python декораторы? 1. @staticmethod, @classmethod. 2. @lru_cache из модуля functools. 3. @dataclass из модуля dataclass
11. Что такое контекстный менеджер? Для чего он нужен? Приведите пример. Контекстные менеджеры позволяют задать поведение при работе с конструкцией with: при входе и выходе из блока. Это упрощает работу с ресурсами в части их захвата и освобождения. Частый кейс - закрытие файловых дескрипторов.
Python. Context manager.

import io

with open('1.txt', 'w') as f:
    f.write('Hello!')
12. Приведите пример обработки исключения.
Python. Exception.

import io

class MyPersonalException(Exception):
    pass

# Example 1
try:
    f.write('Hello')
except io.UnsupportedOperation as e:
    print('UnsupportedOperation!')
finally:
    print('Releasing resources')
    f.close()

# Example 2
try:
    raise MyPersonalException('This is my personal exception!')
except MyPersonalException as e:
    if 'my personal' not in str(e):
        raise e
    print('My personal exception was caught')
13. Назовите основные функции по работе с json в питоне. Запись: dump(), dumps(). Чтение: load(), loads().
Python. JSON.

import json

st = 'String'
lst = [1, 2, 3]
dct = {1: 'One', 2: 'Two'}

# Дампим словарь в json и записываем в файл
with open('1.txt', 'w') as f:
    json.dump(dct, f, indent=4)

# Формируем json на основе словаря и сохраняем в переменную
my_json = json.dumps(dct, indent=4)

# Парсим строку в словарь или список
my_str = '{"1": "One", "2": "Two"}'
my_json = json.loads(my_str)
my_str = '[1, 2, 3]'
my_json = json.loads(my_str)
14. Какой вывод ожидается после третьего вызова функции? Почему?
Python

def my_func(a, lst=[]):
    lst.append(a)
    return lst

print(my_func(10))
print(my_func(100))
print(my_func(1000))
В Python аргументы со значением по умолчанию вычисляются единожды в момент объявления функции. В нашем примере таким аргументом является список lst. И каждый раз при вызове функции - его содержимое будет меняться.
Вывод

[10]
[10, 100]
[10, 100, 1000]
15. Что такое *args, **kwargs? Зачем используются * и ** в записи? Используются для передачи неименованных (*args) и именованных (**kwargs) параметров переменного количества. То есть необходимы в случае, когда заранее неизвестно количество параметров. Важный момент - слова args и kwargs не являются обязательными (их можно заменить на другие). А важны именно знаки * (распаковка параметров в список) и ** (распаковка параметров в словарь).
16. Приведите примеры magic (dunder) методов. Работа с объектом класса: __new__(), __init__(), __del__(). Сравнение: __eq__(), __ne__(), __lt__(). Арифметические операторы: __add__(), __sub__(), __div__(). Информация о своем классе: __str__(), __repr__(), __dir__(), __sizeof__(). Контроль доступа к атрибутам: __getattr__(), __setattr__(). Методы, которые используют контейнеры: __len__(), __getitem__(), __setitem__(), __iter__(), __contains__(). Вызов объекта: __call__(). Контекстный менеджер: __enter__(), __exit__().
17. Как из-под Python выполнить команду в консоли на локальной машине? На удаленной? Локально: os.system(), subprocess.Popen(). Удаленно (по SSH): client = paramiko.SSHClient(); client.connect(), client.exec_command()
18. В чем разница в использовании между is и ==? Оператор == проверяет равенство ЗНАЧЕНИЙ двух объектов. Оператор is проверяет равенство самих объектов, то есть что оба объекта указывают на один и тот же адрес в памяти.
19. Что такое MRO? Method resolution order - порядок разрешения методов. Реализуется через метод mro() в ситуациях, когда мы имеем объект, который унаследован от большого количества других классов. И нам необходимо понять, в каком порядке метод будет искаться в родительских классах. Так же мы можем переопределить этот метод и тем самых изменить порядок обхода родительских классов.
Python. Вызов метода mro()

# Иерархия классов
class O: ...
class A(O): ...
class B(O): ...
class C(O): ...
class D(O): ...
class E(O): ...
class F1(A, B, C): ...
class F2(B, D): ...
class F3(C, D, E): ...
class G(F1, F2, F3): ...

# Выведем порядок обхода классов
print(G.mro())
# [<class '__main__.G'>, <class '__main__.F1'>, <class '__main__.A'>, <class '__main__.F2'>, <class '__main__.B'>, <class '__main__.F3'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.O'>, <class 'object'>]
Python. Переопределение метода mro()

class X: ...
class Y: ...
class A(X, Y): ...
class B(Y, X): ...

# Создаем мета класс
class CustomMRO(type): 
    def mro(cls):
        return (cls, A, B, X, Y, object)

class F(A, B, metaclass=CustomMRO):  
    ...
print_mro(F)  # F -> A -> B -> X -> Y -> object
20. Что такое GIL? Python Global Interpreter Lock - это механизм блокировки, который позволяет только одному потоку управлять интерпретатором Python. Блокировщик нужен для того, чтобы корректно увеличивался/уменьшался счетчик ссылок на переменные. Без блокировки счетчик может изменяться разными потоками, и тем самым могут удалиться еще используемые объекты. По сути этот механизм делает все многопоточные программы однопоточными. Это сказывается на производительности многопоточных программ, поэтому от GIL пытались отказаться.
21. Что выведет данный код?
Python

def division(param):
    print('А')
    return 1 / param

try:
    division(2)
except Exception:
    print('В')
else:
    print('F')
finally:
    print('D')


A
F
D
21. Как удалить первые 100 элементов из списка? del my_lst[0:100]
22. Что такое хэш? Какой метод используется для его вычисления? Хеш-функция - функция, которая задает правило отображения объектов в целые числа или строки. Результат хеширования называют хешем. Наиболее известные типы хеширования: CRC32, MD5, SHA. Для вычисления используется magic метод __hash__() — вызывается функцией hash(). и используется для определения контрольной суммы объекта, чтобы доказать его уникальность. Например, чтобы добавить объект в set, frozenset, или использовать в качестве ключа в словаре dict.
23. Что такое хешируемый тип данных? Хэшируемый объект - объект, который имеет хэш. Чтобы проверить объект/тип данных на хэшируемость, нужно определить, есть ли у данного объекта метод __hash__().
Python (interactive)

>>> dir(12)  # Хешируемый тип данных
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
>>> hash(12)
12

>>> hash('My string for hash')  # Хешируемый тип данных
-8780011054119564709

>>> lst = [1, 2, 3]  # Не хешируемый тип данных
>>> hash(lst)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> 

24. Какие требования предъявляются в ключам словаря? Ключ должен быть хешируем. По сути это означает его неизменяемость.
Python

# При выполнении следующих двух строк
dсе = {}
dct['my_key'] = 'my_value'

# внутри интерпретатора выполняются следующие действия
internal = []
hash = zlib.crc32(b'my_key')
index = abs(hash) % 1000
internal[index] = ['my_key', 'my_value']

6. PyTest

  1. Какие типы скоупов существуют у фикстур? function, class, module, package, session
  2. Какой фикстурой можно воспользоваться, чтобы прогнать один тест несколько раз на разных данных? @pytest.mark.parametrize("num", [1, 5, 10])
  3. Как можно проверить, что блок кода свалится с ожидаемым эксепшеном? with pytest.raises(TypeError):
  4. Что такое hooks в pytest? Это специальные методы, которые позволяют изменить/расширить дефолтное поведение pytest (например, pytest_runtest_makereport(), pytest_exception_interact()). Многие pytest plugins их используют.
  5. В каком порядке вызываются фикстуры? Сначала вызываются фикстуры с более широким скоупом (например, session фикстура вызовется перед method фикстурой). Чтобы самостоятельно задать порядок вызова фикстур, необходимо передать более раннюю фикстуру в более позднюю. Важный момент - нельзя передавать фикстуру с меньшим скоупом (например, method) в фикстуру с бОльшим скоупом (например, session).
1 СЕНТЯБРЯ / 2023
Как вам материал?

Читайте также