Python3でQuine(自己言及プログラム)

はじめに

以下のようなスクリプトを作成し、Python3で実行してみてください。プログラミングの力がある方は、実行前にどういった出力になるかを考えてみてください。

1
_='_=%r;print(_%%_)';print(_%_)

Quine

先ほどのスクリプトを実行すると、ソースコードそのものが出力されます。このようなプログラムのことをQuine、自己言及プログラムと呼びます。ただし、通常以下のようなプログラムはQuineと見なされません。

  • 空のスクリプト
  • ファイル入力を取るスクリプト

空のスクリプト

多くの言語では空のスクリプトを実行することができます。この時、当然標準出力には何も出力されないため、ソースコードそのものが出力となるスクリプトと言えます。といっても、これではあまりにも面白みがなく、Quineとは見なさないのが一般的です。

ファイル入力を取るスクリプト

スクリプトそのものを読み込んで、標準出力するスクリプトもQuineとは見なされません。確かに、これも何でもありになってしまいます。

1
2
3
import sys
f = open(sys.argv[0])
print (f.read().rstrip())

冒頭のコードの解説

はじめにお見せしたコードはやや難解です。これでどうしてQuineが実現できるのか考えてみます。

1
_='_=%r;print(_%%_)';print(_%_)

%rの意味

まずは、Pythonにおけるprintf形式の文字列書式化について復習しましょう。C言語などを触ったことがある方は見慣れた書式ですね。

以下のようなスクリプトがあるとします。

1
2
price = 1100
print('合計は'+str(price)+'円です')

printf形式の文字列書式化で書き換えると

1
2
price = 1100
print('合計は%s円です' % price)

このように、文字列の出力の中に変数の値を用いたい場合に利用できます。

さて、今の例で%sとした箇所はPythonオブジェクトをstr()で変換することを表しています。そのため、priceが数値型であっても文字列型であっても結果は変わりません。

1
2
price = 1100
print('合計は%s円です' % price)
1
2
price = '1100'
print('合計は%s円です' % price)

ただし、%dとした場合はpriceが文字列型だとエラーとなるので注意してください。

1
2
price = 1100
print('合計は%d円です' % price)
1
2
price = '1100'
print('合計は%d円です' % price) # これはエラー

さて、話を冒頭のスクリプトに戻します。スクリプトでは%sが用いられていました。

1
2
3
price = '1100'
print('合計は%s円です' % price)
print('合計は%r円です' % price)
1
2
合計は1100円です
合計は'1100'円です

このように%rとした場合はシングルクォート付きで出力されます。これは、%sとした箇所はPythonオブジェクトをrepr()で変換するためです。本題からそれてしまうため、reprの詳細についてはここでは解説しません。

さて、実際に%sの挙動を追ってみます。ここでは簡単のために以下のように一部文字を書き換えます。

1
_='_=%r;print(_%%_)';print(_%'A')

実行結果は以下のようになります。

1
_='A';print(_%_)

Aがシングルクォート付きで出力されていますね。

次に、print(_%%_)について解説します。printf形式の文字列書式化で以下のようなことが実現できると学びました。

1
2
print('消費税は10%です')
print('消費税は%sパーセントです' % 10)
1
2
消費税は10%です
消費税は10パーセントです

しかしながら、以下のスクリプトはエラーとなります。

1
print('消費税は%s%です' % 10) # これはエラー

これはprintf形式の文字列書式化を用いた場合、%を出力したければエスケープをする必要があるためです。

1
print('消費税は%s%%です' % 10)
1
消費税は10%です

これでOKです。

まとめ

さて、これで必要な材料は揃ったので、最後にもう一度冒頭のスクリプトの流れを追います。

1
_='_=%r;print(_%%_)';print(_%_)

まず、セミコロンの手前までで、変数_に代入を行います。変数_には文字列_=%r;print(_%%_)が格納されています。

続いて、print(_%_)で変数_を出力します。ただし変数内でprintf形式の文字列書式化%rが用いられています。この箇所は一旦保留して[?]としましょう。

なので出力は以下のようになるはずです。%の数が減ることに注意してください。

1
_=[?];print(_%_)

さて[?]には、変数_の中身が代入されます。%rですのでシングルクォートが付きます。

1
_='_=%r;print(_%%_)';print(_%_)

スクリプトそのものと全く同じ出力となりましたね。

記事情報

  • 投稿日:2020年6月7日
  • 最終更新日:2020年6月7日