閉じる

クラス属性を安全に隠すには? Pythonのアンダースコア(__)入門

Pythonでクラスの属性を「隠す」にはアンダースコアの使い分けが重要です。

特にシングルアンダースコアとダブルアンダースコアは目的が異なり、混同するとコードが読みにくくなります

本記事では初心者がつまづきやすいポイントを、挙動が確認できるサンプルコードとともに段階的に解説します。

アンダースコアでクラスのプライベート属性を扱う基礎

アンダースコアとカプセル化の基本

Pythonは他言語(JavaやC++)のような厳密なアクセス修飾子(private/protected)を持ちませんが、「隠したい意思」を表現するための記号としてアンダースコアを使う慣習があります。

これは合意の上でのカプセル化(consenting adults)とも呼ばれ、強制力よりも開発者間の合意を尊重します。

  • シングルアンダースコア_name: 内部用の目印(慣習)
  • ダブルアンダースコア__name: 名前マングリングでクラス名を付加して衝突を避ける
  • ダブル両端__dunder__: 特別メソッド専用(自作しない)

強い秘匿ではなく、あくまで「間違って触らないで」のサインだと理解してください

公開属性とプライベート変数の考え方

公開属性はクラスの外から自由に読み書きされます。

一方で、実装詳細であり外部から触ってほしくないものは隠します。

Pythonでは次の階層で考えると整理しやすいです。

  • 公開API: ドキュメント化する属性やメソッド(name)
  • 内部属性: 外から直接使わない想定(_name)
  • マングル属性: 継承を跨いだ衝突回避が目的(__name)

外からの誤用を減らし、拡張や保守時の予期せぬ破壊的変更を避けるのが狙いです

サンプル: 公開、内部、マングル属性の基本挙動

Python
# Python 3.x

class BankAccount:
    def __init__(self, owner, balance, pin):
        self.owner = owner          # 公開属性: ドキュメントに載せるAPI
        self._balance = balance     # 内部属性: 外から直接触らないでほしい
        self.__pin = pin            # マングル属性: 衝突回避のためにクラス名が付与される

    def deposit(self, amount):
        # 内部属性をメソッド経由で操作
        self._balance += amount

    def __str__(self):
        # 公開向けの表示には機微情報(__pin)を含めない
        return f"BankAccount(owner={self.owner}, balance={self._balance})"

acc = BankAccount("Alice", 1000, "1234")

print(acc.owner)          # OK: 公開属性
print(acc._balance)       # 触れるが非推奨: 内部属性
# print(acc.__pin)        # AttributeError: 存在しないように見える(マングリング済み)
print(dir(acc))           # 実体は _BankAccount__pin という名前で存在する
実行結果
Alice
1000
['__class__', ..., '_BankAccount__pin', '_balance', 'deposit', 'owner', ...]

マングル属性は完全にアクセス不能ではなく、名前が書き換わっているだけという点が重要です。

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

_name は内部用の慣習

シングルアンダースコアは「内部用です」の合図です。

言語仕様で遮断されるわけではないため、テストやデバッグでは読み書き可能です。

モジュールレベルではfrom module import *時に_nameが除外される慣習もありますが、クラス属性では主に可読性向上のために使います。

Python
class Config:
    def __init__(self):
        self.url = "https://example.com"  # 公開
        self._timeout = 5                 # 内部用の目印

まずはシングルアンダースコアで十分なことが多いです。

__name は名前マングリングで衝突を回避

ダブルアンダースコアは継承関係での偶発的な上書き衝突を避けるために使います。

クラス定義時に__name_ClassName__nameに自動変換され、サブクラスで同名を書いても独立します。

Python
class Base:
    def __init__(self):
        self.__token = "base"  # _Base__token に変換

class Child(Base):
    def __init__(self):
        super().__init__()
        self.__token = "child" # _Child__token に変換(親とは別物)

c = Child()
print([k for k in c.__dict__.keys() if "token" in k])
実行結果
['_Base__token', '_Child__token']

親子で同名でも別々に保持でき、意図しない上書きを防げます

__dunder__ は特別メソッドに限定

両端がアンダースコアの名前__name__は特別メソッド専用です。

例えば__init____str__などで、ユーザー定義の通常属性に使ってはいけません。

意味が衝突し、予測不能な動作を招きます。

名前マングリングの仕組みと確認方法

_ClassName__name への変換ルール

ダブルアンダースコアで始まり、末尾がダブルアンダースコアで終わらない名前は、クラス定義のコンパイル時に_ClassName__nameへ変換されます。

これはクラススコープでのみ適用され、グローバルや関数スコープでは行われません。

Python
class Foo:
    __level = 1            # クラス属性もマングリング対象 (_Foo__level)

    def __init__(self):
        self.__x = 42      # インスタンス属性も対象 (_Foo__x)

    def reveal(self):
        # 同一クラス内では書き換え後の名前で参照される
        return Foo.__level, self.__x

f = Foo()
print(f.__dict__)          # 実体名を確認
print(f.reveal())          # メソッドからは自然に参照できる
実行結果
{'_Foo__x': 42}
(1, 42)

クラスの外からは書き換え後の名前でしかアクセスできないことが確認できます。

継承時の上書き衝突を防ぐポイント

マングリングはクラス名をプレフィックスに埋め込むため、サブクラスで同じ__nameを書いても別物になります。

逆に、親の__nameを意図的に触りたい場合は、_ParentClass__nameの形で参照する必要がありますが、通常は避けるべきです。

Python
class A:
    def __init__(self):
        self.__secret = "A"

class B(A):
    def __init__(self):
        super().__init__()
        self.__secret = "B"

b = B()
# 親と子のシークレットは別に存在
print(b._A__secret, b._B__secret)  # デバッグ限定の参照方法
実行結果
A B

衝突を避けたい場面でのみダブルアンダースコアを選ぶのがコツです。

デバッグでのアクセス方法と注意点

デバッグ時にはobj._ClassName__nameの形でアクセスできます。

ただし、この方法を本番コードや外部APIで使うのは原則禁止です。

将来のリファクタリングでクラス名が変わると壊れますし、カプセル化の意図を踏みにじります。

Python
class SafeBox:
    def __init__(self, code):
        self.__code = code  # _SafeBox__code にマングリング

box = SafeBox("9999")

# デバッグのための一時的な確認
print(box._SafeBox__code)  # ただし本番では使わない
実行結果
9999

外部からは公開メソッドやプロパティ経由でアクセスする設計にしましょう

実務でのベストプラクティス

セキュリティではなく意図の明示と理解

アンダースコアはセキュリティ機構ではありません

リフレクションやデバッグで容易に読めますし、機微情報は環境変数や秘密管理を使うべきです。

ダブルアンダースコアの主目的は継承時の偶発的衝突の回避です。

インターフェース設計と命名規約の指針

公開APIを明確にし、そのほかは内部扱いにします。

次のような基準をおすすめします。

  • デフォルトは公開属性/メソッド。外部から使ってほしいものはドキュメント化する。
  • 実装詳細やキャッシュなどは_nameにして内部扱いと明示する。
  • 継承階層で衝突させたくない内部状態だけ__nameにする。
  • 特別メソッド__name__は規定の用途でのみ使う。

むやみにダブルアンダースコアを増やさず、公開APIと内部をはっきり分けることが保守性を高めます

以下は各書き方の早見表です。

書き方呼び方/用途主目的実際の作用
name公開属性name公開API制限なし
_name内部用(慣習)_balance誤用防止の合図言語的な保護なし
__nameマングル属性__token継承時の衝突回避_ClassName__name へ変換
__name__特別メソッド__init__Pythonプロトコルユーザー定義に使わない

設計段階で表に沿って命名を決めておくとチーム内合意が取りやすくなります

初心者が避けたいアンチパターン

  • 秘密情報の隠蔽に__nameを使う: 形だけの隠蔽で意味がありません。
  • すべてを__nameにする: テストや拡張が困難になります。
  • 自作の__name__を作る: 特別メソッドと紛れて危険です。
  • デバッグ用の_ClassName__nameアクセスを常用: リファクタリングに弱く壊れやすいです。

まずはシングルアンダースコアを基本に、必要な場面だけダブルアンダースコアを選ぶのが安全です。

比較サンプル: シングルだと上書き、ダブルだと独立

Python
# 悪い例: シングルアンダースコアで親の内部状態を意図せず上書き

class BaseBad:
    def __init__(self):
        self._state = "base"  # 内部用

class ChildBad(BaseBad):
    def __init__(self):
        super().__init__()
        self._state = "child"  # 親の_stateを上書きしてしまう

b = ChildBad()
print(b._state)  # "child"。親の値は失われる
実行結果
child
Python
# 良い例: ダブルアンダースコアで親子が独立

class BaseGood:
    def __init__(self):
        self.__state = "base"   # _BaseGood__state

class ChildGood(BaseGood):
    def __init__(self):
        super().__init__()
        self.__state = "child"  # _ChildGood__state

g = ChildGood()
print([k for k in g.__dict__.keys() if "state" in k])
実行結果
['_BaseGood__state', '_ChildGood__state']

継承前提のライブラリ設計では、この差がバグ回避に直結します

まとめ

本記事では、アンダースコアの3種類_name__name__name__の役割と使い分けを解説しました。

基本は内部用は_name継承衝突を避けたいときだけ__name特別メソッド__name__はプロトコル専用です。

アンダースコアはセキュリティ機構ではないため、公開APIを明確にし、内部はアンダースコアで意図を示すという姿勢を徹底しましょう。

これにより、設計の見通しがよくなり、継承や保守での偶発的な衝突を未然に防げます。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

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

URLをコピーしました!