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

Комментарии (22)


  1. sshmakov
    10.04.2024 07:13
    +19

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

    Upd: А, я понял, это же реклама курсов, типа как подготовить нормального разраба к типичному собеседованию.


    1. bartenev_ev Автор
      10.04.2024 07:13
      +2

      "Нам разработчики нужны или стрессоустойчивые теоретики?" - это именно тот вопрос, который и должен задать себе интервьюер перед собеседованием кандидатов! :)


  1. FlyingDutchman2
    10.04.2024 07:13
    +12

    У нас в голландском языке есть емкое слово "mierenneuker", что в буквальном переводе означает "тот, кто трахает муравьев". Общеупотребительное значение: "тот, кто придирается к мелочам."

    Просьба к интервьюерам: не будьте такими.


    1. Lemko
      10.04.2024 07:13

      Именно!


    1. bartenev_ev Автор
      10.04.2024 07:13

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


  1. NickDoom
    10.04.2024 07:13
    +2

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

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


    1. bartenev_ev Автор
      10.04.2024 07:13
      +1

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


      1. NickDoom
        10.04.2024 07:13

        Так вот и надо было ответить так, чтобы сразу стало ясно — не теоретик с кратких курсов, на такие вопросы не поймаешь :)


    1. 9982th
      10.04.2024 07:13
      +5

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

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


      1. santjagocorkez
        10.04.2024 07:13

        Я прямо сходу предположил, что размер завязан на INT_MAX, и, в общем-то, оказался прав более-менее.

        В реализации listobject не видел в полях структуры ссылок на внутреннюю реализацию Int, которая не ограничена по хранимому значению и не переполняется, иначе бы сильно удивился и запомнил бы этот момент. Следовательно, поле "текущий размер" может быть объявлено никак иначе, кроме как через стандартные сишные числовые типы, а в максимуме это, собственно, INT_MAX. Для беззнаковых, соответственно, UINT_MAX, кажется. В любом случае, с порядком не ошибся.

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

        Пример с bytearray не показателен в данном случае, потому что он не List, о котором был вопрос.

        --

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

        a = a or b

        А это вот оно и есть.


  1. NewSouth
    10.04.2024 07:13

    Добавлю: -Python компилируемый или интерпретируемый? (После очевидного ответа "интерпретируемый", следует вопрос -А что такое файлы .pyc, который появляются, когда запускаешь python файл?)


    1. werevolff
      10.04.2024 07:13

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


      1. sshmakov
        10.04.2024 07:13

        Но тут внезапно CPython начал компилировать в машинный код

        В альфа выпуск языка программирования Python 3.13.0a6 встроен JIT-компилятор


        1. werevolff
          10.04.2024 07:13
          +2

          JIT компилятор - это тоже одна из особенностей реализации интерпретатора компилирующего типа. Ключевой момент здесь - CPython (интерпретатор) перешëл на использование JIT компиляции. Вот когда CPython станет компилятором, тогда можете говорить, что Python - компилируемый язык. Потому, что код, написанный на нëм, перед запуском, будет компилироваться в байт-код и кладываться в библиотечки, а потом они будут выполняться при запуске исполняемого файла. А сейчас программа на Python запускается по-умолчанию интерпретатором Python. Нельзя взять файлы .pyc, перенести на другую машину с аналогичной архитектурой и выполнить их без интерпретатора. Поэтому, язык и называется интерпретируемым.


          1. sshmakov
            10.04.2024 07:13

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


            1. werevolff
              10.04.2024 07:13
              +1

              Формально, да, грани между языками по типу исполнения стираются. Но, прошу обратить внимание, что если вы на собесе зададите этот вопрос кандидату, он сможет перевернуть его в свою пользу чисто психологически. Просто компиляция может быть этапом интерпретации. Ключевое здесь, всë-таки - это запуск программы. Интерпретируемые языки выполняются при помощи интерпретатора при каждом запуске. Даже если вы соберëте .exe, в нëм будет ваша программа, + код интерпретатора для запуска. А компилируемую программу можно переносить и запускать независимо от наличия компилятора на целевой машине.

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

              Почему я спорю? Наверное, потому, что вопрос про компилируемость Python - это очень популярный маячок, показывающий приверженность разработчика к хайповым высказываниям без проверки истинности. Все сейчас носятся с этим бредовым "Python - компилируемый язык, вы слыхали"?! Хотя, тут достаточно начать спрашивать про то, какие бывают типы интерпретаторов, и почему интерпретаторы компилируемого типа не перестают быть интерпретаторами? Вот это, действительно серьёзные вопросы. Несколько фактов, которые я вам привëл, в частности, о том, что интерпретируемый язык не позволяет собрать программу для запуска без библиотек интерпретатора, в принципе, уже вывели вас из себя и вынудили увести разговор в сторону от спора.


    1. bartenev_ev Автор
      10.04.2024 07:13

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


  1. megamrmax
    10.04.2024 07:13
    +9

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


    1. vladislav_smirnov
      10.04.2024 07:13
      +1

      Хорошо быть опытным разработчиком.

      Но есть ещё и джуны


  1. KongEnGe
    10.04.2024 07:13
    +1

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


  1. AlexTimmo
    10.04.2024 07:13
    +4

    - Не знаю. И сколько?
    И тут может наступить неловкая пауза.


  1. natrium
    10.04.2024 07:13

    Использование символа _ для ненужных переменных в распаковке/цикле вообще довольно холиварный вопрос. С одной стороны действительно для читаемости довольно удобно, с другой - это всё ещё присваивание значения конкретному символу (как-то видела комментарий, что можно и в print присваивать значения, но зачем?). Ну и gettext принято импортировать как _ (что лично я нахожу странным, но так уж сложилось)