【Python】_variable と __variable の違いを徹底解説

オブジェクト指向プログラミングにおいて、カプセル化は非常に重要な概念の一つです。カプセル化とは、クラスの内部状態(データ)を隠蔽し、外部からは定義されたインターフェース(メソッド)を通じてのみアクセスできるようにすることで、不適切な操作によるデータ破壊を防ぎ、コードの保守性や再利用性を高める考え方です。

JavaやC++のような言語では、public, protected, private といったアクセス修飾子を使って、メンバー(属性やメソッド)の可視性を厳密に制御します。しかし、Pythonにはこれらの明示的なアクセス修飾子が存在しません。では、Pythonではどのようにしてカプセル化の考え方を実現するのでしょうか?

その答えが、変数名やメソッド名の先頭に付けるアンダースコア (_) です。Pythonでは、アンダースコアの使い方によって、メンバーが「保護された」ものであることや、「プライベートな」ものであることを慣習的、あるいは擬似的に示すことができます。

この記事では、Pythonにおける以下の2つのアンダースコアの用法に焦点を当て、それぞれの意味、違い、使い方、そして注意点を徹底的に解説します。

  • _variable (先頭にシングルアンダースコア):「保護された (protected)」メンバーという慣習
  • __variable (先頭にダブルアンダースコア):名前マングリングによる「擬似的なプライベート」メンバー

これらのアンダースコアの役割を正しく理解することは、Pythonでより堅牢で読みやすいクラスを設計するために不可欠です。

(この記事はPythonのアンダースコア解説シリーズの一部です。最初の記事「Python「_」(シングルアンダースコア) の基本と応用」も併せてご覧ください。)

スポンサーリンク

_variable (先頭にシングルアンダースコア): 「保護された」メンバーという慣習

変数名やメソッド名の先頭にシングルアンダースコア _ を付けると、それは「このメンバーは内部利用を意図しており、クラスの外部から直接アクセスされるべきではない」という開発者間の慣習的な目印となります。これはしばしば「保護された (protected)」メンバーと呼ばれます。

意味と目的

  • 内部利用の示唆
    • _variable は、そのクラス自身や、そのサブクラス(派生クラス)の内部での利用を想定していることを示します。クラスの公開インターフェースの一部ではないことを意味します。
  • あくまで慣習
    • 重要なのは、これがPythonの言語仕様としてアクセスを強制的に制限するものではないということです。 外部から _variable にアクセスしようと思えば、技術的には可能です。これは「紳士協定」のようなもので、コーダーがその意図を汲み取って慎重に扱うことが期待されます。
  • APIの安定性
    •  公開インターフェースではないため、将来的にクラスの内部実装が変更された際に、_variable の仕様が変わったり削除されたりする可能性があります。外部からこれに依存したコードを書いていると、クラスのバージョンアップ時に問題が生じるリスクがあります。

from module import * での挙動

from some_module import * という形式でモジュールから全ての名前をインポートする場合、先頭にシングルアンダースコア _ が付いた名前は、デフォルトではインポートされません。 これにより、モジュールの利用者が意図せず内部用の変数や関数で自身の名前空間を汚染してしまうのを防ぐ効果があります。

Python

# my_module.py
_internal_var = "This is internal"
public_var = "This is public"

def _internal_function():
    return "Internal function result"

def public_function():
    return "Public function result"

# main.py
# from my_module import * # これを実行した場合

# print(_internal_var)       # NameError: name '_internal_var' is not defined
# print(_internal_function()) # NameError: name '_internal_function' is not defined
# print(public_var)          # This is public
# print(public_function())   # Public function result

具体的なコード例

Python

class CoffeeCup:
    def __init__(self, temperature):
        self.public_content = "コーヒー"
        self._temperature = temperature  # 保護された属性

    def sip(self):
        if self._is_too_hot():
            print("熱すぎます!少し冷ましてください。")
        else:
            print(f"{self.public_content}を飲みました。")
            self._reduce_temperature(5) # 内部メソッドで温度を下げる

    def _is_too_hot(self): # 保護されたメソッド
        return self._temperature > 70

    def _reduce_temperature(self, amount): # 保護されたメソッド
        self._temperature -= amount
        print(f"現在の温度: {self._temperature}°C")

cup = CoffeeCup(80)
cup.sip() # 熱すぎます!少し冷ましてください。

# 外部から保護されたメンバーにアクセスできてしまう(非推奨)
# print(cup._temperature) # 80
# cup._reduce_temperature(20) # 現在の温度: 60°C (非推奨な直接呼び出し)
# cup.sip() # コーヒーを飲みました。 現在の温度: 55°C

この例では、_temperature_is_too_hot_reduce_temperature はクラスの内部ロジックを構成する要素であり、外部から直接操作されることは想定していません。sip メソッドがこれらを適切に利用します。

使うべき場面

  • クラスの内部実装の詳細で、外部の利用者には直接触ってほしくないが、サブクラスからはアクセスしたりオーバーライドしたりする可能性がある場合。
  • ヘルパーメソッドや、クラス内部の状態を管理する属性など。

__variable (先頭にダブルアンダースコア): 名前マングリングによる「擬似的なプライベート」

変数名やメソッド名の先頭にダブルアンダースコア __ を付ける(かつ、末尾がダブルアンダースコアでない場合)と、Pythonインタプリタはその名前に対して「名前マングリング (name mangling)」という特殊な処理を行います。これにより、その名前はクラスの外部やサブクラスから直接アクセスしにくくなり、擬似的なプライベートメンバーとして機能します。

意味と目的

  • 名前衝突の回避: 名前マングリングの主な目的は、継承関係において、サブクラスで意図せずに親クラスの属性やメソッドをオーバーライドしてしまうのを防ぐことです。
  • 強い隠蔽の意図: _variable よりも強く「これはクラス内部だけで使うもので、外部からは触らないでほしい」という意図を示します。
  • 擬似的なプライベート: JavaやC++のような真のプライベートアクセス制御とは異なり、マングリング後の名前を知っていれば外部からもアクセス可能です。そのため、「擬似的」と表現されます。

名前マングリングの仕組み

__variable という名前は、その変数が定義されたクラスの名前を使って、自動的に _ClassName__variable という形式に変換されます。例えば、MyClass というクラス内で定義された __my_attr は、内部的には _MyClass__my_attr として扱われます。

具体的なコード例

Python

class BankAccount:
    def __init__(self, initial_balance):
        self.__balance = initial_balance  # 名前マングリングされる

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"{amount}円入金しました。残高: {self.__balance}円")
        else:
            print("入金額は0より大きくなければなりません。")

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            print(f"{amount}円出金しました。残高: {self.__balance}円")
        else:
            print("出金額が不正か、残高が不足しています。")

    def get_balance(self): # 残高確認用の公開メソッド
        return self.__balance

account = BankAccount(10000)
account.deposit(5000) # 5000円入金しました。残高: 15000円

# print(account.__balance)
# AttributeError: 'BankAccount' object has no attribute '__balance'
# 直接 __balance でアクセスしようとするとエラーになる

# マングリング後の名前でアクセスできてしまう(非推奨)
# print(account._BankAccount__balance) # 15000

print(f"現在の残高: {account.get_balance()}円") # 現在の残高: 15000円

継承時の名前衝突回避の例:

Python

class Parent:
    def __init__(self):
        self.__secret = "Parent's secret"
        self.not_secret = "Parent's public"

    def get_secret(self):
        return self.__secret

class Child(Parent):
    def __init__(self):
        super().__init__()
        self.__secret = "Child's secret" # 親クラスの __secret とは別のものになる

    def get_child_secret(self):
        return self.__secret

    def get_parent_secret_from_child(self):
        # 子クラスから親の __secret を直接参照するのは困難
        # return self.__secret # これは Child の __secret
        return super().get_secret() # 親の公開メソッド経由ならOK

p = Parent()
print(f"Parent secret: {p.get_secret()}") # Parent's secret

c = Child()
print(f"Child secret: {c.get_child_secret()}") # Child's secret
print(f"Parent secret via child: {c.get_parent_secret_from_child()}") # Parent's secret
print(f"Child's public attribute from parent: {c.not_secret}") # Parent's public

この例では、Child クラスで __secret を定義しても、Parent クラスの __secret(実際には _Parent__secret)とは異なる _Child__secret として扱われるため、意図しない上書きが発生しません。

使うべき場面

  • クラスの属性やメソッドが、そのクラス内部でのみ完結して使われるべきで、サブクラスで不用意に上書きされたり、外部から直接変更されたりするのを強く避けたい場合
  • ライブラリ開発などで、内部実装の詳細をユーザーから隠蔽し、将来の変更による影響を最小限に抑えたい場合。
スポンサーリンク

_variable と __variable の主な違いと比較

特徴 _variable (先頭シングル) __variable (先頭ダブル)
主な目的 内部利用の示唆、APIの区別 サブクラスでの名前衝突回避、より強い隠蔽の意図
Pythonインタプリタの挙動 特殊な処理なし(慣習) 名前マングリング (_ClassName__variableに変換)
アクセス制限の度合い 弱い(あくまで慣習、外部からアクセス可能) やや強い(直接アクセス不可、マングリング名で可能)
from module import * インポートされない インポートされない
サブクラスからのアクセス 直接アクセス可能 直接アクセス不可(親クラスのマングリング名でアクセス)
サブクラスでのオーバーライド 容易に可能(意図せず上書きするリスクあり) 意図しない上書きは発生しにくい(別名扱い)
推奨される利用シーン 内部ヘルパー、サブクラスに公開したい内部属性など 継承時の名前衝突を避けたい重要な内部属性/メソッド

どちらを使うべきか?実践的な使い分けのヒント

_variable__variable のどちらを使うべきかは、状況と設計思想によります。

  • 基本は _variable
    • Pythonコミュニティでは、一般的に「私たちは皆、責任ある大人だ (We are all consenting adults here)」という考え方があり、過度な制限よりも慣習とドキュメンテーションを重視する傾向があります。多くの場合、内部利用であることを示す _variable で十分であり、こちらの方がシンプルで理解しやすいコードになります。
  • __variable は慎重に
    • __variable による名前マングリングは、特にライブラリ開発者にとってはサブクラスでの名前衝突を防ぐ強力なツールですが、デバッグがしにくくなったり、サブクラスでの柔軟なカスタマイズを妨げたりする可能性もあります。本当に名前衝突が問題となるケースや、外部に絶対公開したくない強い理由がある場合に限定して使用するのが良いでしょう。
  • 過度な隠蔽は避ける
    •  Pythonの思想として、完全に隠蔽するよりも、ドキュメントで「これは内部用なので使わないでください」と伝える方が好まれることもあります。

迷ったら、まずは _variable を検討し、それでも不十分な理由がある場合に __variable を考えるというステップが良いでしょう。

スポンサーリンク

注意点とベストプラクティス

  • 一貫性を持つ: プロジェクトやチーム内で、どちらのアンダースコアをどのような意図で使うかについて一貫したルールを持つことが重要です。
  • ドキュメンテーション: なぜ特定のメンバーを ___ で始めたのか、ドキュメントストリングなどで理由を記述しておくと、他の開発者(や将来の自分)の助けになります。
  • PEP 8 の参照: Pythonの公式スタイルガイドである PEP 8 にも、これらの命名規則に関する記述があります(特に継承を考慮した設計の部分)。
スポンサーリンク

まとめ

この記事では、Pythonにおける「保護された」メンバーを示す _variable と、「擬似的なプライベート」メンバーを実現する __variable について、その意味、仕組み、使い分けを解説しました。

これらのアンダースコアの用法を理解し、適切に使い分けることで、より堅牢で、意図が明確なクラスを設計することができます。

  • _variable (先頭シングルアンダースコア):
    • **慣習として「内部利用向け」**であることを示す。
    • アクセスは制限されないが、外部からの直接利用は非推奨。
    • from module import * ではインポートされない。
  • __variable (先頭ダブルアンダースコア):
    • 名前マングリングにより、_ClassName__variable という名前に変換される。
    • 主な目的はサブクラスでの意図しない名前衝突の回避
    • _variable よりも強い隠蔽の意図を示すが、完全なプライベートではない。

Pythonでは明示的なアクセス修飾子はありませんが、これらのアンダースコアの慣習を理解し活用することで、カプセル化の恩恵を受け、より質の高いコードを書くことが可能になります。

次は、Pythonのオブジェクトの振る舞いをカスタマイズする強力な仕組みである「ダンダーメソッド (__variable__)」について詳しく見ていきます。

(次の記事へ続く: Pythonのダンダーメソッド (__variable__) を理解する:特殊な振る舞いの秘密)

スポンサーリンク

コメント

タイトルとURLをコピーしました