Python
melangの各noteで他言語と比較した、Pythonの設計をまとめたページ。ここに書かれている内容は、各noteの比較表から自動で集めたものです。
型
値は型を持つが、検査は主に実行時。
age = 20age = 20
age = "Alice" # 再代入で型が変わる
age + 10 # 実行して初めて TypeError になる変数自体には型がなく、値の側に型が紐づく。型の不整合は、その値を実際に使った時点で初めて顕在化する。
型注釈の位置
postfix構文の型ヒントを後付けできるが、実行時には無視される。
age: int = 20age: int = 20
name = "Alice" # 型ヒントなしでも書ける(動的型付け)
def greet(name: str) -> str: # 引数・戻り値ともpostfixで明示できる
return f"Hello, {name}"変数・引数・戻り値のすべてに`: Type`というpostfixの型ヒントを書けるが、これはあくまで静的解析ツール(mypy等)のためのアノテーションで、実行時の型チェックには影響しない。「型注釈の構文を持つこと」と「静的型付けであること」が別問題であることを示す例。
型変換
型名を関数のように呼んで変換し、失敗は例外で表す。
n = int(s)n = int("42")
f = float(n)
s = str(n)
int("abc") # ValueError
int(3.9) # 3(小数部は切り捨て)
try:
n = int(text)
except ValueError:
n = None`int(x)`, `float(x)`, `str(x)`のように型名で変換する。失敗は`ValueError`で表され、`try`/`except`で処理する。整数は任意精度なので数値の範囲外による失敗は起きないが、失敗可能性は関数の型からは分からない。
暗黙の型変換
数値の型階層内では暗黙変換されるが、他の型とは変換されない。
x = 10x = 10
y = 1.5
x + y # 3.5(intがfloatへ暗黙変換されて演算される)
x + "1" # TypeError(数値と文字列の暗黙変換はない)数値の型階層(int -> float -> complex)内では暗黙変換が行われるが、数値と文字列のような異なる種類の型の間では暗黙変換は起きず、実行時にエラーになる。
Enum / Sum Type
enum.Enumはpayloadを持たず、網羅性チェックも基本ない。
class Status(Enum):
PENDING = "pending"from enum import Enum
class Status(Enum):
PENDING = "pending"
RUNNING = "running"
match status:
case Status.PENDING:
...
case Status.RUNNING:
...
# 網羅チェックはmypy等の型チェッカに頼る必要がある標準ライブラリの`enum.Enum`はpayloadを持たず、`match`文自体に網羅性チェックは基本的にない。型チェッカ(`mypy`等)で`assert_never`などを使えば擬似的に検証できる。
値の不変性
list・dict・オブジェクトは変更できる。不変な型は個別に用意されている。
@dataclass(frozen=True) / tuple / frozensetfrom dataclasses import dataclass, replace
@dataclass(frozen=True)
class User:
name: str
tags: tuple = ()
a = User("alice")
# a.name = "bob" # FrozenInstanceError
b = replace(a, name="bob") # 新しい値を生成
tags = ["a"]
tags.append("admin") # OK: listは変更できる
NAME = "x"; NAME = "y" # OK: 定数を宣言する仕組みはない`list`・`dict`・`set`・通常のクラスのインスタンスは変更でき、それが一般的な使い方である。`tuple`・`frozenset`・`str`・`frozen=True`のdataclassは不変だが、`frozen`は浅く、中身が`list`なら変更できる。定数を宣言する仕組みは言語にない。
名前付き引数
位置引数とキーワード引数を併用でき、`*`でキーワード専用にもできる。
def foo(width: int, height: int = 0)def foo(width: int, height: int = 0): ...
foo(width=10, height=20) # キーワード引数
foo(10, height=20) # 位置引数と併用可
def bar(*, width: int, height: int): ... # * より後ろはキーワード専用
bar(width=10, height=20)
# bar(10, 20) # NG: キーワード専用引数は位置渡しできない位置引数とキーワード引数を併用でき、`*`を使うと以降の引数をキーワード専用にできる。デフォルト引数と組み合わせて、オプションが多いAPIでもよく使われる。
関数の引数
すべての引数がオブジェクトへの参照。変更可能な型は共有される。
def f(n, items):def f(n, items):
n = 1 # 呼び出し側には影響しない(再束縛)
items.append(1) # 呼び出し側から見える(同じlist)
xs = []
f(0, xs)
print(xs) # [1]「オブジェクト参照渡し」と呼ばれ、値渡しとも参照渡しとも言い切れない。同じ引数でも、`int`や`str`のような不変な型は影響せず、`list`や`dict`のような可変な型は影響する。可変か不変かを知らないと、呼び出しの結果を予測できない。
if 文/式
`if`はstatement。値の分岐は条件式`a if cond else b`で書き、この形では`else`が必須。
if cond: ... else: ... / a if cond else bx = 1 if cond else 2 # 条件式はelseが必須
if cond:
process()`if`はstatementで、値を返す分岐は条件式(`a if cond else b`)として別に用意されている。この形では`else`を省略できず、「値の分岐は`else`が必須」という点はmelangと同じ考え方になる。ただし、手続きの`else`は自由に使える。
I/O の明示
型ヒントは任意で、I/Oを示す標準の仕組みもない。
def find_user(id: UserId) -> User: ...def find_user(id: UserId) -> User:
return db.query(id)
def get_user(id: UserId) -> User:
return find_user(id) # 呼べる。型チェッカーも止めない`async def`は非同期関数を示すが、型ヒントと同じく強制力がなく、実行時にもI/Oの有無を判別する仕組みはない。
言語名
イギリスのコメディ番組名に由来し、動物のパイソンとは無関係。
開発者Guido van RossumがMonty Python's Flying Circus(イギリスのコメディ番組)のファンだったことに由来し、ヘビの意味ではない。ただし現在ではヘビをモチーフにしたロゴ・グッズ・コミュニティ文化が定着しており、由来と現在のイメージが乖離した例。