créez le programme obscurci le plus étrange qui imprime la chaîne « Hello world ! ». J'ai décidé d'écrire une explication de comment ça marche. Alors, voici l'entrée, en Python 2.7 :
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
Les littéraux de chaîne n'étaient pas autorisés, mais j'ai défini d'autres restrictions pour m'amuser : il devait s'agir d'une seule expression (donc pas d'instruction d'impression) avec une utilisation intégrée minimale et aucun littéral entier.
Pour commencer
Comme nous ne pouvons pas utiliser print, nous pouvons écrire dans l'objet fichier stdout :
import sys sys.stdout.write("Hello world!\n")
Mais utilisons quelque chose de niveau inférieur : os.write(). Nous avons besoin du descripteur de fichier de stdout, qui est 1 (vous pouvez vérifier avec print sys.stdout.fileno()).
import os os.write(1, "Hello world!\n")
Nous voulons une seule expression, nous allons donc utiliser import() :
__import__("os").write(1, "Hello world!\n")
Nous voulons également pouvoir masquer write(), nous allons donc ajouter un getattr() :
getattr(__import__("os"), "write")(1, "Hello world!\n")
C'est le point de départ. À partir de maintenant, tout obscurcira les trois chaînes et l'int.
Enchaîner les ficelles
"os" et "write" sont assez simples, nous allons donc les créer en joignant des parties des noms de différentes classes intégrées. Il existe de nombreuses façons différentes de procéder, mais j'ai choisi la suivante :
"o" from the second letter of bool: True.__class__.__name__[1] "s" from the third letter of list: [].__class__.__name__[2] "wr" from the first two letters of wrapper_descriptor, an implementation detail in CPython found as the type of some builtin classes’ methods (more on that here): ().__class__.__eq__.__class__.__name__[:2] "ite" from the sixth through eighth letters of tupleiterator, the type of object returned by calling iter() on a tuple: ().__iter__().__class__.__name__[5:8]
Nous commençons à faire des progrès !
getattr( __import__(True.__class__.__name__[1] + [].__class__.__name__[2]), ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8] )(1, "Hello world!\n")
"Hello world!n" est plus compliqué. Nous allons l'encoder sous la forme d'un grand entier, qui sera formé du code ASCII de chaque caractère multiplié par 256 à la puissance de l'index du caractère dans la chaîne. Autrement dit, la somme suivante :
∑n=0L−1cn(256n)
où L
est la longueur de la chaîne et cn est le code ASCII du n
ème caractère de la chaîne. Pour créer le numéro :
>>> codes = [ord(c) for c in "Hello world!\n"] >>> num = sum(codes[i] * 256 ** i for i in xrange(len(codes))) >>> print num 802616035175250124568770929992
Nous avons maintenant besoin du code pour reconvertir ce nombre en chaîne. Nous utilisons un algorithme récursif simple :
>>> def convert(num): ... if num: ... return chr(num % 256) + convert(num // 256) ... else: ... return "" ... >>> convert(802616035175250124568770929992) 'Hello world!\n'
Réécriture en une ligne avec lambda :
convert = lambda num: chr(num % 256) + convert(num // 256) if num else ""
Maintenant, nous utilisons la récursion anonyme pour transformer cela en une expression unique. Cela nécessite un combinateur. Commencez par ceci :
>>> comb = lambda f, n: f(f, n) >>> convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else "" >>> comb(convert, 802616035175250124568770929992) 'Hello world!\n'
Maintenant, nous substituons simplement les deux définitions dans l'expression, et nous avons notre fonction :
>>> (lambda f, n: f(f, n))( ... lambda f, n: chr(n % 256) + f(f, n // 256) if n else "", ... 802616035175250124568770929992) 'Hello world!\n'
Maintenant, nous pouvons coller cela dans notre code d'avant, en remplaçant certains noms de variables en cours de route (f → , n → _) :
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 "", 802616035175250124568770929992 ) )
Internes des fonctions
Il nous reste un "" dans le corps de notre fonction de conversion (rappelez-vous : pas de chaîne littérale !), et un grand nombre que nous devrons cacher d'une manière ou d'une autre. Commençons par la chaîne vide. Nous pouvons en créer un à la volée en examinant les composants internes d'une fonction aléatoire :
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
Ce que nous faisons réellement ici, c'est examiner la table des numéros de ligne de l'objet code contenu dans la fonction. Comme c’est anonyme, il n’y a pas de numéro de ligne, donc la chaîne est vide. Remplacez le 0 par _ pour le rendre plus déroutant (cela n'a pas d'importance, puisque la fonction n'est pas appelée) et collez-le. Nous allons également refactoriser le 256 en un argument qui sera transmis à notre convert() obscurci. avec le numéro. Cela nécessite d'ajouter un argument au combinateur :
import sys sys.stdout.write("Hello world!\n")
Un détour
Abordons un peu un problème différent. Nous voulons un moyen de masquer les nombres dans notre code, mais il sera fastidieux (et pas particulièrement intéressant) de les recréer à chaque fois qu'ils seront utilisés. Si nous pouvons implémenter, disons, range(1, 9) == [1, 2, 3, 4, 5, 6, 7, 8], alors nous pouvons envelopper notre travail actuel dans une fonction qui prend des variables contenant les nombres de 1 à 8, et remplacez les occurrences de littéraux entiers dans le corps par ces variables :
import os os.write(1, "Hello world!\n")
Même si nous devons également former 256 et 802616035175250124568770929992, ceux-ci peuvent être créés en utilisant des opérations arithmétiques sur ces huit nombres « fondamentaux ». Le choix de 1 à 8 est arbitraire, mais semble être un bon compromis.
On peut obtenir le nombre d'arguments qu'une fonction prend via son objet code :
__import__("os").write(1, "Hello world!\n")
Créez un tuple de fonctions avec des nombres d'arguments compris entre 1 et 8 :
getattr(__import__("os"), "write")(1, "Hello world!\n")
En utilisant un algorithme récursif, nous pouvons transformer cela en sortie de range(1, 9) :
"o" from the second letter of bool: True.__class__.__name__[1] "s" from the third letter of list: [].__class__.__name__[2] "wr" from the first two letters of wrapper_descriptor, an implementation detail in CPython found as the type of some builtin classes’ methods (more on that here): ().__class__.__eq__.__class__.__name__[:2] "ite" from the sixth through eighth letters of tupleiterator, the type of object returned by calling iter() on a tuple: ().__iter__().__class__.__name__[5:8]
Comme auparavant, nous convertissons cela en forme lambda :
getattr( __import__(True.__class__.__name__[1] + [].__class__.__name__[2]), ().__class__.__eq__.__class__.__name__[:2] + ().__iter__().__class__.__name__[5:8] )(1, "Hello world!\n")
Puis, sous forme anonyme-récursive :
>>> codes = [ord(c) for c in "Hello world!\n"] >>> num = sum(codes[i] * 256 ** i for i in xrange(len(codes))) >>> print num 802616035175250124568770929992
Pour nous amuser, nous allons prendre en compte l'opération argcount dans un argument de fonction supplémentaire et masquer certains noms de variables :
>>> def convert(num): ... if num: ... return chr(num % 256) + convert(num // 256) ... else: ... return "" ... >>> convert(802616035175250124568770929992) 'Hello world!\n'
Il y a un nouveau problème maintenant : nous avons encore besoin d'un moyen de masquer 0 et 1. Nous pouvons les obtenir en examinant le nombre de variables locales dans des fonctions arbitraires :
convert = lambda num: chr(num % 256) + convert(num // 256) if num else ""
Même si les corps des fonctions se ressemblent, _ dans la première fonction n'est pas un argument, et n'est pas non plus défini dans la fonction, donc Python l'interprète comme une variable globale :
>>> comb = lambda f, n: f(f, n) >>> convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else "" >>> comb(convert, 802616035175250124568770929992) 'Hello world!\n'
Cela se produit même si _ est réellement défini dans la portée globale.
Mettre cela en pratique :
>>> (lambda f, n: f(f, n))( ... lambda f, n: chr(n % 256) + f(f, n // 256) if n else "", ... 802616035175250124568770929992) 'Hello world!\n'
Maintenant, nous pouvons remplacer la valeur de funcs, puis utiliser * pour transmettre la liste d'entiers résultante sous forme de huit variables distinctes, nous obtenons ceci :
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 "", 802616035175250124568770929992 ) )
Déplacement des bits
Presque là ! Nous remplacerons les n{1..8} variables par , _, , _, etc., car cela crée une confusion avec les variables utilisées dans nos fonctions intérieures. Cela ne pose pas de problèmes réels, puisque les règles de cadrage signifient que les bonnes seront utilisées. C'est également l'une des raisons pour lesquelles nous avons refactorisé 256 là où _ fait référence à 1 au lieu de notre fonction convert() obscurcie. Ça devient long, donc je ne collerai que la première moitié :
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
Il ne reste plus que deux choses. Nous allons commencer par le plus facile : 256. 256=28
, nous pouvons donc le réécrire comme 1 << 8 (en utilisant un décalage de bit vers la gauche), ou _ << ________ avec nos variables obscurcies.
Nous utiliserons la même idée avec 802616035175250124568770929992. Un simple algorithme diviser pour régner peut le diviser en sommes de nombres qui sont eux-mêmes des sommes de nombres décalés ensemble, et ainsi de suite. Par exemple, si nous avions 112, nous pourrions le diviser en 96 16 puis (3 << 5) (2 << 3). J'aime utiliser les décalages de bits car le << me rappelle std::cout << "foo" en C ou print chevron (print >>) en Python, qui sont tous deux des harengs rouges impliquant d'autres façons d'effectuer des E/S.
Le nombre peut être décomposé de différentes manières ; aucune méthode n'est correcte (après tout, nous pourrions simplement la diviser en (1 << 0) (1 << 0) ..., mais ce n'est pas intéressant). Nous devrions avoir une quantité substantielle d'imbrication, tout en utilisant la plupart de nos variables numériques. Évidemment, faire cela à la main n’est pas amusant, nous allons donc proposer un algorithme. En pseudocode :
import sys sys.stdout.write("Hello world!\n")
L'idée de base ici est que nous testons diverses combinaisons de nombres dans une certaine plage jusqu'à ce que nous trouvions deux nombres, base et shift, tels que base << shift est aussi proche que possible de num (c'est-à-dire que nous minimisons leur différence absolue, diff). Nous utilisons ensuite notre algorithme diviser pour régner pour diviser best_base et best_shift, puis répétons la procédure sur diff jusqu'à ce qu'elle atteigne zéro, en additionnant les termes en cours de route.
L'argument de range(), span, représente la largeur de l'espace de recherche. Cela ne peut pas être trop grand, sinon nous finirons par avoir num comme base et 0 comme décalage (car diff est zéro), et comme la base ne peut pas être représentée comme une variable unique, elle se répétera, de manière récurrente à l'infini. . S'il est trop petit, nous nous retrouverons avec quelque chose comme le (1 << 0) (1 << 0) ... mentionné ci-dessus. En pratique, nous voulons que la durée diminue à mesure que la profondeur de récursion augmente. Par essais et erreurs, j’ai trouvé que cette équation fonctionnait bien :
span=⌈log1.5|num|⌉ ⌊24−profondeur⌋
En traduisant le pseudocode en Python et en apportant quelques ajustements (prise en charge de l'argument de profondeur et quelques mises en garde concernant les nombres négatifs), nous obtenons ceci :
(lambda _, __, ___, ____, _____, ______, _______, ________: getattr( __import__(True.__class__.__name__[_] + [].__class__.__name__[__]), ().__class__.__eq__.__class__.__name__[:__] + ().__iter__().__class__.__name__[_____:________] )( _, (lambda _, __, ___: _(_, __, ___))( lambda _, __, ___: chr(___ % __) + _(_, __, ___ // __) if ___ else (lambda: _).func_code.co_lnotab, _ << ________, (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __) - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______ << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) << ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) << __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______ << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) + _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) << (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ << _))) + (_____ << ______) + (_ << ___) ) ) )( *(lambda _, __, ___: _(_, __, ___))( (lambda _, __, ___: [__(___[(lambda: _).func_code.co_nlocals])] + _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else [] ), lambda _: _.func_code.co_argcount, ( lambda _: _, lambda _, __: _, lambda _, __, ___: _, lambda _, __, ___, ____: _, lambda _, __, ___, ____, _____: _, lambda _, __, ___, ____, _____, ______: _, lambda _, __, ___, ____, _____, ______, _______: _, lambda _, __, ___, ____, _____, ______, _______, ________: _ ) ) )
Maintenant, lorsque nous appelons convert(802616035175250124568770929992), nous obtenons une belle décomposition :
import sys sys.stdout.write("Hello world!\n")
Collez-le en remplacement du 802616035175250124568770929992 et assemblez toutes les pièces :
import os os.write(1, "Hello world!\n")
Et voilà.
Addendum : Prise en charge de Python 3
Depuis la rédaction de cet article, plusieurs personnes ont posé des questions sur le support de Python 3. Je n'y avais pas pensé à l'époque, mais comme Python 3 continue de gagner du terrain (et merci pour cela !), cet article aurait clairement dû être mis à jour depuis longtemps.
Heureusement, Python 3 (au moment de la rédaction, 3.6) ne nous oblige pas à beaucoup changer :
__import__("os").write(1, "Hello world!\n")
Voici la version complète de Python 3 :
getattr(__import__("os"), "write")(1, "Hello world!\n")
Merci d'avoir lu ! Je continue d’être étonné par la popularité de cet article.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!