python でプライベート変数を扱う

### はじめに
まず前提として、Python では厳密に可視性を制御することはできません。  
それを踏まえた上で、Python においてプライベート変数を扱う方法をまとめました。

プライベート変数の定義方法として、以下の二つが存在します。
 - _single_leading_underscore
 - __double_leading_underscore

以下でそれぞれを詳しく見ていきます。

### _single_leading_underscore
#### 概要
 オブジェクト名の先頭に '_' を __1つ__ 付けると、import されなくなります。 

 以下はアンダースコアを 1 つ付けたものと付けていないオブジェクトを定義したモジュールであり、これらを import できるかを見てみます。

```Python:module.py
class PublicClass:
    def no_underscore_method(self):
        return 'public_class_no_underscore_method'

    def _underscore_method(self):
        return 'public_class_underscore_method'

class _PrivateClass:
    def no_underscore_method(self):
        return 'private_class_no_underscore_method'
    
    def _underscore_method(self):
        return 'private_class_underscore_method'


def public_method():
    return 'public'

def _private_method():
    return 'private'


public_variable = 'public'

_private_variable = 'private'
```

##### クラスにアンダースコアを 1 つ付与した場合

 ```python:main.py
from module import *

public_class = PublicClass()
print(public_class.no_underscore_method())
print(public_class._underscore_method())

# 実行結果
# public_class_no_underscore_method
# public_class_underscore_method

private_class = _PrivateClass()
print(private_class.no_underscore_method())
print(private_class._underscore_method())

# 実行結果
# NameError: name '_PrivateClass' is not defined
 ```

アンダースコアを 1 つ付けたクラスはインポートできません
ここで注目すべきは、 PublicClass 内で定義したアンダースコア付きのメソッドにはアクセスできている点です

クラス内部のオブジェクトに対してアンダースコアを付与するのは可視性を制御するためではなく、プログラマーに内部でしか使わないオブジェクトであることを伝えるためです。


##### メソッド、変数にアンダースコアを 1 つ付与した場合
クラスと同様、アンダースコアを 1 つ付けたクラスはインポートできません。

```python:main.py
from module import *
print(public_method())
# public

print(_private_method())
# NameError: name '_private_method' is not defined

print(public_variable)
# public

print(_private_variable)
# name '_private_variable' is not defined
```

#### 使用する場面
 公式ドキュメントには 「アンダースコアで始まる名前は、非 public なAPIとして扱います。これらは、予告なく変更されるかもしれない実装の詳細として扱われるべきです。」 とあります。https://docs.python.org/ja/3.7/tutorial/classes.html#private-variables


 つまり、「プライベートメソッドで内部実装を行い、API ではそのメソッドを呼び出すことで、内部実装を気にせず API を利用してもらいたい」という場合に使用します。

 以下の例では get_data() が API のエントリポイントであり、内部処理を _get_data() で行っています。  
 これにより、データの取得方法を変更したい場合は、_get_data() を変更すればよく、get_data() を変更する必要がありません。

 ```python
 class SampleApi:
    def _get_data(self):
        # データ取得の内部実装
    
    # 公開 API
    def get_data(self):
        return self._get_data()
 ```

### __double_leading_underscore

#### 概要
 オブジェクト名の先頭に '_' を __2つ__ 付けると、マングリング機構を呼び出せます。  
 マングリングとは識別子の名前の衝突を避けるために名前が内部的に変更されることです。

 以下の例では、'__' を変数名に付与しています。  
 それにより、クラス外からは元の変数名 '__mangled' ではアクセスできず、'_Mangling__mangled' でアクセスできるようになっています。

```python
class Mangling:
    def __init__(self):
        self.__mangled = 'hello'

print(Mangling()._Mangling__mangled)
print(Mangling().__mangled)

# 実行結果
# hello
# AttributeError: 'Mangling' object has no attribute '__mangled'
```

#### 使用する場面
 スーパークラスとサブクラスの間で、名前の衝突を避けるために使用します。

 例えば、スーパークラスとサブクラスでそれぞれ同じ名前の変数を定義した場合、
 スーパークラスの get_name() を呼び出してもサブクラスの値が返されてしまいます。

```python
class SuperClass:
    def __init__(self):
        self.name = 'super'
    def get_name(self):
        return self.name

class SubClass(SuperClass):
    def __init__(self):
        self.name = 'sub'

sub = SubClass()
print(sub.name)
print(sub.get_name())

# 実行結果
# sub
# sub
```
 
 同じ名前を定義しないようにすれば良いのでは？ と思う方も多いと思いますが、API として公開するクラスを作成する場合はそうはいきません。  
 API 利用者がどのようなサブクラスを作成するかが分からないためです。  
 
 そのため、スーパークラスで '__' を付与した変数を定義することで、名前の衝突が起こるリスクを軽減できます。


### '_' を付けた場合の可視性について補足
 アンダースコアを 1 つ付けた場合、それが public なクラスの属性であれば、クラス外からアクセスできていました。  
 同様に、アンダースコアを 2 つ付けた場合でも概要の例にで既に説明した通り、あくまで名前が変わっているだけで、クラス外からアクセスできてしまいました。

 このことから、記事冒頭でも述べたように、アンダースコアによって厳密に可視性を制御しているわけではありません。  
 可視性を制御した方がプログラムが安全になりやすいはずですが、何故厳密に可視性を制御しないのでしょうか？

  言語仕様として厳密な可視性を提供しないのは、python の設計思想によるものだと考えられます。

  Java のように可視性を厳密に制御することで安全性を得られる一方で、オブジェクトを public にすることで利便性を得られます。
  Python ではプログラマーを信用して利便性をとっているため、厳密な可視性を提供しない言語仕様になっていると考えられます。

### まとめ
 - 変数等をプライベートにしたい場合は '_' を __1つ__ だけ付与する
 - サブクラスで同名のオブジェクトが定義され、名前の衝突が起こり得る場合は '_' を  __2つ__ 付与することを検討する

### 参考
 Brett Slatkin (2020) 「Effective Python 第2版 - Python プログラムを改良する90項目」(黒川利明訳) オライリー・ジャパン