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に慣れ親しんだ人からすれば当たり前なんだろうけど…
自分は触り始めて間もないので、結構あっさりハマって時間を溶かしました…。
以上、備忘録まで。