Разгадываем картинку из твиттера компании Intel

Имеется страшилка, обладающая невероятным количеством подчеркиваний, лямбд и чрезвычайно редкой функцией __import__:



Что за зверь и что он делает?

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

Код переписать всё-таки придётся. Если вы боитесь поддаться соблазну запуска — пишите лучше на бумажке.

Итак, для начала попробуем отобразить всё это без чёртового наклона, при этом постараемся (насколько это возможно) придать тексту читаемый вид:

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)

И хоть в России для дня матери отведен другой день, данному совету всё же стоит последовать.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 26

    +20
    Если у вас нет питона
    Call your mother!
      +6
      Или если у вас третий питон.
        +10
        Better call Saul.
          +17
          Мангусты ему не страшны
            +14
            И винда не зависнет дома
              +13
              Если у вас, если у вас
                +13
                Если у вас
                  +12
                  Нет винды
                    +7
                    Хотя если у вас нет питона
                      +10
                      То вряд ли у вас OS X
                  +5
                  Python 3.4.3
            –9
            Здорово! Как раз задачу для собеседования искал)
              +2
              И какую задачу решает решающий эту задачу?
                +31
                Эту
                  +4
                  Решающий ничего не решит, но может продемонстрировать нетривиальные знания языка и умение разбираться в чужом коде. Можете минусовать.
                  +1
                  Не все поняли двойную шутку)
                    +3
                    Ого. Да я бы сказал, что вообще не поняли) Не думал сарказм настолько неуловимый.
                      0
                      Ну два плюса то вам поставили, значит кто-то кроме меня понял шутку =)
                      Она как-раз в тему про «невозможные вопросы на собеседованиях»
                        +4
                        Но больше тех, кто получил психологическую травму на собеседованиях)
                  +5
                  Дальше можно не разбираться, ибо модуль «os» имеет только один метод, начинающийся на «wr» и это, очевидно, write

                  А напрасно, как раз в этом месте ломается совместимость программки. Можно было бы сделать её немного хитрее, чтобы она работала и в python2 и в python3. Разница в названии класса:
                  # python2
                  In [4]: ().__iter__().__class__.__name__
                  Out[4]: 'tupleiterator'
                  

                  # python3
                  In [4]: ().__iter__().__class__.__name__
                  Out[4]: 'tuple_iterator'
                  

                  Да, и в python3 помимо os.write() есть os.writev().

                  Спасибо за статью, познавательная. Пытался разгадывать параллельно, но застрял на лямбдах.
                    0
                    Добавил примечание о версиях, благодарю.
                    +7
                    Некст левел:
                    `[`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(((~(~(~(~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(((~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~(~((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~((((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[])))]`[(({}<[])<<({}<[]))::~(~(({}<[])<<({}<[]))<<({}<[]))]

                    (взято отсюда). Ну или здесь попроще :)
                      0
                      А я недавно на checkio решал одно из заданий и написал писал такую штуку (вдохновлялся похожей идеей, реализованной на Perl, плюс стрельнул у одного чувака часть кода, которая регистрирует название функции). Писал на Python 3.

                      (lambda _: globals().__setitem__(_.lower().translate(dict(enumerate(('_', None),
                      1<<5))), lambda: _))((lambda _: ''.join([chr(int(''.join([len(s).__str__() for s
                      in''.join(_.split()).split('O')][i:i+15:5]))-111)for i in range(4,210,15)]))("""
                                      0O0O0O0O0O0O0O0O0      O000         00000O0O0O
                                     0O0O0000O0O0O0O0O0     O0O0O0        O0O0000O0O0
                                     O0O0      O00         0O0  O0O       0O0     O00
                                      O0O0     O0O        0O0    O0O      0O0O0O0000
                                       0000    0O0       O0O0O0O00O0O     0O0  O0O0
                              0O0O0O0O0O00     O0O      0O0O0O00O0O0O0    O0O   00O0O0O0O0O0
                              00000000O0O      0O0     O0O0        0O0O   0O0    O0O0O0O0O0O
                      ​
                              0O00O   OO0O   0O0O0   O0O0         O0O0000O0O      OO0O000O0O
                               0O0O0 O0O0O0 O0O0O   000000        000O0OO0O0O    0O0O0O0O0O0
                                0O0O0O0O0O000O0O   OO0  O00       O0O     0O0    O0O00
                                 O0O0O0OO00O0O0   O0O    O00      00000O0O0O      0O0O0
                                  0O0O0  O0O0O   0O0O0O0O0O00     000  O0O0        O0O0O
                                   00O    OOO   O00OO0OO0O00OO    OOO   00O0OOOO00OOOOO0
                                    O      O   OOO0        OOOO   O00    00OO0O0O0O0000
                      """))
                      

                        0
                        Это получается для python 3.4 можно написать вот так:

                        def recursive_print(number):
                            if number:
                                print(chr(number % 256), end="")
                                recursive_print(number // 256)
                            else:
                                return
                        
                        recursive_print(882504235958091178581291885595786461602115)
                        


                        ?
                          +2
                          Да, а что вас смущает? Вообще else: return здесь лишнее. Да и лучше делать без рекурсии:

                          while number:
                              print(chr(number % 256), end="")
                              number //= 256
                          


                          А вообще, на python2 это можно сконвертировать совсем просто:
                          print ("0%x" % 882504235958091178581291885595786461602115).decode("hex")[::-1]
                          


                            0
                            Спасибо!
                            Я просто сразу не понял, думал, что есть некий сакральный смысл в подключении модуля «from os import write».

                            P.S. И красивое у вас решение.

                        Only users with full accounts can post comments. Log in, please.