100 вопросов для подготовки к собесу Python

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Доброго времени суток!

Представляю подборку из 100 вопросов с собесов на позицию джуна Python-разработчика. На Хабре есть неплохие статьи на тему подготовки к собеседованию и всё в таком духе, но прямо набора вопросов/ответов на понимание Python в формате чек-листа не встречал.

Для кого статья?

  • для тех, кто думает, что знает Python как 5 пальцев — проверьте себя)

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

  • кто начинает изучать Python — статья покажет, что в Python хватает сюрпризов

Вопросы есть глупые простые/сложные, теоретические/практические, в общем, это микс, призванный помочь вам лучше понимать свой рабочий инструмент.

Что ж, поехали!

Содержание:

1. Какие типы данных есть в python? На какие классы делятся?
2. Что такое лямбда-функция? Какое у неё назначение?
3. Что такое docstring?
4. Как получить документацию по атрибутам объекта?
5. В чём разница между типами list и tuple?
6. Может ли быть индекс списка отрицательным?
7. Что значит конструкция pass?
8. Чем отличаются многопоточное и многопроцессорное приложение?
9. Как просмотреть методы объекта?
10. Что такое *args и **kwargs в определении функции?
11. Python полностью поддерживает ООП?
12. Что такое globals() и locals()?
13. Что хранится в атрибуте dict объекта?
14. Как проверить файл .py на синтаксические ошибки, не запуская его?
15. Зачем в python используется ключевое слово self?
16. Что такое декоратор? Как написать собственный?
17. Что может быть ключом в словаре?
18. В чём разница между пакетами и модулями?
19. Для чего используется дандер-метод init?
20. Что такое слайс(slice)?
21. Как проверить, что один кортеж содержит все элементы другого кортежа?
22. Почему пустой список нельзя использовать как аргумент по умолчанию?
23. Что такое @classmethod, @staticmethod, @property?
24. Что такое синхронный код?
25. Что такое асинхронный код? Приведите пример.
26. Каким будет результат следующего выражения?
27. Для чего нужен метод id()?
28. Что такое итератор?
29. Что такое генератор? Чем отличается от итератора?
30. Для чего используется ключевое слово yield?
31. Чем отличаются iter и next?
32. Что такое контекстный менеджер?
33. Как сделать python-скрипт исполняемым в различных операционных системах?
34. Как сделать копию объекта? Как сделать глубокую копию объекта?
35. Опишите принцип работы сборщика мусора в python.
36. Как использовать глобальные переменные? Это хорошая идея?
37. Для чего в классе используется атрибут slots?
38. Какие пространства имен существуют в python?
39. Как реализуется управление памятью в python?
40. Что такое метаклассы и в каких случаях их следует использовать?
41. Зачем нужен pdb?
42. Каким будет результат следующего выражения?
43. Как создать класс без слова class?
44. Как перезагрузить импортированный модуль?
45. Напишите декоратор, который будет перехватывать ошибки и повторять функцию максимум N раз.
46. Каким будет результат следующего выражения?
47. Какие проблемы есть в python?
48. Когда будет выполнена ветка else в конструкции try…except…else?
49. Поддерживает ли python множественное наследование?
50. Как dict и set реализованы внутри? Какова сложность получения элемента? Сколько памяти потребляет каждая структура?
51. Что такое MRO? Как это работает?
52. Как аргументы передаются в функции: по значению или по ссылке?
53. С помощью каких инструментов можно выполнить статический анализ кода?
54. Что будет напечатано в результате выполнения следующего кода?
55. Что такое GIL? Почему GIL всё ещё существует?
56. Опишите процесс компиляции в python
57. Что такое дескрипторы? Есть ли разница между дескриптором и декоратором?
58. Почему всякий раз, когда python завершает работу, не освобождается вся память?
59. Что будет напечатано в результате выполнения следующего кода?
60. Что такое интернирование строк? Почему это есть в python?
61. Как упаковать бинарные зависимости?
62. Почему в python нет оптимизации хвостовой рекурсии? Как это реализовать?
63. Что такое wheels и eggs? В чём разница?
64. Как получить доступ к модулю, написанному на python из C и наоборот?
65. Как ускорить существующий код python?
66. Что такое pycache? Что такое файлы .pyc?
67. Что такое виртуальное окружение?
68. Python — это императивный или декларативный язык?
69. Что такое менеджер пакетов? Какие менеджеры пакетов вы знаете?
70. В чём преимущества массивов numpy по сравнению с (вложенными) списками python?
71. Вам нужно реализовать функцию, которая должна использовать статическую переменную. Вы не можете писать код вне функции и у вас нет информации о внешних переменных (вне вашей функции). Как это сделать?
72. Что будет напечатано в результате выполнения следующего кода?
73. Как имплементировать словарь с нуля?
74. Напишите однострочник, который будет подсчитывать количество заглавных букв в файле.
75. Что такое файлы .pth?
76. Какие функции из collections и itertools вы используете?
77. Что делает флаг PYTHONOPTIMIZE?
78. Какие переменные среды, влияющие на поведение интерпретатора python, вы знаете?
79. Что такое Cython? Что такое IronPython? Что такое PyPy? Почему они до сих пор существуют и зачем?
80. Как перевернуть генератор?
81. Приведите пример использования filter и reduce над итерируемым объектом.
82. Чем фреймворк отличается от библиотеки?
83. Расположите функции в порядке эффективности, объясните выбор.
84. Произошла утечка памяти в рабочем приложении. Как бы вы начали отладку?
85. В каких ситуациях возникает исключение NotImplementedError?
86. Что не так с этим кодом? Зачем это нужно?
87. Что такое магические методы (dunder-методы)?
88. Что такое monkey patching? Приведите пример использования.
89. Как работать с транзитивными зависимостями?
90. Когда использование Python является «правильным выбором» для проекта?
91. Что такое метод?
92. Есть ли в Python оператор switch-case?
93. Поддерживает ли Python регулярные выражения?
94. Напишите регулярное выражение, которое будет принимать идентификатор электронной почты. Используйте модуль re.
95. Как передать необязательные или ключевые параметры из одной функции в другую?
96. Как создать свой собственный пакет в Python?
97. Что такое функции высшего порядка?
98. Назовите модули в Python, связанные с файлами
99. В чем разница между NumPy и SciPy?
100. Что такое аксессоры, мутаторы, @property

1. Какие типы данных есть в python? На какие классы делятся?

Очень простой вопрос, но с чего-то же надо начать. В Python есть такие типы данных (вообще, это классы в ООП, но ладно):

  • Числа: int, float, и complex.

  • Строки: str.

  • Списки: list.

  • Кортежи: tuple.

  • Словари: dict.

  • Множества: set.

  • Булевы значения: bool

Эти типы данных можно объединить в такие группы:

  • Числовые типы данных: int, float, и complex.

  • Строковые типы данных: str.

  • Коллекции: list, tuple, dict, и set.

  • Булевы типы данных: bool.

2. Что такое лямбда-функция? Какое у неё назначение?

Лямбда-функция (a.k.a *"анонимная функция"*) - это функция, которая определяется в одной строке кода без использования ключевого слова `def`. Она может быть использована вместо обычной функции, когда требуется быстрое определение небольшой функции. Как правило лямбда-функции одноразовые.

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

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

double = lambda x: x * 2

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

Например, можно использовать лямбда-функцию вместо объявления обычной функции для преобразования списка:

numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x**2, numbers))

Этот пример создает список квадратов чисел в списке numbers с помощью функции map(), принимающей лямбда-функцию в качестве аргумента.

Таким образом, лямбда-функция в Python позволяет определять небольшие функции быстро и использовать их в качестве аргументов для других функций. Вот ещё пару лямбд:

# применяем lambda к каждому элементу списка
numbers = [1, 2, 3, 4, 5]
squares = list(map(lambda x: x**2, numbers))
print(squares)
# при помощи lambda фильтруем по чётности
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers)
# передаём lambda в качестве аргумента key для сортировки
mylist = ['111', '22', '3']
mylist = sorted(mylist, key=lambda x: len(x))
print(mylist)

3. Что такое docstring?

Docstring в Python - это строка документации, которая описывает, что делает функция, метод, модуль или класс Python. Данная строка располагается в начале определения объекта и используется для генерации документации автоматически. В других словах, docstring используется для создания описания API и содержит информацию о том, как использовать функцию или метод, какие аргументы они принимают и какие значения возвращают.

Например:

def add_numbers(a, b):
    """
    This function takes in two numbers and returns their sum
    """
    return a + b

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

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

def add_numbers(a, b):
    """
    This function takes in two numbers and returns their sum
    """
    return a + b

print(add_numbers.__doc__)
help(add_numbers)

4. Как получить документацию по атрибутам объекта?

В Python вы можете получить документацию по атрибутам объекта с помощью атрибута `doc`. Например, если у вас есть объект с атрибутом `attribute_name`, то вы можете получить его документацию так: `print(attribute_name.__doc__)`

Вы также можете использовать встроенную функцию help() для получения подробной информации о любом объекте, включая его атрибуты. Просто передайте объект в функцию help(), чтобы получить всю доступную документацию:
help(attribute_name)

Небольшое уточнение: doc отображает документацию для конкретного атрибута или метода. Если вы хотите получить общую документацию для объекта, вызовите help() без параметров (т.е. help(object_name)).
Например, если у вас есть класс с атрибутом attribute_name, вы можете получить его документацию следующим образом:

class MyClass:
    """This is the docstring for MyClass."""
    attribute_name = "value"

print(MyClass.attribute_name.__doc__)

Этот код выведет документацию для атрибута attribute_name, которая будет равна None, так как мы не определили документацию для него в классе. Теперь мы можем использовать функцию help() для получения документации для самого класса:
help(MyClass)
Это приведет к выводу всей доступной документации для MyClass, включая документацию для его атрибута attribute_name.

5. В чём разница между типами list и tuple?

В Python, список (list) и кортеж (tuple) являются двумя различными типами данных, которые предоставляют набор элементов в определенном порядке.

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

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

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

Различие в изменяемости во многом определяет отличие между списком и кортежем в скорости доступа к элементам. За счет того, что кортежи являются неизменным типом данных, они обрабатываются быстрее, чем списки.

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

my_list = [1, 2, 3, 4, 5]  # Это список
my_tuple = (1, 2, 3, 4, 5)  # Это кортеж

Cписок может быть изменен, например, можно добавить элемент в список:

my_list.append(6)

Но не можем добавить элемент в кортеж, так как он неизменяем:

my_tuple.append(6) # Эта строка вызовет ошибку
# в Jupyter Notebook можно замерить время так, будет видно замедление list
%time a = list(range(10**7))

%time b = tuple(range(10**7))

Кстати, забавный факт со списками — их можно делать бесконечно вложенными. Есть идеи, что возвращает код ниже?

a = [1, 2, 3, 4]
a.append(a)
print(a)

6. Может ли быть индекс списка отрицательным?

Да, индекс списка может быть отрицательным. В таком случае, отрицательное значение считается от конца списка, где -1 соответствует последнему элементу, -2 — предпоследнему элементу и так далее.

Например, чтобы получить последний элемент списка my_list в Python, можно использовать следующую команду:

last_element = my_list[-1]

Также можно использовать отрицательные значения для срезов (slicing) списка, например:

my_list[-3:] # вернет последние три элемента списка
my_list[:-2] # вернет все элементы списка, кроме последних двух
my_list[::-1] # вернет список в обратном порядке

Но следует учесть, что если индекс отрицательный и его абсолютное значение больше или равно длине списка, будет возбуждено исключение IndexError.

7. Что значит конструкция pass?

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

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

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

def my_function():
    pass # заглушка для функции, которая будет реализована позже

for i in range(10):
    if i < 3:
        pass # ничего не делать на первых трёх итерациях
    else:
        print(i) # вывести значения на всех остальных итерациях

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

К примеру, вот валидный код:

class MyClass:
    pass 

for i in range(10):
    pass

def main():
    pass

myclass = MyClass()
myclass.name = 'Bob'  # мы можем даже создавать атрибуты экземпляра класса

main()

Кстати, в Python вместо pass можно писать и ... (вот интересная статья на Хабр: Объект многоточие в Python)

8. Чем отличаются многопоточное и многопроцессорное приложение?

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

В многопроцессорных приложениях каждый процесс имеет свой собственный набор ресурсов, включая память, открытые файлы, сетевые соединения и другие системные ресурсы.
Многопроцессорность в Python может быть достигнута с помощью библиотек multiprocessing и concurrent.futures.

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

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

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

img
img

Отличное видео с объяснением

А так выглядит многопроцессорность

img
img

Отличное видео, объясняющее многопроцессорность

9. Как просмотреть методы объекта?

Чтобы посмотреть все методы и атрибуты, связанные с определенным объектом в Python, можно использовать функцию dir(). Она принимает объект в виде аргумента и возвращает список имен всех атрибутов и методов объекта.

Например, если нужно увидеть все методы и атрибуты, связанные с объектом my_list, следующее:

my_list = [1, 2, 3]
print(dir(my_list))

Это выведет список всех методов и атрибутов, которые можно использовать с объектом my_list.

10. Что такое *args и **kwargs в определении функции?

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

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

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

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

Например:

def my_fun(a, b, *args):
	print(a, b, args)

my_fun(1, 2, 3, 4, 5) 
# 1 2 (3, 4, 5)

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

Например:

def my_fun(a, b, **kwargs):
    print(a, b, kwargs)

my_fun(1, 2, x=3, y=4, z=5)
# 1 2 {'x': 3, 'y': 4, 'z': 5}

Краткий итог: использование *args и **kwargs позволяет создавать более гибкие функции, которые могут принимать любое количество аргументов. А именно: *args собирает все "лишние" аргументы в кортеж, а **kwargs собирает в словарь именованные аргументы.

11. Python полностью поддерживает ООП?

Да, Python является полностью объектно-ориентированной языком. Он поддерживает все основные принципы ООП: наследование, инкапсуляцию и полиморфизм.

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

В целом, Python предоставляет множество инструментов для написания кода в объектно-ориентированном стиле, и это один из главных его преимуществ, особенно для написания крупных и сложных приложений.

12. Что такое globals() и locals()?

globals() и locals() - это встроенные функции в Python, которые возвращают словари глобальных и локальных переменных соответственно.

globals() возвращает словарь, содержащий все глобальные переменные, доступные в текущей области видимости, включая встроенные переменные.
locals() возвращает словарь, содержащий все локальные переменные, определенные в текущей области видимости. Это включает аргументы функции и переменные,
которым присвоено значение внутри функции.

Например, вот как можно использовать эти функции:

x = 5
y = 10

def my_func(z):
    a = 3
    print(globals()) # выводит все глобальные переменные
    print(locals()) # выводит все локальные переменные

my_func(7)

В этом примере функция my_func() принимает один аргумент и определяет две локальные переменные (a и z). Когда она вызывается, она выводит на экран словари
глобальных и локальных переменных.

13. Что хранится в атрибуте dict объекта?

Атрибут dict содержит словарь, который хранит атрибуты объекта в виде пар ключ-значение. Этот словарь заполняется значениями при создании объекта и может быть
изменен позже. Например, если у вас есть объект класса Person, и вы создаете его экземпляр person1, то вы можете добавить новый атрибут age и присвоить ему значение
25 следующим образом:

class Person:
    def __init__(self, name):
        self.name = name
        
    def say_hello(self):
        print("Hello, my name is", self.name)

person1 = Person("Alice")
person1.age = 25
print(person1.__dict__)

Это выведет словарь, содержащий пару ключ-значение {'name': 'Alice', 'age': 25}.
Вы можете обратиться к любому атрибуту объекта, используя либо обычную запись person1.name, либо запись, использующую словарь python
person1.__dict__["name"].

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

    def say_hello(self):
	    print("Hello, my name is", self.name)
person1 = Person("Alice")
person1.age = 25
print(person1.__dict__)
class MyClass:
    pass

myclass = MyClass()
myclass.name = 'Steve'
myclass.age = 12
myclass.lastname = 'King'

print(myclass.__dict__)

14. Как проверить файл .py на синтаксические ошибки, не запуская его?

Утилита py_compile, позволит проверить файл .py на наличие синтаксических ошибок без его запуска.

Используется py_compile очевидно:

  1. открываем командную строку/терминал.

  2. переходим в каталог, содержащий файл .py, который вы хотите проверить

  3. выполняем: python -m py_compile yourfile.py где yourfile.py - это имя файла, который вы хотите проверить.

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

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

15. Зачем в python используется ключевое слово self?

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

К примеру, рассмотрим класс Person, который имеет атрибут name и метод say_hello:

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

    def say_hello(self):
        print(f"Hello, my name is {self.name}")

Здесь мы можем обратиться к атрибуту name объекта класса Person с помощью ключевого слова self. Аналогично, мы можем вызвать метод say_hello, который также использует self для доступа к атрибуту name:

person = Person("Alice")
person.say_hello() # выведет "Hello, my name is Alice"

Подводя итог, self позволяет нам работать с конкретным экземпляром класса (именно с "Alice" или "Bob"), с атрибутами и методами этого экземпляра, не трогая другие.

16. Что такое декоратор? Как написать собственный?

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

Вот пример создания декоратора:

def my_decorator(func):
    def wrapper():
        print("Дополнительный код, который исполняется перед вызовом функции")
        func()
        print("Дополнительный код, который исполняется после вызова функции")
    return wrapper

@my_decorator
def say_hello():
    print("Привет!")
    
say_hello()

# Дополнительный код, который исполняется перед вызовом функции
# Привет!
# Дополнительный код, который исполняется после вызова функции

Этот код создает декоратор my_decorator, который добавляет дополнительный код до и после выполнения функции say_hello(). Декоратор применяется к say_hello() с помощью синтаксиса @my_decorator.

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

Вот, к примеру, декоратор, который позволяет измерять время выполнения функции:

from time import time

def executiontime(func):
    def wrapper():
        start = time()
        func()
        end = time()
        print(f'Функция {func} выполнялась: {end - start} сек')
    return wrapper

@executiontime
def create_tuple():
    return tuple(range(10**7))
    
create_tuple()

Суть двумя словами: по сути декоратор принимает на вход другую функцию и позволяет её модифицировать снаружи, не меняя внутренней реализации самой функции. Кстати, один из полезнейших декораторов — @njit() из библиотеки numba, позволяет космически ускорить Python.

Ну и неплохая статья — 6 Python декораторов, которые значительно упростят ваш код

17. Что может быть ключом в словаре?

В Python ключом в словаре может быть любой неизменяемый объект, такой как число, строка или кортеж.
Например:

my_dict = {1: 'one', 'two': 2, (3, 4): 'three four'}

В этом примере ключами словаря являются число 1, строка 'two' и кортеж (3, 4).

Однако, если вы попытаетесь использовать изменяемый объект, такой как список, как
ключ словаря, вы получите TypeError:

my_dict = {[1, 2]: 'one two'}
# this will raise a TypeError: unhashable type: 'list'

Также, если вы попытаетесь добавить два ключа в словарь с одинаковым хеш-кодом, то второй ключ перезапишет первый:

my_dict = {1: 'one', '1': 'one again'}
# {1: 'one again'}

Кстати, а что вернёт данный код?

a = {True : 'a', 1 : 'b', '1' : 'c', 1.0 : 'd'}
print(a[True])

18. В чём разница между пакетами и модулями?

Модуль - это файл, содержащий код Python, который может быть повторно использован в других программах.

Пакет - это директория, содержащая один или несколько модулей (или пакетов внутри пакетов), а также специальный файл init.py, который выполняется при импорте пакета. Он может содержать код, который инициализирует переменные, функции и классы, и становится доступным для использования внутри модулей, находящихся внутри этого пакета.

Таким образом, основная разница между модулем и пакетом заключается в том, что модуль - это файл с кодом, который можно использовать повторно, а пакет - это директория, которая может содержать один или несколько модулей. Код, находящийся в файле init.py, может инициализировать переменные, функции и классы, что обеспечивает общую функциональность для всех модулей, находящихся внутри пакета.
Например, если у нас есть пакет mypackage, в нем может находится несколько модулей, таких как module1.py, module2.py. В файле init.py определяются функции и переменные, которые могут использоваться внутри module1 и module2.

Некоторые примеры импорта:

import mymodule # импортируем модуль
from mypackage import mymodule # импортируем модуль из пакета
from mypackage.mymodule import myfunction # импортируем функцию из модуля в пакете

19. Для чего используется дандер-метод init?

Функция (дандер-метод, если точнее) __init__ является конструктором класса, и она вызывается автоматически при создании нового экземпляра класса. __init__ используется для инициализации атрибутов, которые будут принадлежать объектам, создаваемым с помощью класса.

Внутри функции __init__ определяются атрибуты объекта, которые будут доступны через
ссылку на экземпляр, на который ссылается переменная self.

Например, пусть каждый экземпляр класса Person создаётся с атрибутами name и age:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

person1 = Person("John", 30)
person2 = Person("Alice", 25)
print(person1.name) # output: John
print(person2.age) # output: 25

Тут функция init инициализирует атрибуты name и age для каждого экземпляра, создаваемого с помощью класса Person.
Когда мы создаем новый объект, мы передаем эти аргументы в функцию init, чтобы инициализировать соответствующие атрибуты.

20. Что такое слайс(slice)?

Совсем банальный вопрос, но ладно.

Слайс (slice) - это способ извлечения определенной части последовательности (например, строки, списка, кортежа) с использованием индексации.

Синтаксис для создания слайса:

sequence[start:end:step]

где start - индекс, с которого начинается извлечение (включительно), end - индекс, на котором заканчивается извлечение (не включая его), и step - шаг для извлечения элементов (по умолчанию равен 1).

Обратите внимание, что если не указывать start, то по умолчанию он равен 0, а если не указывать end, то по умолчанию он равен длине последовательности.

Вот пример использования слайса для выбора подряд идущих элементов списка (list):

my_list = [0, 1, 2, 3, 4, 5]
my_slice = my_list[1:4] # выбираем элементы с индексами от 1 до 3 включительно
print(my_slice) # выведет [1, 2, 3]

В этом примере мы использовали слайс my_list[1:4] для выбора элементов списка с индексами от 1 до 3 включительно.

В общем, слайс/срез используется для того, чтобы взять какую-то подпоследовательность любого итерируемого объекта, будь то строка, список или кортеж; при этом мы можем указывать начало среза, конец и шаг.

21. Как проверить, что один кортеж содержит все элементы другого кортежа?

Для проверки того, содержит ли один кортеж все элементы другого кортежа в Python, можно воспользоваться встроенной функцией all(), передав ей выражение генератора
списков, которое проверяет наличие каждого элемента из второго кортежа в первом кортеже.

Например:

first_tuple = (1, 2, 3, 4, 5)
second_tuple = (2, 4, 5)

contains_all = all(elem in first_tuple for elem in second_tuple)
print(contains_all) # True

Этот код создает два кортежа first_tuple и second_tuple и затем использует генератор списка, чтобы проверить, содержит ли first_tuple все элементы из second_tuple.
Результат будет True, если все элементы второго кортежа содержатся в первом кортеже, и False в противном случае.

Есть и другой вариант для наборов неповторяющихся элементов. Можно использовать issubset(), то есть проверить, является ли одно множество подмножеством другого:

first_tuple = (1, 2, 3, 4, 5)
some_list = [2, 4, 5]

contains_all = set(some_list).issubset(set(first_tuple))
print(contains_all) # True

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

22. Почему пустой список нельзя использовать как аргумент по умолчанию?

Значения по умолчанию для аргументов функции вычисляются только 1 раз, когда функция определяется, а не каждый раз, когда она вызывается. Таким образом, если вы попытаетесь использовать изменяемый тип данных (например, список) как аргумент по умолчанию для функции, то каждый вызов функции, который изменяет это значение, также изменит значение по умолчанию для всех последующих вызовов функции. Это может привести к разным сюрпризам и неожиданным последствиям.

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

Типо того:

def my_function(my_list=None):
    if my_list is None:
        my_list = []
        # do something with my_list
        pass 

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

P.S. На самом деле есть обходной путь: если внутри функции входной аргумент (список) никак не меняется, то код будет абсолютно валидным.

Это реально работает:

def foo(var: int, checks: list[Callable] = []):
	for check in checks:
		check(var)

Но линтеру это может не понравиться, или кто-то может заставить функцию изменять список и всё сломается, да и не по феншую это.

23. Что такое @classmethod, @staticmethod, @property?

@classmethod, @staticmethod, and @property - это декораторы методов класса в языке Python.

@classmethod используется для создания методов, которые будут работать с классом в целом, а не с отдельным экземпляром. В качестве первого параметра этот метод принимает класс, а не экземпляр объекта, и часто используется для создания фабричных методов и методов, которые работают с класс-уровнем методов.

@staticmethod декоратор работает подобно @classmethod, но он не получает доступ к классу в качестве первого параметра.

@property декоратор используется для создания свойств объекта, которые можно получить и задать, но выглядят как обычные атрибуты объекта. Это позволяет управлять доступом к атрибутам объекта, установив условиями доступа и возможностью заложить дополнительную логику при чтении, установке или удалении атрибута.

Например, явное использование декораторов может выглядеть так:

class MyClass:
    def __init__(self, value):
        self._value = value
    
    @classmethod
    def from_string(cls, input_string):
        value = process_input_string(input_string)
        return cls(value)
        
    @staticmethod
    def process_input_string(input_string):
        # implementation details
        pass 

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, new_value):
        if new_value < 0:
            raise ValueError("Value must be positive")
        self._value = new_value

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

24. Что такое синхронный код?

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

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

items = [1, 2, 3, 4, 5]

for item in items:
    print(item)

Здесь цикл for будет обрабатывать каждый элемент в списке items последовательно, один за другим, и не будет переходить к следующему элементу, пока не завершится обработка текущего элемента.

Выполнение синхронного кода может занять много времени и может вызвать проблемы с производительностью, особенно когда код выполняет блокирующие операции, такие как чтение и запись файлов, обращение к сети, или поиск значений в базе данных. Для решения этой проблемы в Python используют асинхронное программирование с использованием конструкций async/await и библиотеки asyncio. Они позволяют выполнять несколько задач асинхронно, не блокируя выполнение других задач, и добиваться более высокой производительности.

25. Что такое асинхронный код? Приведите пример.

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

Примером использования асинхронного кода является библиотека asyncio в Python.

Например, вот простой пример кода, который использует asyncio для запуска
нескольких задач одновременно и ожидания их завершения:

import asyncio

async def hello():
    await asyncio.sleep(1)
    print("Hello")

async def world():
    await asyncio.sleep(2)
    print("World")
    
async def main():
    await asyncio.gather(hello(), world())
    
if __name__ == '__main__':
    asyncio.run(main())

В этом примере мы определяем 3 асинхронные функции: hello(), world() и main(). Функции hello() и world() печатают соответствующие сообщения и ждут 1 и 2 секунды
соответственно.

Функция main() запускает эти две функции одновременно с помощью asyncio.gather() и ждет, пока они завершат свою работу. Затем мы запускаем
функцию main() с помощью asyncio.run(). В результате мы получим сообщения "Hello" и "World", каждое через 1 и 2 секунды соответственно, при этом результаты двух
задач были получены почти одновременно.

26. Каким будет результат следующего выражения?

Уверен, все знают, эту фишку, но я рискнул и поместил этот вопрос.

-31 % 10

Результатом выражения -31 % 10 будет 9. Это происходит потому, что для отрицательных чисел оператор % возвращает остаток от деления первого числа на второе немного другим образом. -31 % 10 = -3 - 1/10 и в ответ мы получим 10 - 1 = 9

27. Для чего нужен метод id()?

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

Например, если у вас есть две переменные, которые ссылаются на один и тот же объект, то их идентификаторы будут равны:

a = [1, 2, 3]
b = a
print(id(a)) # выведет адрес в памяти объекта a
print(id(b)) # выведет адрес в памяти объекта b

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

a = [1, 2, 3]
b = [1, 2, 3]
print(id(a)) # выведет адрес в памяти объекта a
print(id(b)) # выведет адрес в памяти объекта b (отличный от идентификатора a)

Использование метода id() может быть полезно при отладке или проверке, какие переменные ссылаются на один и тот же объект. Однако, в общем случае, использование метода id() не рекомендуется, поскольку это может быть неэффективным при работе с большим количеством объектов в памяти.

28. Что такое итератор?

Итератор (Iterator) — это объект, который возвращает свои элементы по одному за раз.

Он должен иметь метод next(), который возвращает следующий элемент и вызывает исключение StopIteration, когда элементы закончились. Итератор также может быть написан с помощью генераторов.

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

my_list = [1, 2, 3, 4, 5]
my_iterator = iter(my_list)  # Получаем итератор из списка

print(next(my_iterator)) # выведет 1
print(next(my_iterator)) # выведет 2
print(next(my_iterator)) # выведет 3

В этом примере мы создаем список и получаем из него итератор. Затем мы выводим элементы итератора с помощью функции next(), которая вызывает метод next()
объекта итератора. Каждый вызов функции next() выводит следующий элемент, пока не закончатся элементы списка, после чего будет вызвано исключение StopIteration.

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

Например (спасибо поддержке юникода в питоне):

# Определяем генератор
def my_generator():
    yield 						
Источник: https://habr.com/ru/articles/782266/


Интересные статьи

Интересные статьи

Приветствую, читатели. Думаю, что для каждого, кто хоть раз в жизни интересовался темой программирования, знаком такой язык, как Python. Все-таки он самый популярный в мире на данный момент. И это не...
Компьютерное зрение - это область искусственного интеллекта, которая призвана наделить компьютеры способностью видеть и интерпретировать мир так же, как это делают люди. Для этого необходимо понимать ...
Фреймворки помогают ускорить разработку и сделать её приятнее. Программу, которая раньше писалась неделю и занимала 1000 строк, с помощью фреймворка вы можете создать за пару часов и уместить в 50 стр...
Привет, Хаброжители! Python — стремительно развивающийся язык программирования современности. В этом увлекательном и необычном руководстве материал разбивается на доступные пошаговые фра...
Python — особенный язык в плане итераций и их реализации, в этой статье мы подробно разберём устройство итерируемых объектов и пресловутого цикла for. Особенности, с которыми вы часто можете сто...