Написать пост

99 вопросов с ответами, которые должен знать Python-разработчик

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

Новые вопросы будут добавляться в Github. Если вам понравился проект, ваш вклад сердечно приветствуется.

1. Что такое Python

Python — это язык программирования высокого уровня, интерпретируемый, с динамической типизацией и автоматическим управлением памятью. Он был разработан в конце 1980-х годов Гвидо ван Россумом и имеет широкую популярность среди разработчиков благодаря своей простоте и эффективности. Python широко применяется в различных областях, включая науку о данных, машинное обучение, веб-разработку, игровую индустрию, GIS и многие другие.

2. В каком году написана первая статья про Python

Автором Python является голландец Гвидо Ван Россум. Этот язык он начал проектировать в конце 1980-х годов, во время работы в голландском институте CWI. За основу он взял язык программирования ABC, в разработке которого он когда участвовал.

3. Какие типы данных есть в Python и на какие классы делятся

Python поддерживает множество различных встроенных типов данных, включая:

  • числа: int, float, и complex;
  • строки: str;
  • списки: list;
  • кортежи: tuple;
  • словари: dict;
  • множества: set;
  • булевы значения: bool.

Эти типы данных можно разделить на несколько классов:

  • числовые типы данных: int, float, и complex;
  • строковые типы данных: str;
  • коллекции: list, tuple, dict, и set;
  • булевы типы данных: bool.

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

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

Лямбда-функция (tакже известна как «анонимная функция») — это функция, которая определяется в одной строке кода без использования ключевого слова 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 позволяет определять небольшие функции быстро и использовать их в качестве аргументов для других функций.

5. Что такое PEP 8

PEP 8 (Python Enhancement Proposal 8) — это документ, который содержит рекомендации по написанию кода на языке Python. Он содержит стилевые соглашения, которые, следуя практике, повышают читабельность кода, делая его более понятным, расширяемым и поддерживаемым. Документ был опубликован в 2001 году и рекомендуется как основной стандарт написания кода Python. PEP 8 охватывает такие темы, как именование переменных, расположение отступов, длина строк, комментарии, импорты и многое другое.

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

В 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.

7. Что такое 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 разработке, так как важно документировать ваш код для себя и для других разработчиков. Документированный код легче поддерживать и понимать, что облегчает разработку и сотрудничество.

8. В чём разница между типами 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) # Эта строка вызовет ошибку
		

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

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

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

			last_element = my_list[-1]
		

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

13. Что такое *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 позволяет создавать более гибкие функции, которые могут принимать любое количество аргументов.

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

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

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

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

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

15. Что такое 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). Когда она вызывается, она выводит на экран словари глобальных и локальных переменных.

16. Что хранится в атрибуте 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"]
		

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

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

Вы можете использовать командную строку или терминал для проверки файла .py на наличие синтаксических ошибок, не запуская его, используя флаг -m с модулем py_compile. Вот как это сделать:

Откройте командную строку или терминал. Перейдите в каталог, содержащий файл .py, который вы хотите проверить. Выполните следующую команду:

			python -m py_compile yourfile.py
		

где yourfile.py — это имя файла, который вы хотите проверить.

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

18. Зачем в 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 позволяет нам работать с атрибутами и методами объекта класса внутри его методов.

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

Декоратор в 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.

Выходные данные:

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

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

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

В 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'}
# this will result in {1: 'one again'}
		

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

Модуль — это файл, содержащий код 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 # импортируем функцию из модуля в пакете
		

22. Как перевести строку, содержащую двоичный код (1 и 0), в число

Для того, чтобы перевести строку, содержащую двоичный код, в целое число в Python, нужно воспользоваться функцией int(), передав ей вторым аргументом основание системы счисления — в данном случае 2. Например:

			binary_str = '110101'
decimal_num = int(binary_str, 2)
print(decimal_num)
		

Вывод:

			53
		

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

			binary_str = '110101'
decimal_num = 0
for i in range(len(binary_str)):
    decimal_num += int(binary_str[i]) * 2**(len(binary_str)-i-1)

print(decimal_num)
		

Этот код также выведет 53. Вывод:

			53
		

23. Для чего используется функция init

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

Пример:

			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 устанавливает два атрибута экземпляра для каждого объекта, создаваемого с помощью класса Person: name и age. Когда мы создаем новый объект, мы передаем эти аргументы в функцию init, чтобы инициализировать соответствующие атрибуты.

24. Что такое слайс(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 включительно.

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

Для проверки того, содержит ли один кортеж все элементы другого кортежа в 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 в противном случае.

Если вам нужно проверить, содержит ли кортеж все элементы из другой последовательности, не обязательно кортежа, вы можете использовать преобразование типа set() для сравнения их элементов, как показано ниже:

			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
		

Этот код дает тот же результат, что и предыдущий пример, но здесь мы преобразуем элементы some_list и first_tuple в множество и используем метод issubset() для проверки, содержит ли первое множество все элементы второго множества.

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

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

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

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

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

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

			@classmethod, @staticmethod, and @property
		
			@classmethod
		
			@staticmethod
		
			@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

    @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
		

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

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

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

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

			items = [1, 2, 3, 4, 5]
for item in items:
    print(item)
		

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

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

29. Расскажите, что такое асинхронный код, и приведите пример

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

Примером использования асинхронного кода является библиотека 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 секунды соответственно, при этом результаты двух задач были получены почти одновременно.

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

			```python
>>> -30 % 10
```
		

Результатом выражения «-30 % 10» будет — 0. Это происходит потому, что оператор % возвращает остаток от деления первого числа на второе, и в данном случае -30 можно разбить на целое количество десяток и остаток 0. Поэтому -30 % 10 равно 0.

31. Для чего нужен метод 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() не рекомендуется, поскольку это может быть неэффективным при работе с большим количеством объектов в памяти.

32. Что такое итератор

Итератор (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 1
    yield 2
    yield 3
    yield 4
    yield 5

# Получаем итератор из генератора
my_iterator = my_generator()

# Выводим элементы итератора
print(next(my_iterator))   # выведет 1
print(next(my_iterator))   # выведет 2
print(next(my_iterator))   # выведет 3
print(next(my_iterator))   # выведет 4
print(next(my_iterator))   # выведет 5
		

33. Что такое генератор и чем отличается от итератора

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

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

Пример использования генератора, который генерирует последовательность чисел от 0 до n включительно:

			def my_generator(n):
    for i in range(n + 1):
        yield i
        
my_gen = my_generator(5)

for i in my_gen:
    print(i)
		

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

34. Для чего используется ключевое слово yield

Ключевое слово «yield» используется для создания генераторов. Генератор — это функция, которая может возвращать последовательность значений используя инструкции yield вместо return. При каждом вызове инструкции yield генератор возвращает значение, после чего сохраняет свое состояние и приостанавливает свое выполнение до следующего вызова. Это позволяет генерировать последовательности значений без необходимости создания и хранения всех значений в памяти, что может быть особенно полезно при работе с большими объемами данных. Кроме того, генераторы являются итерируемыми и могут использоваться в циклах for.

35. Чем отличаются iter и next

iter и next являются методами специальных методов в Python, которые обеспечивают поддержку итерации для объектов.

Метод iter возвращает объект, который может быть использован для итерации по элементам контейнера. Объект, возвращаемый iter, должен содержать метод next.

Метод next должен вернуть следующий элемент в итерации или вызвать исключение StopIteration, если элементов больше нет.

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

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

Например:

			class MyIterator:
    def __init__(self, data):
        self.index = 0
        self.data = data
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        result = self.data[self.index]
        self.index += 1
        return result
		

Метод iter возвращает сам объект, а метод next возвращает следующий элемент data каждый раз, когда вызывается.

36. Что такое контекстный менеджер

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

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

			with open('file.txt', 'r') as f:
    data = f.read()
		

В этом примере open() возвращает контекстный менеджер f. Когда блок with начинается, вызывается метод enter() контекстного менеджера, который открывает файл. Затем выполняется код в блоке, который использует f для чтения данных из файла. При завершении блока with вызывается метод exit() контекстного менеджера, который закрывает файл.

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

37. Как сделать Python-скрипт исполняемым в различных операционных системах

Для того чтобы сделать Python-скрипт исполняемым в различных операционных системах, можно воспользоваться утилитой PyInstaller, которая позволяет упаковать скрипт в исполняемый файл для Windows, Linux и macOS.

Чтобы установить PyInstaller, можно выполнить следующую команду в командной строке:

			pip install pyinstaller
		

После установки PyInstaller необходимо перейти в директорию с Python-скриптом и запустить утилиту с соответствующими параметрами для создания исполняемого файла. Например:

			pyinstaller myscript.py --onefile
		

Эта команда создаст единый исполняемый файл myscript.exe (для Windows) или myscript (для Linux/macOS), который можно запустить на соответствующих операционных системах.

Если нужно создать исполняемый файл с определенными параметрами, можно воспользоваться другими параметрами PyInstaller, такими как —icon для добавления иконки, —name для задания имени исполняемого файла и т.д.

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

38. Как сделать копию объекта и как сделать глубокую копию объекта

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

Метод deepcopy() создает глубокую копию объекта, то есть создает новый объект, который содержит копии всех объектов, на которые ссылаются элементы исходного объекта. Если вы измените какой-либо из этих объектов, изменения не отразятся на копии или на исходном объекте.

Вот примеры использования этих методов:

			import copy

# создание копии объекта
new_list = old_list.copy()

# создание глубокой копии объекта
new_list = copy.deepcopy(old_list)
где old_list - исходный список, а new_list - его копия.
		

Примечание: для выполнения глубокого копирования объектов, сами объекты также должны поддерживать копирование. Если объекты в ваших данных не поддерживают копирование, deepcopy() вернет исходный объект, а не его копию.

39. Опишите принцип работы сборщика мусора в Python

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

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

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

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

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

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

			# объявляем глобальную переменную
global_var = 42

def my_func():
    # можно использовать глобальную переменную
    global global_var
    print(global_var)

# вызываем функцию
my_func()
		

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

41. Для чего в классе используется атрибут slots

Атрибут slots в классе Python используется для оптимизации памяти и ускорения работы с объектами класса. Он позволяет явно указать, какие атрибуты объекта будут использоваться, а какие нет.

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

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

Например, если у вас есть класс Person с атрибутами name и age, вы можете определить slots следующим образом:

			class Person:
    __slots__ = ['name', 'age']

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

Таким образом, каждый экземпляр класса Person будет содержать только атрибуты name и age, и никакие другие атрибуты не будут созданы.

42. Какие пространства имен существуют в Python

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

Python имеет множество встроенных пространств имен. Некоторые из них включают:

builtins: содержит встроенные функции и типы, которые доступны в любой области видимости по умолчанию.

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

name: это атрибут, который содержит имя текущего модуля. Если модуль импортирован, то значение name будет именем модуля. Если модуль запускается как скрипт, то значение name будет «main».

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

locals(): это функция, которая возвращает словарь, содержащий все имена в локальной области видимости.

Это далеко не полный список, но это некоторые из наиболее распространенных пространств имен в Python.

43. Как реализуется управление памятью в Python

Управление памятью осуществляется автоматически с помощью механизма сборки мусора (Garbage collector). Когда объект в Python больше не нужен (например, после того как на него уже нет ссылок), он помечается как garbage (мусор), после чего он будет автоматически удален при следующем запуске сборщика мусора.

Используется метод подсчета ссылок для отслеживания того, когда объект уже не нужен, и этот объект должен быть освобожден. Кроме того, Python также использует циклический сборщик мусора (Cycle detector), который может определить и удалить объекты, на которые ссылается другой объект, на который уже нет ссылок.

Сборка мусора в Python использует алгоритм под названием «reference counting», который подсчитывает количество ссылок на каждый объект в памяти. Когда количество ссылок на объект становится равным нулю, он помечается как мусор и память автоматически освобождается. В Python также реализованы другие алгоритмы сборки мусора, такие как «generational garbage collection», который разбивает объекты на несколько «поколений» и собирает мусор с различной частотой в зависимости от поколения, в котором они находятся, но reference counting является основой управления памятью в Python.

Модуль gc в Python также предлагает дополнительный функционал для управления памятью. Например, метод gc.collect() позволяет сделать принудительную сборку мусора.

44. Что такое метаклассы и в каких случаях их следует использовать

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

Метаклассы могут быть полезны в следующих случаях:

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

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

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

			class MyMeta(type):
    def __new__(cls, name, bases, dct):
        dct['my_attribute'] = 42
        return super(MyMeta, cls).__new__(cls, name, bases, dct)

class MyClass(metaclass=MyMeta):
    pass

print(MyClass.my_attribute)
		

В этом примере создается метакласс MyMeta, который добавляет атрибут my_attribute к любому классу, который использует данный метакласс для своего создания. Затем создается класс MyClass, который использует метакласс MyMeta. При вызове print(MyClass.my_attribute) выводится значение 42, так как этот атрибут был добавлен в момент создания класса.

45. Зачем нужен pdb

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

Модуль pdb предоставляет интерфейс командной строки, который можно использовать для взаимодействия с кодом Python во время его выполнения. Вы можете войти в режим pdb в своей программе Python, вставив следующую строку кода там, где вы хотите остановить отладчик: импортировать PDB;

			import pdb; 
pdb.set_trace()
		

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

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

			>>> [0, 1][10:]
		

Выражение >>> [0, 1][10:] возвращает пустой список [], так как срез [10:] означает извлечение элементов начиная с индекса 10 и до конца списка [0, 1], но таких элементов нет.

Таким образом, результатом выражения >>> [0, 1][10:] является пустой список [].

47. Как создать класс без слова class

Kласс можно создать без использования ключевого слова class, используя типы type или metaclass. Например, следующий код определяет класс MyClass без использования ключевого слова class:

			MyClass = type('MyClass', (), {'x': 42, 'foo': lambda self: self.x})
		

Этот код эквивалентен определению класса с использованием ключевого слова class:

			class MyClass:
    x = 42
    
    def foo(self):
        return self.x
		

Оба определения класса эквивалентны и создают объект класса MyClass. Однако, использование ключевого слова class обычно является более явным и удобным.

Jбратите внимание, что использование типов type или metaclass для создания класса может быть менее читабельным и более сложным для понимания, чем использование ключевого слова class.

48. Как перезагрузить импортированный модуль

Чтобы перезагрузить импортированный модуль в Python, вы можете использовать функцию reload() из модуля importlib. Вот как это сделать:

			from importlib import reload
import module_name

reload(module_name)
		

Замените module_name на фактическое имя модуля, который вы хотите перезагрузить.

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

49. Напишите декоратор, который будет перехватывать ошибки и повторять функцию максимум N раз

Вот пример декоратора на Python, который будет перехватывать ошибки и повторять функцию максимум N раз:

			import functools

def retry(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        max_retries = 3
        for i in range(max_retries):
            try:
                result = func(*args, **kwargs)
                return result
            except Exception as e:
                print(f'Error occurred: {e}. Retrying ({i+1}/{max_retries})...')
        raise Exception(f'Function {func.__name__} failed after {max_retries} attempts.')
    return wrapper
		

Для использования декоратора вам нужно добавить @retry перед определением функции, которую вы хотите обернуть:

			@retry
def my_function(arg1, arg2):
    # ваш код здесь
		

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

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

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

			>>> len(' '.join(list(map(str, [[0], [1]]))))
		

Вот разбивка того, как был получен этот результат:

  • Внутреннее выражение map(str, [[0], [1]]) преобразует целые значения 0 и 1 в строки, в результате чего получается [‘0’, ‘1’].
  • Затем функция списка преобразует этот итератор в список.
  • Метод соединения соединяет элементы списка пробелом, в результате чего получается строка «0 1».
  • Наконец, функция len возвращает длину этой строки, которая равна 3.

Результат — 3.

51. Лёгкий ли Python язык

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

52. Какие проблемы есть в Python

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

  • Глобальная блокировка интерпретатора (GIL) — это механизм в реализации Python на CPython, который предотвращает одновременное выполнение кода Python несколькими потоками. В некоторых случаях это может ограничить производительность задач, связанных с процессором;
  • Управление пакетами и зависимостями. Управление сторонними пакетами и зависимостями в Python иногда может быть сложным, особенно для крупных проектов или в сложных средах;
  • Производительность. Хотя Python обычно считается быстрым языком, он не может быть оптимальным выбором для задач, требующих высокой производительности, таких как машинное обучение или научные вычисления;
  • Типизация и статический анализ. Python — это язык с динамической типизацией, что может затруднить обнаружение определенных типов ошибок во время компиляции;
  • Управление памятью: автоматическое управление памятью в Python может в некоторых случаях привести к утечке памяти или неэффективному использованию памяти;
  • Документация: Хотя сообщество Python уделяет большое внимание документации, некоторые пакеты или библиотеки могут иметь неполную или устаревшую документацию, что может затруднить их эффективное использование.

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

53. Когда будет выполнена ветка else в конструкции try…except…else

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

Пример, в котором будет выполнена ветка else:

			try:
    # some code here
except:
   # code to handle the exception
else:
   # code to execute if there is no exception
		

Если в блоке try не возникает исключений, то выполняется код в блоке else.

54. Поддерживает ли Python множественное наследование

Да, Python поддерживает множественное наследование. Это означает, что класс может наследовать функциональность от нескольких предков, путем указания их имен в скобках при определении класса.

Например:

			class MyBaseClass1:
    pass

class MyBaseClass2:
    pass

class MyDerivedClass(MyBaseClass1, MyBaseClass2):
    pass
		

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

Существует несколько способов объявления класса, который наследует от нескольких родительских классов, но один из распространенных способов — это просто указать несколько родительских классов в скобках при определении класса-потомка.

Cледующий код определяет класс MyClass, который наследует от классов Parent1 и Parent2:

			class Parent1:
    def method1(self):
        print("This is a method from Parent1")

class Parent2:
    def method2(self):
        print("This is a method from Parent2")

class MyClass(Parent1, Parent2):
    pass

obj = MyClass()
obj.method1() # outputs "This is a method from Parent1"
obj.method2() # outputs "This is a method from Parent2"
		

Приведенный выше код создает MyClass, который наследует свойства и методы как от класса Parent1, так и от класса Parent2. Вы можете вызвать методы как от Parent1, так и от Parent2 через объект MyClass.

55. Как dict и set реализованы внутри, какова сложность получения элемента и сколько памяти потребляет каждая структура

Dict и Set реализованы в виде хэш-таблицы.

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

Сложность получения элемента в Dict и Set в наилучшем случае составляет O(1), поскольку элемент может быть получен просто с помощью хэш-функции в качестве индекса массива. Однако в худшем случае, когда возникают хэш-коллизии, сложность может вырасти до O(n), где n — количество элементов в таблице.

Также стоит заметить, что сложность операций добавления, удаления и поиска элементов в Set и Dict также составляет O(1) в наилучшем случае и O(n) в худшем случае.

56. Что такое MRO и как это работает

MRO (Method Resolution Order) — это порядок разрешения методов, который используется в языке программирования Python при наследовании классов.

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

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

Например, если класс A наследуется от классов B и C, а класс B наследуется от класса D, а класс C наследуется от класса E, то MRO для класса A будет определен как [A, B, D, C, E, object]. Это означает, что если существует метод, определенный в классе A и в одном из его родительских классов, то метод из класса A будет вызван, а не из его родительских классов.

57. Как аргументы передаются в функции: по значению или по ссылке

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

Например:

			def increment(x):
    x += 1
    return x

y = 10
print(increment(y)) # Output: 11
print(y) # Output: 10
		

Здесь модификации x внутри функции не влияют на значение переменной y, так как теперь x ссылается на новый объект в памяти (увеличенное значение на 1), но y по-прежнему ссылается на старый объект (изначальное значение 10).

При работе со изменяемыми объектами (например, списками), модификация объекта внутри функции будет отражаться вне функции. Например:

			def modify_list(lst):
    lst.append(4)

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list) # Output: [1, 2, 3, 4]
		

Здесь модификации списка lst в функции modify_list отражаются и на переменной my_list, так как обе переменные ссылаются на один и тот же список в памяти.

58. С помощью каких инструментов можно выполнить статический анализ кода

Для статического анализа кода есть несколько инструментов:

  • Pylint — это инструмент, который анализирует исходный код на соответствие PEP8, а также предупреждает о потенциальных ошибках в коде;
  • Flake8 — это комбинированный инструмент, который объединяет в себе Pylint, PyFlakes и множество других правил, обеспечивающих соответствие стиля написания кода и обнаруживающих ошибки в исходном коде;
  • Mypy — это статический типизатор для Python, который позволяет находить ошибки в типах переменных в исходном коде;
  • Bandit — это инструмент для поиска уязвимостей в исходном коде Python;
  • Black — это инструмент для автоматического форматирования кода Python, который придерживается только одного стиля написания кода;
  • Pycodestyle — это простая консольная утилита для анализа кода Python, а именно для проверки кода на соответствие PEP8. Один из старейших анализаторов кода, до 2016 года носил название pep8, но был переименован по просьбе создателя языка Python Гвидо ван Россума;
  • Vulture — это небольшая утилита для поиска “мертвого” кода в программах Python. Она использует модуль ast стандартной библиотеки и создает абстрактные синтаксические деревья для всех файлов исходного кода в проекте. Далее осуществляется поиск всех объектов, которые были определены, но не используются. Vulture полезно применять для очистки и нахождения ошибок в больших базовых кодах.

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

59. Что будет напечатано в результате выполнения следующего кода

			import sys
    arr_1 = []
    arr_2 = arr_1
    print(sys.getrefcount(arr_1))
		

В результате выполнения данного кода будет напечатано число, равное количеству ссылок на объект arr_1, которые существуют в настоящий момент времени. Так как мы создаем две переменные, arr_1 и arr_2, которые ссылаются на один и тот же пустой список [], то количество ссылок на него будет равно 2. Поэтому в результате выполнения данного кода будет напечатано число 2. Эта величина может быть немного больше, чем ожидается, из-за внутренней оптимизации CPython, которая добавляет временные ссылки на объекты.

60. Что такое GIL и почему GIL всё ещё существует

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

GIL всё ещё существует, потому что он является важной частью интерпретатора CPython и его логики работы с потоками. Однако, недавние версии Python имеют некоторые механизмы для обхода ограничений GIL, такие как использование многопроцессных вычислений вместо многопоточных и использование асинхронного программирования. Кроме того, есть и другие реализации языка Python, такие как Jython и IronPython, которые не используют GIL.

Таким образом, вопрос насколько существование GIL ограничивает производительность Python в настоящее время является разногласием в сообществе.

61. Опишите процесс компиляции в Python

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

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

Байт-код хранится в каталоге pycache с расширением .pyc. Python проверяет, есть ли у файла .py уже соответствующий файл .pyc, и, если файл .pyc старше файла .py, он компилирует файл .py в новый файл .pyc.

Таким образом, процесс «компиляции» в Python включает интерпретатор, который компилирует исходный код в байтовый код, который затем выполняется PVM. Однако этот процесс происходит автоматически и за кулисами, без необходимости пользователю явно вызывать отдельный шаг компиляции.

62. Как тиражировать Python код

Реплицировать (т.е. создавать копии) Python кода, то можно воспользоваться различными инструментами и техниками, такими как использование систем контроля версий, например, Git или SVN, или создание образов виртуальных машин с помощью Docker, VirtualBox, Vagrant и т.д. Это позволит вам легко скопировать и развернуть копии вашего приложения на других устройствах или серверах. Вы также можете использовать специальные инструменты для сборки вашего приложения в исполняемый файл, такие как pyinstaller или cx_Freeze, что позволит запускать ваше приложение на других машинах без установки Python.

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

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

			import shutil

# copy file from source to destination
shutil.copy('/path/to/source/file.txt', '/path/to/destination')
Обратите внимание, что для использования этого модуля необходимо импортировать его в ваш код.
		

63. Что такое дескрипторы и есть ли разница между дескриптором и декоратором

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

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

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

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

64. Почему всякий раз, когда Python завершает работу, не освобождается вся память

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

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

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

65. Что будет напечатано в результате выполнения следующего кода

			class Variable:

    def __init__(self, name, value):
        self._name = name
        self._value = value

    @property
    def value(self):
        print(self._name, 'GET', self._value)
        return self._value

    @value.setter
    def value(self, value):
        print(self._name, 'SET', self._value)
        self._value = value

    var_1 = Variable('var_1', 'val_1')
    var_2 = Variable('var_2', 'val_2')
    var_1.value, var_2.value = var_2.value, var_1.value
		

При выполнении этого кода будет выведено следующее:

			var_2 GET val_2
var_1 GET val_1
var_2 SET val_1
var_1 SET val_2
		

В этом коде определяется класс Variable со свойствами «name» и «value». Метод @property используется для определения свойства значения, которое можно прочитать с помощью «getter» (функция, используемая для получения значения свойства) и установить новое значение с помощью «setter» (функция, используемая для установки нового значения свойства). Затем создаются два экземпляра класса, и значения их свойств «value» меняются по очереди с помощью кортежа. При каждом вызове метода ‘value’ класса Variable выводится сообщение о том, что происходит (GET — когда значение свойства читается, SET — когда устанавливается новое значение свойства).

66. Что такое интернирование строк и почему это есть в Python

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

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

В Python интернирование строк применяется для строковых констант, которые состоят из символов ASCII и имеют длину не более 20 символов. Это объясняется тем, что длинные строки могут занимать слишком много места в памяти, что может привести к проблемам производительности.

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

Пример кода, который демонстрирует интернирование строк в Python:

			a = 'hello'
b = 'hello'
print(a is b) # True, потому что обе переменные ссылаются на один и тот же объект в памяти

c = 'hello world'
d = 'hello world'
print(c is d) # False, потому что строка "hello world" длиннее 5 символов и не является интернированной

e = '_123'
f = '_123'
print(e is f) # True, потому что строка содержит только цифры и символ '_'
		

67. Как упаковать бинарные зависимости

Для упаковки бинарных зависимостей в проект следует использовать менеджеры пакетов. Для Python наиболее распространены pip и conda. Для Java — Maven или Gradle.

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

  • Установите необходимые библиотеки и зависимости в проекте:
			pip install requests numpy pandas
		
  • Создайте файл requirements.txt с полным списком зависимостей:
			requests
numpy
pandas
		
  • Упакуйте зависимости в архив:
			pip freeze > requirements.txt
		

Можно передать файл requirements.txt другим пользователям вашего проекта, которые могут установить все зависимости одной командой:

			pip install -r requirements.txt
		

Для упаковки бинарных зависимостей можно использовать инструмент wheel. Wheel-файлы — это zip-архивы, содержащие установочные файлы для Python-пакетов, и могут содержать бинарные расширения (например, скомпилированные модули C), которые необходимо собрать и установить на целевой машине.

Для создания wheel-файла для Python-пакета можно использовать команду pip wheel. Например, если есть файл с требованиями requirements.txt, содержащий список зависимостей вашего проекта, можете создать wheel-файлы для всех зависимостей с помощью следующей команды:

			pip wheel -r requirements.txt
		

Вы также можете установить wheel-файлы с помощью pip install, указав имя файла:

			pip install mypackage-1.0.0-py3-none-any.whl
		

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

68. Почему в Python нет оптимизации хвостовой рекурсии и как это реализовать

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

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

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

			import sys
sys.setrecursionlimit(4000)
		

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

			def factorial(n):
    result = 1
    for i in range(1, n+1):
        result *= i
    return result
		

это вместо использования рекурсивного подхода с вызовом factorial(n-1) внутри функции factorial(n).

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

69. Что такое wheels и eggs, и в чём разница

В Python wheels и eggs — это форматы пакетов для установки и дистрибуции пакетов с помощью утилиты управления пакетами pip.

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

Wheels — это новый формат дистрибуции пакетов, который был введен в Python 2.7. Он поддерживается большинством пакетов на PyPI и имеет множество преимуществ, например:

Он не содержит .pyc файлов, что снижает вероятность конфликтов.

Он легко переносится между платформами и версиями Python.

Он поддерживает сжатие библиотек и упрощает установку требований.

В целом, wheels считается более продвинутой и предпочтительной формой дистрибуции пакетов в Python.

70. Как получить доступ к модулю, написанному на Python из C и наоборот

Для того чтобы получить доступ к модулю, написанному на Python из C, можно использовать библиотеку Python/C API, которая позволяет вызывать Python функции и работать с объектами Python из C программы. Для того чтобы получить доступ к модулю, сначала нужно получить указатель на объект модуля с помощью функции PyImport_ImportModule(). Затем можно получить указатель на функции или объекты модуля с помощью функции PyObject_GetAttrString().

Например, вот пример кода на C, который вызывает функцию «hello» из модуля «example» на Python:

			#include 

int main() {
    Py_Initialize();
    PyObject* module = PyImport_ImportModule("example");
    PyObject* func = PyObject_GetAttrString(module, "hello");
    PyObject* result = PyObject_CallObject(func, NULL);
    printf("Result: %s\n", PyUnicode_AsUTF8(result));
    Py_DECREF(func);
    Py_DECREF(module);
    Py_DECREF(result);
    Py_Finalize();
    return 0;
}
		

Аналогичным образом можно вызвать функции из библиотек, написанных на C из Python, используя библиотеку ctypes. Например, вот пример кода на Python, который вызывает функцию sqrt из библиотеки math:

			from ctypes import cdll
libm = cdll.LoadLibrary('libm.so')
print(libm.sqrt(4.0))
		

Здесь мы загружаем библиотеку libm.so (которая содержит функцию sqrt) и вызываем её с помощью атрибута dot-notation.

71. Как ускорить существующий код Python

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

  • Векторизация: векторизация позволяет оптимизировать код, который выполняет большое количество операций над массивами данных, например, использование библиотеки NumPy;
  • Выбор правильных структур данных: выбор правильных структур данных и алгоритмов может значительно ускорить выполнение кода. Например, использование словарей может быть более эффективным, чем использование списков;
  • Компиляция: компиляция Python-кода в байт-код или в машинный код может ускорить выполнение кода. Для этого можно использовать Cython, Nuitka или PyPy;
  • Многопоточность: использование многопоточности может ускорить выполнение задач, которые можно разделить на несколько независимых частей;
  • Параллелизм: параллельное выполнение задач на нескольких ядрах процессора может ускорить выполнение кода;
  • Оптимизация: такие инструменты, как cProfile и line_profiler, могут помочь оптимизировать код, выявляя узкие места в его выполнении и предоставляя информацию о времени выполнения каждой строки кода.

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

72. Что такое pycache и что такое файлы .pyc

В Python, когда вы запускаете программу, интерпретатор сначала компилирует ее в байт-код и сохраняет в папке pycache Это делается для того, чтобы в следующий раз выполнить программу быстрее, поскольку байт-код можно напрямую загрузить в память, а не приходится компилировать заново. Файлы байт-кода имеют расширение .pyc и обычно хранятся в подкаталоге каталога, содержащего соответствующие файлы .py. Каталог pycache автоматически создается интерпретатором Python и используется для хранения скомпилированных файлов байт-кода. Каталог содержит скомпилированные версии импортированных сценариев Python, а также любые модули, импортированные этими сценариями. Этот каталог обычно находится в том же каталоге, что и файлы .py, но может также находиться во временном каталоге системы, если исходный каталог доступен только для чтения. Как правило, вам не нужно напрямую взаимодействовать с каталогом pycache или файлами .pyc в нем, поскольку они автоматически управляются интерпретатором Python. Однако вы можете удалить файлы .pyc, если хотите заставить интерпретатор перекомпилировать соответствующие скрипты Python.

Файлы .pyc — это скомпилированные байт-коды Python, которые создаются при импорте модулей. Когда вы импортируете модуль в Python, интерпретатор компилирует его и создает файл .pyc, который содержит байт-коды для модуля. Этот файл будет использоваться для ускорения повторных импортов модуля, так как он может быть загружен вместо повторной компиляции каждый раз.

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

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

73. Что такое виртуальное окружение

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

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

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

			python3 -m venv myenv
		

где myenv — имя виртуального окружения.

После создания виртуального окружения вы можете активировать его, выполнив команду (для Unix-системы):

			source myenv/bin/activate
		

или (для Windows):

			myenv\Scripts\activate
		

После активации виртуального окружения вы можете устанавливать и использовать пакеты Python без влияния на глобальное окружение вашего компьютера.

74. Python — это императивный или декларативный язык

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

«Императивный язык» это термин, который относится к классу языков программирования, использующих прямые команды для управления компьютером, в отличие от декларативных языков. В императивных языках программист явно описывает действия, которые нужно выполнить компьютеру, а не просто описывает желаемый результат. Примеры императивных языков программирования это Java, C, C++, Python и JavaScript.

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

75. Что такое менеджер пакетов и какие менеджеры пакетов вы знаете

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

  • pip — это стандартный менеджер пакетов Python. Он позволяет устанавливать пакеты из Python Package Index (PyPI) и других источников, а также управлять зависимостями проекта;
  • conda — это менеджер пакетов и среда управления, который позволяет управлять пакетами и зависимостями для проектов на Python, а также для других языков программирования и платформ;
  • easy_install — инструмент для установки и управления пакетами Python, который был стандартным до выпуска Python 3. Используется редко в настоящее время;
  • poetry — новый менеджер пакетов, предназначенный для замены в некоторой степени pip и virtualenv.

76. В чём преимущества массивов numpy по сравнению с (вложенными) списками python

Основное преимущество массивов NumPy перед списками Python заключается в том, что NumPy использует более оптимизированную память и имеет более эффективные методы работы с массивами, что делает его подходящим выбором для работы с большими объемами данных и научных вычислений. Например, с NumPy вы можете выполнять бродкастинг (broadcasting), матричные операции и другие векторизованные вычисления с более высокой производительностью, чем при использовании вложенных списков.

Некоторые из основных преимуществ NumPy:

  • Более оптимизированная память, что позволяет NumPy работать быстрее с большим объемом данных;
  • Встроенные методы для выполнения арифметических операций, таких как сумма и произведение, которые могут работать сразу над всеми элементами массивов;
  • Возможность выполнять матричные операции и другие векторизованные вычисления;
  • Простой синтаксис для выполнения операций над массивами;
  • Возможность конвертировать массивы NumPy в другие формы данных, такие как списки Python или таблицы Pandas.

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

77. Как реализовать функцию, которая должна использовать статическую переменную, если вы не можете писать код вне функции и у вас нет информации о внешних переменных

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

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

			def my_function():
    static_var = 0
    def inner_function():
        nonlocal static_var
        static_var += 1
        return static_var
    return inner_function

# создаем объект функции, который использует статическую переменную
f = my_function()

# вызываем функцию несколько раз, чтобы увидеть изменение значения статической переменной
print(f())  # выводит 1
print(f())  # выводит 2
print(f())  # выводит 3
		

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

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

78. Что будет напечатано в результате выполнения следующего кода

			def f_g():
    yield 43
    return 66

    print(f_g())
		

Результат выполнения кода будет объект генератора (generator object). Когда мы вызываем функцию с yield, то это создает генератор, который возвращает объект-итератор. Так как print(f_g()) вызывает только генератор, а не запускает его выполнение, то мы получим объект-итератор в качестве результата, а не значение, возвращенное посредством yield или return. Если мы хотим получить значение из генератора, мы должны использовать ключевое слово next, чтобы продвинуть генератор на следующее значение или использовать цикл for для извлечения всех значений из итератора. Вот пример вызова генератора с помощью цикла for:

			def f_g():
    yield 43
    return 66
for i in f_g():
    print(i)
		

Этот код выведет только 43, потому что выполнение генератора останавливается после первого вызова yield.

79. Как имплементировать словарь с нуля

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

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

			class MyDictionary:
    def __init__(self):
        self.size = 10 # размер таблицы
        self.keys = [None] * self.size
        self.values = [None] * self.size
        
    def __setitem__(self, key, value):
        index = hash(key) % self.size # вычисляем индекс
        self.keys[index] = key
        self.values[index] = value
        
    def __getitem__(self, key):
        index = hash(key) % self.size
        return self.values[index]
		

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

			d = MyDictionary()
d['apple'] = 'red'
d['banana'] = 'yellow'
print(d['apple']) # выведет 'red'
print(d['banana']) # выведет 'yellow'
		

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

80. Напишите однострочник, который будет подсчитывать количество заглавных букв в файле

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

			num_uppercase = sum(1 for line in open('filename.txt') for character in line if character.isupper())
		

В этом однострочнике мы открываем файл ‘filename.txt’ и пробегаемся по всем его строкам и символам в каждой строке. Для каждого символа, который является заглавной буквой метод isupper() возвращает True, и мы добавляем 1 к счетчику с помощью функции sum(). В конце, num_uppercase будет содержать количество заглавных букв в файле.

81. Что такое файлы .pth

Файлы с расширением .pth — это файлы, которые могут быть использованы для добавления директорий в путь поиска модулей Python. Директивы .pth выполняются при запуске интерпретатора Python и добавляют определенные каталоги в переменную sys.path. Это удобно, когда нужно импортировать модули из нестандартных директорий без необходимости переноса файлов в директории по умолчанию. Использование директив .pth достаточно распространено в мире Python и они встречаются в различных средах разработки и фреймворках, таких как PyTorch.

Файлы .pth могут быть также использованы злоумышленниками для внедрения вредоносного кода в систему Python, так как они могут изменять список каталогов, в которых выполняется поиск модулей Python. Поэтому необходимо быть внимательными при работе с такими файлами и использовать только те файлы .pth, которые вы знаете и доверяете.

82. Какие функции из collections и itertools вы используете

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

  • defaultdict: это удобный способ создания словаря с заданным значением по умолчанию для любого ключа, который еще не был добавлен в словарь;
  • сounter: это удобный способ подсчета количества встречаемых элементов в списке или другом итерируемом объекте. Он возвращает объект, который можно использовать как словарь, где ключами являются элементы, а значения — количество их вхождений;
  • namedtuple: можно создать именованный кортеж с заданными полями, что может быть удобно для работы с данными, которые имеют структуру, но не требуют создания класса;
  • itertools.chain: позволяет конкатенировать несколько итерируемых объектов в единый итератор;
  • itertools.groupby: позволяет группировать элементы итерируемого объекта по заданному ключу;
  • itertools.combinations и itertools.permutations: генерируют все различные комбинации или перестановки элементов из заданного множества.

83. Что делает флаг PYTHONOPTIMIZE

Флаг -O или PYTHONOPTIMIZE в Python используется для оптимизации скомпилированного кода, что может привести к ускорению выполнения программы. Этот флаг удаляет отладочную информацию, отключает asset checks, asserts и отладочные проверки.

Стандартная оптимизация -O удаляет docstrings из скомпилированного byte-code, а также удаляет assert statements. С флагом -OO удаляются все docstrings в модулю (включая те, которые не соответствуют многострочным строкам) и также удаляются assert statements.

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

Например, для запуска скрипта с флагом -O, можно использовать следующую команду в командной строке:

			python -O my_script.py
		

84. Что будет напечатано в результате выполнения следующего кода

			arr = [[]] * 5
arr_1, arr_2 = arr, arr
for k, arr in enumerate((arr_1, arr_2)):
   arr[0].append(k)
arr = (arr_1, 5, arr_2)
print(arr)
		

Вывод в консоли: ([0, 1], 5, [0, 1]).

Первоначально arr представляет собой список из одного пустого списка, который умножается на 5, в результате чего arr представляет собой список из 5 ссылок на один и тот же внутренний пустой список. Затем arr_1 и arr_2 устанавливаются в этот же список. Функция enumerate() вызывается для кортежа, содержащего arr_1 и arr_2, который перебирает обе переменные одновременно с переменной цикла k. Для каждой итерации цикла arr присваивается текущей переменной в кортеже, это означает, что на первой итерации arr присваивается arr_1, а на второй итерации arr присваивается arr_2. Текущий внутренний список, присвоенный arr, затем модифицируется путем добавления значения переменной цикла k к его первому элементу. Наконец, arr переназначается кортежу, содержащему arr_1, целое число 5 и arr_2. Когда этот кортеж печатается, он показывает модифицированный внутренний список, на который ссылаются как arr_1, так и arr_2, целое число 5 и снова модифицированный внутренний список, на который ссылаются как arr_1, так и arr_2.

85. Какие переменные среды, влияющие на поведение интерпретатора Python, вы знаете

Несколько известных переменных среды, влияющих на поведение интерпретатора Python:

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

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

PYTHONSTARTUP — определяет путь к файлу, который содержит инициализационный код Python, он выполняется в начале каждой сессии интерпретатора.

PYTHONIOENCODING — задает кодировку, которую интерпретатор Python должен использовать для обработки ввода / вывода.

PYTHONLEGACYWINDOWSSTDIO — если установлено в любое ненулевое значение, указывает интерпретатору Python использовать режим Windows для ввода-вывода вместо UNIX-стиля.

В зависимости от операционной системы, может быть и другие переменные среды, которые влияют на поведение интерпретатора Python. Чтобы увидеть все переменные среды, которые влияют на вашу систему, вы можете использовать команду «env» в терминале, если вы используете UNIX-подобную систему, или команду «set» в командной строке Windows.

Эти альтернативные реализации продолжают существовать, поскольку каждая из них предлагает уникальные функции и преимущества по сравнению со стандартной реализацией Python (CPython). Например, Cython может обеспечить значительное повышение производительности по сравнению со стандартным кодом Python, а IronPython позволяет коду Python легко взаимодействовать с другими приложениями .NET. PyPy также может обеспечить значительное повышение производительности по сравнению со стандартным кодом Python, особенно при работе с задачами, требующими большого количества вычислений. В целом эти альтернативные реализации Python расширяют функциональные возможности языка и предоставляют больше возможностей разработчикам, решившим использовать Python в своих проектах.

86. Что такое Cython, что такое IronPython, что такое PyPy, почему они до сих пор существуют и зачем

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

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

PyPy — это высокопроизводительная реализация языка программирования Python. Он был создан с целью предоставления более быстрой и эффективной альтернативы стандартному интерпретатору CPython. PyPy включает компилятор Just-In-Time (JIT), который может оптимизировать выполнение кода Python во время выполнения, что может привести к значительному повышению производительности по сравнению с CPython, особенно для определенных типов рабочих нагрузок. PyPy также поддерживает многие из тех же функций и модулей, что и CPython, включая объектно-ориентированное программирование, динамическую типизацию и стандартную библиотеку Python.

87. Как перевернуть генератор

Можно перевернуть генератор в Python, используя функцию reversed(). Вот пример, который демонстрирует это:

			my_list = [1, 2, 3, 4, 5]
my_generator = (x**2 for x in my_list)

for item in reversed(list(my_generator)):
    print(item)
		

В этом примере мы используем функцию reversed() вместе с функцией list(), чтобы создать обратный список элементов, сгенерированных генератором. Затем мы используем этот список с циклом for для перебора элементов в обратном порядке. Если вы работаете с большими наборами данных, может быть полезно использовать обратное итерирование без использования list(), чтобы избежать создания полной копии. Вот пример, который демонстрирует это:

			my_list = [1, 2, 3, 4, 5]
my_generator = (x**2 for x in my_list)

for item in reversed(tuple(my_generator)):
    print(item)
		

Здесь мы используем функцию reversed() вместе с функцией tuple() для обратного итерирования через генератор без создания полной копии.

88. Приведите пример использования filter и reduce над итерируемым объектом

Пример использования filter() и reduce() над итерируемым объектом в Python:

			from functools import reduce

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Пример использования filter() для отфильтровывания четных чисел
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # выводит [2, 4, 6, 8, 10]

# Пример использования reduce() для нахождения суммы чисел от 1 до 10
sum_of_numbers = reduce(lambda x, y: x + y, numbers)
print(sum_of_numbers) # выводит 55
		

В этом примере мы использовали filter() для отбора только четных чисел в списке numbers, и reduce() для нахождения суммы всех чисел в списке от 1 до 10.

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

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

89. Что будет напечатано в результате выполнения кода

			>>> print(_)
		

(_) можно использовать подчеркивание в качестве переменной в цикле. Примеры ниже:

			## lopping ten times using _
for _ in range(5):
    print(_)

## iterating over a list using _
## you can use _ same as a variable
languages = ["Python", "JS", "PHP", "Java"]
for _ in languages:
    print(_)

_ = 5
while _ < 10:
    print(_, end = ' ') # default value of 'end' id '\n' in python. we're changing it to space
    _ += 1
		
			0
1
2
3
4
Python
JS
PHP
Java
5 6 7 8 9
		

90. Чем фреймворк отличается от библиотеки

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

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

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

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

91. Расположите функции в порядке эффективности, объясните выбор

			def f1(arr):
   l1 = sorted(arr)
   l2 = [i for i in l1 if i < .5]
   return [i * i for i in l2]

def f2(arr):
   l1 = [i for i in arr if i < .5]
   l2 = sorted(l1)
   return [i * i for i in l2]

def f3(arr):
   l1 = [i * i for i in arr]
   l2 = sorted(l1)
   return [i for i in l1 if i < (.5 * .5)]
		

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

  • f1 сортирует весь список с помощью функции sorted, которая имеет временную сложность O(n log n), где n — длина входного списка. После сортировки он отфильтровывает все элементы, большие или равные 0,5, и вычисляет квадраты оставшихся элементов. Фильтрация списка занимает время O(n), а окончательное вычисление занимает время O(m), где m — длина отфильтрованного списка. Следовательно, общая временная сложность этой функции равна O(n log n + n + m);
  • f2 сначала фильтрует входной список, чтобы включить только элементы меньше 0,5, что занимает O(n) времени. Затем он сортирует этот отфильтрованный список с помощью функции sorted, которая имеет временную сложность O(m log m), где m — длина отфильтрованного списка. Наконец, он вычисляет квадраты отсортированных элементов. Вычисление квадратов занимает O(m) времени. Поэтому, общая временная сложность этой функции составляет O (n + m log m + m);
  • f3 вычисляет квадраты всех элементов во входном списке, что занимает O(n) времени. Затем он сортирует список в квадрате с помощью функции sorted, которая имеет временную сложность O(n log n). Наконец, он отфильтровывает все элементы, большие или равные 0,25, что занимает время O(n). Таким образом, общая временная сложность этой функции равна O(n log n).

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

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

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

Установите Memory Profiler с помощью pip:

			pip install memory-profiler
		

Используйте декоратор @profile перед функцией, которая может вызывать утечки памяти.

			from memory_profiler import profile

@profile
def my_func():
    # Some code that may cause a memory leak
		

Запустите вашу программу с помощью команды python -m memory_profiler my_script.py. Будет выведен подробный отчет о том, сколько памяти используется в каждой строке программы, а также общее использование памяти и любые утечки.

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

			import objgraph
my_list = [1, 2, 3]
objgraph.show_refs([my_list], filename='my_list.png')
		

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

93. В каких ситуациях возникает исключение NotImplementedError

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

94. Что не так с этим кодом и зачем это нужно

			if __debug__:
assert False, ("error")
		

Этот код вызывает ошибку утверждения assert с сообщением «error», если debug равен True. debug — это встроенная переменная Python, которая является истинной, если к интерактивной консоли или скрипту был присоединен флаг оптимизации -O. Для типичных скриптов в режиме отладки эта переменная равна True. Если оптимизация включена, то интерпретатор Python игнорирует все операторы утверждения assert, поэтому этот код не вызовет ошибку в optimized mode.

Такой код может быть использован для проверки инвариантов в программе или для отладки кода. Если утверждение не выполняется и вызывается AssertionError, это означает, что в программе произошло что-то непредвиденное, что нарушило заданное утверждение, и программа остановится с сообщением об ошибке.

95. Что такое магические методы dunder

Магические методы, также известные как «dunder» (double underscore) методы в Python, это специальные методы, которые начинаются и заканчиваются двойным подчеркиванием. Они позволяют определить, как объекты этого класса будут вести себя в различных контекстах, например, при использовании операторов Python, таких как +, -, *, / и т.д., при вызове функций и методов, при сериализации и многое другое.

Некоторые примеры магических методов в Python включают:

  • init: инициализирует новый экземпляр объекта;
  • str: определяет, как объект будет представлен в строковом формате;
  • add: определяет, что происходит при использовании оператора +;
  • len: определяет, как объект будет представлен при вызове функции len();
  • getitem: позволяет получать доступ к элементам объекта, как к элементам списка.

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

96. Объясните, почему такое возможно

			_MangledGlobal__mangled = "^_^"

class MangledGlobal:

   def test(self):
       return __mangled

assert MangledGlobal().test() == "^_^"
		

Это возможно из-за того, что Python имеет функцию под названием «name mangling», которая изменяет имена атрибутов класса или методов путем добавления двойного подчеркивания «__» в начале их имен. Это сделано для того, чтобы предотвратить случайное переименование атрибутов в подклассах, которые будут унаследованы суперклассом.

В этом примере, «__mangled» является приватным и скрытым атрибутом, и он был переименован в «_MangledGlobal__mangled» во время исполнения. Это означает, что вы можете обращаться к атрибуту с исходным именем «__mangled» только внутри определения класса. Если вы попытаетесь обратиться к атрибуту с исходным именем «__mangled» извне класса, вы получите ошибку «AttributeError» потому что атрибут фактически был переименован.

В нашем коде, метод «test» возвращает значение приватного атрибута «__mangled», но мы успешно можем обратиться к этому значению снова, используя измененное имя атрибута «_MangledGlobal__mangled». Поэтому у нас нет ошибки и утверждение «assert» успешно проходит.

97. Объясните, что такое monkey patching и приведите пример

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

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

			class MyBaseClass:
    def my_method(self):
        print('Hello from MyBaseClass')

def monkey_patch():
    def new_method(self):
        print('Hello from new_method')
    MyBaseClass.my_method = new_method

monkey_patch()
obj = MyBaseClass()
obj.my_method()  # выведет "Hello from new_method"
		

В этом примере мы добавляем новый метод new_method() в класс MyBaseClass, используя функцию monkey_patch(). После этого, вызов метода obj.my_method() выведет строку

			Hello from new_method
		

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

98. Как работать с транзитивными зависимостями

Для работы с транзитивными зависимостями можно использовать систему управления зависимостями, например, pipenv, poetry или pip. Эти системы позволяют устанавливать зависимости и их транзитивные зависимости, а также контролировать версии зависимостей. Например, при использовании pipenv для установки и работы с зависимостями можно использовать следующие команды:

			pipenv install <имя пакета>
		

Эта команда установит пакет и его транзитивные зависимости и создаст файл Pipfile с перечнем зависимостей и версиями.

			pipenv shell
		

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

			pipenv install --dev <имя пакета>
		

Эта команда установит пакет в качестве зависимости разработки.

			pipenv uninstall <имя пакета>
		

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

Также можно использовать файлы requirements.txt или setup.py для установки зависимостей и их транзитивных зависимостей.

			print(__name__)
         print(__file__)
		

99. Почему иногда Python так долго запускается в Windows

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

  • Перезагрузка компьютера может занять длительное время и затормозить работу Python;
  • Некоторые антивирусные программы и брандмауэры могут занимать ресурсы и замедлять выполнение команд в Python;
  • Операционная система Windows может использовать много ресурсов, когда запускаются приложения, и это может сказаться на производительности Python;
  • Зависимости и модули Python, которые используются в приложении, могут занимать много времени на импорт и загрузку;
  • Неэффективный код Python может приводить к значительным задержкам и замедлениям;
  • Другие приложения, работающие на компьютере, могут занимать много времени на выполнение задач и затруднять работу Python;
  • Наличие большого количества файлов и папок в директории проекта, а также наличие многочисленных процессов в фоновом режиме, могут приводить к замедлению работы с Питоном.

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

Следите за новыми постами
Следите за новыми постами по любимым темам
32К открытий41К показов