型アノテーションと
mypy ってなに?

この文章の目的は、型を意識していただくことです。 そのために、型アノテーションをご紹介させていただきます。 なぜご紹介するかというと、この後 if 文, for 文, try 文の話をするときに、 結局、全部型の話に落ち着いてしまうからです。

if 文では bool 型, for 文では Iterator 型と Iterable 型, try 文では Optional 型について見て行きます。

変数や属性に int か str などのどの型が入っているかなんて、 そんなのどう考えたって自明じゃんという話です。 下の型アノテーションのコードを見ても、 なにが嬉しいのかさっぱりです。

# 変数 i には int が代入されるという型アノテーション
i: int
i = 0

# 変数 s には str が代入されるという型アノテーション
s: str
s = 'Hello, world!'

ただ、このさき全編に渡って、そういった話が薄っすらと薄っすらと続くので、 これをボカし続けるのは、逆に理解する上で厳しいなと感じました。

そこで貴重なお時間をいただき、 型アノテーションを見ていきたいと思います。

ここで型を明示するコードの書き方や背景をご紹介させていただきますが、 ぜひ使いましょうという話でもなければ、 こうやると上手くいくよという話でもなく、 ただ、型アノテーション、こんなのがあるんだなくらいに、 流し読みしていただければと思います。

# 1. クラスと型

class を使ったユーザ定義クラスは、クラス。 int, str などの組み込み型は、型と呼ばれる傾向にある気がします。 この記事では基本的にユーザ定義クラスも型と呼称していきます。

Python 2 では組み込み型を type 型、 ユーザ定義クラスを class クラス と呼び区別していました。 Python 3 では完全に統合されました。

>>> # Python 2
>>> C
<class __main__.C at 0x10ad70a78>
>>> int
<type 'int'>
>>> 
>>> # Python 3
>>> C
<class '__main__.C'>
>>> int
<class 'int'>
>>> 

# 2. 型アノテーションってなに?

答え: 変数や属性に代入される型をコードに書くことです。

少人数で書くときには効果は薄いですが、多人数大規模で書くときは有効らしいです。 とりあえず、詳細は置いておいて雰囲気だけ掴んでもらいたいと思います。

# 普通の Python コード
class Person(object):
    def __init__(self, name, age, friends):
        self.name = name
        self.age = age
        self.friends = friends
    
    def set_name(name):
        self.name = name
    
    def get_name():
        return self.name
    
    def add_friend(person):
        self.friends.append(person)

person = Person('岩倉玲音', 14, [])

型アノテーションを使うとこのようになります。

# 型アノテーション
from __future__ import annotations
from typing import List

class Person(object):
    name: str
    age: int
    friends: List[Person]
    
    def __init__(self: Person, name: str, age: int, friends: List[Person]):
        self.name = name
        self.age = age
        self.friends = friends
    
    def set_name(name: str):
        self.name = name
    
    def get_name() -> str:
        return self.name
    
    def add_friend(person: Person):
        self.friends.append(person)

person = Person('岩倉玲音', 14, [])

なんだかごちゃごちゃしてきましたね。 これの何が嬉しいのでしょうか。 まず、個々の書き方をご紹介させていただきます。

# 2.1. 変数へのアノテーション

# 変数: 型名 = オブジェクト
x: int = 0
c: str = 'a'
lst: list = [0, 1, 2]

さてここで問題なのが、 リストの中身については型について明記できないということです。

lst: list[int] = [0, 1, 2]
# TypeError を返されてしまう。

lst[0] のように書くことを添字表記 subscription と言います。 添字表記でリストの中身の型についても明示できるようにするために 標準ライブラリ typing から List を import します。

from typing import List
lst: List[int] = [0, 1, 2]

26.1.4. ジェネリクス - 標準ライブラリ
コンテナ内のオブジェクトについての型情報は 一般的な方法では静的に推論出来ないため、 抽象基底クラスが拡張され、 コンテナの要素に対して期待される型を示すために添字表記をサポートするようになりました。

# 2.2. 引数へのアノテーション

変数へのアノテーションと書式は同じです。

def f(x: int):
    return x**2

# 2.3. 返り値へのアノテーション

矢印を書きます。

def f(x) -> int:
    return x**2

# 2.4. インスタンス変数へのアノテーション

インスタンス変数へのアノテーションは、 クラス変数を宣言する箇所に書きます。

from typing import List

class Person(object):
    name: str  # <- インスタンスの属性の型はここで宣言
    age: int
    friends: List['Person']  # <- なんで文字列?
    
    def __init__(self, name, age, friends):
        self.name = name
        self.age = age
        self.friends = friends

person = Person('岩倉玲音', 14, [])

Person を文字列にしないと NameError で弾かれます。 なぜなら変数 Person が定義されていないからです。

from typing import List

class Person(object):
    name: str
    age: int
    friends: List[Person]
    
    def __init__(self, name, age, friends):
        self.name = name
        self.age = age
        self.friends = friends

person = Person('岩倉玲音', 14, [])
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in Person
NameError: name 'Person' is not defined
>>> 

from __future__ import annotations を使うことで 文字列でない状態で使用することができます。

Enabling the future behavior in Python 3.7
上記で記された機能は Python 3.7 から下記の特別な import を使うことで有効にすることができます。
The functionality described above can be enabled starting from Python 3.7 using the following special import:

from __future__ import annotations

この機能の参照実装は GitHub から閲覧できます。
A reference implementation of this functionality is available on GitHub.

mypy のマニュアルにチートシートがありました。

# 3. いつ型アノテーションを使うの?

答え: 大規模なものを堅牢に作らないといけないとき

個人で少数の時は使わなくていいかなと思ったりします。 Guido のいる DropBox について書かれています。Guido のブログです。

Dropbox においては 120 万行のコードに型アノテーションをつけました (これは私たちの Python コードのおよそ 20% にあたります)、
At Dropbox we’ve annotated over 1.2 million lines of code (about 20% of our total Python codebase),

これだけたくさん書いたので型アノテーションをつけることが、どれだけの作業になるか知っています。
so we know how much work this can be.

大変でしたが価値はありました: 見返りは素晴らしいものです。 It’s worth it though: the payoff is fantastic.
Dropbox releases PyAnnotate -- auto-generate type annotations for mypy

また PEP 484 では、型アノテーションの温度感について、 以下のように記述しています。

Python は依然として動的型付け言語のままです。 Python の作者たちは(たとえ規約としてであっても) 型ヒントを必須とすることを望んではいません。
PEP 484 -- 型ヒント

Python に移行したそうです。 型アノテーションがあるから Python を選んだわけでは無いと思うのですが、 堅牢なものを作ろうとするときは、1つの要因になるくらいに重要だったりする気配があります。

Pythonを選んだ理由としては以下の通りです。

  • (中略)
  • 動的言語でありながら型定義による事前検査が可能

一休レストラン Python 移行の進捗

# 4. メリットとデメリット

# 4.1. メリット

# 4.1.1. 安全性

主たる要因。型安全

Guido のブログの中にすこしだけ、 導入の背景について触れられているところがあったのでご紹介します。

選択的静的型付けとはなにか? - All Things Pythonic
What is Optional Static Typing? - All Things Pythonic

Python コンパイラは、Python プログラムの中にあるオブジェクトの型について知らない; ランタイム(仮想マシン) だけが知っている (訳注)Python のコードは、まず コンパイラ によって抽象構文木からバイトコードに変換され、 つぎに ランタイム(仮想マシン)によってバイトコードが実行されます。
The Python compiler doesn't know aboutthe types of the objects you pass around in a Python program; only the run-time (the Virtual Machine) does.

このことは Python の表現を豊かにし、そして柔軟にもしますが。 時として、かなり些細な類のバグ(例えばメソッド名の書き間違え)が、 開発者が期待していたよりも後になって見つかるような事態を引き起こすことを意味しています。
This makes Python expressive and flexible, but sometimes means that bugs of a rather trivial kind (like typos in method names) are found later than the developer would have liked.

さきほど挙げた Python の動的型付けの利点を失うことなく、 メソッドの引数や変数などに対して、型を明示するという選択肢があれば それは素敵でしょう。 Without losing the benefits of Python's dynamic typing, it would be nice if you had the option to add type declarations for your method arguments, variables and so on,

そしてコンパイラが与えられた引数や変数の型を知っていれば、 もしあなたが実行不可能なコードを実行した時に、 警告を発してくれるでしょう。
and then the compiler would give you a warning if you did something that wasn't possible given what the compiler knows about those types.

型に関するバグだけは実行する前に検知してくれます。 個人、少人数で書いたり小さいコードを書くときにはあまり効果を発揮しません。 これは、型を書くのは思いのほか面倒だからです。

しかし、大人数で大きなコードを書くときにはとても有効だったりします。 型を書くことで型に関わるエラーだけについては、実行する前に発見することができるからです。

型を明示することは、完全解ではありません。 しかし、それでも実際に動かしてみるまでエラーが出るかわからないという不安を、部分的に改善してくれます。

昔は動的型付け言語を多く見ました。 Perl, PHP, Ruby, Python, JavaScript です。 しかし近年登場してきた言語はどれも静的型付けです。 動的型付けの言語でさえ、型アノテーションを導入しています。 JavaScript, Python, Ruby, PHP でも議論されたりしています。

これはウェブアプリケーションの大型化に伴い、 静的型付け、あるいは型アノテーションが求められるようになったというのが 背景としてあるかなと思います。 また、型推論によって型を明示しなくても、 コンパイラが型を類推してくれたりするので、煩雑さがなくなったというのも 背景としてあるかなと思います。

# 4.1.2. 可読性

副次的な要因。引数に何が返ってくるのかわからない。 ドキュメントを読めという話なんだけど、ドキュメントは書式が統一されているわけでは無い。

例えば、以下は Django のコードなのですが引数 request が何かもわかりませんし、 なにを返しているかもわかりません。 調べればいいという話ではあるのですが、文書化されていなかったりすると、最悪です。

def view(request):
  context = {
    'blog_title' : 'やる夫の日記',
    'content'    : 'ケーキを食べた。'}
  return render_to_response(
    'app_name/template_name.html',
    context
  )

型名が明示されていると 「あー、要求を受けて、応答を返しているんだな」くらいのことはわかります。

def view(request: HttpRequest) -> HttpResponse:
  context = {
    'blog_title' : 'やる夫の日記',
    'content'    : 'ケーキを食べた。'}
  return render_to_response(
    'app_name/template_name.html',
    context
  )

# 4.2. デメリット

書くのが面倒、読みづらくなることもある。 読むのも面倒くさい。

# 4.2.1. 煩雑さ

型を書くのが面倒です。 あまり大きい理由ではないのですが、 純粋に何かロジックを考えている時に int, str とか書いているのが面倒になります。

# 4.2.2. 可読性

読みやすくなる面もあれば、当然読みにくくなる面もあります。

一番最初に提示したコード、とても煩雑なコードになってしまいました。 int, str, list などの組み込み型を組み合わせただけの短いコードです。

ごくごく短いコードであれば、そこに何が代入されるかは明白です。 わかり切っているものを書かれるのは、ひどく冗長に感じさせます。

自分もよくわからないライブラリのコードの関数を使うときは Django のようなあまりよく知らない大規模なアプリケーションを使うときは、 いいかもしれません。

しかし、わかっていたとしても、 型アノテーションを使って何が返ってくるか明示させます。 ある程度、覚えてきたら面倒になったら書かなくなるのですが。 すでにある程度わかってるものを触るときは型が書いてあると、邪魔だったりします。

# 5. 静的型付けと動的型付け

# 5.1. 仕組みの違い

実行してみないと変数や属性に代入されているオブジェクトの型がわからないのが「動的型付け」で、 実行する前から変数や属性に代入されているオブジェクトの型がわかっているのが「静的型付け」です。

静的型付けと動的型付け - Wikipedia
プログラムを実行前に型検査を行うのが静的な型付け、静的型付けであり、 プログラムを実行しながら型検査を行うのが動的な型付け、動的型付けである。

Python は「動的型付け」に分類されます。 しかし、実行しないとわからないとはどういうことでしょうか? 例えば、以下のコードを見れば Person クラスが代入されていることは一目瞭然です。 なぜ Python は、実行してみないとわからないのでしょうか?

person = Person('岩倉玲音')

Python のランタイム(仮想マシン)は、 中身のコードを読むと変数の中に代入されたオブジェクトが、 変数や属性に代入されたオブジェクトを読み込み度に、 そのオブジェクトがどの型に属しているかを調べながら実行していきます。 つまり Python はコードを動かしてみないと、 ランタイム(仮想マシン)は、 変数や属性に、どのクラスのオブジェクトが代入されているか、知らないのです。

静的型付の場合は、ランタイム(仮想マシン)は、型の情報をすでに "知っている" ので、 そういった分類の作業が不要になります。

# 5.2. 実行速度の違い

この型を都度判定するという作業が重いのほか重いらしく、 静的型付け言語の方が、動的型付け言語よりも、処理速度は速いです。

昔、競プロの問題が解けなくて、もどかしさのあまり数年ぶりに C 言語で書き直したら 100 倍速くなりました笑 これは極端な例ですが 10 倍くらいは一般的に速くなるらしいです。

詰まるところ Python は遅い。という訳ですが、 速度よりも可読性重視というところでしょうか。

パフォーマンスについては気にしないで。必要であれば、後から最適化を行うので。
Pythonの設計哲学 - The History of Python.jp

しかし Python で型アノテーションを書けば速くなるのかというと、 残念ながら、そんなことはありません。 型アノテーションは、コメント # と同じで Python は、これを理解できないからです。

バイトコードを見れば理解が速いかもしれません。 コードをバイトコードに変換すると、全く同じコードが出てきす(バイトコードを加筆予定)。 つまり Python のインタープリタは型アノテーションの存在すら知らないのです。

じゃあ、型を書いたら速くなる機能を実装してよ、とも思うのですが、 mypyc というプロジェクトがあるらしいです。 ほとんどの方が使われている Python は CPython と呼ばれる C 言語で書かれた Python です。 それとは別の Python のインタープリタということです。

この記事は、すこしずつ型付けをして Python の高速化をはかっている記事で、とても面白いです。 これは正確には Python ではなく Cython という Python のコードを C に変換してくれる ツールを使って実験をしています。

あまり関係ないですが、この記事が結構面白いです。

# 5.3. 動的型付け言語への批判

Ruby がよく槍玉に挙げられるのをみます。

Web アプリケーションが小さいうちは、 型がなくても読めばだいたいわかりますし、 処理速度も高速さは必要ないですし、 安全面でもとりあえず動かして問題ないなら、 それで OK という感覚でやっていけばいいと思うのですが。

Web アプリケーションが大きくなるにつれて、 多少の煩わしさは許容しても型を明示した方が、 可読性、高速性、安全性の面で受けられる メリットが大きくなったからかなと感じたりもます。

だからと言って Ruby はダメだという話ではないと感じています。 それは単にいま作ってるものがでかくなり過ぎたという話かなと感じたりもします。 何にでも対応できる「銀の弾丸などない」 という話かなと思います。

感覚的には Ruby が使われるようになりだした直後にちらほら見かけて、 Ruby が普通に使われるようになると見かけなくなって、 Ruby で組まれたものが大きくなると、また出現したような感じです。 2008 年ごろは、以下の様な空気感でした。

2008年11月23日 「Ruby は型宣言がないけど、ちゃんとしたシステムに使えるのか」という質問にどう答えるか

型宣言はシステムの信頼性をたいして向上させない、というのが答えです。

型宣言のおかげで発見されるバグ(不具合)が存在するのは事実です。 例えば、a という変数が文字列型を持つと宣言されているのに、そこに整数型の値を格納する処理があれば、それは間違いです。

しかし、現代のプログラミングにおいては、この種類のバグはあまり生じないのです。

2013年03月1日 「変数に型がない」はメリットなのか、それともデメリットなのか。宗教戦争勃発
ここでは Perl や Java、C++ などの例を使い、 変数に型がないことの利点として「どのような型の値でも代入できる」 「記述量がとても短くなる」「変数に型がないと変更に強い」「関数のオーバーロードが不要になる」「複数の型を受け取りたいときに、 インターフェースを実装する必要がない」「C++ のテンプレートのような機能も必要がない」などが挙げられている。

いまは静的型付けが好まれる傾向があります。 これは論理的に正しいわけではなく、いまは、ただそういう傾向にあるということです。

プログラミング言語の盛衰をたどってみれば、 最新の言語設計では静的型付けが好まれるという確かなトレンドがあることに気づくでしょう。
動的型付けの衰退 - ORACLE.COM/JAVAMAGAZINE

同じ動的言語でも Python が槍玉に上がらないのは、 型アノテーションが導入されたということ以上に、 データサイエンスや機械学習等で使われているから表立って言えないのかなと思います。。

# 5.4. なぜ Python は動的型付けを採用したのか?

答え: わからない。

動的言語の批判なんて嘘っぱちや、と上で書いておきながら、 正直動的言語のメリットがわからないというのが正直な感想でした。 はじめてさわったときの感想でした。

型を書かないメリットは、読みやすくなること、書きやすくなること。 型を書かないデメリットは、デメリットは、安全性と高速性を失うこと。 コードサイズが大きくなれば、読みやすさ可読性さえ、失われる。

得るものよりも失うものの方が大きい気がしてならない。 メリットにしても、すこし読みやすくなること、すこし書きやすくなること。

ただ、当時の Guido が ABC の影響を受けていて、 そこまで大きいものを作ることを、あまり念頭に置いていなかったとすると、 動的言語という選択肢がでてくるのかもしれない。

確かに自分一人でひっそりとコードを書いてるときは、 どの変数にどの型がはいってるかなんて覚えてるから。 ロジックそのものに集中したいと思うとき、型の表記が邪魔な時がある。

簡単なアルゴリズムを説明するために書かれた Python のコードを見てると Python のコードはひどく綺麗に見えたりします。

この辺りを丁寧に読み解いていけば何か得られるかもしれない。 でも、きっとどこかにあるはずだから探そう。

# 5.5. 型推論

いまは型推論とかも発展しているから mypyc に期待かな。 型推論機でさえ型がわからないところは、 逆に人間も書いてもらわないとわからないだろうし。

# 6. 強い型付けと弱い型付け

「強い型付け」、「弱い型付け」という言葉があります。 「静的型付け」の方が強そうなので、 じゃあ「動的型付け」は「弱い型付け」なのかというとそういうわけでは無いらしいです。

C 言語は「静的型付け」で「弱い型付け」らしいのですが、 サンプルコードを探しているのですが、見当たらない... orz

なぜ Python は動的言語でありながら強い型付けに分類されるのですか?
Why is Python a dynamic language and also a strongly typed language

よく強い型付けという言葉を、「静的型付け」と「強い型付け」の両方に対して使ってしまっています。 静的型付けというのは、変数宣言で明示された型が変数に代入される言語 -- より一般には、例えば型推論などで、プログラムを実行することなく、 変数がどちらの型を参照しているか判別することができるコンパイラを持つ言語を指しています。 強い型付けというのは、型を混同することを制限する言語を指しています。
People often use the term strongly-typed language to refer to a language that is both statically typed (types are associated with a variable declaration -- or, more generally, the compiler can tell which type a variable refers to, for example through type inference, without executing the program) and strongly-typed (restrictive about how types can be intermingled).

そのため、もし動的型付けと強い型付けを直交する概念だと理解できるなら (訳注: 動的型付けと強い型付けが別のものだと理解できるなら)、 Python は動的型付けでもあり、強い型付けであると言えます。
So, if you look at dynamic typing and strong-typing as orthogonal concepts, Python can be both dynamically and strongly typed.

ただ完全、完璧に強いのかというとそうでもなさそうな感じがします。 Flask の作者 Armin Ronacher の記事です。

# 7. primitive と composite

プログラミング言語において、一般に型は2種類に分けられます。 primitive と composite です。 primitive というのは int, str のような1つしか値を持たない型です。 composite とは tuple, list, class 文で定義されるユーザ定義クラスです。

でも実際には Python は基本的に primitive の存在しない、すべて composite です。 例えば、int も str も次のような具合で属性や要素を参照することができます。

>>> a = 1
>>> a.real  # 実部
1
>>> a.imag  # 虚部
0
>>>

実際 int 型の CPython の実装を見てみると C 言語の構造体で 属性を持った composite として存在しています。 あまり、まだこの中身がどうなっているかは知りません。

struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

ここまでの話も基本的には composite に対する話で、 あるいはごくごく表面的な話をしていたと考えていただけると幸いです。

つまり、いままでオブジェクトとは、クラスとは見たいな話をしながら、 実際には 0, 1 のビット列で表現されたより低レベルな層の話は、 いままで一切してこなかったということです。

実は Flask の作者である Armin Ronacher の記事で Python は primitive だけだよ。 という箇所を見つけ若干自信がないのですが、 おそらく Julia の説明とあわせて、上記のような理解でいいかなと考えています。 おそらく別の視点で Armin Ronacher は primitive しかないと言っているのだと思います、信じています。

しかし、Python にはないものが1つあります。それは複合型です。 Python の型はすべて基本型 (primitive) です。 つまり、基本的には一度に1つの型のみが作用することを意味します。 基本型の反対は複合型 (composite) です。時々、別のコンテキストで Python の複合型を見かけるでしょう。
Revenge of the Types: 型の復 - Qiita

Julia の公式ドキュメントでもそういう説明なのでいいかなと感じています。 Python の公式ドキュメントも漁って見たのですが、 primitive と composite に関する記述は見当たりませんでした。

Primitive Types - Julia
primitive 型は、実体のある型で、たんに古くからあるビット列からなるデータを持ったものです。 primitive 型の典型的な例としては、整数や浮動小数点といったものがあります。
A primitive type is a concrete type whose data consists of plain old bits. Classic examples of primitive types are integers and floating-point values.

Composite Types - Julia
composite 型は、様々な言語でレコード、構造、またはオブジェクトと呼ばれています。 composite 型は名前のついた属性の集まりで、 その属性に束縛されたインスタンスはまとめて、1つの値として取り扱われます。 多くの言語では composite 型は、唯一ユーザが定義できる型で、 Julia においても同様に現在のところ最も一般的に使われるユーザ定義型です。
Composite types are called records, structs, or objects in various languages. A composite type is a collection of named fields, an instance of which can be treated as a single value. In many languages, composite types are the only kind of user-definable type, and they are by far the most commonly used user-defined type in Julia as well.

ちょっと文章が怪しいですが Wikipedia の記事がわかりやすいです。

プリミティブ型 - Wikipedia
理論計算機科学的に代数的データ型によって考えれば 「そのデータ型の定義の中に部分として他の[1]型を含まないような型」が プリミティブ型であるが...

より低レベルな型、つまり primitive の話については、 書籍「コーディングを支える技術」の 「第8章 型」が面白いかもしれません。

もう少し説明がないと厳しいかもしれませんが、 浮動小数点数の表現のされ方とかの雰囲気とかもわかります。

# 8. 契約による設計

「型アノテーション」で検査する方法とそれ以上に さらに強い制約をかそうという考えがありました。 「契約による設計」と呼ばれるものです。

流行らなかったらしいですが、たまに名著として紹介されます。 覚えておいても悪くない単語かなと思います。 流行らなかったのは面倒だったからかなと思います。

ちょうどこの記事を書くにあたりかなり参考にさせていただいた Tetsuya Morimoto さんという方が、 「契約による設計」の現在の立ち位置みたいなところ について説明されていたので、ご紹介します。

21 契約による設計 - forest book
この節はどうなんでしょうね? あらかじめ要件や仕様を厳密にしやすい業務系では こういった設計が定着しているのでしょうか? 私は経験がなくて実践的にこの手法が使われているのかどうか知りません。

Web 業界ではまだまだここまで厳格な設計手法は 定着していないように私は思います。 また別の節にある表明プログラミングもそうですが、 厳密さを保証するためのオーバーヘッド (実行効率や保守性など) も かかることから敬遠されがちなところもあると思います。

僕も、ここで紹介されている書籍「達人プログラマ」で、 「契約による設計」という言葉を知りました。

ざっくり言えば引数と返り値を型だけを検査するのではなく、 もっと色んな条件を検査しようという設計あるいは考えです。

契約による設計に基づいたコードでは、 事前条件を満たした引数をあるオブジェクトのメソッドに与えた場合、 オブジェクトとメソッドの返り値は事後条件を満たし、 かつオブジェクトはメソッドの実行前後で不変条件を満たさなければなりません。

Python の疑似コードに 不変条件、事前条件、事後条件をコメントで書き込んで見ます。

# クラス不変条件
#   メソッドを実行前後でソートが崩れない。
#   実行中は崩れててもいい
class SortedList:

    def __init__(self):
        self._list:List = []

    def add(self, new_element):
        # 事前条件
        #     追加する要素はリストの中に無い

        i = 0
        while self._list[i] > new_element:
            i = i + 1
            
        self._list.append(element)
        # 事後条件
        #     追加した要素がリストの中にある

やろうとすると結構、面倒になりますね。 普及しなかったのも、なんかわかる気がします。 この辺の機能を言語的に取り入れるか、いれないかという匙加減が、 また言語設計の面白いところなのかもしれません。

「事前条件」は、いまでも引数チェックはしてるから、 そこまで必要ではない気はします。 「事後条件」と「クラス不変条件」は、良い方法が思いつかないですね。 しかもそれを満たしているかどうかを実行時に確認するのは、 かなりオーバーヘッドな気がします。

「事後条件」と「クラス不変条件」については、 とりあえず開発時には assert 文や __debug__ を使って表現して、 運用時には使わないのがベターな方法な気がします。

それでも Python の型アノテーションと同じように、 ツールやあるいは言語レベルで契約の書式を統一してしまうことは、すごく魅力的には感じます。

型検査と同じように静的に契約を検査できれば、いいのかもしれませんが。 難しそうですね。 「事後条件」と「クラス不変条件」は、定義するところが、使いどころが なかなか思いつきません。

Tetsuya Morimoto さんの記事の中でこんなのもあるけど.. みたいな感じで紹介されている 契約による設計の紹介 という記事は、「型アノテーション」を飛び越えて、いっきに「契約による設計」の話に進んでいます。

言い換えれば「型検査」で済む話を「契約による設計」で対応しようとしているので、 ちょっとありがたみが薄れてしまっているかもしれません。 個人的には、「契約による設計は、静的型検査以上のものを検査する」であるという方が 理解しやすいかなと思います。

とはいえ説明しようと思ったんですけど、大変なんですよね... 書籍「達人プログラマ」にサンプルも含めて乗っているので、 気になる方は、オススメです。 アノテーションとしての契約の記述のされ方が面白いです。 Java は iContract というツールを使っています。 ただサンプルコードは Java とあと Eiffel というあまり使われていない言語です。 iContract も使われていないツールだと訳注がありました。

上の記事の中では、この後、例外の話をしますが、なぜ例外の話をするために、 型アノテーションの話をしたのか、というのがつながっていきます。 以上「型アノテーション」による型検査のさらに上を求めてという趣旨で、 「契約による設計」をご紹介させていただきました。

# 9. 型アノテーションのリンク集

上は Tetsuya Morimoto さんの動画です。 いい動画なのに両方とも PV が 1,000 もない。 見ないとこの先わからないという動画ではないです。 参考としてご紹介させていただきました。 ざっくり聞き流すと理解が深まるかなと思います。 2番目の動画はまだ自分も見ていません。 3番目の動画いつか見たいです。見ても理解できないと思いますが笑 PyCon というのは年に一回ある Python 祭りですね。 ワイも死ぬまでに一回は生きたい、聖地巡礼感 笑







型アノテーションの経緯が書かれている。 ほとんど Tetsuya Morimoto さんという方が翻訳されたか、書かれた資料です。 ありがたや、ありがたや。

# 10. おわりに

このあと if 文では bool 型, for 文では Iterator 型と Iterable 型, try 文では Optional 型について見て行きます。

Last Updated: 11/27/2019, 10:40:32 PM