Вопросы на собеседовании Python, ответы, на которые вам стоит знать.
Постоянно изучая все тонкости моего любимого языка программирования, я всегда отслеживаю интересные материалы, которые могут оказаться полезными как для меня, так и для вас.
В этой статье я превратил некоторые из своих заметок в 20 вопросов для собеседований, которые охватывают структуры данных, основные концепции программирования и лучшие практики Python.
Интересно, что многие из этих вопросов также задаются на собеседованиях по Data Science.
Надеюсь, благодаря этой статье вы освежите свои знания Python или найдёте что-то новое для себя. Эти вопросы задают на собеседовании Python в 2023.
@python_job_interview – в нашем канале разобраны вопросы с собеседований, актуальные на 2023 год.
Без лишних слов, давайте сразу перейдем к делу!
1. В чём разница между списками и кортежами? Когда стоит применять именно кортежи?
Список – это изменяемая структура данных, в то время как кортеж – это неизменяемая структура.
Разница заключается в том, что мы можем менять значения объектов, содержащихся в списке.
Списки динамичны: вы можете добавлять в них элементы или удалять уже существующие. При этом список не имеет фиксированного размера.
Кортежи имеют фиксированный размер: в них нельзя добавлять новые элементы и нельзя удалять уже существующие.
Как кортежи, так и списки поддерживают индексацию и позволяют использовать оператор in
для проверки наличия в них существующих элементов.
Вот пример ситуаций, когда я думаю, что кортежи могут быть полезнее:
- Если вы объявляете коллекцию элементов, которая, как вы уверены, никогда не изменится или которую вы будете перебирать только без изменения её значений, используйте кортежи.
- Если для вас важна производительность, кортежи работают быстрее, чем списки, поскольку они являются структурами, доступными только для чтения. Если вам не нужны операции записи, рассмотрите возможность использования кортежей.
- Кортежи могут сделать ваш код более безопасным, если вы хотите предотвратить случайную запись данных, которые не нужно изменять.
Вот пример кода, который показывает, чем кортежи отличаются от списков:
>>> numbers = [1, 2, 3, 4, 5]
>>> numbers[1] = 100
>>> print(numbers)
[1, 100, 3, 4, 5]
>>> names = ("john", "joe", "alice")
>>> names[0] = "bob")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-25012ce34a87> in <module>
----> 1 names[0] = "bob"
TypeError: 'tuple' object does not support item assignment
2. В чём разница между многопроцессорностью и многопоточностью?
Многопроцессорность и многопоточность – это парадигмы программирования, направленные на ускорение вашего кода.
Многопроцессорность – это вариант реализации вычислений, когда для решения некоторой прикладной задачи используется несколько независимых процессоров. Процессоры независимы и не взаимодействуют друг с другом: они не используют одну и ту же область памяти и имеют строгую изоляцию между собой. Что касается приложений, то многопроцессорная обработка подходит для рабочих нагрузок с интенсивным использованием ЦП. Однако он имеет большой объем памяти, который пропорционален количеству процессоров.
С другой стороны, в многопоточных приложениях потоки находятся внутри одного процессора. Следовательно, они используют одну и ту же область памяти: они могут изменять одни и те же переменные и могут мешать друг другу. В то время как процессы строго выполняются параллельно, в Python в данный момент времени выполняется только один поток, и это связано с глобальной блокировкой интерпретатора (GIL). Многопоточность подходит для приложений, связанных с вводом-выводом, таких как очистка веб-страниц или извлечение данных из базы данных.
Если вы хотите узнать больше о многопоточности и многопроцессорности, я рекомендую вам ознакомиться с этой статьей.
3. В чем разница между модулем, пакетом и библиотекой?
Модуль – это просто файл Python, который предназначен для импорта в скрипты или в другие модули. Он содержит функции, классы и глобальные переменные.
Пакет – это набор модулей, которые сгруппированы вместе внутри папки для обеспечения согласованной функциональности. Пакеты могут быть импортированы точно так же, как модули. Обычно в них есть __init__.pyfile
, который указывает интерпретатору Python обрабатывать их.
Библиотека – это набор пакетов.
4. В чём заключается проблема с многопоточностью в python?
Глобальная блокировка интерпретатора (или GIL) не позволяет интерпретатору Python выполнять более одного потока одновременно. Проще говоря, GIL требует, чтобы в Python всегда выполнялся только один поток.
5. Что такое декораторы? Можете ли вы описать ситуацию, в которой стоит использовать декораторы?
Декоратор – это функция, которая получает функцию в качестве входных данных и возвращает её в качестве выходных данных, но расширяя её функционал без изменения основной модели.
У декоратора есть одна приятная особенность: вы можете написать код для одной функции, а далее использовать его по-необходимости с другими.
Типичный случай использования декораторов – это логирование.
Представьте, что вы хотите записать в терминал все значения параметров, которые передаются каждой функции, вызываемой в вашей программе. Вы можете просмотреть каждое определение функции и записать его, или вы можете просто написать один декоратор, который выполняет эту задачу логирования, и применить его ко всем функциям, которые в этом нуждаются.
Применение декоратора к функции – это всего лишь вопрос добавления одной строки над определением этой функции.
# without decorator
def my_awesome_function():
# do awesome stuff
# with a decorator
@my_awesome_decorator
def my_awesome_function():
# do even more awesome stuff
Вот пример кода, который создает декоратор с именем log
, который регистрирует значения параметров, передаваемых функции.
import logging
logging.basicConfig(
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
level=logging.INFO,
datefmt="%Y-%m-%d %H:%M:%S",
stream=sys.stdout,
)
logger = logging.getLogger("notebook")
def log(func):
def wrapper(*args, **kwargs):
output = func(*args, **kwargs)
msg = f"{func.__name__} was run with the following args: {args} and the following kwargs {kwargs}"
logger.info(msg)
return output
return wrapper
@log
def print_args(*args, **kwargs):
print(args)
print(kwargs)
>>> print_args(10, a=2, b="test")
(10,)
{'a': 2, 'b': 'test'}
2022-03-06 18:07:05,248 - notebook - INFO - print_args was run with the following args: (10,) and the following kwargs {'a': 2, 'b': 'test'}
>>> print_args(10, 100, a=2, b="test")
(10, 100)
{'a': 2, 'b': 'test'}
2022-03-06 18:07:05,562 - notebook - INFO - print_args was run with the following args: (10, 100) and the following kwargs {'a': 2, 'b': 'test'}
Декораторы также могут использоваться для других целей, таких как функции синхронизации, проверка входных данных, обеспечение контроля доступа и аутентификации, кэширование и т.д.
6. Как правильно записать данные в файл? Что может пойти не так в ином случае?
Ключевым моментом является использование контекстного менеджера.
Когда вы используете инструкцию open
без контекстного менеджера и перед закрытием файла возникает какое-либо исключение, могут возникнуть проблемы с памятью. В таком случае файл будет повреждён.
Если вы используете инструкцию with
для открытия файла и возникает проблема, Python гарантирует, что файл будет корректно закрыт.
d = {"foo": 1}
# bad practice
f = open("./data.csv", "wb")
f.write("some data")
v = d["bar"] # KeyError
# f.close() never executes which leads to memory issues
f.close()
# good practice
with open("./data.csv", "wb") as f:
f.write("some data")
v = d["bar"]
# python still executes f.close() even if the KeyError exception occurs
7. Аргументы функции передаются по ссылке или по значению?
Все аргументы функции передаются именно по ссылке (в Python): это означает, что если вы передаёте параметр функции, функция получает ссылку на этот же объект.
Если объект является изменяемым и функция изменяет его, параметр будет изменяться во внешней области действия функции. Давайте посмотрим на пример:
>>> def append_number(numbers):
numbers.append(5)
>>> numbers = [1, 2, 3, 4]
>>> print(f"before: {numbers}"
[1, 2, 3, 4]
>>> append_number(numbers)
>>> numbers
[1, 2, 3, 4, 5]
8. Как изменить способ вывода объектов?
Используйте методы __str__
и __repr__
.
Вот пример, который демонстрирует, как экземпляр из класса Person может быть красиво отформатирован при выводе в консоль.
class Person:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
def __str__(self):
return f"{self.first_name} {self.last_name} ({self.age})"
def __repr__(self):
return f"{self.first_name} {self.last_name} ({self.age})"
>>> person = Person("John", "Doe", 30) # thanks to __str__
John Doe (30)
>>> person # thanks to __repr__
John Doe (30)
9. Напишите функцию, которая вычисляет факториал целого числа n
Это можно сделать с помощью рекурсии:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
10. В чем разница между операторами is
и ==
?
==
– это оператор, который проверяет равенство значений каких-либо объектов, в то время как is
– это оператор, который проверяет их идентичность.
Два объекта могут иметь одинаковые значения, но ссылаться на разные адреса памяти.
Помните, что a is b
== id(a) == id(b)
.
11. Когда вам не следует использовать оператор assert?
Оператор assert
полезен для внутреннего тестирования и проверки работоспособности программы.
Однако его не следует использовать для выполнения проверки данных или обработки ошибок, поскольку он обычно отключён по соображениям производительности.
Представьте, что вы проверяете права администратора с помощью assert
: это может привести к большой утечке безопасности в рабочей среде.
Вместо использования инструкции assert
, вы можете выдать пользовательскую ошибку.
# Dangerous code!
def delete_product(user, product_id):
assert user.is_admin()
user.delete_product(product_id)
# Handle this properly by raising an error
def delete_product(user, product_id):
if not user.is_admin():
raise AuthError("User must have admin privileges")
else:
user.delete_product(product_id)
12. Что такое генератор в Python?
Генератор — это объект, который сразу при создании не вычисляет значения всех своих элементов.
Генераторы выглядят как типичные функции, но их поведение отличается. Для начала, вместо использования оператора return
, они используют оператор yield
.
Допустим, у вас есть файл, который весит десяток гигабайт. Из него нужно выбрать и обработать строки, подходящие под какое-то условие, а то и сравнить со строками другого большого файла. Вот задача, которую может реализовать генератор.
>>> def repeat(n, message):
for _ in range(n):
yield message
repeat_hello_five_times = repeat(5, hello)
>>> for message in repeat_hello_five_times:
print(message)
"hello"
"hello"
"hello"
"hello"
"hello"
>>> repeat_hello_five_time = ("hello" for _ in range(5))
>>> repeat_hello_five_times
<generator object <genexpr> at 0x7fb64f2362d0>
>>> for message in repeat_hello_five_times:
print(message)
"hello"
"hello"
"hello"
"hello"
"hello"
13. В чём разница между методом класса и статическим методом?
Статический метод – это метод, который знает что-либо о классе или экземпляре, который его вызвал. Он логически принадлежит классу, но не имеет неявных аргументов.
Статический метод может быть вызван либо для класса, либо для любого из его экземпляров.
Метод класса – это метод, который передаётся классу, для которого он был вызван, подобно тому, как self
передается другим методам экземпляра в классе.
Типичным примером использования методов класса является предоставление альтернативного способа создания экземпляров: метод класса, который делает это, известен как factory of the class.
class Employee(object):
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_nale
@classmethod
def from_string(cls, name_str):
first_name, last_name = map(str, name_str.split(' '))
employee = cls(first_name, last_name)
return employee
ahmed = Employee.from_string('Ahmed Besbes')
14. Приведите пример того, как вы используете функции zip
и enumerate
Функция zip
принимает несколько итераций в качестве входных данных и объединяет их в кортеж. Это может быть полезно, если вы хотите выполнить цикл по двум спискам одновременно.
>>> names = ["john", "bob", "alice"]
>>> ages = [10, 16, 20]
>>> for name, age in zip(names, ages):
print(name, age)
john 10
bob 16
alice 20
Функция enumerate
позволяет выполнять цикл по итерируемому элементу и получать доступ как к текущему индексу, так и к элементу одновременно.
>>> names = ["john", "bob", "alice"]
>>> for index, name in enumerate(names):
print(index, name)
0 john
1 bob
2 alice
15. Как бы вы использовали * args и **kwargs?
*args и **kwargs – это конструкции, которые делают функции Python более гибкими, принимая изменчивое количество аргументов.
- *args передаёт изменчивое количеств
- во аргументов без ключевых слов в список
- **kwargs передаёт изменчивое количество аргументов с ключевыми словами в словарь
Вот пример функции, которая принимает изменчивое количество аргументов с ключевыми словами, которые собираются в словаре под названием data
:
16. Приведите пример функционального программирования с использованием функции map
>>> numbers = [1, 2, 3, 4, 5]
>>> numbers_times_2 = list(map(lambda n: n * 2, numbers))
>>> numbers_times_2
[2, 4, 6, 8, 10]
17. В чём разница между операторами continue
и break
?
Оператор break
завершает цикл, в котором он содержится. Программа немедленно переходит к разделу кода, который находится во внешней области цикла.
Оператор continue
пропускает остальную часть кода текущей итерации и переходит к следующей итерации.
18. Как предотвратить вызов функции ненужное количество раз?
Используйте кэширование.
Если выходные данные, связанные с заданным вводом, не меняются в течение определенного периода времени, использование кэширования было бы хорошим вариантом.
Типичным сценарием был бы запрос к веб-серверу: если вы запрашиваете URL-адрес в первый раз и знаете, что ответ не изменится, вы можете кэшировать результат.
from cachetools import cached, TTLCache
cache = TTLCache(maxsize=100, ttl=86400)
@cached(cache)
def extract_article_content(url):
response = requests.get(url)
content = response.content
return content
19. Дайте несколько рекомендаций по PEP8
- Используйте 4 пробела на каждый уровень отступа.
- Импорт должен быть сгруппирован в следующем порядке:
- Импорт стандартной библиотеки.
- Импорт сторонних элементов.
- Импорт локального приложения/библиотеки.
- Имена функций и переменных должны быть строчными и разделяться символами подчеркивания
- Имена классов должны состоять из заглавных букв.
20. Как прочитать файл объемом 8 ГБ на Python с помощью компьютера с 2 ГБ ОЗУ?
Это решение работает для любых больших файлов.
Когда вы открываете файл, всё, что вам нужно сделать, это использовать объект файла в качестве итератора: при циклическом просмотре этого объекта, вы будете извлекать по одной строке за раз, а предыдущие строки будут удалены из памяти.
Таким образом, файл никогда не будет полностью загружен в память, и ваша обработка будет выполняться на ходу.
with open("./large_dataset.txt") as input_file:
for line in input_file:
process_line(line)