Имеется страшилка, обладающая невероятным количеством подчеркиваний, лямбд и чрезвычайно редкой функцией __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)

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

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


  1. a553
    12.05.2015 15:10
    +20

    Если у вас нет питона
    Call your mother!


    1. georgthegreat
      12.05.2015 17:25
      +6

      Или если у вас третий питон.


    1. JC_Piligrim
      12.05.2015 18:11
      +10

      Better call Saul.


    1. fshp
      12.05.2015 18:32
      +17

      Мангусты ему не страшны


      1. obiwanus
        13.05.2015 02:38
        +14

        И винда не зависнет дома


        1. fshp
          13.05.2015 02:41
          +13

          Если у вас, если у вас


          1. obiwanus
            13.05.2015 02:49
            +13

            Если у вас


            1. fshp
              13.05.2015 03:23
              +12

              Нет винды


              1. obiwanus
                13.05.2015 03:44
                +7

                Хотя если у вас нет питона


                1. obiwanus
                  13.05.2015 05:52
                  +10

                  То вряд ли у вас OS X


          1. Oreolek
            13.05.2015 02:56
            +5

            Python 3.4.3


  1. mrvol
    12.05.2015 17:12
    -9

    Здорово! Как раз задачу для собеседования искал)


    1. igordata
      12.05.2015 19:18
      +2

      И какую задачу решает решающий эту задачу?


      1. frig
        12.05.2015 19:22
        +31

        Эту


      1. mrvol
        12.05.2015 21:10
        +4

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


    1. TimsTims
      12.05.2015 20:34
      +1

      Не все поняли двойную шутку)


      1. mrvol
        12.05.2015 21:01
        +3

        Ого. Да я бы сказал, что вообще не поняли) Не думал сарказм настолько неуловимый.


        1. TimsTims
          12.05.2015 21:11

          Ну два плюса то вам поставили, значит кто-то кроме меня понял шутку =)
          Она как-раз в тему про «невозможные вопросы на собеседованиях»


          1. mrvol
            12.05.2015 21:24
            +4

            Но больше тех, кто получил психологическую травму на собеседованиях)


  1. JIghtuse
    12.05.2015 18:15
    +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().

    Спасибо за статью, познавательная. Пытался разгадывать параллельно, но застрял на лямбдах.


    1. lynxknight Автор
      12.05.2015 20:12

      Добавил примечание о версиях, благодарю.


  1. hellman
    12.05.2015 19:39
    +7

    Некст левел:

    `[`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(((~(~(~(~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(((~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~(~((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~(~(~((~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~(~((~(~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~((~((~((~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((((((({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%(~((((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(((~(~(~({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))),`'%\xcb'`[{}<[]::~(~({}<[])<<({}<[]))]%((~(((~(({}<[])<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[]))<<({}<[])))]`[(({}<[])<<({}<[]))::~(~(({}<[])<<({}<[]))<<({}<[]))]

    (взято отсюда). Ну или здесь попроще :)


  1. Xu4
    13.05.2015 01:53

    А я недавно на 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
    """))
    


  1. pcdesign
    13.05.2015 11:27

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

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


    ?


    1. hellman
      13.05.2015 20:00
      +2

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

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


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



      1. pcdesign
        14.05.2015 08:35

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

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