'raise NotImplementedError'を使用する場合


108

クラスを正しく実装することをあなた自身とあなたのチームに思い出させるためですか?私はこのような抽象クラスを完全には使用していません:

class RectangularRoom(object):
    def __init__(self, width, height):
        raise NotImplementedError

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

    def isTileCleaned(self, m, n):
        raise NotImplementedError

5
クロスサイト
重複

2
私は言うでしょう:それが「驚き最小の原則」を満たすとき。
MSeifert 2017年

3
これは、ドキュメントによるUrielによる明確な回答と、Jérômeによる抽象的な基本クラスに関する付加価値のある回答によって証明されるように、有用な質問です。
ダボス

また、派生クラスが基本クラスの抽象メソッドを意図的に実装していないことを示すためにも使用できます。これにより、双方向の保護が提供されます。以下を参照してください
pfabri

回答:


80

ドキュメントに[docs]と記載されているように

ユーザー定義の基本クラスでは、抽象メソッドがメソッドをオーバーライドするために派生クラスを必要とする場合、または実際の実装を追加する必要があることを示すためにクラスが開発されているときに、この例外を発生させる必要があります。

主に述べられているユースケースでは、このエラーは継承されたクラスに実装する必要がある抽象メソッドの表示ですが、TODOマーカーの表示など、好きなように使用できることに注意してください。


6
抽象メソッドabcについては使用することを好みます(私の答えを参照)。
ジェローム

@Jérômeなぜ両方を使用しないのですか?飾っabstractmethodて上げましょうNotImplementedError。これsuper().method()method、派生クラスでの実装を禁止します。
timgeb

@timgeg super()。method()を禁止する必要はないと思います。
ジェローム・

51

以下のようウリエルが言う、それは子クラスで実装しなければならない抽象クラスのメソッドのためのものですが、同様にTODOを示すために使用することができます。

最初のユースケースの代替手段があります:抽象基本クラス。それらは抽象クラスの作成に役立ちます。

Python3の例を次に示します。

class C(abc.ABC):
    @abc.abstractmethod
    def my_abstract_method(self, ...):
        ...

をインスタンス化するとCmy_abstract_method抽象的であるためエラーが発生します。子クラスに実装する必要があります。

TypeError: Can't instantiate abstract class C with abstract methods my_abstract_method

サブクラス化Cして実装しmy_abstract_methodます。

class D(C):
    def my_abstract_method(self, ...):
        ...

これで、インスタンス化できますD

C.my_abstract_method空である必要はありません。をD使用して呼び出すことができますsuper()

これに対する利点は、メソッド呼び出し時ではなく、インスタンス化時にNotImplementedError明示的Exceptionに取得できることです。


2
Python2.6以降でも利用できます。ただ、from abc import ABCMeta, abstractmethodおよびを使用してABCを定義します__metaclass__ = ABCMeta。ドキュメント:docs.python.org/2/library/abc.html
BoltzmannBrain

メタクラスを定義するクラスでこれを使用する場合は、クラスの元のメタクラスとABCMetaの両方を継承する新しいメタクラスを作成する必要があります。
rabbit.aaron

27

代わりに次のようになったかどうかを検討してください。

class RectangularRoom(object):
    def __init__(self, width, height):
        pass

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

そして、あなたはサブクラス化して、それをどのように行うisTileCleaned()か、あるいはおそらくそれをタイプミスするかを伝えるのを忘れますisTileCLeaned()。次に、コードで、Noneそれを呼び出すとを取得します。

  • 必要なオーバーライドされた関数を取得できますか?絶対にありません。
  • あるNone有効な出力は?知るか。
  • それは意図された動作ですか?ほぼ間違いなくそうではありません。
  • エラーが発生しますか?場合によります。

raise NotImplmentedError 実行しようとすると例外スローされるため、実装を強制します。これにより、多くのサイレントエラーが削除されます。それ裸の場合を除いて、ほとんど決して良い考えではない理由と似ています。人々は間違いを犯し、これにより、敷物の下に流されないようにするためです。

注:他の回答で述べたように、抽象基本クラスを使用する方がさらに優れています。エラーがフロントロードされ、実装するまでプログラムは実行されません(NotImplementedErrorを使用すると、実際に呼び出された場合にのみ例外がスローされます)。


12

-decorated基本クラスメソッドの子メソッドの raise NotImplementedError() 内部で実行することもでき@abstractmethodます。


測定モジュール(物理デバイス)のファミリー用の制御スクリプトを作成することを想像してみてください。各モジュールの機能は狭く定義されており、1つの専用機能を実装しています。1つはリレーのアレイ、もう1つはマルチチャネルDACまたはADC、もう1つは電流計などです。

使用中の低レベルコマンドの多くは、たとえばID番号を読み取ったり、モジュールにコマンドを送信したりするために、モジュール間で共有されます。この時点で私たちが持っているものを見てみましょう:

基本クラス

from abc import ABC, abstractmethod  #< we'll make use of these later

class Generic(ABC):
    ''' Base class for all measurement modules. '''

    # Shared functions
    def __init__(self):
        # do what you must...

    def _read_ID(self):
        # same for all the modules

    def _send_command(self, value):
        # same for all the modules

共有動詞

次に、モジュール固有のコマンド動詞の多く、したがってそれらのインターフェースのロジックも共有されていることに気付きます。これは、ターゲットモジュールの数を考慮するとその意味が自明である3つの異なる動詞です。

  • get(channel)

  • リレー:リレーのオン/オフステータスをオンにしますchannel

  • DAC:取得出力の電圧をchannel

  • ADC:取得入力の電圧をchannel

  • enable(channel)

  • リレー:リレーの使用を有効にしますchannel

  • DAC:出力チャネルの使用を有効にしますchannel

  • ADC:入力チャネルの使用を有効にしますchannel

  • set(channel)

  • リレー:リレーのchannelオン/オフを設定します

  • DAC:出力電圧をオンに設定しますchannel

  • ADC:うーん...論理的なことは何も思い浮かびません。


共有動詞が強制動詞になる

上記の動詞がモジュール間で共有されるという強いケースがあると私は主張します。それらの意味はモジュールのそれぞれに明らかであることがわかりました。私は次のGenericように基本クラスを書き続けます:

class Generic(ABC):  # ...continued
    
    @abstractmethod
    def get(self, channel):
        pass

    @abstractmethod
    def enable(self, channel):
        pass

    @abstractmethod
    def set(self, channel):
        pass

サブクラス

これで、サブクラスはすべてこれらのメソッドを定義する必要があることがわかりました。ADCモジュールでどのように見えるか見てみましょう。

class ADC(Generic):

    def __init__(self):
        super().__init__()  #< applies to all modules
        # more init code specific to the ADC module
    
    def get(self, channel):
        # returns the input voltage measured on the given 'channel'

    def enable(self, channel):
        # enables accessing the given 'channel'

あなたは今疑問に思うかもしれません:

しかし、これはADCモジュールでsetは機能しません。これは、上記で見たように意味がありません。

setその通りです。ADCオブジェクトをインスタンス化しようとするとPythonが以下のエラーを発生させるため、実装しないことはオプションではありません。

TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'

したがって、他の2つのモジュールで共有される強制動詞(別名 '@abstractmethod')を作成しsetたため、何かを実装する必要がありますが、同時に、この特定のモジュールにとって意味がないため、何も実装 しないでください。set

レスキューへのNotImplementedError

次のようにADCクラスを完了することによって:

class ADC(Generic): # ...continued

    def set(self, channel):
        raise NotImplementedError("Can't use 'set' on an ADC!")

あなたは一度に3つの非常に良いことをしています:

  1. このモジュールに実装されていない(そして実装されるべきではない!)コマンド( 'set')を誤って発行することからユーザーを保護しています。
  2. あなたは彼ら問題が何であるかを明確に伝えています(これが重要である理由については、「ベア例外」に関するTemporalWolfのリンクを参照してください)
  3. 強制された動詞 が意味をなす他のすべてのモジュールの実装を保護しています。つまり、これらの動詞意味をなすモジュールがこれらのメソッドを実装し、他のアドホック名ではなく、これらの動詞を使用実装することを確認します。

-1

@propertyデコレータを使用することをお勧めします。

>>> class Foo():
...     @property
...     def todo(self):
...             raise NotImplementedError("To be implemented")
... 
>>> f = Foo()
>>> f.todo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in todo
NotImplementedError: To be implemented

13
これがどのように問題に対処するのか、つまりいつ使用するのかわかりません。
TemporalWolf
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.