LoginSignup
18

More than 5 years have passed since last update.

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

Posted at

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

以上、備忘録まで。

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
18