【Python】定数とは?基本的な使い方から実践的な応用方法まで解説

はじめに

Pythonを使って開発を進めるとき、定数を扱う方法が気になることがあるかもしれません。

実はPythonには、他のプログラミング言語のように厳密な「定数」を宣言する仕組みはありません。 けれども、実務においては変化させたくない値やルールを扱う場面が多数あります。

このようなケースで、定数をどのように管理しておけば良いのか。 この記事ではPython 定数を理解するために、基本的な考え方から具体的な書き方、そして実務に即した活用方法までを説明します。

短いプログラムなら変数だけでも十分に対応できますが、大規模になれば管理が複雑になるものです。 そのときに役立つテクニックをまとめてみましたので、ぜひ参考にしてみてください。

この記事を読むとわかること

  • Pythonで定数を扱うときの基本的な考え方
  • 大文字変数や typing.final を使った定数の書き方
  • enumdataclass(frozen=True) で管理する方法
  • 実務における定数の具体的な利用シーン
  • 定数ファイルを分割するときのポイント

Python定数の特徴と役割

Pythonには、他の言語のような「変数の再代入を禁止する構文」は存在しません。 しかし、上書きされたくない値、つまりいわゆる定数として扱いたい値は確かにあります。

多くの場合、Pythonのコミュニティでは変数名を大文字にすることで「この値は変更しないでくださいね」という意思を表現します。 たとえば API_KEYMAX_RETRIES のように大文字で書くと、開発者同士で「これは定数扱いだな」と察してもらいやすくなります。

ただし大文字にしただけで、Pythonが再代入を強制的に防いでくれるわけではありません。 もしコードのどこかで値を再代入してしまえば、それが上書きとして普通に受け入れられます。

では、なぜわざわざ定数として書き分けるのでしょうか。 理由は以下のようなものがあります。

  • 重要な値に注目しやすくなる
  • 絶対に変えてはいけない値が見つけやすくなる
  • 他の開発者が意図せず変更するリスクを減らせる
  • コードを読む際に、特定の値をグローバルな設定として把握しやすい

実務においても、こうしたメリットは大きいです。 たとえばサービスにおける課金額や、システムのバージョン情報などを大文字で管理することで、コード全体の可読性と保守性を高める効果があります。

大文字変数で定数を表現する方法

最もシンプルな方法は、変数名を大文字にして定数を表現するやり方です。 以下のように書くと、大文字を使うことで「これは定数ですよ」という意図を示します。

MAX_CONNECTIONS = 5
DEFAULT_TIMEOUT = 30
SERVICE_NAME = "MyService"

この方法は非常にシンプルでわかりやすいですが、再代入を物理的に止める仕組みはありません。 もしチームで開発している場合でも、変数名の慣習を統一しておけば、上書きしてはいけない値であることを互いに共有しやすくなります。

大文字変数は次のような場面で役立ちます。

  1. 数値や文字列が数行程度で済む小規模のプログラム
  2. チーム全員がコード規約を守ることを徹底している場合
  3. フレームワークやライブラリに依存せず、簡潔に定数を扱いたい場合

一方で、規模の大きなプロジェクトになると、各所で定数が増えていくかもしれません。 そのようなときは、大文字変数だけでは管理しきれないケースが出てきます。

いわゆる「物理的な定数化」をするための方法

Pythonには、実は定数を厳密に固定する機能はありません。 しかし、近年はいくつかの手段が提案されています。 たとえば typing モジュールの final を使った方法です。

typing.final を使うと、「この変数は再代入不可である」という意図を型ヒントで示せます。 コードエディタや静的解析ツールなどを使えば、定数として扱うことを警告によって表現してくれる可能性があります。 以下のセクションで詳しく見ていきましょう。

typing.final を使った定数

Pythonの型ヒントでは、final という注釈を用いることで、再代入をしないことを表明できます。 たとえば次のように書きます。

from typing import final

@final
class Constants:
    API_ENDPOINT = "https://example.com"
    MAX_ATTEMPTS = 3

または単純に変数に対して Final を使う方法もあります。

from typing import Final

API_ENDPOINT: Final[str] = "https://example.com"
MAX_ATTEMPTS: Final[int] = 3

上記のように記述すると、開発ツールや静的解析ツールが「この変数は定数です」という扱いをしやすくなります。 ただし先ほども述べたように、Pythonのランタイムが強制的に再代入をエラーにするわけではありません。

それでも大規模プロジェクトでは、静的解析ツールを導入している場合が多いです。 そのような環境では finalFinal を活用することで、定数としての役割がより明確になり、意図しないバグを早期に発見できるようになります。

実務での活用シーン

実際の開発現場では、定数を管理するシーンは数多くあります。

1. APIのエンドポイント

REST APIを呼び出す際や外部サービスとの連携では、接続先のURLや認証用の文字列などを管理することが多いです。 変更が入ること自体は稀ですが、万一改修が必要になったときに、一箇所を修正すれば対応できるようにするメリットがあります。

2. アプリケーション全体で共有する設定値

タイムアウトの秒数や再試行回数、あるいはログ出力先ディレクトリなど、システム全体の動きを左右する値です。 こうした設定値をハードコーディングしてしまうと、後々変更が面倒になる可能性があります。

3. サービス固有のルールや定義

料金プランや割引率、課金サイクルなど、ビジネスロジックに直結する数値や文字列は頻繁に登場します。 これらを散在させず、ひとつのファイルやクラスに定義しておけば、修正ミスを防ぐ効果があります。

固定化したい値をどこにまとめるかで、後々の保守性が大きく変わることがあります。 大規模開発では特に、管理方針や命名規則を統一しておくことが大事です。

enumによる定数管理

定数の集合を管理したい場合は、 列挙型 (enum) の利用も検討してみると便利です。 Pythonには標準ライブラリに enum モジュールがあり、定義されたメンバーは基本的に上書きしないことを想定した実装ができます。

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

上記の例では Color.REDColor.GREEN のように定義することで、決められた色以外を使わせない意図を示せます。 もし誤って値を変えたり、新しい色を追加したい場合は、列挙型のクラス定義自体を変更しなければなりません。

実務でよく使われる場面としては、以下のようなケースがあります。

  • ステータスコードやエラーコードなど、決まった名前と数値を対応づけるとき
  • アプリケーション内で使う区分値が厳格に決まっているとき
  • 仕様書で定義された識別子をコードとして扱うとき

列挙型は値と名前がセットになっていて、かつ追加や変更に対して明示的な修正が必要なので、バグの発生リスクを下げられます。 「どのような種類があるのか?」というのを一覧しやすい点もメリットです。

dataclass(frozen=True)による定数の集約

Pythonのデータクラス dataclass では、frozen=True を設定することで「インスタンスが不変である」という扱いにできます。 これは、生成したインスタンスの属性を書き換えられなくする仕組みです。

具体的には、以下のように書きます。

from dataclasses import dataclass

@dataclass(frozen=True)
class AppSettings:
    api_url: str
    timeout: int
    use_cache: bool

このクラスを使ってインスタンスを生成すると、属性の再代入が禁止されます。

settings = AppSettings(api_url="https://example.com", timeout=30, use_cache=True)
# settings.api_url = "https://new-url.com"  # これはエラーになる

上記のようにすると、コードのどこかで settings.api_url を変更しようとすると例外が発生します。 厳密に定数クラスを作りたいときに便利ですが、クラスとして管理する分、単に大文字変数を置いておくよりも少し複雑になります。

それでも、アプリケーション全体で使う設定値をひとつのオブジェクトにまとめたい場合は有益です。 たとえばサービスごとに異なる設定を用意して、どの環境を使うかで設定インスタンスを切り替える、という運用にも向いています。

定数を管理するファイルを分割するポイント

プロジェクトが大きくなると、定数をすべて constants.py のような単一のファイルに集約することが一般的です。 その方がメンテナンスしやすく、関連する設定を探し回る手間が省けます。

しかし、一箇所に大量の定数を詰め込みすぎると、読みづらくなることがあります。 たとえば下記のように分類すると、後から見直すときに探しやすいです。

  • constants_api.py : APIや外部サービスに関連する定数
  • constants_db.py : データベース接続やクエリに関連する定数
  • constants_ui.py : UI上のメッセージや画面に関連する定数

このように、役割ごとに分割しておくと、定数が衝突するリスクも下がります。 また、後から追加するときに、既存のファイルに追記すれば良いか、新たにファイルを作るべきかを判断しやすくなります。

定数を分割しすぎると管理が煩雑になり、逆に1つにまとめすぎると見つけにくくなります。
どちらに寄り過ぎない中庸が大事です。

コード例:API関連の定数をモジュールにまとめる

ここでは簡単な例として、API関連の定数をまとめるモジュールを作る方法を示します。

# constants_api.py
from typing import Final

BASE_URL: Final[str] = "https://api.example.com"
ENDPOINT_USERS: Final[str] = "/users"
ENDPOINT_ITEMS: Final[str] = "/items"

TIMEOUT_SECONDS: Final[int] = 10
MAX_RETRIES: Final[int] = 3
# main.py
import requests
from constants_api import BASE_URL, ENDPOINT_USERS, TIMEOUT_SECONDS, MAX_RETRIES

def fetch_users():
    for attempt in range(MAX_RETRIES):
        try:
            response = requests.get(
                BASE_URL + ENDPOINT_USERS,
                timeout=TIMEOUT_SECONDS
            )
            # 何かの処理を入れる
            return response.json()
        except requests.exceptions.RequestException:
            if attempt == MAX_RETRIES - 1:
                raise
            # リトライロジックを記述

上記のように定数を管理ファイルへ切り出しておけば、API呼び出し側のコードは読みやすくなります。 BASE_URLENDPOINT_USERS がどのような値かを探すときも、ひとつのモジュールを見れば済むので、後から見直す際に便利です。

環境変数と併用する例

実務では、環境によって異なるURLやキーを使うことが多いため、定数と環境変数を組み合わせる方法もよく見かけます。 たとえば開発環境と本番環境で異なるAPIキーを使用する場合などです。

import os

API_KEY = os.getenv("API_KEY_PRODUCTION", "dummy-key")

この場合、コード上は定数ライクに扱いますが、実際の値は環境変数で書き換えができる可能性があります。 そのため、定数としてハードコーディングしたくない値については、こうした仕組みを使うことがよくあります。

ただし何でもかんでも環境変数にすると混乱が生じるので、よく使う値は定数としてまとめつつ、どうしても環境ごとに変えたい値だけを環境変数にするなど、使い分けが肝心です。

テストとの関連

定数をテストするときは、「値が変更されたときに何が起こるか」を考慮することがポイントです。 たとえばテストを実行するたびに固定した時刻や固定したレスポンスを使いたい場合、定数にしておくと便利な場合があります。

実際の運用では定数を変更するケースは少ないですが、定数の変更が即座にアプリケーションのロジックを左右する場合もあります。 そのため、定数の値がコード全体でどこで使われているのかをテストコードの中で明確に示しておけば、変更時の影響範囲を把握しやすいです。

パフォーマンスへの影響はある?

初心者の方が意外と気にするかもしれませんが、定数かどうかがPythonのパフォーマンスに大きな差を与えることはほとんどありません。 大文字変数であっても通常の変数であっても、取り扱いは基本的に同じです。

ただし、ライブラリやアプリケーション全体で読み込まれる定数が膨大になり、しかもそれらを頻繁に文字列操作するような処理を組み込むと、多少のパフォーマンス低下はあり得ます。 しかし、多くのケースでは最適化するほどの問題にはならないでしょう。

現場では、パフォーマンスよりも可読性や保守性を優先して定数管理を行うことがほとんどです。 もし特別に高速化が必要な場合は、他の部分でアルゴリズムの改善やキャッシュの導入を検討する方が効果的です。

Python定数を活用するメリット

改めて、Pythonで定数を活用する主なメリットをまとめてみます。

コードの意図が明確になる

大文字や final を使うことで、再代入を想定していない重要な値であるとわかりやすくなります。

管理しやすくなる

定数を集約ファイルにまとめる、またはクラスで管理することで、修正時のリスクや探索時間を削減できます。

バグを防止できる

うっかり再代入してしまうミスを避けられます。 特に静的解析ツールと組み合わせれば、早期に誤りを検知できます。

可読性の向上

「これは変わらない値だな」という予測がつくため、コードレビューや保守の際に理解しやすくなります。

上記のように、定数管理を正しく行うだけで、プロジェクト全体の保守性がぐっと上がります。 実務では細かい数値や文字列が増えがちなので、一度整備しておくと後々の作業が楽になります。

まとめ

Python 定数に関しては、「絶対に上書きされない仕組み」はありませんが、実務では大文字変数や typing.finalenumdataclass(frozen=True) などの方法を使うことで、擬似的に定数を表現できます。

プロジェクトが小規模であれば、大文字変数を使うだけでも十分なケースがあります。 一方で、環境ごとの切り替えや変更を見越して設計をするなら、ファイル分割やクラスによる管理、さらには静的解析ツールと組み合わせた運用が重要になるでしょう。

定数を正しく設計しておくことで、将来的にサービスの仕様が変わったり拡張したりするときにも、修正ポイントが明確になります。 ぜひ、自分の開発環境やプロジェクト規模に応じて、適切な定数管理の方法を検討してみてください。

Pythonをマスターしよう

この記事で学んだPythonの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。