Имеется страшилка, обладающая невероятным количеством подчеркиваний, лямбд и чрезвычайно редкой функцией __import__:
Что за зверь и что он делает?
Конечно же, мы можем как нормальные люди перепечатать код в интерпретатор и посмотреть, что будет. Но раз уж время давно за полночь, разбираться интереснее руками.
Код переписать всё-таки придётся. Если вы боитесь поддаться соблазну запуска — пишите лучше на бумажке.
Итак, для начала попробуем отобразить всё это без чёртового наклона, при этом постараемся (насколько это возможно) придать тексту читаемый вид:
От PEP8 мы далеки, да и отправлять такое на код ревью пока не стоит, но уже гораздо лучше.
Имеется getattr, значит первые скобки «вернут» нам функцию, а вторые будут списком аргументов.
Первым аргументом getattr берёт объект, вторым — предполагаемый атрибут. Начнём с объекта.
Фактически, функция __import__ — это то, во что превращаются привычные нам «from X import Y as Z». Функция очень редкая и её использование в «боевой» ситуации не на каждом углу встретишь. Подробнее разобраться в её устройстве можно в документации, мы же для ускорения процесса заявим, что в нашем случае данная функция аналогична выражению:
С первой частью просто — идем по ступенькам. Под нужды True и False в питоне имеется специальный тип «bool», и именно это вернет нам цепочка __class__.__name__. Возьмем первый элемент, это будет буква «o».
Поиски второй буквы не многим сложнее — [] это список, список это «list», «list»[2] это «s».
Первая часть мини-головоломки успешно решена:
Разбираемся со второй частью. "()" это кортеж, т.е. tuple, __eq__ это «магическая» обёртка для оператора сравнения "==". Тут мы впервые наступим в известную субстанцию, если подумаем, что "==.__class__" это какой-нибудь «function». В действительности это «wrapper_descriptor», что никто бы и не заметил в другом случае, но здесь это очень важно. К сожалению, я не посвящен в тайну именования классов для встроенных магических методов, поэтому надеемся на её раскрытие в комментариях. Возьмем срез «wrapper_descriptor»[:2], получим «wr».
Дальше можно не разбираться, ибо модуль «os» имеет только один метод, начинающийся на «wr» и это, очевидно, write.
Разбор второй части этого слова вы можете выполнить самостоятельно, ничего сложного.
Как мы выяснили ранее, теперь мы должны вызвать функцию write с не очень понятными аргументами.
Первым аргументом должен идти дескриптор, в который мы собираемся писать. В нашем случае это 1, и это… stdout! Проще говоря, наш os.write будет работать как print.
Дальше происходит следующее: первая анонимная функция оборачивается в скобки, значит её вызов осуществляется здесь же.
Чтобы лучше понять что она делает, запишем её как обыкновенный метод, предварительно немного подумав над содержимым.
Мы принимаем два аргумента, далее происходит обращение к методу __call__ первого. Логично предположить, что первый аргумент является функцией, тогда:
Не происходит ничего, кроме «проброса» аргументов к функции, передавшейся нам первым параметром. Что же это за функция?
Видим метод chr, следовательно, мы преобразуем цифру в символ. Перепишем по-человечески:
Внимательно посмотрев на сиё, осознаём, что происходит следующее: мы берем число, делим его с остатком на 256, остаток от деления сохраняем как символ, а частное рекурсивно передаем дальше до тех пор, пока число не станет меньше 256 (т.е. число // 256 == 0). Не так уж и хитро.
Огромное число, которое мы передаем, записано выше. Раз уж мы со всем разобрались, давайте попробуем собрать всё воедино и написать что-то подобное на человеческом питоне.
И хоть в России для дня матери отведен другой день, данному совету всё же стоит последовать.
Что за зверь и что он делает?
Конечно же, мы можем как нормальные люди перепечатать код в интерпретатор и посмотреть, что будет. Но раз уж время давно за полночь, разбираться интереснее руками.
Код переписать всё-таки придётся. Если вы боитесь поддаться соблазну запуска — пишите лучше на бумажке.
Итак, для начала попробуем отобразить всё это без чёртового наклона, при этом постараемся (насколько это возможно) придать тексту читаемый вид:
getattr(
__import__(True.__class__.__name__[1] + [].__class__.__name__[2]),
().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8]
)
(
1,
(
lambda _, __: _(_, __)
)(
lambda _, __: chr(__ % 256) + _(_, __ // 256) if __ else "",
882504235958091178581291885595786461602115
)
)
От PEP8 мы далеки, да и отправлять такое на код ревью пока не стоит, но уже гораздо лучше.
Имеется getattr, значит первые скобки «вернут» нам функцию, а вторые будут списком аргументов.
Первым аргументом getattr берёт объект, вторым — предполагаемый атрибут. Начнём с объекта.
Фактически, функция __import__ — это то, во что превращаются привычные нам «from X import Y as Z». Функция очень редкая и её использование в «боевой» ситуации не на каждом углу встретишь. Подробнее разобраться в её устройстве можно в документации, мы же для ускорения процесса заявим, что в нашем случае данная функция аналогична выражению:
from True.__class__.__name__[1] + [].__class__.__name__[2]
import ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8])
С первой частью просто — идем по ступенькам. Под нужды True и False в питоне имеется специальный тип «bool», и именно это вернет нам цепочка __class__.__name__. Возьмем первый элемент, это будет буква «o».
Поиски второй буквы не многим сложнее — [] это список, список это «list», «list»[2] это «s».
Первая часть мини-головоломки успешно решена:
from os import ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8])
Разбираемся со второй частью. "()" это кортеж, т.е. tuple, __eq__ это «магическая» обёртка для оператора сравнения "==". Тут мы впервые наступим в известную субстанцию, если подумаем, что "==.__class__" это какой-нибудь «function». В действительности это «wrapper_descriptor», что никто бы и не заметил в другом случае, но здесь это очень важно. К сожалению, я не посвящен в тайну именования классов для встроенных магических методов, поэтому надеемся на её раскрытие в комментариях. Возьмем срез «wrapper_descriptor»[:2], получим «wr».
Дальше можно не разбираться, ибо модуль «os» имеет только один метод, начинающийся на «wr» и это, очевидно, write.
Разбор второй части этого слова вы можете выполнить самостоятельно, ничего сложного.
from os import write
Как мы выяснили ранее, теперь мы должны вызвать функцию write с не очень понятными аргументами.
from os import write
write(1,
( lambda _, __: _(_, __) ) (
lambda _, __: chr(__ % 256) + _(_, __ // 256) if __ else "",
882504235958091178581291885595786461602115
)
)
Первым аргументом должен идти дескриптор, в который мы собираемся писать. В нашем случае это 1, и это… stdout! Проще говоря, наш os.write будет работать как print.
Дальше происходит следующее: первая анонимная функция оборачивается в скобки, значит её вызов осуществляется здесь же.
Чтобы лучше понять что она делает, запишем её как обыкновенный метод, предварительно немного подумав над содержимым.
lambda _, __: _(_, __)
Мы принимаем два аргумента, далее происходит обращение к методу __call__ первого. Логично предположить, что первый аргумент является функцией, тогда:
def function_one(inner, argument):
return inner(inner, argument)
Не происходит ничего, кроме «проброса» аргументов к функции, передавшейся нам первым параметром. Что же это за функция?
lambda _, __: chr(__ % 256) + _(_, __ // 256) if __ else "",
Видим метод chr, следовательно, мы преобразуем цифру в символ. Перепишем по-человечески:
def function_two(inner, ordC):
if ordC:
return chr(ordC % 256) + inner(inner, ordC // 256)
else:
return ""
Внимательно посмотрев на сиё, осознаём, что происходит следующее: мы берем число, делим его с остатком на 256, остаток от деления сохраняем как символ, а частное рекурсивно передаем дальше до тех пор, пока число не станет меньше 256 (т.е. число // 256 == 0). Не так уж и хитро.
Огромное число, которое мы передаем, записано выше. Раз уж мы со всем разобрались, давайте попробуем собрать всё воедино и написать что-то подобное на человеческом питоне.
from os import write
def recursive_print(number):
if number:
write(1, chr(number % 256))
recursive_print(number // 256)
else:
return
recursive_print(882504235958091178581291885595786461602115)
И хоть в России для дня матери отведен другой день, данному совету всё же стоит последовать.
a553
georgthegreat
Или если у вас третий питон.
JC_Piligrim
Better call Saul.
fshp
Мангусты ему не страшны
obiwanus
И винда не зависнет дома
fshp
Если у вас, если у вас
obiwanus
Если у вас
fshp
Нет винды
obiwanus
Хотя если у вас нет питона
obiwanus
То вряд ли у вас OS X
Oreolek
Python 3.4.3
mrvol
Здорово! Как раз задачу для собеседования искал)
igordata
И какую задачу решает решающий эту задачу?
frig
Эту
mrvol
Решающий ничего не решит, но может продемонстрировать нетривиальные знания языка и умение разбираться в чужом коде. Можете минусовать.
TimsTims
Не все поняли двойную шутку)
mrvol
Ого. Да я бы сказал, что вообще не поняли) Не думал сарказм настолько неуловимый.
TimsTims
Ну два плюса то вам поставили, значит кто-то кроме меня понял шутку =)
Она как-раз в тему про «невозможные вопросы на собеседованиях»
mrvol
Но больше тех, кто получил психологическую травму на собеседованиях)
JIghtuse
А напрасно, как раз в этом месте ломается совместимость программки. Можно было бы сделать её немного хитрее, чтобы она работала и в python2 и в python3. Разница в названии класса:
Да, и в python3 помимо
os.write()
естьos.writev()
.Спасибо за статью, познавательная. Пытался разгадывать параллельно, но застрял на лямбдах.
lynxknight Автор
Добавил примечание о версиях, благодарю.
hellman
Некст левел:
(взято отсюда). Ну или здесь попроще :)
Xu4
А я недавно на checkio решал одно из заданий и написал писал такую штуку (вдохновлялся похожей идеей, реализованной на Perl, плюс стрельнул у одного чувака часть кода, которая регистрирует название функции). Писал на Python 3.
pcdesign
Это получается для python 3.4 можно написать вот так:
?
hellman
Да, а что вас смущает? Вообще else: return здесь лишнее. Да и лучше делать без рекурсии:
А вообще, на python2 это можно сконвертировать совсем просто:
pcdesign
Спасибо!
Я просто сразу не понял, думал, что есть некий сакральный смысл в подключении модуля «from os import write».
P.S. И красивое у вас решение.