Вопросы на собеседовании 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:

Вопросы на собеседовании Python, ответы, на которые вам стоит знать.

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

Вопросы на собеседовании Python, ответы, на которые вам стоит знать.

Оператор continue пропускает остальную часть кода текущей итерации и переходит к следующей итерации.

Вопросы на собеседовании Python, ответы, на которые вам стоит знать.

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)

Вот и все мы рассмотрели вопросы на собеседовании Python, актуальные в 2023 году. Спасибо за прочтение данной статьи!

+1
12
+1
4
+1
1
+1
0
+1
1

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *