閉じる

Pythonのプライベート変数:シングル/ダブルアンダースコアの違い

Pythonでは他言語のような厳密なアクセス修飾子はありませんが、アンダースコアを使うことで「この変数は内部用」という意図を表現します。

本記事ではシングルアンダースコア(_var)とダブルアンダースコア(__var)の違いを、名前マングリングの仕組みや継承時の挙動も含めて詳しく解説し、実務で迷わない使い分け指針をまとめます。

Pythonのプライベート変数とは

Pythonにおけるプライベート変数の考え方

Pythonには、JavaやC++のような完全に外部アクセスを禁止するprivate修飾子はありません。

代わりに、「この属性は内部実装であり、外部から直接触らないでほしい」という合図を命名で表現します。

特にクラス内の属性やメソッドに対して、次のようなルールが慣習として使われています。

  • 通常名: value など
    → 公開インターフェース(外部から自由に使ってよい)
  • シングルアンダースコア: _value
    → 非公開「扱い」にしたい内部実装(外部から触らない前提)
  • ダブルアンダースコア: __value
    → 名前マングリングによりクラス外からアクセスしにくくする特別な内部属性

ここで重要なのは、Pythonでは「隠す」よりも「合意する」ことが重視される点です。

アンダースコアは「アクセスできないようにする」機能というより、「原則として触らないでほしい」という意図を伝えるための記号です。

他言語のprivateとの違い

JavaやC++などの言語では、private修飾子を付けると、クラス外からアクセスした瞬間にコンパイルエラーになります。

これは言語仕様レベルでの強制です。

一方、Pythonでは次のような違いがあります。

  • コンパイル時にアクセスが禁止されるわけではない
    → どの属性にも、頑張ればアクセスできます。
  • 「できること」と「してよいこと」が明確に分けられている
    → 「触れるけれど、触らない約束」という設計思想です。
  • その代わり、アンダースコアやプロパティ(property)を使って安全にカプセル化を表現します。

つまりPythonの「プライベート変数」は、他言語とは異なり完全な遮断ではなく、合意ベースの制約+最低限の技術的抑止と理解するとよいです。

シングルアンダースコア(_var)の意味

シングルアンダースコアが示す「内部実装」の目印

シングルアンダースコアで始まる名前(_value_helper()など)は、「内部用であり、公開APIではない」という意思表示です。

技術的には普通にアクセスできますが、「ここに依存すると壊れやすいですよ」という警告だと理解してください。

以下は簡単な例です。

Python
class User:
    def __init__(self, name: str) -> None:
        # 公開属性(外から利用してよい)
        self.name = name

        # 内部用属性(外部から直接触るのは非推奨)
        self._login_count = 0

    def login(self) -> None:
        """ログイン処理を行う公開メソッド"""
        print(f"{self.name} logged in")
        # 内部的なカウンタの更新
        self._login_count += 1

    def get_login_count(self) -> int:
        """ログイン回数を取得する公開メソッド"""
        return self._login_count


user = User("Alice")

# 技術的にはアクセスできる
print(user._login_count)  # 非推奨なアクセス
実行結果
0

この例では、_login_countは内部的な状態を表すため、外部コードはget_login_count()を通じてアクセスすべきです。

直接_login_countを触ると、将来の実装変更に弱くなってしまいます。

from import時の挙動とシングルアンダースコア

シングルアンダースコアには、モジュールレベルでも「内部用」を示す意味があります。

特に重要なのは、from module import *を使ったときの挙動です。

Python
# mymodule.py

def public_func() -> None:
    print("public_func called")

def _internal_func() -> None:
    print("_internal_func called")
Python
# main.py

from mymodule import *

public_func()      # OK
_internal_func()   # NameError になる
実行結果
public_func called
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    _internal_func()
NameError: name '_internal_func' is not defined

from mymodule import *では、先頭がアンダースコアの名前は自動的に除外されます。

これは、_internal_funcが「モジュール外に公開するつもりがない」ことを示しているためです。

もちろん、明示的にfrom mymodule import _internal_funcと書けばインポートできますが、それは原則として避けるべきやり方です。

コーディング規約(PEP8)とシングルアンダースコア

Pythonの公式スタイルガイドであるPEP 8では、シングルアンダースコアについて次のような役割が示されています。

  • モジュール内部でのみ使う関数・変数
    → 名前の先頭を_にすることで「内部用」を示す
  • クラス内部で外部に公開しないメソッド・属性
    _method_valueのようにする

また、PEP 8では「公開APIと内部実装を名前で区別しなさい」と述べられています。

ライブラリやパッケージを公開する場合、_付きの名前は将来削除や変更をしても後方互換性の保証対象に含めないことが多いため、ユーザ側もそれを前提に使うべきではありません。

ダブルアンダースコア(__var)と名前マングリング

ダブルアンダースコアによる名前マングリングの仕組み

ダブルアンダースコアで始まる属性名(__value)は、名前マングリング(name mangling)と呼ばれる変換の対象になります。

具体的には、クラス内で__valueと書くと、実際には_クラス名__valueという名前に自動変換されます。

次の例で挙動を確認してみます。

Python
class Example:
    def __init__(self) -> None:
        self.__secret = 42   # ダブルアンダースコア

    def show(self) -> None:
        print(self.__secret)


e = Example()
e.show()

# 直接アクセスを試みる
# print(e.__secret)  # AttributeError になる

# 実際の属性名を確認
print(dir(e))
実行結果
42
['_Example__secret', '__class__', '__delattr__', '__dict__', '__dir__', ...]

出力に_Example__secretという名前が含まれていることから、クラス名Exampleが前に付与されていることが分かります。

この変換によって、クラス外から単純にe.__secretと書いてもアクセスできなくなります。

ただし、次のように「本当の名前」を知っていればアクセスは可能です。

Python
print(e._Example__secret)  # 名前マングリング後の本当の名前を指定
実行結果
42

このように、ダブルアンダースコアは完全な秘匿ではなく、「偶発的なアクセスや衝突を防ぐための仕組み」です。

サブクラス継承時のダブルアンダースコアの挙動

名前マングリングの狙いの1つが、サブクラスとの名前衝突を避けることです。

親クラスと子クラスが同じ__nameという属性名を使っても、内部的には別名になります。

Python
class Parent:
    def __init__(self) -> None:
        self.__value = "parent"

    def show_parent(self) -> None:
        print(self.__value)


class Child(Parent):
    def __init__(self) -> None:
        super().__init__()
        self.__value = "child"

    def show_child(self) -> None:
        print(self.__value)


c = Child()
c.show_parent()
c.show_child()

print(dir(c))
実行結果
parent
child
['_Child__value', '_Parent__value', '__class__', '__delattr__', '__dict__', ...]

dir(c)の結果から、_Parent__value_Child__valueという2つの別々の属性が存在していることが分かります。

これにより、子クラスの__valueが親クラスの__valueを上書きしてしまう問題を避けられます。

この特性は次のような場面で有効です。

  • 親クラスで厳密に隠しておきたい内部状態がある
  • サブクラスが同名の属性を使っても親の内部実装に影響を与えたくない

逆に言えば、サブクラスから親クラスの__valueに直接アクセスすることを想定していないデザインに向いています。

ダブルアンダースコアを使うべきケース

ダブルアンダースコアは強力ですが、乱用は推奨されません

次のような場合に絞って使うのがよいです。

  • ライブラリやフレームワークの基底クラスで、 : サブクラスと名前が衝突してほしくない内部属性を定義したいとき
  • クラス設計上「絶対に外部から触るべきでない」内部実装を明示したいとき
  • メソッド名・属性名がサブクラスでうっかり上書きされると危険なとき

一方、次のような場合はシングルアンダースコアで十分です。

  • 通常のアプリケーションコードでの「内部用」属性
  • テストコードからアクセスしたい可能性がある内部状態
  • チームメンバー全員が名前マングリングの挙動に詳しくない場合

ダブルアンダースコアを使うと、デバッグやテスト時に属性へアクセスするのが少し面倒になります。

そのため、「名前衝突を本気で避けたい継承設計」の場面に限定するという方針が実務では多く採用されています。

シングル/ダブルアンダースコアの使い分け

_varと__varの具体的な使い分け指針

実務で迷いやすいのが_var__varの選択です。

シンプルにまとめると、次のような指針が有効です。

  1. まずは「公開API」か「内部用」かを決める
    1. 外部モジュールから自由に利用させたい
      → 普通の名前(アンダースコアなし)
    2. 内部実装であり、公開APIとしては約束しない
      _nameを使う
  2. 継承やライブラリとしての利用を強く意識するなら
    1. サブクラスに影響されたくない重要な内部状態
      __nameを検討する
    2. サブクラスからのアクセスを前提とするなら
      → あえて_nameに留める
  3. テストやデバッグとのバランスを考える
    1. テストコードからアクセスしたいなら
      → シングルアンダースコアにして、チームのルールとして「本番コードからは直接触らない」と決める
    2. 「外部からの直接操作は絶対に避けたい」なら
      → ダブルアンダースコアでマングリングしてしまう

次のように考えると分かりやすいです。

  • _name「原則触らないでね」というラベル
  • __name「サブクラスからも勝手にいじってほしくない」強めのラベル&技術的防壁

Pythonのプライベート変数を安全に扱うベストプラクティス

Pythonで「プライベート変数」を安全かつPythonicに扱うためには、アンダースコアだけでなくプロパティ(property)との組み合わせが非常に有効です。

ベストプラクティス1: 内部状態はシングルアンダースコアで持つ

Python
class Account:
    def __init__(self, balance: int) -> None:
        # 内部状態は _balance に保持
        self._balance = balance

内部実装は_balanceに集約し、外部から直接書き換えさせない前提とします。

ベストプラクティス2: 外部にはプロパティで公開する

Python
class Account:
    def __init__(self, balance: int) -> None:
        self._balance = balance

    @property
    def balance(self) -> int:
        """残高を読み取り専用で公開"""
        return self._balance

    def deposit(self, amount: int) -> None:
        """入金メソッド(公開API)"""
        if amount <= 0:
            raise ValueError("amount must be positive")
        self._balance += amount
Python
acc = Account(1000)
print(acc.balance)   # プロパティ経由で取得
# acc.balance = 500  # AttributeError(書き込み不可)
実行結果
1000
Traceback (most recent call last):
  ...
AttributeError: can't set attribute 'balance'

内部状態への直接アクセスは_balanceに閉じ込め、外部にはbalanceプロパティとdeposit()メソッドだけを公開することで、安全で拡張しやすいAPIになります。

ベストプラクティス3: 継承を伴うフレームワークでは__varを慎重に使う

Python
class FrameworkBase:
    def __init__(self) -> None:
        # サブクラスに上書きされたくない内部ID
        self.__internal_id = 0

    def _next_id(self) -> int:
        self.__internal_id += 1
        return self.__internal_id
Python
class Plugin(FrameworkBase):
    def __init__(self) -> None:
        super().__init__()
        # プラグイン側も __internal_id を使いたくなっても、
        # 実際には _Plugin__internal_id となり、フレームワーク側と衝突しない
        self.__internal_id = 100
Python
p = Plugin()
print(p._FrameworkBase__internal_id)  # フレームワーク側の内部ID
print(p._Plugin__internal_id)         # プラグイン側の内部ID
実行結果
0
100

このように、フレームワーク側の内部実装をサブクラスから守りたいときには、ダブルアンダースコアが有効です。

ただし、日常的なアプリケーションコードでは、まずシングルアンダースコア+プロパティで十分なことがほとんどです。

まとめ

Pythonでは、プライベート変数を言語仕様で完全に隠すのではなく、アンダースコアによる命名規約と名前マングリングで「触れるけれど触らない約束」を実現しています。

通常はシングルアンダースコア_varで内部実装を示し、必要に応じてプロパティで安全な公開APIを用意する設計が有効です。

継承設計で親クラスの内部状態を厳密に守りたい場合に限り、ダブルアンダースコア__varで名前マングリングを利用するとよいでしょう。

このバランスを意識すれば、Pythonらしい柔軟で保守しやすいコードを書くことができます。

クラスとオブジェクト指向

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!