Kapan menggunakan 'raise NotImplementedError'?

108

Apakah untuk mengingatkan diri sendiri dan tim Anda untuk menerapkan kelas dengan benar? Saya tidak sepenuhnya mendapatkan penggunaan kelas abstrak seperti ini:

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

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

    def isTileCleaned(self, m, n):
        raise NotImplementedError
Antonio Araujo
sumber
5
Dupe
2
Saya akan berkata: Ketika itu memenuhi "Prinsip yang paling tidak mengherankan" .
MSeifert
3
Ini adalah pertanyaan yang berguna, sebagaimana dibuktikan oleh jawaban yang jelas oleh Uriel sesuai dokumen, dan jawaban nilai tambah mengenai kelas dasar abstrak oleh Jérôme.
Davos
Ini juga dapat digunakan untuk menunjukkan bahwa kelas turunan sengaja tidak mengimplementasikan metode abstrak kelas dasar, yang dapat menawarkan perlindungan dua arah. Lihat di bawah .
pfabri

Jawaban:

80

Seperti yang dinyatakan dalam dokumentasi [dokumen] ,

Dalam kelas dasar yang ditentukan pengguna, metode abstrak harus memunculkan pengecualian ini ketika mereka memerlukan kelas turunan untuk mengganti metode, atau saat kelas sedang dikembangkan untuk menunjukkan bahwa implementasi sebenarnya masih perlu ditambahkan.

Perhatikan bahwa meskipun kasus penggunaan utama yang dinyatakan kesalahan ini adalah indikasi metode abstrak yang harus diimplementasikan pada kelas yang diwariskan, Anda dapat menggunakannya sesuka Anda, seperti untuk indikasi TODOmarker.

Uriel
sumber
6
Untuk metode abstrak, saya lebih suka menggunakan abc(lihat jawaban saya ).
Jérôme
@ Jérôme mengapa tidak menggunakan keduanya? Hiasi dengan abstractmethoddan biarkan terangkat NotImplementedError. Ini melarang super().method()dalam implementasi methoddi kelas turunan.
timgeb
@timgeg Saya tidak merasa perlu untuk melarang super (). method ().
Jérôme
51

Seperti yang dikatakan Uriel , ini dimaksudkan untuk metode di kelas abstrak yang harus diimplementasikan di kelas anak, tetapi dapat digunakan untuk menunjukkan TODO juga.

Ada alternatif untuk kasus penggunaan pertama: Kelas Dasar Abstrak . Itu membantu membuat kelas abstrak.

Berikut contoh Python 3:

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

Saat membuat instance C, Anda akan mendapatkan error karena my_abstract_methodabstrak. Anda perlu menerapkannya di kelas anak.

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

Subclass Cdan implementasinya my_abstract_method.

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

Sekarang Anda dapat membuat contoh D.

C.my_abstract_methodtidak harus kosong. Itu bisa dipanggil dari Dpenggunaan super().

Keuntungan dari ini NotImplementedErroradalah Anda mendapatkan yang eksplisitException pada waktu pembuatan instance, bukan pada waktu panggilan metode.

Jérôme
sumber
2
Tersedia dalam Python 2.6+ juga. Cukup from abc import ABCMeta, abstractmethoddan tentukan ABC Anda dengan __metaclass__ = ABCMeta. Docs: docs.python.org/2/library/abc.html
BoltzmannBrain
Jika Anda ingin menggunakan ini dengan kelas yang mendefinisikan metaclass, Anda perlu membuat metaclass baru yang mewarisi metaclass asli dari kelas dan ABCMeta.
Rabbit.aaron
27

Pertimbangkan jika sebaliknya:

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

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

dan Anda membuat subkelas dan lupa memberitahukannya bagaimana isTileCleaned()atau, mungkin lebih mungkin, salah ketik sebagai isTileCLeaned(). Kemudian dalam kode Anda, Anda akan mendapatkan Noneketika Anda memanggilnya.

  • Apakah Anda akan mendapatkan fungsi pengganti yang Anda inginkan? Tentu saja tidak.
  • Adalah None keluarannya valid? Siapa tahu.
  • Apakah itu perilaku yang dimaksudkan? Hampir pasti tidak.
  • Apakah Anda akan mendapatkan kesalahan? Tergantung.

raise NotImplmentedError memaksa Anda untuk menerapkannya, karena ini akan memunculkan pengecualian saat Anda mencoba menjalankannya sampai Anda melakukannya. Ini menghilangkan banyak kesalahan diam. Ini mirip dengan mengapa telanjang kecuali hampir tidak pernah merupakan ide yang baik : karena orang membuat kesalahan dan ini memastikan mereka tidak tersapu ke bawah permadani.

Catatan: Menggunakan kelas dasar abstrak, seperti yang disebutkan oleh jawaban lain, lebih baik lagi, karena kesalahan tersebut dimuat di depan dan program tidak akan berjalan sampai Anda mengimplementasikannya (dengan NotImplementedError, ini hanya akan memunculkan pengecualian jika benar-benar dipanggil).

TemporalWolf
sumber
11

Seseorang juga bisa melakukan raise NotImplementedError() di dalam metode anak dari @abstractmethodmetode kelas dasar berdekorasi.


Bayangkan menulis skrip kontrol untuk keluarga modul pengukuran (perangkat fisik). Fungsionalitas setiap modul didefinisikan secara sempit, hanya menerapkan satu fungsi khusus: satu dapat berupa array relai, yang lain DAC atau ADC multi-saluran, yang lain ammeter, dll.

Banyak dari perintah tingkat rendah yang digunakan akan dibagikan antar modul misalnya untuk membaca nomor ID mereka atau untuk mengirim perintah kepada mereka. Mari kita lihat apa yang kita miliki saat ini:

Kelas Dasar

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

Kata Kerja Bersama

Kami kemudian menyadari bahwa banyak dari kata kerja perintah khusus modul dan, oleh karena itu, logika antarmukanya juga digunakan bersama. Berikut adalah 3 kata kerja berbeda yang artinya sudah cukup jelas mengingat sejumlah modul target.

  • get(channel)

  • relai: nyalakan / matikan status relaichannel

  • DAC: mendapatkan output yang tegangan padachannel

  • ADC: mendapatkan masukan tegangan padachannel

  • enable(channel)

  • relai: aktifkan penggunaan relaichannel

  • DAC: aktifkan penggunaan saluran keluaranchannel

  • ADC: aktifkan penggunaan saluran inputchannel

  • set(channel)

  • relai: nyalakan channel/ matikan relai

  • DAC: setel tegangan keluaranchannel

  • ADC: hmm ... tidak logis datang ke pikiran.


Kata Kerja Bersama Menjadi Kata Kerja yang Dipaksakan

Saya berpendapat bahwa ada alasan kuat untuk kata kerja di atas untuk dibagikan di seluruh modul karena kita melihat bahwa maknanya terbukti untuk masing-masing modul. Saya akan terus menulis kelas dasar saya Genericseperti ini:

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

    @abstractmethod
    def enable(self, channel):
        pass

    @abstractmethod
    def set(self, channel):
        pass

Subkelas

Kita sekarang tahu bahwa semua subclass kita harus mendefinisikan metode ini. Mari kita lihat seperti apa modul 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'

Anda sekarang mungkin bertanya-tanya:

Tetapi ini tidak akan berfungsi untuk modul ADC karena settidak masuk akal di sana karena kita baru saja melihat ini di atas!

Anda benar: tidak mengimplementasikan setbukanlah pilihan karena Python kemudian akan mengaktifkan kesalahan di bawah ini ketika Anda mencoba membuat instance objek ADC Anda.

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

Jadi, Anda harus menerapkan sesuatu, karena kami membuat setsebuah ditegakkan kata kerja (alias '@abstractmethod'), yang dibagi oleh dua modul lain tetapi, pada saat yang sama, Anda juga harus tidak melaksanakan apa-apa karena set tidak masuk akal untuk modul tertentu.

NotImplementedError to the Rescue

Dengan menyelesaikan kelas ADC seperti ini:

class ADC(Generic): # ...continued

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

Anda melakukan tiga hal yang sangat baik sekaligus:

  1. Anda melindungi pengguna dari kesalahan mengeluarkan perintah ('set') yang tidak (dan tidak boleh!) Diimplementasikan untuk modul ini.
  2. Anda memberi tahu mereka secara eksplisit apa masalahnya (lihat tautan TemporalWolf tentang 'Bare pengecualian' mengapa ini penting)
  3. Anda melindungi implementasi dari semua modul lainnya yang kata kerjanya masuk akal. Yaitu Anda memastikan bahwa mereka modul yang kata kerja ini melakukan make akal akan menerapkan metode ini dan bahwa mereka akan melakukannya dengan menggunakan persis kata kerja ini dan tidak beberapa nama ad-hoc lainnya.
pfabri.dll
sumber
-1

Anda mungkin ingin menggunakan @propertydekorator,

>>> 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
Simeon Aleksov
sumber
13
Saya tidak mengerti bagaimana ini menjawab pertanyaan, yaitu kapan menggunakannya .
TemporalWolf