Как стать автором
Обновить
215.89
Яндекс Практикум
Помогаем людям расти

Банановые шкурки на интервью Python-разработчика

Уровень сложностиСредний
Время на прочтение13 мин
Количество просмотров10K

Крупная IT-компания, кабинет 404. Собеседование на позицию Python-разработчика. Состав: руководитель отдела, старший разработчик, HR-менеджер. И я — кандидат.

Руководитель:
— Кажется, мы всё обсудили.

Выглядывает в коридор.
— О, Виталик! Не хочешь задать последний вопрос кандидату?

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

Виталик:
— А знаешь, каков максимальный размер списка в Python??

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

Руководитель:
— Хм, давай посмотрим ещё кандидатов.

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

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

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

Операторы? Чего уж проще!

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

Например:

  • Арифметические операторы. Результатом вычисления арифметического выражения с числами будет числовое значение. Например, 2 + 2 = 4, где + — это арифметический оператор, 2 и 2 — это операнды, а 4 — это результат.

  • Операторы сравнения. Результатом выражения сравнения будет значение булева типа. Например, результатом сравнения 2 > 4 будет False. Здесь > — это оператор сравнения, а 2 и 4 — операнды.

  • Операторы присваивания. Используются для присвоения значений переменным. Самый базовый оператор присваивания — это знак равно =, но существуют и составные операторы присваивания, такие как +=, -= и другие, которые выполняют операцию с переменной и присваивают ей новое значение.

  • Операторы принадлежности in и not in. Используются для проверки принадлежности значения к последовательности, например, ‘a’ in ‘plane’, где in — это оператор, а ‘a’ и ‘plane’ — операнды. Результатом выражения будет значение булева типа.

  • Операторы тождественности. Операторы is и is not применяются для сравнения идентичности объектов, то есть проверки, являются ли операнды одним и тем же объектом. Например, value is None, где результатом выражения будет значение булева типа.

Вопрос на собеседовании: «Что такое операторы?»

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

В лучшем случае от кандидата может быть получен ответ типа такого:

Операторы — это такие… символы или слова, которые Python воспринимает как инструкцию «выполни определённые действия с операндами и верни результат этих действий!».

Иногда от кандидата можно услышать и дополнительные детали:

…А операнды — это значения, над которыми выполняются действия.

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

Логические операторы

Ещё одна разновидность операторов — логические.

 сегодня понедельник И хорошая погода

В приведённом псевдовыражении И — это логический оператор, а сегодня понедельник и хорошая погода — операнды.

Если ответы на оба вопроса-операнда вернут True (истина), это выражение вернёт True. Если ответ хотя бы на один вопрос вернёт False (ложь), выражение вернёт False.

Или вот другой пример:

 сегодня понедельник ИЛИ хорошая погода

Если ответ хотя бы на один вопрос будет True, выражение вернёт True. А если оба условия вернут False, выражение вернёт False.

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

В Python примерами логических операторов могут служить and (логическое «и») и or (логическое «или»). А логические выражения в Python могут принимать в качестве операндов разные варианты, например:

  • значения True и False,

  • выражения сравнения — ведь они возвращают True и False,

  • числа — ведь их можно привести к True или False,

  • строки — их тоже можно привести к True или False,

  • значение None — оно приводится к False.

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

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

Что возвращают логические операторы?

Оператор and

Разберем логическое выражение операнд_1 and операнд_2. В качестве операндов возьмем для начала True и False.

В соответствии с таблицей истинности

  • выражение True and True вернёт True,

  • выражение True and False вернёт False.

Проверим, так ли это в Python:

print(True and True)
print(True and False)

# Вывод:
# True
# False

Но что такое True и False в выводе? Это результат вычисления выражения? Это приведение результата к типу? Или это сами операнды? Попробуем разобраться.

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

print(True and 'Карась!')

# Вывод:
# Карась!

Продолжим эксперименты: заменим на строку и первый операнд:

print('Щука!' and 'Карась!')

# Вывод:
# Карась!

Усугубим ситуацию — заменим первый операнд на выражение сравнения:

print(4 < 5 and 'Карась!')

# Вывод:
# Карась!

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

Проведём ещё один эксперимент, из трёх частей. В качестве первого операнда поочерёдно укажем

  • просто False,

  • значение, которое можно привести к False ,

  • выражение сравнения.

print(False and 'Карась!')
print(0 and 'Карась!')
print(4 > 5 and 'Карась!')

# Вывод:
# False
# 0
# False

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

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

Получается, что при вычислении выражения с оператором and Python действует так:

  1. Если левый операнд выражения с and равен False, возвращает или приводится к False, то выражение вернёт левый операнд, не приводя его к типу bool.

  • Выражение 5 < 3 and 10 > 5 вернёт False, ведь 5 < 3 — это False.

  • Выражение 0 and 10 > 5 вернёт 0, ведь 0 приводится к False, и выражение вернёт левый операнд.

  • Выражение None and 10 > 5 вернёт None, ведь None приводится к False, и выражение вернёт левый операнд.

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

Тут внимательный читатель может возмутиться — неувязочка вышла, ведь выше написано, что если левый операнд выражения с and равен, возвращает или приводится к False, то результатом выражения будет левый операнд. Левый операнд в первом примере — это 5 < 3, но напечатано будет именно False, а не 5 < 3, как было обещано.

Дело в том, что Python незаметно выполнил ещё одну операцию. Выражение с and и в самом деле вернуло левый операнд. Но этот операнд — сам по себе выражение. А результатом выражения сравнения всегда будет значение булева типа.

  1. Если левый операнд выражения с and равен True, возвращает или приводится к True, то выражение вернёт правый операнд, не приводя его к типу bool.

  • Выражение 5 > 3 and 10 > 5 вернёт True: левый операнд вернул True, возвращаем правый операнд, а 10 > 5 — это True.

  • Выражение 1 and 0 вернёт 0: левый операнд приводится к True, возвращаем правый операнд, а это 0.

  • Выражение 10 > 5 and None вернёт None: левый операнд вернул True, возвращаем правый, а это None.

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

Оператор or

Теперь рассмотрим выражение операнд_1 or операнд_2 на примере тех же самых операндов True и False.

Согласно таблице истинности,

  • выражение True or True вернёт True,

  • выражение True or False вернёт True.

Доверяй, но проверяй:

print(True or True)
print(True or False)

# Вывод:
# True
# True

Проверено, всё работает, как и ожидалось.

Усложним эксперимент: опишем три выражения, в которых

  • первый операнд равен True,

  • приводится к True,

  • возвращает True.

print(True or 'Карась!')
print('Щука!' or 'Карась!')
print(4 < 5 or 'Карась!')

# Вывод:
# True
# Щука!
# True

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

print(False or 'Карась!')
print(0 or 'Карась!')
print(4 > 5 or 'Карась!')

# Вывод:
# Карась!
# Карась!
# Карась!

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

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

В итоге:

  1. Если левый операнд выражения с or равен True, возвращает или приводится к True, то выражение вернёт левый операнд, не приводя его к типу bool.

  • Выражение 5 > 3 or 10 > 5 вернёт True, ведь 5 > 3 — это True.

  • Выражение 1 or 10 > 5 вернёт 1, ведь 1 приводится к True, и выражение вернёт левый операнд.

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

  1. Если левый операнд выражения с or равен False, возвращает или приводится к False, то выражение вернёт правый операнд, не приводя его к типу bool.

  • Выражение 5 < 3 or 10 > 5 вернёт True: левый операнд вернул False, возвращаем правый операнд, а 10 > 5 — это True.

  • Выражение 0 or 1 вернёт 1: левый операнд приводится к False, возвращаем правый операнд, а это 1.

  • Выражение 10 < 5 or None вернёт None: левый операнд вернул False, возвращаем правый, а это None.

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

В CPython, как и в большинстве реализаций Python, работа логических операторов and и or реализована через механизм вычисления по короткой схеме (short-circuit evaluation). Именно этот механизм мы и разобрали выше.

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

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

Когда, где и как в Python-коде используют символ подчёркивания?

Знак подчёркивания _ (underscore) достаточно часто применяется Python. И вопросы про использование этого символа на собеседовании позволяют оценить кругозор кандидата и его внимание к деталям.

Рассмотрим несколько самых популярных сценариев его использования.

Временная переменная в циклах

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

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

Имя переменной, которая указывается после for, придумывает разработчик. При возможности это имя создают на основе имени коллекции, которая обрабатывается в цикле. Как правило, имя коллекции — это существительное во множественном числе, а переменная цикла — то же самое существительное, но в единственном числе:

  • коллекция cakes — переменная цикла cake;

  • коллекция tables — переменная цикла table;

  • коллекция flowers — переменная цикла flower.

Такой подход улучшает читаемость кода, а также понимание того, что в нём происходит.

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

Это просто соглашение, способ сообщить читателю кода, что эта переменная не будет применяться в теле цикла.

for _ in range(5):
    print('Привет!'')

В этом примере цикл будет выполнен пять раз, и на каждой итерации будет выводиться слово Привет!. Само значение итератора (число из диапазона от 0 до 4) не используется в теле цикла.

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

Игнорирование значений при распаковке кортежей и списков

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

# Кортеж с данными пользователя.
user_data = ('Степан Осечкин', 28, 'Рыбинск', 'Программист')

# Нужно извлечь только имя и профессию, остальное неважно.
name, _, _, profession = user_data

print(name)
print(profession)

# Вывод:
# Степан Осечкин
# Программист

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

Синтаксис записи чисел

Символ _ в коде на Python применяется и в записи чисел.

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

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

billion = 1, 000, 000, 000
print(type(billion))

# Вывод:
# <class 'tuple'>

Использование подчёркиваний к качестве разделителей поможет при написании или чтении кода.

standard_option = 432246123
advanced_option = 432_246_123

Значение standard_option такое же, как и у advanced_option, но значение переменной advanced_option легче читается и воспринимается.

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

advanced_option_1 = 432_246_123.101
advanced_option_2 = 432_246_123.10_10_10

print(advanced_option_1)
print(advanced_option_2)

# Вывод:
# 432246123.101
# 432246123.10101

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

Последнее значение в интерактивном режиме

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

# Предположим, вы выполнили операцию сложения
>>> 3 + 4
7

# Результат (7) теперь может быть получен и использован с помощью _
>>> _ * 2
14

# Теперь _ содержит значение 14
>>> _ + 1
15

Именование переменных, атрибутов и методов

Стиль snake_case — это соглашение об именовании, при котором каждое слово или аббревиатура в имени начинается с маленькой буквы, а слова разделяются символом подчёркивания.

Этот стиль применяется в Python для именования переменных и функций.

standard_option = 432246123

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

global_ = 'value'

Помимо этого символ подчёркивания в начале имени используется как признак «защищённости» или «приватности» метода или атрибута.

class BaseExampleClass:
    __private_attribute = 'value'
    
    def _internal_method():
        pass

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

В классах есть ещё и «магические» методы и атрибуты; их имена окружены двойными подчёркиваниями (__init__, __str__, __class__). Это специальные методы и атрибуты, которые Python автоматически вызывает или использует в определённых ситуациях, например, при создании объекта или его преобразовании в строку.

class MyClass:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return str(self.value)

Универсальный обработчик случаев

Начиная с версии Python 3.10 у символа подчёркивания появился ещё один вариант использования — в конструкции match.

Оператор match берёт переменную или выражение и сравнивает со значениями, заданными в виде одного или нескольких блоков case. По применению этот оператор похож на оператор switch в C, Java, JavaScript и многих других языках.

Инструкция case _ в конструкции match используется как универсальный обработчик случаев, когда ни один из предшествующих вариантов не совпал с проверяемым значением. Это аналогично блоку else в конструкции if...elif...else.

match x:
    case 1:
        print('x равно 1')
    case 2:
        print('x равно 2')
    case _:
        print('x не равно ни 1, ни 2')
        
# Если x равно 1, 
#      будет выполнен первый блок кода.
# Если x равно 2
#      будет выполнен второй блок кода.
# Для любого другого значения x 
#      будет выполнен последний блок кода, обозначенный как case _.

Такой подход гарантирует, что любое значение x будет обработано.

Можно ли в Python использовать «оператор моржа»?

Оператор := используется в разных языках программирования; его называют «оператор моржа» (англ. walrus operator) из-за его визуального сходства с глазами и бивнями моржа.

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

value := 1

А в Python связь переменной с конкретным значением можно установить при помощи оператора =.

value = 1

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

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

a = [1, 5, 2, 7, 8, 3, 5, 6, 0, 7, 7, 3, 6]

if (n := len(a)) > 10:
    print(f'Список очень длинный ({n} элементов)')

# Вывод:
# Список очень длинный (13 элементов)

В выражении (n := len(a)) > 10 происходит не только сравнение, но и присваивание значения переменной n.

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

Можно ли в Python выполнить несколько инструкций в одной строке?

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

Вот пример стандартного формата записи инструкций в Python:

a = 5
b = 10
c = a + b
print(c)

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

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

a = 5; b = 10; c = a + b; print(c)

Здесь в одной строке выполняются четыре операции: a присваивается значение 5, b присваивается значение 10, и c присваивается результат сложения a и b. Затем значение с выводится в терминал.

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

В чем разница между eval() и exec()?

Начинающие разработчики, как правило, не всегда знакомы с функциями eval() и exec(). А те, кто знаком, — зачастую путают их. Посмотрим повнимательнее и на сами функции, и на разницу между ними.

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

Функция eval() предназначена только для вычисления выражений:

print(eval('4 + 5'))

value = 5
result = eval('value + 6')
print(result)

# Вывод:
# 9
# 11

А функция exec() нужна для выполнения кода в целом:

value = 5
exec('print(value)')

exec('result = value * 2')
print(result)

# Вывод:
# 5
# 10

Использование eval() и exec() позволяет писать код, использующий доступную в момент выполнения информацию. А функция exec() — это, практически, встроенный в Python интерпретатор.

И что в итоге?

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

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

Теги:
Хабы:
+16
Комментарии23

Публикации

Информация

Сайт
practicum.yandex.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
Ира Ко