最近の型付きPythonについて

# はじめに
[株式会社みらい翻訳](https://miraitranslate.com/)でカスタマーサクセスを担当している @shhshn です。
日常的に使用するものの業務とは特に関係のないPythonの話をします。

製品開発にPythonを使用する上で、型の明示が重要になっています。
「[Pythonで型を極める](https://qiita.com/papi_tokei/items/bf652696d6b98f23565a)」話もありますが、主な理由は以下2つです。

- 静的型チェックを使いたい
- ついでにPythonを高速化したい

前者は「[Dropboxの取り組み](https://navi.dropbox.jp/our-journey-to-type-checking-4-million-lines-of-python)」が有名ですが、後者をあまり見かけません。
この記事ではNumbaやTorchScriptなど3種類の高速化方法を紹介します。

# Numba
https://numba.pydata.org/

condaで有名なAnaconda主導で開発されているJITコンパイラです。

```py
from numba import jit
import random

@jit(nopython=True)
def monte_carlo_pi(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples
```

例えば、上記のコードは特に型を明示していませんが、型推論によりLLVMでコンパイルされます。その結果、C++で書いたコードと同等の速度を発揮します。C++からの呼び出しも可能です。

## Numbaの強み :thumbsup:

- 既存コードに `@jit` とアノテーションするだけで高速化できます
- `numpy` との互換性が高く、ほぼそのままJIT化できます
- `@jit(nogil=True)` でGILを外すことでCPythonの限界を突破できます

## Numbaの弱み :thinking:

- Python言語仕様の一部しかサポートしていません
    - 最近まで `dict` すらありませんでした
- 型推論でエラーになると原因究明が困難です

# TorchScript

https://pytorch.org/docs/stable/jit.html

FacebookがPyTorchの一部として開発しているコンパイル機能です。

```py
@torch.jit.script
def scripted_fn(x : torch.Tensor):
    for i in range(12):
        x = x + x
    return x

def fn(x):
    x = torch.neg(x)
    import pdb; pdb.set_trace()
    return scripted_fn(x)

traced_fn = torch.jit.trace(fn, (torch.rand(4, 5),))
traced_fn(torch.rand(3, 4))
```

この例では2通りの方法で型付きPythonとして処理しています。
`torch.jit.trace` はNumbaのように関数を丸ごと型推論します。
一方 `@torch.jit.script` はmypy形式で明示された型を使用します。

## TorchScriptの強み :thumbsup:

- Python言語仕様のほとんどをサポートしています
    - comprehensionなど着々と対応されています
- 型推論エラーが分かりやすく親切です

## TorchScriptの弱み :thinking:

- 公式ドキュメント以外の情報源が薄いです
- C++からの呼び出しにはコード全体のアノテーションが必要です
- もはやPythonではない別の言語になります

# 別の言語を使うという選択肢

Pythonより高速性に主眼を置くスクリプト言語も多いです。例えば[Julia](https://julialang.org/)とはよく比較されますが、JuliaはJIT対応に一日の長があります。正直、Pythonの文法・エコシステムのような些細な違いを除けば、他の言語は十分選択肢として考えられます。

また始めから終わりまでC++で書いていくストロングスタイルもあります。保守運用（特に人的資源）に問題がなければ、C++で書くのが一番です。

## 例: PythonからC++へのコンパイル

https://github.com/shhshn/sonish （概念実証コードです）

最近はC++にも型推論が備わっているため、型を明示せずともPythonからC++に変換できます。

```py
def main():
    print("Hello and, again!")
    l = [3, 2]
    l.append(1)
    for x in l:
        print(x)
    d = {4: "HUGE", 5: "SUCCESS"}
    print(d[5])

if __name__ == "__main__":
    main()
```

例えば、上記Pythonを全く同じ出力のC++へ変換することができます。

```cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <iomanip>

int main() {
std::cout << std::setprecision(17) << "Hello and, again!" << std::endl;
auto l = std::vector<int>{3, 2};
l.push_back(1);
for (auto x : l) {
std::cout << std::setprecision(17) << x << std::endl;
}
auto d = std::unordered_map<int, const char *>{{4, "HUGE"}, {5, "SUCCESS"}};
std::cout << std::setprecision(17) << d[5] << std::endl;return 0;
}
```

# おわりに

Pythonでのラピッド・プロトタイピングとC++での製品化のギャップを埋める話をしました。

この記事は[みらい翻訳 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/miraitranslate)に参加しています。
~~次はAWSマスターの @kobarasukimaro が再度登場するようです~~
その前に @reonyanarticle が「CLI作成モジュールclickについて」話してくれるそうです！