コードをもっとPythonic(ニシキヘビ的)に！任意のPythonコードをアンダースコアでワンライナー化

2019/4/8: 誤植を1か所修正しました。

#はじめに

pythonのコードは、インデントスコープやイテレータにより読みやすくすっきりとした見た目を実現しています。

```python:fizzbuzz.py
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(ニシキヘビ的)に改造しましょう！

```python:fizzbuzz_pythonic.py
from ___ import _
-_.__.___.__.___().__.___.____.____().__.____._.___().___._._().__.___.___.__().___._._().__.___.___.__().__.___.____.___().___._._().__.____._.___().__.___._.__().__.___.____.___().__.___.__.____().__.___.__.__().___.___._().____._.__().____._._().____._._().___.___.__().____.___.___().___.___().___._._().___._._().___._._().___._._().__.___.___.__().__.___.__.___().___._._().__.___.___.__().___._._().___.__.__().___._._().____._.__().____.__.__().___._._().____.____.__().____.____.__().___._._().____._._().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.__.___().__.___.___.__().__.____.___.___().__.____.___.___().__.___._.___().__.____.__.__().__.____.___.___().__.____.___.___().___.__.____().___.___.__().___.___().___._._().___._._().___._._().___._._().__.___.__.__().__.___.____._().__.___.___.__().__.___.__.___().___._._().__.___.___.__().___._._().___.__.__().___._._().____._.____().___._._().____.____.__().____.____.__().___._._().____._._().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.__.___().__.___.___.__().__.____.___.___().__.____.___.___().___.__.____().___.___.__().___.___().___._._().___._._().___._._().___._._().__.___.__.__().__.___.____._().__.___.___.__().__.___.__.___().___._._().__.___.___.__().___._._().___.__.__().___._._().____.__.__().___._._().____.____.__().____.____.__().___._._().____._._().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___._.___().__.____.__.__().__.____.___.___().__.____.___.___().___.__.____().___.___.__().___.___().___._._().___._._().___._._().___._._().__.___.__.__().__.___.____._().__.____._.____().__.___.__.__().____.___.___().___.___().___._._().___._._().___._._().___._._().___._._().___._._().___._._().___._._().__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().__.___.___.__().___.___.__().___.___()
```


「あなたの知らない超絶技巧プログラミングの世界」で紹介されている「アンダースコアだけでHello, world!」(言語はRuby)のコードに感動し、
Pythonでも似たようなことができないかと考え作成しました。
https://www.amazon.co.jp/dp/4774176435

#仕組み
蛇コードは特殊メソッドを悪用して作成しています。
モジュールの中身は以下の通りです。

```python:___.py
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)


_ = __()
```

変数`_`はクラス`__`のインスタンスです。
モジュールを読み込むことで、インスタンス`_`を読み込み、
`_`のメソッドを使用して蛇コードを作っています。

蛇コードは以下の流れで実行されています。

1. インスタンスは新しい文字の文字コード(int)とソース(str)を保持
2. 存在しないインスタンス変数を呼び出して文字コードをセット
3. インスタンスを関数呼び出しして文字コードを文字に変換しソースに追記
4. printするとソースを表示
5. 単項演算子`-`をつけるとソースを実行

これを特殊メソッドでどのように実装したかを紹介していきます。

##`__getattr__`メソッド: 存在しないインスタンス変数の参照時に呼び出される
`__`クラスのインスタンス変数は、asciiコードを表す整数`n`とソース文字列`codes`です。
これ以外のインスタンス変数(`hoge`、`piyo`等)は存在しません。
`__`クラスは`__getattr__`メソッドをもっているので、
ここで`_.hoge`を参照すると`AttributeError`が起きる代わりに`__getattr__`メソッドが呼び出されます。
そのため、`__getattr__`メソッドの戻り値を`__`クラスのインスタンスとすることで
`_.hoge`も`__`クラスのインスタンスとなり、`_.hoge.foo.bar`のようにいくらでも属性をつなげられるようになります。

さらに、`__getattr__`メソッドは存在しなかった属性名(ここでは`hoge`)を引数にとれるので、

```python:
def __getattr__(self, name):
    return __(self.n * 4 + len(name) - 1, self.codes)
```

とすることで、属性のチェーンを使って`n`を属性の文字数による4進数で表すことができます。

```python:
_.__.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に戻します)。

```python:
def __call__(self):
    return __(0, self.codes + chr(self.n))
```

こうすることで、インスタンス変数`codes`に任意の文字列をセットできるようになります。

```python:
# 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`インスタンスを返すので、

```python:
print(_.__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.___._().__.___.__.__().__.___.____._().__.___.____._().__.___.____.____().___.____._().___._._().__.____.__.____().__.___.____.____().__.____._.___().__.___.____._().__.___.__._().___._.__().___.__.____().___.___.__())
```

により

```
print('hello, world!')
```
が表示されます。

##`__isub__`メソッド: 単項演算子`-`をオーバーロードする
`__isub__`の戻り値が`-`をつけた結果となります。
`__`クラスでは、`__isub__`は`None`を返し、副作用として`self.codes`を`exec`で実行しています。
例えば、

```python:
- _.__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.___._().__.___.__.__().__.___.____._().__.___.____._().__.___.____.____().___.____._().___._._().__.____.__.____().__.___.____.____().__.____._.___().__.___.____._().__.___.__._().___._.__().___.__.____().___.___.__()
```

によってコード`print('hello, world!')`が実行されます。

#最後に:手持ちのコードを蛇コード化したい！
以下のコードで変換できます。
"Pythonic"な見た目をお楽しみください。

```Python:encode.py
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表すために１つ余分に長くする)
		#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))
```