[Python, Программирование] Каверзные вопросы по Python
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Мне кажется, в каждом языке программирования есть моменты, которые требуют повышенной концентрации внимания или больше практики для своего понимания. Python в этом плане не исключение, и сегодня я расскажу вам о нескольких каверзных вопросах, с которыми вы можете столкнуться как в повседневной разработке, так и в ходе прохождения собеседования.
Каждый пример будет построен так, чтобы сначала можно было подумать самому, потом получить подсказку, почитать объяснение, и только в конце увидеть ответ.
Обратите внимание, что первичное объяснение не всегда корректно. Чтобы получить правильный ответ, дочитывайте пример до конца.
Первый пример
Очень короткий. Про операторы и порядок их вычислений.
11 > 0 is True
С ходу можно интерпретировать это выражение следующим образом:
- Приоритет операторов сравнения и is одинаков.
- 11 > 0 — это True.
- Упрощаем выражение, получая True is True.
- Всё выражение в итоге станет True.
На самом же деле это выражение вернёт False.
Вот еще пара похожих выражений.
0 < 0 == 0 # False
1 in range(2) == True # False
Также обратите внимание, что расстановка скобок изменит результат.
(11 > 0) is True # True
(0 < 0) == 0 # True
(1 in range(2)) == True # True
Как всегда, никакой магии в этих примерах нет. Приведенные выражения — это chained comparisons, которые следует читать так: a op1 b op2 c ... y opN z эквивалентно a op1 b and b op2 c and ... y opN z.
Итак, ответ: исходное выражение эквивалентно (11 > 0) and (0 is True), что, очевидно, является ложью.
Остался вопрос про скобки? Расстановка скобок превращает выражение в обычное, не chained comparisons. То есть благодаря скобкам приоритет смещается на выражение в них, оно вычисляется первым, а затем выполняется вторая операция.
Второй пример
В отличие от остальных примеров, незнание следующего факта вряд ли приведёт к ошибке в коде, но знать о таких вещах, мне кажется, как минимум любопытно.
a = 123
b = 123
a == b
a is b
Двойное равно проверяет объекты на равенство (и очевидно, что 123 == 123). А оператор is проверяет, что переменные ссылаются на один и тот же объект. a и b — разные объекты, поэтому a is b вернёт False.
На самом деле в Python есть оптимизация, касающаяся небольших int-ов (от -5 до 256 включительно). Эти объекты загружаются в память интерпретатора при его запуске. Получается небольшой кеш. Из-за этого объект получается один, и результат будет True.
Аналогичный пример для числа > 256 сработает ожидаемо:
a = 257
b = 257
a == b # True
a is b # False
Второй пример. Продолжение
Давайте попробуем копнуть глубже и посмотрим на следующий пример:
def test():
a = 257
b = 257
print(a is b)
test()
257 не входит в кеш, и должно отобразиться False.
Отличие от предыдущего примера в том, что тут за счёт функции все инструкции интерпретатору подаются единым блоком. Чтобы понять, что происходит, давайте обратимся к байткоду этой функции:
import dis
dis.dis(test)
Мы увидим следующие инструкции:
2 0 LOAD_CONST 1 (257)
2 STORE_FAST 0 (a)
3 4 LOAD_CONST 1 (257)
6 STORE_FAST 1 (b)
4 8 LOAD_GLOBAL 0 (print)
10 LOAD_FAST 0 (a)
12 LOAD_FAST 1 (b)
14 COMPARE_OP 8 (is)
16 CALL_FUNCTION 1
18 POP_TOP
20 LOAD_CONST 0 (None)
22 RETURN_VALUE
Предпоследняя колонка — это аргументы для операций. Для LOAD_CONST — это индекс в массиве констант. Поскольку для обеих операций LOAD_CONST подаётся один и тот же индекс, в байткоде у нас лишь один объект, отвечающий за число 257.
Получается, что интерпретатор способен на подобные оптимизации: код предварительно анализируется, и некоторые константы переиспользуются (float-ы тоже, но не tuple-ы).
Итак, исходный код выведет True.
Третий пример
Этот вопрос однажды встретился мне на собеседовании. Он про классы и методы.
class C:
a = lambda self: self.b()
def __init__(self):
self.b = lambda self: None
c = C()
c.a()
Здесь стоит внимательно пройти цепочку вызовов, отличая методы класса от обычных функций.
Вспоминаем, что вызов метода применительно к экземпляру класса c.method() — это то же самое, что вызов метода применительно к классу с первым аргументом в качестве экземпляра: C.method(c).
Теперь проверим, во что превратились параметры a и b класса C.
type(c.a) # <class 'method'>
type(c.b) # <class 'function'>
Параметр а превратился в метод класса, такой же, как при определении метода внутри класса через def a(self).
А вот b — это обычная функция, потому что она присваивается атрибуту экземпляра класса, а не определяется (как a) в момент создания класса.
Получается, что при вызове c.a() мы получаем C.a(c). Тут в качестве аргумента self в метод валидно передастся экземпляр класса. Далее внутри a вызывается функция b. Поскольку это обычная функция, то «автоматической» передачи экземпляра в качестве первого аргумента не произойдёт. И получается, что функция b вызовется без аргументов. Но она требует аргумент! Ведь она задана как lambda self: None. Не обращайте внимание, что аргумент называется self. Это сделано для дополнительного запутывания.
Итак, ответ:
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 2, in <lambda>
TypeError: <lambda>() missing 1 required positional argument: 'self'
Это происходит потому, что функции b не передан аргумент.
Четвёртый пример
Он про определение переменных в замыкании. Взят из списка хитрых вопросов с toptall:
def create_multipliers():
return [lambda x : i * x for i in range(5)]
for multiplier in create_multipliers():
print(multiplier(2))
Кажется, ничего сложного. create_multipliers вернёт список из 5 функций (назовём их list_lamba_f). Каждая list_lamba_f будет умножать свой аргумент на свой индекс в результирующем массиве.
Получается, что на экране мы увидим:
0
2
4
6
8
Дальнейший разбор предполагает, что вам знакомо замыкание (closure) при использовании вложенных функций (nested functions).
Свои коррективы в наивное объяснение выше вносит позднее связывание. Согласно ему, значение переменной из замыкания (это переменная i) вычисляется в тот момент, когда вызывается внутренняя функция (наши list_lamba_f).
Получается, что значение i в list_lamba_f вычисляется в момент вызова multiplier(2) в пятой строчке. Но в этот момент create_multipliers уже отработала целиком. и значение i — это 4. То есть для всех list_lamba_f значение i равно 4.
Итак, ответ:
8
8
8
8
8
Надеюсь, вам было интересно и понятно. С удовольствием почитаю комментарии с вопросами и задачами, которые кажутся вам полезными для понимания Python и просто занятными. Только не пишите ответ сразу!
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, Проектирование и рефакторинг] Не бойтесь кода
- [Программирование, Машинное обучение, Искусственный интеллект, Голосовые интерфейсы] Open Source синтез речи SOVA
- [Программирование, Разработка под Android] DIP vs IoC vs DI в мире Android
- [Программирование, Совершенный код, Учебный процесс в IT, Карьера в IT-индустрии] Что такое красивый код и как научиться его писать
- [Python, Анализ и проектирование систем, NoSQL, API] Рейт лимиты с помощью Python и Redis (перевод)
- [Программирование, Разработка под MacOS, Разработка под Linux, Разработка под Windows] Фреймворки для кроссплатформенной разработки десктопных программ с GUI
- [Python] Разбираемся с доступом к атрибутам в Python (перевод)
- [Программирование, Java] Динамическое создание Spring Bean в рантайме (перевод)
- [] 50 вопросов по Docker, которые задают на собеседованиях, и ответы на них (перевод)
- [Программирование, C++, C, Программирование микроконтроллеров] Включаем периферию контроллера за 1 такт или магия 500 строк кода
Теги для поиска: #_python, #_programmirovanie (Программирование), #_python, #_interview, #_question_and_answers, #_trick, #_blog_kompanii_domklik (
Блог компании ДомКлик
), #_python, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:58
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Мне кажется, в каждом языке программирования есть моменты, которые требуют повышенной концентрации внимания или больше практики для своего понимания. Python в этом плане не исключение, и сегодня я расскажу вам о нескольких каверзных вопросах, с которыми вы можете столкнуться как в повседневной разработке, так и в ходе прохождения собеседования. Каждый пример будет построен так, чтобы сначала можно было подумать самому, потом получить подсказку, почитать объяснение, и только в конце увидеть ответ. Обратите внимание, что первичное объяснение не всегда корректно. Чтобы получить правильный ответ, дочитывайте пример до конца. Первый пример Очень короткий. Про операторы и порядок их вычислений. 11 > 0 is True
С ходу можно интерпретировать это выражение следующим образом:
На самом же деле это выражение вернёт False. Вот еще пара похожих выражений. 0 < 0 == 0 # False
1 in range(2) == True # False Также обратите внимание, что расстановка скобок изменит результат. (11 > 0) is True # True
(0 < 0) == 0 # True (1 in range(2)) == True # True Как всегда, никакой магии в этих примерах нет. Приведенные выражения — это chained comparisons, которые следует читать так: a op1 b op2 c ... y opN z эквивалентно a op1 b and b op2 c and ... y opN z. Итак, ответ: исходное выражение эквивалентно (11 > 0) and (0 is True), что, очевидно, является ложью. Остался вопрос про скобки? Расстановка скобок превращает выражение в обычное, не chained comparisons. То есть благодаря скобкам приоритет смещается на выражение в них, оно вычисляется первым, а затем выполняется вторая операция. Второй пример В отличие от остальных примеров, незнание следующего факта вряд ли приведёт к ошибке в коде, но знать о таких вещах, мне кажется, как минимум любопытно. a = 123
b = 123 a == b a is b Двойное равно проверяет объекты на равенство (и очевидно, что 123 == 123). А оператор is проверяет, что переменные ссылаются на один и тот же объект. a и b — разные объекты, поэтому a is b вернёт False. На самом деле в Python есть оптимизация, касающаяся небольших int-ов (от -5 до 256 включительно). Эти объекты загружаются в память интерпретатора при его запуске. Получается небольшой кеш. Из-за этого объект получается один, и результат будет True. Аналогичный пример для числа > 256 сработает ожидаемо: a = 257
b = 257 a == b # True a is b # False Второй пример. Продолжение Давайте попробуем копнуть глубже и посмотрим на следующий пример: def test():
a = 257 b = 257 print(a is b) test() 257 не входит в кеш, и должно отобразиться False. Отличие от предыдущего примера в том, что тут за счёт функции все инструкции интерпретатору подаются единым блоком. Чтобы понять, что происходит, давайте обратимся к байткоду этой функции: import dis
dis.dis(test) Мы увидим следующие инструкции: 2 0 LOAD_CONST 1 (257)
2 STORE_FAST 0 (a) 3 4 LOAD_CONST 1 (257) 6 STORE_FAST 1 (b) 4 8 LOAD_GLOBAL 0 (print) 10 LOAD_FAST 0 (a) 12 LOAD_FAST 1 (b) 14 COMPARE_OP 8 (is) 16 CALL_FUNCTION 1 18 POP_TOP 20 LOAD_CONST 0 (None) 22 RETURN_VALUE Предпоследняя колонка — это аргументы для операций. Для LOAD_CONST — это индекс в массиве констант. Поскольку для обеих операций LOAD_CONST подаётся один и тот же индекс, в байткоде у нас лишь один объект, отвечающий за число 257. Получается, что интерпретатор способен на подобные оптимизации: код предварительно анализируется, и некоторые константы переиспользуются (float-ы тоже, но не tuple-ы). Итак, исходный код выведет True. Третий пример Этот вопрос однажды встретился мне на собеседовании. Он про классы и методы. class C:
a = lambda self: self.b() def __init__(self): self.b = lambda self: None c = C() c.a() Здесь стоит внимательно пройти цепочку вызовов, отличая методы класса от обычных функций. Вспоминаем, что вызов метода применительно к экземпляру класса c.method() — это то же самое, что вызов метода применительно к классу с первым аргументом в качестве экземпляра: C.method(c). Теперь проверим, во что превратились параметры a и b класса C. type(c.a) # <class 'method'>
type(c.b) # <class 'function'> Параметр а превратился в метод класса, такой же, как при определении метода внутри класса через def a(self). А вот b — это обычная функция, потому что она присваивается атрибуту экземпляра класса, а не определяется (как a) в момент создания класса. Получается, что при вызове c.a() мы получаем C.a(c). Тут в качестве аргумента self в метод валидно передастся экземпляр класса. Далее внутри a вызывается функция b. Поскольку это обычная функция, то «автоматической» передачи экземпляра в качестве первого аргумента не произойдёт. И получается, что функция b вызовется без аргументов. Но она требует аргумент! Ведь она задана как lambda self: None. Не обращайте внимание, что аргумент называется self. Это сделано для дополнительного запутывания. Итак, ответ: Traceback (most recent call last):
File "<input>", line 1, in <module> File "<input>", line 2, in <lambda> TypeError: <lambda>() missing 1 required positional argument: 'self' Это происходит потому, что функции b не передан аргумент. Четвёртый пример Он про определение переменных в замыкании. Взят из списка хитрых вопросов с toptall: def create_multipliers():
return [lambda x : i * x for i in range(5)] for multiplier in create_multipliers(): print(multiplier(2)) Кажется, ничего сложного. create_multipliers вернёт список из 5 функций (назовём их list_lamba_f). Каждая list_lamba_f будет умножать свой аргумент на свой индекс в результирующем массиве. Получается, что на экране мы увидим: 0
2 4 6 8 Дальнейший разбор предполагает, что вам знакомо замыкание (closure) при использовании вложенных функций (nested functions). Свои коррективы в наивное объяснение выше вносит позднее связывание. Согласно ему, значение переменной из замыкания (это переменная i) вычисляется в тот момент, когда вызывается внутренняя функция (наши list_lamba_f). Получается, что значение i в list_lamba_f вычисляется в момент вызова multiplier(2) в пятой строчке. Но в этот момент create_multipliers уже отработала целиком. и значение i — это 4. То есть для всех list_lamba_f значение i равно 4. Итак, ответ: 8
8 8 8 8 Надеюсь, вам было интересно и понятно. С удовольствием почитаю комментарии с вопросами и задачами, которые кажутся вам полезными для понимания Python и просто занятными. Только не пишите ответ сразу! =========== Источник: habr.com =========== Похожие новости:
Блог компании ДомКлик ), #_python, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 15:58
Часовой пояс: UTC + 5