Pythonのススメ9

はじめに

Pythonでプログラムを書くにあたり、文法や言語仕様などの個人的なメモを記載する。
今回のネタは変数や関数につくアンダースコア。
いろんなところでアンダースコアが1つの変数/関数定義の場合と2つの場合の定義を見るが、これらには意味があるのかが気になったので調べた。
結論を言うと意味はある


アンダースコア(_)が1つの場合と2つの場合の違い

  • アンダースコア(_)が1つの場合

 - 参照はできるが、基本的に外部から参照しない ということを慣習化させたもの。

  • アンダースコア(_)が2つの場合

 - 外部の参照を受けないもの。


また、Python - Pythonの変数やメソッドの命名について(アンダーバー)|teratail では、アンダースコアの使い分けの質問に対して、Pythonの標準コーディングスタイルを定めるPEP 8に記されているガイドラインを用いて回答されている。

実践されている命名方法

【_single_leading_underscore】
 “内部でだけ使う” ことを示します。たとえば「from M import *」は、アンダースコアで始まる名前のオブジェクトをインポートしません。
【__double_leading_underscore】
 クラスの属性に名前を付けるときに、名前のマングリング機構を呼び出します (クラスFoobarの__booという名前は_FooBar__booになります)

メソッド名とインスタンス変数

公開されていないメソッドやインスタンス変数にだけ、アンダースコアを先頭に付けてください。
サブクラスと名前が衝突した場合は、Pythonのマングリング機構を呼び出すためにアンダースコアを先頭に二つ付けてください。

継承の設計

・公開されている(public)属性の先頭にはアンダースコアを付けない
・もしあなたが公開している属性の名前が予約語と衝突する場合は、属性の名前の直後にアンダースコアを追加します。省略語を使ったり、スペルミスをするよりはマシです。
・サブクラス化して使うクラスがあるとします。サブクラスで使って欲しくない属性があった場合、その名前の最後ではなく、先頭にアンダースコアを二つ付けることを検討してみましょう。これによって Python のマングリングアルゴリズムが呼び出され、その属性にはクラス名が付加されます。これはサブクラスにうっかり同名の属性が入ってしまうことによる属性の衝突を避けるのに役立ちます。

ここで述べられているのは、「アンダースコアを2つつけることはprivateを実現するためではない。サブクラスとの名前の衝突を防ぐため」ということに注意。


サンプルプログラム

例えば、以下のようなParentクラスとそれを継承したChildクラスがあるとする。

class Parent:
    def __hoge(self):
        print("Parent #___hoge is called.")

    def _hoge(self):
        print("Parent #_hoge is called.")

    def hoge(self):
        print("Parent #hoge is called.")

    def call__hoge(self):
        self.__hoge()

    def call_hoge(self):
        self._hoge()

    def callhoge(self):
        self.hoge()


class Child(Parent):
    def __hoge(self):
        print("Child #___hoge is called.")

    def _hoge(self):
        print("Child #_hoge is called.")

    def hoge(self):
        print("Child #hoge is called.")

このとき、以下のコードを実行すると、

parent = Parent()
parent.call__hoge()
parent.call_hoge()
parent.callhoge()

実行結果は以下の通り。

Parent #___hoge is called.
Parent #_hoge is called.
Parent #hoge is called.

この実行結果はイメージしやすい。
では、以下のコードを実行するとどうなるか。

child = Child()
child.call__hoge()
child.call_hoge()
child.callhoge()

実行結果は以下の通り。

Parent #___hoge is called.
Child #_hoge is called.
Child #hoge is called.

Childクラスで定義されたメソッドはいずれもオーバーライドできているように思えるが、実際動かしてみるとそうでないことがわかる。
具体的には、

  • アンダースコアを2つつけたメソッドは子クラスでオーバーライドされない。

 - ParantクラスのメソッドはChildクラスの__hogeメソッドは参照しない。
 - ダブルアンダースコアはそれが定義されたクラス内でしか参照できない。
 - アンダースコアを2つつけることには、文法的な意味はある。

  • アンダースコアを2つつけないメソッドは子クラスでオーバーライドされる。

 - ParantクラスのメソッドはChildクラスの_hogeメソッドおよびhogeメソッドを参照する。


終わりに

本投稿では、変数やメソッドにつけられるアンダースコア(_)について、何か文法的な意味があるのかどうかを調べ、その結果について述べた。
結論として、アンダースコアを2つつけることは文法的な意味がある。
アンダースコアを2つつけると、「それが定義されたクラス内でしか参照できない」という意味が付与される。
サンプルプログラムでは、アンダースコアが2つつけられたメソッドは子クラスでオーバーライドされないことを示した。
アンダースコアが1つまたはアンダースコア無しの場合は外部から参照される。
サンプルプログラムでは、これらのメソッドは子クラスでオーバーライドされることを示した。


参考文献

【備忘録】Python3系でアンダーバー2つ付きのクラスメソッドをオーバーライドしようとしてコケた話 - Qiita
Python - Pythonの変数やメソッドの命名について(アンダーバー)|teratail
【備忘録】Pythonにおけるアンダースコア"_"の役割について - Qiita
Python初心者がステップアップするために覚えたいこと | 株式会社キャパ CAPA,Inc.