演算子をオーバーロードしてメソッド呼び出しを簡略化する

この記事は [Pythonのコードを短く簡潔に書くテクニック Advent Calendar 2017](https://qiita.com/advent-calendar/2017/python-short-code) の24日目です。

# はじめに

Pythonでは自作クラスの演算子をオーバーロードできます。

演算子を使うことで通常のメソッド呼び出しより短く書けるだけでなく、直感的でわかりやすいコードになる場合もあります。

```py3:メソッド呼び出しで書いた場合
Command('ls').pipe('cat').redirect_to('out.txt')
```
↓

```py3:演算子を使った場合
Command('ls') | 'cat' > 'out.txt'
```


# 標準モジュールで演算子を使っている例

先ずは標準モジュールで演算子を使っている例を紹介します。

## pathlib.Pathの"/"

[pathlib.Path]:https://docs.python.jp/3/library/pathlib.html
[joinpath()]:https://docs.python.jp/3/library/pathlib.html#pathlib.PurePath.joinpath
[演算子"/"]:https://docs.python.jp/3/library/pathlib.html#operators

Python3.4から追加でされた [pathlib.Path] では [joinpath()] で２つのパスを連結できますが、[演算子"/"] でも同じことができます。

```pycon:joinpath()
>>> from pathlib import Path
>>> Path('/aaa/bbb').joinpath('ccc/ddd')
PosixPath('/aaa/bbb/ccc/ddd')
```

```pycon:演算子"/"
>>> from pathlib import Path
>>> Path('/aaa/bbb') / 'ccc/ddd'
PosixPath('/aaa/bbb/ccc/ddd')
```

## strの"%"

[str.format()]:https://docs.python.jp/3/library/stdtypes.html#str.format
[printf形式の文字列書式化]:https://docs.python.jp/3/library/stdtypes.html#printf-style-string-formatting

文字列の書式化には現在は [str.format()] がよく使われていますが、それ以前は演算子"%"を使った[printf形式の文字列書式化]が使われていました。

これを文字列専用の特殊な構文だと思っている人がいるかもしれませんが、実は演算子"%"を使っているだけなんです。

```pycon
>>> 'Hello %s' % ('Python',)
'Hello Python'
```
```pycon
>>> 'Hello %s'.__mod__(('Python',))
'Hello Python'
```

# 自作クラスで演算子を使えるようにするには

自作クラスに特殊メソッドを実装することで演算子が使えるようになります。

例えば二項算術演算子を使えるようにするには以下の特殊メソッドを実装します。

|演算子|特殊メソッド|備考|
|:---:|---|---|
|`+`|`__add__`|
|`-`|`__sub__`|
|`*`|`__mul__`|
|`@`|`__matmul__`|バージョン 3.5 で追加.|
|`/`|`__truediv__`|[※2.7の仕様はこちらを参照](https://docs.python.jp/2.7/reference/datamodel.html#object.__truediv__)|
|`//`|`__floordiv__`|
|`%`|`__mod__`|
|`**`|`__pow__`|
|`<<`|`__lshift__`|
|`>>`|`__rshift__`|
|`&`|`__and__`|
|`^`|`__xor__`|
|`｜`|`__or__`|

# 実例：subprocessでパイプとリダイレクトを実装する

subprocessをラップしてシェルスクリプトのようにパイプ"|"やリダイレクト">"を使えるようにしてみます。

先ずはsubprocessをラップしたクラスを普通に作成します。
ここではまだ演算子は実装しません。

```py3
import shlex
import subprocess
from subprocess import PIPE


class Command:

    def __init__(self, command, stdin=PIPE):
        self.p = subprocess.Popen(shlex.split(command),
                                  shell=True,
                                  stdin=stdin,
                                  stdout=PIPE)

    def pipe(self, command):
        next_command = Command(command, self.p.stdout)
        return next_command

    def redirect_to(self, file):
        with open(file, 'w') as f:
            for line in self.p.stdout:
                f.write(line.decode())
```

この状態ではパイプやリダイレクトは以下のようなメソッド呼び出しをしなければなりません。

```py3
Command('ls').pipe('cat').redirect_to('out.txt')
```

次に以下のメソッドをCommandクラスに追加します。

```py3
    def __or__(self, command):
        return self.pipe(command)

    def __gt__(self, file):
        return self.redirect_to(file)
```

以下のように演算子でパイプとリダイレクトが使えるようになり、シェルスクリプトのような記述ができるようになります。

```py3
Command('ls') | 'cat' > 'out.txt'
```

# 参考

- Python3ドキュメント
    - 言語リファレンス
        - 3. データモデル
            - [3.3.7. 数値型をエミュレーションする](https://docs.python.jp/3/reference/datamodel.html#emulating-numeric-types)
    - ライブラリーリファレンス
        - [10.3. operator — 関数形式の標準演算子](https://docs.python.jp/3/library/operator.html)

- Qiita
    - [Python3.4以降ならos.pathはさっさと捨ててpathlibを使うべき](https://qiita.com/tag1216/items/29bf3b9c3c4b563ff0fe)