python

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

  • среда, 13 мая 2015 г. в 02:10:29
http://habrahabr.ru/post/257757/

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

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