Quine(クワイン)とQuineリレーの作り方

目次

  1. 背景
  2. Quineの構造を理解する
    1. Step 1: データと出力
    2. データ定義のパラドックス
    3. データの中にデータ自体を埋め込む
    4. エスケープを頑張る
  3. Quineリレー
    1. PHPを出力するPython
    2. 自己複製するロジック
    3. コードを難読化する
  4. まとめ
  5. PR
  6. 参考資料

背景

こんにちは。 かりんとうマニア(@karintozuki)です。
私が初めてQuineについてのWikipedia記事を読んだとき10分くらい悩んだ末、何もわからん!と諦めた記憶があります。今思えば、当時はJavaでプログラミングをしていて、JavaはボイラープレートがあるせいでQuineに向いてない言語だったのが原因かもしれません。

最近、改めてQuineを勉強したところ、何がどうなっているのか腹落ちした感じがしました。なのでこの記事では誰でもわかるようにシンプルにQuineをゼロから作る過程を紹介しようと思います。

注: 記事の校正にAIを利用しましたが、コードはすべて自作です。


Quineの構造を理解する

ここでは、構文が簡潔で人気のあるPythonを使って説明します。この記事の原理を理解すれば、大体どの言語でもQuineが書けるようになるはずです。

まず、今回作成するPythonのQuineがこちらです。

quine.py
1
s='s=%c%s%c; print(s%%(39,s,39)'; print(s%(39,s,39))

実際に動かしたい場合は、https://pythononline.net/ のようなオンラインインタープリタが便利です。

このコードが今理解できていなくても全然問題ありません。順を追って説明していきます。

Step 1: データと出力

さて、最初に以下のようなコードを考えてみます。

ver1.py
1
2
3
4
5
6
# Ver.1
s="print(s)"
print(s)

# 実行結果
print(s)

このコードはシンプルに文字列print(s)を出力するだけですが、Quineに必要な2つの要素を含んでいます。

  1. データ定義 — プログラムのコードをデータとして保持する
  2. データ出力 — そのデータを画面に出力する
ver1_explain.py
1
2
3
s="print(s)" # データ定義
print(s) # データ出力

データ定義のパラドックス

先程のVer 1では、データ定義と出力のうちデータ出力部分を出力していました。完全なQuineにするには、データ定義の部分も出力する必要があります。そこで、コードを次のように編集しました。

ver2.py
1
2
3
4
5
6
7
8
# Ver. 2
s="""s="print(s)"
print(s)"""
print(s)

# Ver. 2の実行結果
s="print(s)"
print(s)

(文字列に改行を含めるため、Pythonの"""を使用したことに注意してください。)

これでVer. 1のコードが出力されました。しかし出力結果をもとのコードに合わせるためにデータ定義を変更したことで、元のコード自体が変わってしました!

当たり前といえば当たり前なのですが、このパラドックスがQuineを難しくしています。

データの中にデータ自体を埋め込む

Ver. 2をよく見ると、実はデータ定義が2箇所あることが分かるでしょうか?

1
2
3
↓ sのデータ定義
s="""s="print(s)"
↑ データの内側のデータ定義

Quineを成立させる秘密は、外側で定義されたデータを使って、内側のデータ定義を埋めることです。

具体的には、Pythonのprint()内で%演算子を使うことで、文字列に値を挿入できます。

%演算子は以下のように、文字列に変数を展開するときに使います。

1
2
3
print("%s 最高!" % "Python")
# 実行結果
# Python 最高!

これを先程のコードに適用すると、以下のようになります。

ver3.py
1
2
3
4
5
6
7
8
9
10
# Ver 3
s="""s="%s"
print(s)"""
print(s%s) # %sをsの値で置き換える

# Ver. 3の出力
s="s="%s"
print(s)"
print(s)

出力結果が元のコードとだいぶ似てきました。

このテクニックを使うことでQuineのパラドックスを解消することができます。

エスケープを頑張る

ここまで来るとQuineとしての基本的な概念はカバーしたと言えます。

ここからは(個人的には)Quineのめんどくさいところ、エスケープ処理を頑張っていきます。

Ver. 3を再確認します。

ver3_recap.py
1
2
3
4
5
# Ver. 3 (再掲)
s="""s="%s"
print(s)"""
print(s%s)

ここから、以下のような特殊文字をQuineできるように扱っていきます。

  • 改行
  • 引用符
  • パーセント記号

私はコードをシンプルにするためQuineは1行にしてしまうのが良いと思っています。改行を含むコードは、たいていの言語で(Pythonのトリプルクオートなど)特殊な構文が必要になります。

そこで、改行を削除し、代わりにセミコロンを使用しました。トリプルクオートが不要になったので、シングルクオートに置き換えます。

ver4.py
1
2
3
# Ver. 4
s='s='%s'; print(s)'; print(s%s)

しかし、このコードは、シングルクオートの中にシングルクオートを使っているため、構文エラーになります。
通常のエスケープ(\'など)を使っても、出力されたコードが構文エラーになってしまうため、クワインではうまくいきません。

ver4_1.py
1
2
3
4
5
6
# Ver. 4.1
s='s=\'%s\'; print(s)'; print(s%s) # これは正しいコード

# 実行結果
s='s='%s'; print(s)'; print(s) # これは構文エラー!

この問題を解決する方法の1つが、%sでデータ展開をしたのと同じように、フォーマット文字列で%cを使うことです。

ver4_2.py
1
2
3
4
5
# Ver. 4.2
s='s=%c%s%c; print(s)'; print(s%(39,s,39))

# 実行結果
s='s=%c%s%c; print(s)'; print(s)

マジックナンバーの39は、シングルクオートのASCIIコードです。

Ver. 4.2はほとんどQuineですね。残っているのは、新しく追加したデータ内の出力部分print(s%(39,s,39))です。

ここでの注意は、% 記号ですが、これは二重パーセント(%%)を使うだけでエスケープできます。

これでPythonでのQuineが完成しました!

ver5.py
1
2
3
4
5
# Ver. 5
s='s=%c%s%c; print(s%%(39,s,39))'; print(s%(39,s,39))

# 実行結果がもとのコードになっている!
s='s=%c%s%c; print(s%%(39,s,39))'; print(s%(39,s,39))

Quineリレー

Quineの書き方をマスターしたところで、次はQuineリレーを紹介します。

Quineリレーとは、ある言語のコードが出力され、それが別の言語のコードを出力し、それがまた別の言語のコードを出力…と続き、最終的に最初のコードを出力するプログラムです。

ここではPythonとPHPの間でQuineリレーを作成する過程を、順を追って見ていきます。

PHPを出力するPython

最初にシンプルなphpコードをPythonで出力してみます。

1
2
3
4
5
# Ver. 1
s='ここにpythonコードを出力';print('<?php print("%s");' % s)

# 実行結果
<?php print("ここにpythonコードを出力");

出力された結果は、正しいPHPコードです。Quineリレーでは、Pythonから出力されたPHPコードに元のPythonコードを出力させるよう頑張っていきましょう。

変数sの内容を、より現実的なものに置き換えてみます。

1
2
3
4
5
6
7
8
# Ver. 2
s='s=\\'%s\\';print(\\'<?php print(%s);\\' %% s)';print('<?php print("%s");' % s)

# 実行結果はPHPになる
<?php print("s='%s';print('<?php print(%s);' %% s)");

# PHPの実行結果
s='%s';print('<?php print(%s);' %% s)

割といい感じですね。

最終的なPHPコードの出力結果を見ると、元のコードの最初の%sを、Pythonで定義された**s自身の値**で置き換えれば良いことがわかります。

自己複製するロジック

そこで、置き換えのロジックを追加しました。

1
2
3
4
5
# Ver. 3
s='s=\\'%s\\';print(\\'<?php print(%%s);\\' %% s)';print('<?php print("%s");' % (s%s))

# 実行結果
<?php print("s='s='%s';print('<?php print(%%s);' %% (s%%s))';print('<?php print(%s);' % (s%s))");

この部分が、自己複製を可能にするポイントです。

1
print('<?php print("%s");' % (s%s))

(s%s)によって、sのなかの%sを自身で展開した結果が生成され、それがPHPコードの出力として埋め込まれます。

PHPが出力しようとしている文字列は、元のPythonコードにかなり近づきましたが、PHPコード自体のエスケープがまだ不完全です。これを修正します。

試行錯誤の末、以下のようなコードでエスケープを機能させることができました。

1
2
3
4
5
6
7
8
# Ver. 4
s="s=%r;print('<?php print(%%r);' %% (s%%s))";print('<?php print(%r);' % (s%s))

# 実行結果のPHP
<?php print('s="s=%r;print(\'<?php print(%%r);\' %% (s%%s))";print(\'<?php print(%r);\' % (s%s))');

# そのPHPの実行結果
s="s=%r;print('<?php print(%%r);' %% (s%%s))";print('<?php print(%r);' % (s%s))

ここでのポイントは、Pythonの%rフォーマッタです。これは%sに似ていますが、文字列を有効なPythonコードとして出力してくれるため、引用符のエスケープを自動的に処理してくれています。

コードを難読化する

オンラインにあるQuineの例は、多少難読化というか奇抜な感じになっていますよね。せっかくなので、私たちのQuineリレーも読みづらくしてみます。

まず、変数名s_に変えることでヤバさを増やします。また、空白もすべて削除して読みにくくしましょう。

1
2
3
# Ver. 5
_="_=%r;print('<?php print(%%r);'%%(_%%_))";print('<?php print(%r);'%(_%_))

結構やばくなってきましたね。

更にPHPでは、<?php print("something");の代わりに、<?="something"?>という短縮構文が使えるのでそれを利用しましょう。

これがQuineリレーの最終バージョンです。

1
2
3
4
5
# Ver 5.1
_="_=%r;print('<?=%%r?>'%%(_%%_))";print('<?=%r?>'%(_%_))

# 実行結果のPHP
<?='_="_=%r;print(\\'<?=%%r?>\\'%%(_%%_))";print(\\'<?=%r?>\\'%(_%_))'?>

まとめ

オンラインで公開されているQuineは、多くの場合、いかに奇妙にするか、アートのような要素が追求されています(それはそれで面白いのですが、概念の理解には不向きですね😅)。
この記事を通じて、この奇妙で美しい概念を、読みやすいコードで理解する手助けができたなら幸いです。

コードの難読化をしてみて思ったのですが、コードを難しくするのは意外と簡単ですが、それをした後は指数関数的に読むのが難しくなるという非対称性に気づきました。また、Quineのようなトリッキーなとっつきづらいコードの解説をどこかで書くかもしれません。

最後までお読みいただき、ありがとうございました😆

それじゃ今日はこの辺で。

PR

あなたの会社はあなたの技術を評価してくれていますか?
技術力を高めようと頑張っているのに、
技術が評価されないような会社にいたらそれは良い環境なのでしょうか?
エンジニアとして技術を高めたいのなら環境を選ぶことも大事です。

レバテックキャリア
エンジニアとして働いていて実務経験があるなら、
求人数の充実具合からレバテックキャリアがおすすめです。
IT転職ではデファクト・スタンダードですね。
▼レバテック キャリア 登録はこちら▼


Tech Clips
Tech Clipsは年収500万以上&自社サービスを持った会社に特化した求人サイトです。
首都圏限定になってはしまいますが、
収入を増やしたい、自社サービスを持った企業への転職をしたい人におすすめです。

▼Tech Clips 登録はこちら▼


参考資料

ご自身でQuineを書いてみたい方は、これらの記事が非常に参考になります。

Wikipedia Quine (コンピュータ)

Quine How To

128言語Quineリレー