2019/4/8: 誤植を1か所修正しました。
#はじめに
pythonのコードは、インデントスコープやイテレータにより読みやすくすっきりとした見た目を実現しています。
for i in range(100):
if i % 15 == 0:
print('fizzbuzz')
elif i % 3 == 0:
print('fizz')
elif i % 5 == 0:
print('buzz')
else:
print(i)
でも、ちょっと待ってください。
Python(ニシキヘビ)なのに蛇らしくないですよね…
蛇はにょろにょろと地面を這っていくものです。
あなたのコードもPythonic(ニシキヘビ的)に改造しましょう!
from ___ import _
-_.__.___.__.___().__.___.____.____().__.____._.___().___._._().__.___.___.__().___._._().__.___.___.__().__.___.____.___().___._._().__.____._.___().__.___._.__().__.___.____.___().__.___.__.____().__.___.__.__().___.___._().____._.__().____._._().____._._().___.___.__().____.___.___().___.___().___._._().___._._().___._._().___._._().__.___.___.__().__.___.__.___().___._._().__.___.___.__().___._._().___.__.__().___._._().____._.__().____.__.__().___._._().____.____.__().____.____.__().___._._().____._._().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.__.___().__.___.___.__().__.____.___.___().__.____.___.___().__.___._.___().__.____.__.__().__.____.___.___().__.____.___.___().___.__.____().___.___.__().___.___().___._._().___._._().___._._().___._._().__.___.__.__().__.___.____._().__.___.___.__().__.___.__.___().___._._().__.___.___.__().___._._().___.__.__().___._._().____._.____().___._._().____.____.__().____.____.__().___._._().____._._().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.__.___().__.___.___.__().__.____.___.___().__.____.___.___().___.__.____().___.___.__().___.___().___._._().___._._().___._._().___._._().__.___.__.__().__.___.____._().__.___.___.__().__.___.__.___().___._._().__.___.___.__().___._._().___.__.__().___._._().____.__.__().___._._().____.____.__().____.____.__().___._._().____._._().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___._.___().__.____.__.__().__.____.___.___().__.____.___.___().___.__.____().___.___.__().___.___().___._._().___._._().___._._().___._._().__.___.__.__().__.___.____._().__.____._.____().__.___.__.__().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().__.___.___.__().___.___.__().___.___()
「あなたの知らない超絶技巧プログラミングの世界」で紹介されている「アンダースコアだけでHello, world!」(言語はRuby)のコードに感動し、
Pythonでも似たようなことができないかと考え作成しました。
https://www.amazon.co.jp/dp/4774176435
#仕組み
蛇コードは特殊メソッドを悪用して作成しています。
モジュールの中身は以下の通りです。
class __:
def __init__(self, n=0, codes=''):
self.n = n
self.codes = codes
def __getattr__(self, name):
return __(self.n * 4 + len(name) - 1, self.codes)
def __call__(self):
return __(0, self.codes + chr(self.n))
def __str__(self):
return self.codes
def __neg__(self):
exec(self.codes)
_ = __()
変数_
はクラス__
のインスタンスです。
モジュールを読み込むことで、インスタンス_
を読み込み、
_
のメソッドを使用して蛇コードを作っています。
蛇コードは以下の流れで実行されています。
- インスタンスは新しい文字の文字コード(int)とソース(str)を保持
- 存在しないインスタンス変数を呼び出して文字コードをセット
- インスタンスを関数呼び出しして文字コードを文字に変換しソースに追記
- printするとソースを表示
- 単項演算子
-
をつけるとソースを実行
これを特殊メソッドでどのように実装したかを紹介していきます。
##__getattr__
メソッド: 存在しないインスタンス変数の参照時に呼び出される
__
クラスのインスタンス変数は、asciiコードを表す整数n
とソース文字列codes
です。
これ以外のインスタンス変数(hoge
、piyo
等)は存在しません。
__
クラスは__getattr__
メソッドをもっているので、
ここで_.hoge
を参照するとAttributeError
が起きる代わりに__getattr__
メソッドが呼び出されます。
そのため、__getattr__
メソッドの戻り値を__
クラスのインスタンスとすることで
_.hoge
も__
クラスのインスタンスとなり、_.hoge.foo.bar
のようにいくらでも属性をつなげられるようになります。
さらに、__getattr__
メソッドは存在しなかった属性名(ここではhoge
)を引数にとれるので、
def __getattr__(self, name):
return __(self.n * 4 + len(name) - 1, self.codes)
とすることで、属性のチェーンを使ってn
を属性の文字数による4進数で表すことができます。
_.__.n # 1
_.__._.n # 4 (4*1 + 0)
_.__._.____.n # 19 (4^2*1 + 4*0 + 3)
つづいて、asciiコードnを文字列に変換します。
##__call__
メソッド: インスタンスを関数として呼び出し
クラスが__call__
メソッドを持っているとき、そのインスタンスは関数呼び出しができるようになり、
このメソッドが実行されます。
__
クラスでは__call__
メソッドが呼ばれると、インスタンス変数n
をasciiコードに変換し
インスタンス変数codes
に追記したものを返します(この際n
の値は0に戻します)。
def __call__(self):
return __(0, self.codes + chr(self.n))
こうすることで、インスタンス変数codes
に任意の文字列をセットできるようになります。
# 1文字目
_.__.___.___._.n # 104 (asciiコードの"h")
_.__.___.___._.codes # ""
# ()で__.__call__を呼び出しあらたなインスタンス作成
# nのasciiコードをcodesに追記
_.__.___.___._().n # 0 (リセット)
_.__.___.___._().codes # "h"
# 2文字目
_.__.___.___._().__.___.___.__.n # 105 (asciiコードの"i")
_.__.___.___._().__.___.___.__.codes #"h"
# __.__call__呼び出し
_.__.___.___._().__.___.___.__().n # 0
_.__.___.___._().__.___.___.__().codes # hi
# 3文字目…
あとはこの文字列を表示・実行すれば完成です。
##__str__
メソッド: インスタンスをstrに変換する(print()
で表示する等)ときに呼び出される
このメソッドを持っているインスタンスは、文字列に変換することができます。
__
クラスでは、__str__
はcodes
インスタンスを返すので、
print(_.__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.___._().__.___.__.__().__.___.____._().__.___.____._().__.___.____.____().___.____._().___._._().__.____.__.____().__.___.____.____().__.____._.___().__.___.____._().__.___.__._().___._.__().___.__.____().___.___.__())
により
print('hello, world!')
が表示されます。
##__isub__
メソッド: 単項演算子-
をオーバーロードする
__isub__
の戻り値が-
をつけた結果となります。
__
クラスでは、__isub__
はNone
を返し、副作用としてself.codes
をexec
で実行しています。
例えば、
- _.__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.___._().__.___.__.__().__.___.____._().__.___.____._().__.___.____.____().___.____._().___._._().__.____.__.____().__.___.____.____().__.____._.___().__.___.____._().__.___.__._().___._.__().___.__.____().___.___.__()
によってコードprint('hello, world!')
が実行されます。
#最後に:手持ちのコードを蛇コード化したい!
以下のコードで変換できます。
"Pythonic"な見た目をお楽しみください。
import sys
def to_base_n(num, base):
convertedNums = []
while num:
convertedNums.append(num % base)
num //= base
#昇順になっているので降順に直す
convertedNums.reverse()
return convertedNums
def encode_to_underlines(programmingCodes):
#インスタンス名(チェーンのはじめ)
nameChains = ['_']
for char in programmingCodes:
#asciiコードを4進数展開
charNumbers = to_base_n(ord(char), base=4)
#チェーンは4進数 (0表すために1つ余分に長くする)
#ex: 104 = 1 * 4**3 + 2 * 4**2 + 2 * 4 ** 1 + 0 * 4**0 -> ____.___.___._()
methodNames = '.'.join(['_' * (n + 1) for n in charNumbers]) + '()'
nameChains.append(methodNames)
return '.'.join(nameChains)
if __name__ == '__main__':
fileName = sys.argv[1]
with open(fileName, 'r', encoding='utf-8') as f:
codes = f.read()
print(encode_to_underlines(codes))