Posted at

【備忘録】Python3系でアンダーバー2つ付きのクラスメソッドをオーバーライドしようとしてコケた話

More than 1 year has passed since last update.

Python3系を使っているプロジェクトでハマった時のお話。


こんなクラスがあったとします


import threading

class ThreadRunner(object):
def __init__(self):
self.__thread = None
self.__event = threading.Event()

def start(self):
self.__event.clear()
self.__thread = threading.Thread(self.__run)

def stop(self):
self.__event.set()
self.__thread.join()
self.__thread = None

def is_running(self):
return not self.__event.is_set()

def __run(self):
raise NotImplementedError("class is abstract.")

threadingのThread()とEvent()をまとめたごく簡単なユーティリティです。

子クラスには、__run()をオーバーライドしてもらって、

よしなに使えるようにと処理部分は未実装にしてありました。


実際に継承して動かしてみました。

import time

from .ThreadRunner import ThreadRunner

class TimeWaiter(ThreadRunner):
def __init__(self):
super().__init__()

def __run(self):
while True:
# なんかの処理
time.sleep(1)

↑こんな感じで継承して、

from TimeWaiter import TimeWaiter

waiter = TimeWaiter()
waiter.start()

いざ。

Exception in thread Thread-1:

Traceback (most recent call last):
File "C:\Program Files\Python36\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "C:\Program Files\Python36\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "C:\test\ThreadRunner.py", line 23, in __run
raise NotImplementedError("class is abstract.")
NotImplementedError: class is abstract.

エラー吐きました。

オーバーライドして無効化されているはずなのに、

親メソッドのNotImplementedError()を投げる処理が呼ばれいます。


先頭にアンダーバーが2つある関数・変数はちょっと特殊らしい

片っ端から情報をあさっていくと、以下の記事にたどり着きました。

【備忘録】Pythonにおけるアンダースコア"_"の役割について

http://qiita.com/ikki8412/items/ab482690170a3eac8c76

Pythonの変数やメソッドの命名について(アンダーバー)

https://teratail.com/questions/41277

1つ目の記事のコメント欄と、2つ目の記事の回答に、自分がコケていた内容に対する回答が載っていました。

関数名や変数名の先頭にアンダーバーを2つ付けると、

内部で自動的に名前が変わり(マングリングというらしいです)、別物になるとのこと。

class ThreadRunner(object):

def __init__(self):
self.__thread = None
self.__event = threading.Event()

def start(self):
self.__event.clear()
self.__thread = threading.Thread(self.__run)

def stop(self):
self.__event.set()
self.__thread.join()
self.__thread = None

def is_running(self):
return not self.__event.is_set()

def __run(self):
raise NotImplementedError("class is abstract.")

先程も例に挙げたThreadRunnerクラスですが、これは以下と同義ということに。


class TimeWaiter(object):
def __init__(self):
self.__thread = None
self.__event = threading.Event()

def start(self):
self.__event.clear()
self.__thread = threading.Thread(self.ThreadRunner__run)

def stop(self):
self.__event.set()
self.__thread.join()
self.__thread = None

def is_running(self):
return not self.__event.is_set()

def ThreadRunner__run(self):
raise NotImplementedError("class is abstract.")

def TimeWaiter__run(self):
while True:
# なんかの処理
time.sleep(1)

※あくまでイメージです

子クラスでいくら継承しても、親クラス側で名前が変わってしまい

オーバーライドしたことになってないのでした。

この場合、アンダーバーを1つにすると良いようでした。

アンダーバーを1つにすることによりマングリングが効かなくなり、

内部でオーバーライドとして扱ってるくれるようになります。

 

Pythonに慣れ親しんだ人からすれば当たり前なんだろうけど…

自分は触り始めて間もないので、結構あっさりハマって時間を溶かしました…。

以上、備忘録まで。