エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

エムスリーが難読プログラミングオタクに送るノベルティ、Python Quineクリアファイルの作り方

早速ですが、こちらに書いてあるソースコード、実際に動くコードとして作成しました。実行結果はどのようになるでしょう?

答えはこれから各種イベントで配られるノベルティを受け取って打ち込んでみてください!!!

まずはあすから行われるコンピュータビジョンの学会MIRU2024のスポンサーブースで配布します!!!

と、いうのは冗談で、さすがに受け取れたとして打ち込みが大変ですし、以下にソースコードを貼り付けます。 本稿では以下のQuineの作り方について解説していきます。ただし、難読プログラミングが好きな人はまずは解説を読まずに自力で読んでみてください!

exec('''m=lambda_x:exec("".join(x.split()).replace("~",chr(32)),globals())'''.replace("_",chr(32)));
m("""import~base64~as~b6;import~zlib;tr=lambda~x,y,z:~(y~if~x~else~z);dba=lambda~x:zlib.decompress(b
6.b64decode(x),-15).decode();a=1;lme=lambda~x:list(map(lambda~x:tr(x=="1",1,0),x));s="ZXhlYygnJydtPW
xhbWJkYV94OmV4ZWMoIiIuam9pbih4LnNwbGl0KCkpLnJlcGxhY2UoIn4iLGNocigzMikpLGdsb2JhbHMoKSknJycucmVwbGFjZS
giXyIsY2hyKDMyKSkpO20oIiIiaW1wb3J0fmJhc2U2NH5hc35iNjtpbXBvcnR+emxpYjt0cj1sYW1iZG    F+eCx5LHo6fih5fm
lmfnh+ZWxz             ZX56KTtkYmE9bGFtYmRhfn             g6emxpYi5kZWNvbXB            yZXNzKGI2LmI2
NGRlY29kZSh4 K         SwtMTUpLmRlY29kZSgpO2            E9MTtsbWU9bGFtYmR                hfng6bGlzdC
htYXAobGFtYmRhf         ng6dHIoeD09IjEiLDEs         MCkseCkpO3M9Int9Ijt                   kaT16bGliL
mRlY29tcHJlc3Mo           YjYuYjY0ZGVjb2Rl          KCI3WlpCRG9Vd0NFVDN   QUTI5LytX       K2lab0NuUU
crSmhxTnJDeU1QQ           WVpc2ZVTG9uMlFX            eUd5UkFBUkhid0RsOUNpY1NJOWhwUV       JUdURIRlZG
S05VU2hrTGxWT0V            1ZFBRSEpOM1lF            SXJYQ1NZaVVDam5EUTlZeWFMYWVDaEN       rUUU1QXMrM
FFRNmhaNEVUcmpj             RVlJajVTaUoy             WXZlQVVUOHNndXQrNG9iQVRKS09Rb       1p6bVZxWmdD
T29IY3pVbnU0eE     Q         wTXYvSnlSM    0F        sdFNpRWw1MFF3SnhCUUNuMlFrMn       Fqb1ZSdjVmckR
mTDJRUVRGaERpSV    Rz        YU83REsvM    nBQ        eEl2aC93QSIpLC0xNSkuZG             Vjb2RlKCk7cH
JpbnQoIiIuam9p    bihb        dHIoYz0     9Ij        EiLGI2LmI2NGRlY29kZS                 hzLmVuY29k
ZSgidXRmLTgiKS    kuZGV         jb2R     lKCJ         1dGYtOCIpLmZvcm1hdCh  zKVtzd          W0obG1lK
GRpKVs6al0pXSx    0cihjP         T0     iMCIs        Y2hyKDMyKSxjKSlmb3IoaixjKWluKGV        udW1lcmF
0ZShkaSkpXSkpI    iIiKSMj        I     yMjIyMj        IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyM       jIyMjIyM
jIyMjIyMjIyMj     IyMjIyN             3ZV9hcm         VfaGlyaW5nPXJlcXVlc3RzLmdldCgi         aHR0cHM
6Ly9qb2JzLm0z     LmNvbS9l           bmdpbmVl         ci8iKSMjIyMjIyMjIyMjIyMjIyMjIy        MjIyMjIy
MjIyMjIyMjIyM     jIyMjIyMj         IyMjIyMjIy        Mj";di=zlib.decom  press(b6.b6       4decode("
7ZZBDoUwCET3P    Q29/+W+iZoC       nQG+JhqNrCy        MPAeisfULon2QWy    GyRAARHbwD       l9CicSI9hp
QRTuDHFV  F        K  NUShkLl      VOEudPQ H            J  N3YEIrXCSY                   iUCjnDQ9YyaL
aeChCkQ               E5As+0QQ    6hZ4ETrj                 cEYIj5SiJ2Yv              eAUT8sgut+4obAT
JKOQoZzmVqZgCOoHczUnu4xD0Mv/JyR3AltSiEl50QwJxBQCn2Qk2qjoVRv5frDfL2QQTFhDiITsaO7DK/2pPxIvh/wA"),-15).
decode();print("".join([tr(c=="1",b6.b64decode(s.encode("utf-8")).decode("utf-8").format(s)[sum(lme(
di)[:j])],tr(c=="0",chr(32),c))for(j,c)in(enumerate(di))]))""")#####################################
###############we_are_hiring=requests.get("https://jobs.m3.com/engineer/")##########################

ということで、改めまして、AI・機械学習チームリーダーの大垣@Hi_kingです。 本稿はエムスリーAI・機械学習チームで2週間連続で行われるブログリレー1日目の記事となります!

エムスリー難読プログラミング部

エムスリーエンジニア公式X をフォローいただいている方は、朝のポストで流れてくるからご存知かと思いますが、エムスリーでは難読プログラミング部が盛んに活動しています。

せっかくなのでこの盛り上がりを会社の外でも知ってもらおうということで、#crazy_programmingチャンネルで以下のように呼びかけられました

その中から、今回は、まず機械学習エンジニア向けということで、Python難読おしゃれノベルティを作ってみた次第です。

おしゃれプログラムを書こうとワイワイするエンジニアたち

難読プログラミングジャンル、整形Quineをやろう!

さて、難読プログラミング界隈が好きな人は、最初に見た目で予測がついたかと思いますが、こちら、Quine となっています。

クワイン (プログラミング) - Wikipedia

クワイン(英: Quine)は、コンピュータープログラムの一種で、自身のソースコードと完全に同じ文字列を出力するプログラムである。

つまり、それ自体が実行可能で、かつ出力した文字列が自分自身のソースコードとなります。別の言い方をすると、exec(exec(exec(quine)))...と何度でも実行可能なコードとも言えますね。そして、Quineのなかでも、Quineの機能を保ったまま色んな機能をつけるという遊び方があり、今回作ったQuineは、見た目に意味を持たせたQuineです。

さて、このような整形Quineを作るうえで難しいポイントは以下の3つになります。解説の見通しを良くするためにパートごとに色を付けたコードもおいておきます。

  • ソースコード自分自身を出力するために、自分の情報を圧縮して自分に埋め込むこと (赤パート)
  • 狙った形に整形すること (青パート橙パート)
  • 改行や空白が重要なPythonで上記を実現すること (紫パート緑パート)

自分で整形したPython Quineを作ってみたい方へ

Jupyter notebookを用意しました。ちょっと形状によってコメントアウトの文字数とか弄る必要がありますが、基本的にすぐに動くQuine作れると思います!Quine文化を流行らせていきたいので、皆さんぜひ作ってみてください。完成作品のコメントお待ちしてます。

github.com

まずはとりあえず整形ではないQuineを作る(赤パート)

さて、ここからは動作原理を知りたい難読プログラミングオタク向け文章です。

Quine全体のうち最も大きな部分を占める赤パートですが、こちらはPythonでQuineを書いている先達に習って、プログラムをbase64でプログラム自身に埋め込む戦略を使わせてもらいました。

【Python3】Base64を使ってQuine - Jaded Electric Sphere

import base64 as b
s="aW1wb3J0IGJhc2U2NCBhcyBiCnM9Int9IgpwcmludChiLmI2NGRlY29kZShzLmVuY29kZSgidXRmLTgiKSkuZGVjb2RlKCJ1dGYtOCIpLmZvcm1hdChzKSk="
print(b.b64decode(s.encode("utf-8")).decode("utf-8").format(s))

この時点で見た目がいかついっちゃいかついんですが、要点としては、プログラム全体をbase64エンコードしておくことで、任意のプログラムをQuineにできることです。つまり、このコードの出力部分に整形機能を足していく(+全体からスペースとか改行を消していく)ことで、整形Quineが完成すると見通せます。

次に、AAを圧縮する(青パート)

d       i=zlib.decompress
(b6.b64decode(      "7dRJDoA         gDAXQPac       p97+cGxPp                     8DuIGBZ0Y2i1j1S09R
+iHWR3hJ7osEK5      QgbBrQik         oTOHDBlP       cRBCeRJZsAy                 Q+6rzvA0aXgmRY3H2ym9
LIv0NUh0X35TI2      ohTQwhTQk       QdiToik98       hpue2wlACUbngd           KWP8PhEHkF1jPAP0N/qLEJr
EeO/760WIapn/cWPStCVhzImEYPRRIDk5hEIGlkTB9kQuQA="),-15).decode()

の部分ですね

展開すると、こちらでやってることは要するに

import base64 as b6
import zlib
di=zlib.decompress(
    b6.b64decode(
        "7dRJDoAgDAXQPacp97+cGxPp8DuIGBZ0Y2i1j1S09R+iHWR3hJ7osEK5QgbBrQikoTOHDBlPcRBCeRJZsAyQ+6rzvA0aXgmRY3H2ym9LIv0NUh0X35TI2ohTQwhTQkQdiToik98hpue2wlACUbngdKWP8PhEHkF1jPAP0N/qLEJrEeO/760WIapn/cWPStCVhzImEYPRRIDk5hEIGlkTB9kQuQA="
    ),-15
).decode()

出力は、

1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111100000000000111111111111111000000000000111111111111100000000000011111111111111111111111
1111111111111100000000000011111111111110000000000000111111111100000000000000000011111111111111111111
1111111111111100000000000011111111111110000000000000111111111000000000110000000001111111111111111111
1111111111111100000000000001111111111100000000000000111111110000000111111110000000111111111111111111
1111111111111100000011000001111111111100000010000000111111100000001111111111000000011111111111111111
1111111111111100000011000000111111111000000110000000111111111111111111111111000000111111111111111111
1111111111111100000011100000111111111000000110000000111111111111111111111110000000111111111111111111
1111111111111100000011100000011111110000001110000000111111111111111111111000000011111111111111111111
1111111111111100000011110000001111110000001110000000111111111111111110000000001111111111111111111111
1111111111111100000011110000001111100000011110000000111111111111111110000000001111111111111111111111
1111111111111100000011111000000111100000011110000000111111111111111111111100000000111111111111111111
1111111111111100000011111000000111000000111110000000111111111111111111111111000000011111111111111111
1111111111111100000011111000000011000000111110000000111111111111111111111111100000001111111111111111
1111111111111100000011111100000010000001111110000000111111111111111111111111100000001111111111111111
1111111111111100000011111100000000000001111110000000111111111111111111111111100000001111111111111111
1111111111111100000011111110000000000011111110000000111111100000001111111111100000001111111111111111
1111111111111100000011111110000000000011111110000000111111110000000111111111000000011111111111111111
1111111111111100000011111111000000000111111110000000111111111000000000000000000000111111111111111111
1111111111111100000011111111000000000111111110000000111111111110000000000000000011111111111111111111
1111111111111100000011111111100000001111111110000000111111111111110000000000011111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111

ってことです。

こちらの実装は、jsで整形Quineを実装されていた id:shoponpon の記事を参考にさせていただきました。

shoponpon.hatenablog.com

AAの作成は AA変換(アスキーアート生成)|Web便利ツール@ツールタロウ を利用させていただきました。

展開されたAAの形に、展開したプログラムをprintする (橙パート)

青パートでAAを01の列にできて、赤パートでプログラムをエンコード・デコード出来たので、あとは組み合わせて文字列にするだけです

print("".join([tr(c=="1",b6.b64decode(s.encode("utf-8")).decode("utf-8").format(s)[sum(lme(
di)[:j])],tr(c=="0",chr(32),c))for(j,c)in(enumerate(di))]))

展開するとこういう感じになります。

tr=lambda x,y,z: (y if x else z)
lme=lambda x:list(map(lambda x:tr(x=="1",1,0),x))
print(
"".join([
    tr(
        c=="1",
        b6.b64decode(s.encode("utf-8")).decode("utf-8").format(s)[sum(lme(di)[:j])], # プログラムをデコード
        tr(c=="0",chr(32),c)
    )
    for(j,c) in (enumerate(di)) # アスキーアート01の文字列についてのループ
    ])
)

さて、ここで普通のプログラムを書きなれてる人には、なんでループの中で毎回プログラムをデコードしてるんだろう?ってなるかと思います。

p = デコードされたプログラム
for c in アスキーアート:
   if c==1:
       pから1文字ずつ出力

と書くのがシンプルですので。ただし、これをやるためには、pの何文字目まで来たかのカウンターをあらたに導入する必要があります。一方で先程のように、出力時に毎回 sum(lme(di))[:j] という形で、jまでに何回1があったはずかを毎回計算すると、計算的には無駄が多いですが、プログラム上は不要なクラスを作らずすんで、コードゴルフ的な考えでお得です。なるべくコードが短いほうがデザインは自由が効くので*1。この辺は標準ライブラリでもっと短縮できる方法とかもあるかもしれません。要研究です。

Pythonで改行や空白を自由に整形する (紫パート)

空白と改行をなくすための基本的なアイディアはシンプルで、2行目から後ろを見ていただくと、

m("""import
....
""")###

このような形で、複数行文字列 + コメントという形式になっているのがわかります。つまり、この m(""" 以降は文字列として扱われるので、改行や空白を自由に入れられるわけです。

それを1行目の

m=lambda x:exec("".join(x.split()).replace("~"," ")

という関数で包むことで、評価のときには、空白は無視されて、~は空白として扱われる、わけです。

シンプルにはmという関数をあらたに導入しないでも、exec("".join("""任意のプログラム""").split())という形で書けそうなのですが、

  • 関数宣言で空白がどうしても避けられない(def にせよ lambda にせよ空白が必要)という点から、replace("~", " ")として、~を空白にreplaceしてから評価する
  • メソッドを後置する形だと、メソッドの中に改行が来たときに実行不可能になるので、必須機能は空白のない1行目に集約するほうがデザインの自由度が高まる

という点でmという関数を事前定義してexecの代わりとしました。この辺はPythonならではの難しさであり、replaceでどうにかしたのとか、1行目を改行出来ないデザインにしたのとかは自分の中でもちょっと逃げだと思ってるので、良い手段があればぜひコメント下さい!

ちなみにm自身の定義は

exec('''m=lambda_x:exec("".join(x.split()).replace("~",chr(32)),globals())'''.replace("_",chr(32)));

という形で、_を空白にreplaceしてevalで実現してます(空白をchr(32)とすることで空白文字がなくなってることにも留意)。

ところで、これ書いてるときにハマったのが、

m=lambda y: exec(y)
m("a=1;b=lambda:print(a);b()")

の形で書くとexecのスコープがlambdaの中で厳しいスコープとなり、execの中で他の変数(この場合a)を参照できなくてエラーになることでした。解決策としては m=lambda y: exec(y, globals())の形で、lambdaの外側のスコープを注入する必要があります。

まとめ

本稿では上記の整形Quineの作成過程を追ってきました。整形ができるようになったのはQuineへの入口の第一歩にしか過ぎないので、ここから、実行のたびに切り替わっていくQuineだったり、入力を受け付けてゲームとして遊べるQuineだったり、わけのわからない世界に足を踏み込んでください。私も次は動きのあるQuineに挑戦したいです。

We are hiring !!

エムスリーでは、難読プログラミングは出来つつも難読ではないプロダクションコードを書くエンジニアを募集しています!ぜひ入社して社内でQuine文化を流行らせましょう!

私の本業は難読プログラミングエンジニアではなく機械学習エンジニアなので、一緒にMLやるエンジニアも募集してます!

エンジニア採用ページはこちら

jobs.m3.com

カジュアル面談もお気軽にどうぞ

jobs.m3.com

インターンも常時募集しています

open.talentio.com

*1:じつはここを書いている時点では、関数宣言とクラス宣言をゼロにしてこのあとの姑息な手を使わずとも空白なしで行けるのを目指してたから、という理由もあります