【Python】アンダースコア( _ )の使い方(特殊属性、dunders)

アンダースコアなのかアンダーバーなのか

そんなことはどっちでも良い。
問題はpythonでアンダースコア(アンダーバー)がよく登場することだ。

お品書き。

  • 1つのアンダースコアから始まる属性名(_var
  • 2つのアンダースコアから始まる属性名(__var
  • 1つのアンダースコアで終わる属性名(var_
  • 2つのアンダースコアから始まり、2つのアンダースコアで終わる属性名(__var__
  • アンダースコアだけ(_

属性っていうと、メソッドと変数の両方をさせるらしい。

1つのアンダースコアから始まる属性名(_var

PEP8

_single_leading_underscore: weak "internal use" indicator. E.g. from M import * does not import objects whose name starts with an underscore. (https://www.python.org/dev/peps/pep-0008/)

1つのアンダースコアから始まる属性名は内部での使用を目的としている属性であることを示唆する。

変数の場合

以下のように初期化したクラスを外部から読み込んで変数名の付け方による挙動の違いを見てみる。

class Test:
    def __init__(self):
        self.foo = 11
        self._bar = 23
>>> t = Test()
>>> t.foo
11
>>> t._bar
23

上記のように、どちらの場合も外部からアクセスできてしまう。だめやん。

モジュールの場合

一方、モジュール名として、1つのアンダースコアから始まるモジュールを定義すると、

def ext_func():
    return 23
def _int_func():
    return 42

外部からワイルドカード指定でimportした際には読み込まれない。 いいね。

>>> from my_module import *
>>> ext_func()
23
>>> _int_func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_int_func' is not defined

しかし、ワイルドカードで指定しない場合、この変数名は通常通り呼び出すことができる。え、だめやん。

>>> import my_module
>>> my_module.ext_func()
23
>>> my_module.int_func()
42

まとめ

PEP8ではモジュールの読み込みにワイルドカードを使用することは推奨されていない。
モジュールを読み込む時は明示的に指定することが想定される。

けどそれだと普通に外部から読み込まれてしまう。。

1つのアンダースコアから始まる属性名は、プライベート変数だよ!!
だけど動作にはほとんど影響しないので、そこんとこ理解しといて!!
ってことらしい。

2つのアンダースコアから始まる属性名(__var

PEP8

double_leading_underscore: when naming a class attribute, invokes name mangling (inside class FooBar, boo becomes _FooBar__boo; see below). (https://www.python.org/dev/peps/pep-0008/)

この属性名で定義すると、名前のマングリング機構を呼び出すことで、擬似的にプライベート属性を作成する。
具体的には以下のようなマングリングを引き起こす。

__attr_Class__attr

To avoid name clashes with subclasses, use two leading underscores to invoke Python's name mangling rules.

Generally, double leading underscores should be used only to avoid name conflicts with attributes in classes designed to be subclassed.

このマングリングは、プライベート変数にするためというより、
親クラスと子クラスでメソッド名が重複した場合に、コンフリクトを避ける目的で使うのが一般的らしい。

2つのアンダースコアから始まり、2つのアンダースコアで終わる属性名(__var__

PEP8

__double_leading_and_trailing_underscore__: "magic" objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented. (https://www.python.org/dev/peps/pep-0008/)

公式ドキュメント

クラスは、特殊な名前のメソッドを定義して、特殊な構文 (算術演算や添え字表記、スライス表記など) による特定の演算を実装できます。これは、Python の演算子オーバロード (operator overloading) へのアプローチです。これにより、クラスは言語の演算子に対する独自の振る舞いを定義できます。例えば、あるクラスが__getitem__() という名前のメソッドを定義しており、 x がこのクラスのインスタンスであるとすると、 x[i] は type(x).__getitem__(x, i) とほぼ等価です。(https://docs.python.jp/3.3/reference/datamodel.html)

俗にいう特殊属性(magic variable)。 何も "magic" ではなく、この呼び方はPythonコミュニティで嫌われている(らしい)。 定められた特殊メソッド名がクラス内で定義されている場合、特定の演算が行われた値が返るようになっている。 自作関数の属性名をこの形式で作るのはやめるべし。

1つのアンダースコアで終わる属性名(var_

PEP8

single_trailing_underscore_: used by convention to avoid conflicts with Python keyword (https://www.python.org/dev/peps/pep-0008/)

属性名のpython予約語とのコンフリクトを避けるために使用される。
予約語は以下の通り。

False      class      finally    is         return
None       continue   for        lambda     try
True       def        from       nonlocal   while
and        del        global     not        with
as         elif       if         or         yield
assert     else       import     pass
break      except     in         raise

以下のように使用される。

Tkinter.Toplevel(master, class_="ClassName")

アンダースコアだけ(_

アンダーバー1文字の使い方。

for i in range(10):
    print('Hello World!')

forループの中で格納した値を使用していない場合。こういう時は_を使う。

for _ in range(10):
    print('Hello World!')

またはタプル中の要素のうち、特定の要素だけ使用したい場合。

>>> test = ('a', 'b', 'iranai_1', 'iranai_2')
>>> a, b, _, _ = test
>>> a
'a'
>>> b
'b'

その他、対話型ウィンドウ (REPL)で起動している際は、直前に実行したコマンドの標準出力が格納されている。
ちょっとしたテストに便利。

>>> 3 + 5
8
>>> _
8
>>> print(_)
8
>>> list()
[]
>>> _.append(1)
>>> _.append(2)
>>> _.append(3)
>>> _
[1, 2, 3]

dundersとはなんなのか。

pythonのコードではこの2つ続きのアンダースコアがよく使われるため、
腕利きのpythonista達は、2つ続きのアンダースコア(__)をdundersと呼ぶらしい。

double underscoredunders

そいういうことだ。

参考