LoginSignup
5
0

More than 5 years have passed since last update.

ハロウィンなのでPythonを八つ裂きにしてみる

Last updated at Posted at 2018-10-31

前置き

Pythonにはスライスと呼ばれる非常に便利な構文がある。

>>> src = 'spamhamegg'
>>>
>>> src[2:]
'amhamegg'
>>> src[:-3]
'spamham'
>>> src[::-1]
'ggemahmaps'

それを悪用して、変なコードを書いてみた話。
去年も全く生産的でない記事を書いたので、ハロウィンネタ第二段とも言える。

成果物

こんなコードを書いてみた。
Pythonのコードとして実行可能であることはもちろん、PEP8にもある程度準拠している。1

from brainbite import Biter

_ = Biter()[::...][::...][::...][::...][::...][::...][::...][::...][:...]
_ = _[:...:...][::...][::...][::...][::...][:...][:...:...][::...][::...]
_ = _[:...:...][::...][::...][::...][:...:...][::...][::...][::...]
_ = _[:...:...][::...][...:...][...:...][...:...][...:...][...:][...::...]
_ = _[:...:...][::...][:...:...][::...][:...:...][...:][:...:...][:...:...]
_ = _[::...][:...][...:...][...::...][...:...][...:][...::...][:...:...]
_ = _[:...:...][:][:...:...][...:][...:][...:][:][::...][::...][::...]
_ = _[::...][::...][::...][::...][:][:][::...][::...][::...][:][:...:...]
_ = _[:...:...][:][...:...][...:][:][...:...][:][::...][::...][::...][:]
_ = _[...:][...:][...:][...:][...:][...:][:][...:][...:][...:][...:][...:]
_ = _[...:][...:][...:][:][:...:...][:...:...][::...][:][:...:...][::...]
_ = _[::...][:]()

実行結果

Hello World!

ここでインポートしているbrainbiteパッケージは、ちゃっかりPyPIに登録済みだ。
試してみたかったらpip install brainbiteで入る。2

brainbiteの実装はこちら: GitHub - LouiS0616/brainbite

コアな部分だけここにも貼っておく。
data_model.py
import sys


class BFDataModel:
    """
    This holds cells what behaves as the Turing Machine's tape.
    """

    def __init__(self, size=30000):
        self._data = [0 for _ in range(size)]
        self._index = 0

    @property
    def pointer(self):
        return self._index

    @pointer.setter
    def pointer(self, value):
        self._index = value % len(self._data)

    @property
    def pointee(self):
        return self._data[self._index]

    @pointee.setter
    def pointee(self, value):
        self._data[self._index] = value

    def increment_pointer(self):
        """This corresponds to operation '>'."""
        self.pointer += 1

    def decrement_pointer(self):
        """This corresponds to operation '<'."""
        self.pointer -= 1

    def increment_pointee(self):
        """This corresponds to operation '+'."""
        self.pointee += 1

    def decrement_pointee(self):
        """This corresponds to operation '-'."""
        self.pointee -= 1

    def conditional_skip(self) -> bool:
        """This corresponds to operation '['."""
        return self.pointee == 0

    def unconditional_jump(self):
        """This corresponds to operation ']'."""
        pass

    def print_pointee(self):
        """This corresponds to operation '.'."""
        sys.stdout.write(
            chr(self.pointee)
        )

    def input_to_pointee(self):
        """This corresponds to operation ','."""
        self.pointee = ord(sys.stdin.read(1))

biter.py
from .data_model import BFDataModel as Model


class _BFLikeInterpreter:
    """
    This class should be considered as abstract one.
    """

    def __init__(self, parent):
        self._parent = parent
        self._op_list = []

        self._op_dict = {
            (None, None, None): Model.print_pointee,
            (None, None,  ...): Model.increment_pointee,
            (None,  ..., None): 'begin of brace',
            (None,  ...,  ...): Model.increment_pointer,
            (...,  None, None): Model.decrement_pointee,
            (...,  None,  ...): 'end of brace',
            (...,   ..., None): Model.decrement_pointer,
            (...,   ...,  ...): Model.input_to_pointee,
        }

    @property
    def op_list(self):
        return self._op_list

    def __getitem__(self, arg):
        if not isinstance(arg, slice):
            raise TypeError(
                f'BFLikeInterpreter indices must be slices, not {type(arg).__name__}'
            )

        operation = self._op_dict[
            arg.start, arg.stop, arg.step
        ]

        # begin of brace
        if operation == 'begin of brace':
            child = _BFLikeInnerInterpreter(parent=self)
            child.op_list.append(
                Model.conditional_skip
            )
            self._op_list.append(child)

            return child

        # end of brace
        if operation == 'end of brace':
            self._op_list.append(
                Model.unconditional_jump
            )
            return self._parent

        # other
        self._op_list.append(operation)
        return self


class _BFLikeRootInterpreter(_BFLikeInterpreter):
    """
    This class handles whole of program.
    So this must be instantiated just one time, no more no less.
    """

    def __init__(self):
        super().__init__(parent=None)
        self._model = Model()

    def __call__(self):
        for operation in self._op_list:
            operation(self._model)

        self._op_list = []
        return self


class _BFLikeInnerInterpreter(_BFLikeInterpreter):
    """
    This class handles part of program what is located between '[' and ']'.
    """

    def __call__(self, model):
        conditional_skip, *op_list, unconditional_jump = self._op_list

        assert conditional_skip is Model.conditional_skip
        assert unconditional_jump is Model.unconditional_jump

        while True:
            if conditional_skip(model):
                return self._parent

            for operation in op_list:
                operation(model)


Biter = _BFLikeRootInterpreter

仕組み

パッケージ名で察している読者もいると思うが、結局Brainf*ckの焼き直しである。
Wikipedia - Brainfuck

Brainf*ckの各命令が、次のようにスライスに対応している。

機能 (Wikipediaより引用)
> [:...:...] ポインタをインクリメントする。
< [...:...] ポインタをデクリメントする。
+ [::...] ポインタが指す値をインクリメントする。
- [...:] ポインタが指す値をデクリメントする。
. [:] ポインタが指す値を出力に書き出す。
, [...:...:...] 入力から1バイト読み込んで、ポインタが指す先に代入する。
[ [:...] ポインタが指す値が0なら、対応する ] の直後にジャンプする。
] [...::...] ポインタが指す値が0でないなら、対応する [ (の直後)にジャンプする。

...はあまり見慣れないかもしれないが、Ellipsisと呼ばれる立派な組み込み変数である。

>>> ...
Ellipsis

もう少し詳しい仕組み [スライス編]

メソッド__getitem__を適切に用意すれば、スライス操作を受け付けるクラスを作ることができる。

class SliceableClass:
    def __getitem__(self, key):
        print(key)

sliceable = SliceableClass()
sliceable[1:]     # slice(1, None, None)
sliceable[::2]    # slice(None, None, 2)
sliceable[...:]   # slice(Ellipsis, None, None)

スライスオブジェクトには三つの値を指定できるが、省略された場合Noneが渡される。
もちろん明示的に渡しても良い。

>>> src
'spamhamegg'
>>>
>>> src[1:]
'pamhamegg'
>>> src[1:None:None]
'pamhamegg'

もう少し詳しい仕組み [モデル編]

brainbiteの実行モデルの核となるクラスは次の二つである。

Biter
スライスを命令に変換し、キューに押し込んでいく。
命令はcallされたときにまとめて実行される。

実際にはもうちょっと抽象的な作りにしているが、ここでは割愛。3

BFDataModel
実際のデータを保持しているモデルで、Brainf*ckの各命令に対応するメソッドを持つ。

コーディング例

100までの素数を出力するコード。
このコードの大元となるbfコードは、白月のIT入門 - Brainfuck - からお借りした。

from brainbite import Biter

_ = Biter()[:...:...][::...][::...][::...][::...][:...][...:...][::...]
_ = _[::...][::...][::...][::...][::...][::...][::...][:...:...][...:]
_ = _[...::...][:...:...][::...][::...][::...][::...][::...][::...][::...]
_ = _[::...][:...][...:...][::...][::...][::...][::...][::...][::...]
_ = _[:...:...][...:][...::...][...:...][::...][::...][:][...:...][:]
_ = _[:...:...][::...][:][...:...][:][:...:...][::...][::...][:][...:...][:]
_ = _[:...:...][::...][::...][:][...:...][:][:...:...][...:][...:][...:]
_ = _[...:][...:][...:][:][:][...:...][:][:...:...][:][::...][::...][:]
_ = _[...:...][:][:...:...][...:][...:][:][::...][::...][::...][::...]
_ = _[::...][::...][:][...:...][:][:...:...][...:][...:][...:][...:][...:]
_ = _[...:][:][:...:...][::...][::...][::...][:...][...:...][::...][::...]
_ = _[::...][:...:...][...:][...::...][...:...][...:][:][...:...][:]
_ = _[:...:...][...:][...:][...:][...:][...:][...:][...:][:][::...][:]
_ = _[...:...][:][:...:...][...:][:][::...][::...][::...][::...][::...]
_ = _[::...][::...][:][...:...][:][:...:...][...:][...:][...:][...:][...:]
_ = _[...:][:][...:][...:][:][...:...][:][:...:...][::...][::...][:][::...]
_ = _[::...][::...][::...][:][...:...][:][:...:...][...:][...:][...:][:]
_ = _[...:][...:][...:][:][...:...][:][:...:...][::...][::...][::...][:]
_ = _[...:][:][...:...][:][:...:...][::...][:][::...][::...][::...][:]
_ = _[...:...][:][:...:...][...:][...:][:][...:][...:][:][...:...][:]
_ = _[:...:...][::...][::...][:][::...][::...][::...][::...][:][...:...][:]
_ = _[:...:...][...:][...:][...:][:][...:][...:][...:][...:][...:][:]
_ = _[...:...][:][:...:...][::...][::...][::...][::...][::...][:][::...][:]
_ = _[...:...][:][:...:...][:][...:][...:][...:][...:][...:][...:][:]
_ = _[...:...][:][:...:...][::...][::...][::...][::...][::...][::...][:]
_ = _[...:][...:][...:][...:][:][...:...][:][:...:...][::...][::...][::...]
_ = _[::...][:][::...][::...][:][...:...][:][:...:...][...:][:][...:][...:]
_ = _[...:][...:][...:][:][...:...][:][:...:...][::...][::...][::...][::...]
_ = _[::...][:][::...][:][...:...][:][:...:...][:][...:][...:][:]()

実行結果

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

後書き

相も変わらず、誰の役にも立たないコードを書いてしまった。
しかし書いていてなかなか楽しかったし、パッケージ配布の練習にもなったので良かったこととする。

パッケージのmainモジュールやドキュメントが中途半端なのは、飽きたからである。


  1. 少なくともpycodestyle先生は文句を言わない。 

  2. Python3.6以降で動作する。 

  3. 単に私の説明能力が低いだけである。 

5
0
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
5
0