python アンダースコアに関して

python　のアンダースコアの位置について


アンダースコアの位置	例
アンダースコアのみ	`_`
先頭に1つのアンダースコア	`_foo`
末尾に1つのアンダースコア	`foo_`
先頭に2つのアンダースコア	`__foo`
先頭と末尾に2つずつのアンダースコア	`__foo__`
途中に1つ以上のアンダースコア	`foo_bar_baz`, `10_000_000`

アンダースコアのみの場合は、必要のない値（または一時的な変数）の代入先として使用される。

例えば以下のようにfor文でインデックスへのアクセスが不要な場合や

```py
for _ in range(47):
    print('Hello, world')
```

必要のない値や返り値の代入先として使われる

```py
temp_list = [0, 1, 2, 3]
a, b, _, _ = temp_list
print(a)
# 0
print(b)
# 1
print(_)
# 3
# 3次元座標を取得する関数でx軸だけ必要な場合
x, _, _ = get_coordinate(area)  
```

先頭に、アンダースコアが一つの場合
先頭に一つの、アンダースコアを付与する対象は、以下のパターンがある。

・クラス内の変数やメソッド：慣用的な意味合い’
・関数：システム的な規制を受ける場合がある

クラス内の変数やメソッドの先頭にアンダースコアを1つ付与
クラス内の変数やメソッドの先頭にアンダースコアを1つ付与する場合は、慣例的な意味として、「クラス内でのみで参照・使用されるもの」を示します。つまり、クラス外からアクセスされることを意図されていない変数・メソッドということです。と言っても慣例的な意味合いなので、アクセス自体は可能ですので、あくまでも意思表示(注意喚起)というような形です。

```py
class Test:
    def __init__(self):
        # _fooはクラス外から直接呼ばれることを意図していない
        self._foo = 0
    # barメソッドはクラス外から直接呼ばれることも意図している
    def bar()
        self._foo += 1
        return self._foo
    # _bazメソッドはクラス外から直接呼ばれることを意図していない
    def _baz()
        self._foo = 0
        return self._foo
t =  Test()
# 慣例的にアクセスしてほしくはないが、クラス外からアクセス可能
print(t._foo)
# 0
# クラス外からアクセスされることを想定されており、アクセス可能
print(t.bar())
# 1
# 慣例的にアクセスしてほしくはないが、クラス外からアクセス可能
print(t._baz())
# 0
```

関数の先頭にアンダースコアを一つ付与
関数の先頭にアンダースコアを一つ付与する場合は、システムな制約を受けます。関数が含まれるモジュールからワイルドカードでインポートする場合に、アンダースコアで始まる関数は読み込まれません。

```py:test_module.py
def foo():
    return 0
def _bar():
    return 1
```

```python
from test_module import *

foo() # test_moduleからインポートされる
_bar() # _が先頭にあるためインポートされない
```

と言ってもワイルドカードによるインポートは、何をインポートしているか分からなくなる（名前空間が分からなくなる）ため、使用をい推奨されていないので、この事例に当たることは少ないかもしれない。



頭に2つのアンダースコア
先頭に2つのアンダースコアを付与する対象は、クラスの変数やメソッドです。 上記の場合、対象に対してネームマングリング(Name Mangling)が適用されます。
ネームマングリング
ネームマングリングとは、クラス内の先頭に`__`がついた変数・メソッドに対して、その名前を「`_クラス名`」+`変数名(or メソッド名)`に変換します。 その結果、先頭に1つのアンダースコアの場合と違い、定義した変数名・メソッド名でクラス外からアクセスできなくなります。

```py
class Test():
    def __init__(self, name):
        self.name = name
        self._name = name
        self.__name = name

t = Test('foo')
# クラス外からアクセス可能
a = t.name
# 慣例的にアクセスしてほしくはないが、クラス外からアクセス可能
b = t._name
# クラス外からアクセス不可(そもそも存在しない)
c = t.__name
# マングリング後の名前ならばクラス外からアクセス可能
d = t._Test__name
```

つまり、ネームマングリングが実施されることで、擬似的にではありますが、プライベート変数及びメソッドのような挙動となります。 もちろん、クラス内からであれば、マングリングされる前の名前でアクセス可能です。
マングリングの注意点
マングリングは便利ですが、気をつけなればいけないこともあります。それは継承時の挙動です。
マングリングされた変数の継承時の挙動
以下のように、継承元クラスでマングリングされた変数に継承先でアクセスしようとすると、エラーになります。これは継承元で'アリス'という文字列が`_Base__name`に代入され ますが、継承先では、`_Child__name`を参照しているため、発生します。

```py
class Base():
    def __init__(self):
        self.__name = 'アリス'
# Baseクラスを継承
class Child(Base):
    def print_name():
        print(self.__name)
c = Child()
c.print_name()
# AttributeError: 'Child' object has no attribute '_Child__name'    
```

マングリングされたメソッドの継承時の挙動
以下のスクリプトは`__get_title`メソッドで取得されたタイトル名を`print_title`メソッドで出力しています。Childクラスで`__get_title`をオーバライドしているので、オーバライド後のメソッドが呼ばれることを期待しています。 ですが、`__get_title` メソッドはマングリングされているおり、そもそも`Base`クラスの`print(self.__get_title())`で参照されている`__get_title`の実態が`_Base__get_title`のため、期待した挙動になりません。

```py
class Base():
    def print_title(self):
        print(self.__get_title()) # _Base__get_titleの返り値を出力している
    def __get_title(self): # _Base__get_titleを定義している
        return '不思議の国のアリス'
# Baseクラスを継承
class Child(Base):
    def __get_title(self): # _Child__get_titleを定義している
        return '鏡の国のアリス'
c = Child()
# 以下は'鏡の国のアリス'を出力してほしいが、実際にはBaseクラスの'不思議の国のアリス'を出力してしまう
c.print_title()
# 不思議の国のアリス
```


先頭と末尾に２つずつアンダースコア

先頭と末尾に２つずつのアンダースコアがある場合は、pythonで特別な用途で使用するためのマジックメソッドを意味する。なお、マングリングの対象外。
`__Init__`や`__len__`などがあるが、
すでにオブジェクトに対する用途が決まっているので、用途を理解せずに定義や上書きすることはやめた方が良い。
dir関数でオブジェクトの属性がわかるので、オブジェクトどんな、マジックメソッドがあるかを確認できる。