3
8

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-11-27

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

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

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

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

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

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

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

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

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

test_module.py
def foo():
    return 0
def _bar():
    return 1
from test_module import *

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

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

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

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を参照しているため、発生します。

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のため、期待した挙動になりません。

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()
# 不思議の国のアリス

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

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

3
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
8