🐱 うさねこ教室 Python と可観測性の教室

静的型付けの世界

🐱 この章の目次

型はコンパイル時に確定する

Python の型ヒントはあくまでオプションであり、mypy という外部ツールで検査します。 JVM 言語では 静的型付け が必須であり、型の不整合はコンパイル時にエラーになります。 つまり、実行する前に型の問題がすべて検出されます。

# Python: 型ヒントは任意、実行時には無視される
def greet(name: str) -> str:
    return f"Hello, {name}"

greet(123)  # mypy はエラーにするが、実行はできてしまう
// Java: 型が合わないとコンパイルエラー
public String greet(String name) {
    return "Hello, " + name;
}

greet(123);  // コンパイルエラー: int を String に変換できない
// Kotlin: 型推論でより簡潔に書ける
fun greet(name: String): String = "Hello, $name"

ジェネリクスと型消去

ジェネリクス は Python の list[str] に相当する仕組みです。 ただし JVM では 型消去(type erasure) により、実行時にはジェネリクスの型情報が失われます。 これは Python の List[str] が実行時にただの list であるのと似た制約です。

// Kotlin: コンパイル時には型安全
val names: List<String> = listOf("Alice", "Bob")
// names.add(123)  // コンパイルエラー

Null 安全

Python では Optional[str] で None の可能性を示しますが、実行時チェックは開発者の責任です。 Kotlin は Null 安全 を型システムに組み込み、String?(nullable)と String(non-null)を区別します。 null 参照によるクラッシュをコンパイル時に防げるのは、Python にはない大きな利点です。

// Kotlin: null を許容する型と許容しない型
fun length(s: String): Int = s.length      // null を渡せない
fun safeLength(s: String?): Int = s?.length ?: 0  // null 安全な呼び出し
# Python: Optional はあくまでヒント
from typing import Optional

def safe_length(s: Optional[str]) -> int:
    return len(s) if s is not None else 0

Interface と Protocol

Python の Protocol は構造的部分型(structural subtyping)を提供します。 JVM の interface は名前的部分型(nominal subtyping)であり、クラスが明示的に実装を宣言する必要があります。 この違いにより、JVM では依存関係がコンパイル時に明確になります。

// Kotlin: interface を明示的に実装
interface Greeter {
    fun greet(name: String): String
}

class HelloGreeter : Greeter {
    override fun greet(name: String) = "Hello, $name"
}
# Python: Protocol は構造的に一致すれば OK
from typing import Protocol

class Greeter(Protocol):
    def greet(self, name: str) -> str: ...

class HelloGreeter:  # 明示的な継承不要
    def greet(self, name: str) -> str:
        return f"Hello, {name}"