Pythonでクラスの属性を「隠す」にはアンダースコアの使い分けが重要です。
特にシングルアンダースコアとダブルアンダースコアは目的が異なり、混同するとコードが読みにくくなります。
本記事では初心者がつまづきやすいポイントを、挙動が確認できるサンプルコードとともに段階的に解説します。
アンダースコアでクラスのプライベート属性を扱う基礎
アンダースコアとカプセル化の基本
Pythonは他言語(JavaやC++)のような厳密なアクセス修飾子(private/protected)を持ちませんが、「隠したい意思」を表現するための記号としてアンダースコアを使う慣習があります。
これは合意の上でのカプセル化(consenting adults)とも呼ばれ、強制力よりも開発者間の合意を尊重します。
- シングルアンダースコア
_name
: 内部用の目印(慣習) - ダブルアンダースコア
__name
: 名前マングリングでクラス名を付加して衝突を避ける - ダブル両端
__dunder__
: 特別メソッド専用(自作しない)
強い秘匿ではなく、あくまで「間違って触らないで」のサインだと理解してください。
公開属性とプライベート変数の考え方
公開属性はクラスの外から自由に読み書きされます。
一方で、実装詳細であり外部から触ってほしくないものは隠します。
Pythonでは次の階層で考えると整理しやすいです。
- 公開API: ドキュメント化する属性やメソッド(
name
) - 内部属性: 外から直接使わない想定(
_name
) - マングル属性: 継承を跨いだ衝突回避が目的(
__name
)
外からの誤用を減らし、拡張や保守時の予期せぬ破壊的変更を避けるのが狙いです。
サンプル: 公開、内部、マングル属性の基本挙動
# 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
が除外される慣習もありますが、クラス属性では主に可読性向上のために使います。
class Config:
def __init__(self):
self.url = "https://example.com" # 公開
self._timeout = 5 # 内部用の目印
まずはシングルアンダースコアで十分なことが多いです。
__name は名前マングリングで衝突を回避
ダブルアンダースコアは継承関係での偶発的な上書き衝突を避けるために使います。
クラス定義時に__name
は_ClassName__name
に自動変換され、サブクラスで同名を書いても独立します。
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
へ変換されます。
これはクラススコープでのみ適用され、グローバルや関数スコープでは行われません。
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
の形で参照する必要がありますが、通常は避けるべきです。
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で使うのは原則禁止です。
将来のリファクタリングでクラス名が変わると壊れますし、カプセル化の意図を踏みにじります。
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
アクセスを常用: リファクタリングに弱く壊れやすいです。
まずはシングルアンダースコアを基本に、必要な場面だけダブルアンダースコアを選ぶのが安全です。
比較サンプル: シングルだと上書き、ダブルだと独立
# 悪い例: シングルアンダースコアで親の内部状態を意図せず上書き
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
# 良い例: ダブルアンダースコアで親子が独立
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を明確にし、内部はアンダースコアで意図を示すという姿勢を徹底しましょう。
これにより、設計の見通しがよくなり、継承や保守での偶発的な衝突を未然に防げます。