12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-08-08

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

#はじめに

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(ニシキヘビ的)に改造しましょう!

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

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

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

___.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です。
これ以外のインスタンス変数(hogepiyo等)は存在しません。
__クラスは__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.codesexecで実行しています。
例えば、

- _.__.____._._().__.____._.___().__.___.___.__().__.___.____.___().__.____.__._().___.___._().___.__.____().__.___.___._().__.___.__.__().__.___.____._().__.___.____._().__.___.____.____().___.____._().___._._().__.____.__.____().__.___.____.____().__.____._.___().__.___.____._().__.___.__._().___._.__().___.__.____().___.___.__()

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

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

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表すために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))
12
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?