Khi nào sử dụng 'raise NotImplementedError'?


103

Đó là để nhắc nhở bản thân và nhóm của bạn thực hiện đúng lớp học? Tôi không hoàn toàn hiểu được việc sử dụng một lớp trừu tượng như thế này:

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

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

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


2
Tôi sẽ nói: Khi nó thỏa mãn "Nguyên tắc ít gây ngạc nhiên nhất" .
MSeifert

3
Đây là một câu hỏi hữu ích, bằng chứng là câu trả lời rõ ràng của Uriel theo tài liệu và câu trả lời giá trị gia tăng liên quan đến các lớp cơ sở trừu tượng của Jérôme.
Davos

Nó cũng có thể được sử dụng để chỉ ra rằng một lớp dẫn xuất cố tình không triển khai một phương thức trừu tượng của lớp cơ sở, phương thức này có thể cung cấp một bảo vệ hai chiều. Xem bên dưới .
pfabri

Câu trả lời:


78

Như tài liệu nêu rõ [docs] ,

Trong các lớp cơ sở do người dùng xác định, các phương thức trừu tượng sẽ nêu ra ngoại lệ này khi chúng yêu cầu các lớp dẫn xuất ghi đè phương thức hoặc trong khi lớp đang được phát triển để chỉ ra rằng việc triển khai thực vẫn cần được thêm vào.

Lưu ý rằng mặc dù trường hợp sử dụng chính đã nêu, lỗi này là dấu hiệu của các phương thức trừu tượng nên được triển khai trên các lớp kế thừa, bạn có thể sử dụng nó theo bất kỳ cách nào bạn muốn, chẳng hạn như chỉ báo của một TODOđiểm đánh dấu.


6
Đối với các phương thức trừu tượng, tôi thích sử dụng hơn abc(xem câu trả lời của tôi ).
Jérôme

@ Jérôme tại sao không sử dụng cả hai? Trang trí với abstractmethodvà để nó nâng cao NotImplementedError. Điều này cấm super().method()trong việc triển khai methodtrong các lớp dẫn xuất.
timgeb

@timgeg Tôi không cảm thấy cần phải cấm phương thức super (). ().
Jérôme

50

Như Uriel nói , nó dành cho một phương thức trong lớp trừu tượng nên được triển khai trong lớp con, nhưng cũng có thể được sử dụng để chỉ ra VIỆC CẦN LÀM.

Có một giải pháp thay thế cho trường hợp sử dụng đầu tiên: Các lớp cơ sở trừu tượng . Chúng giúp tạo ra các lớp trừu tượng.

Đây là một ví dụ Python 3:

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

Khi khởi tạo C, bạn sẽ gặp lỗi vì my_abstract_methodnó trừu tượng. Bạn cần thực hiện nó trong một lớp con.

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

Phân lớp Cvà thực hiện my_abstract_method.

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

Bây giờ bạn có thể khởi tạo D.

C.my_abstract_methodkhông phải để trống. Nó có thể được gọi từ Dviệc sử dụng super().

Một lợi thế của điều này NotImplementedErrorlà bạn nhận được một cách rõ ràng Exceptiontại thời điểm khởi tạo, không phải tại thời gian gọi phương thức.


2
Cũng có sẵn trong Python 2.6+. Chỉ from abc import ABCMeta, abstractmethodvà xác định ABC của bạn với __metaclass__ = ABCMeta. Tài liệu: docs.python.org/2/library/abc.html
BoltzmannBrain

Nếu bạn muốn sử dụng điều này với một lớp xác định một siêu kính, bạn cần tạo một siêu kính mới kế thừa cả siêu kính gốc của lớp và ABCMeta.
Rabbit.aaron

26

Hãy xem xét nếu thay vào đó nó là:

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

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

và bạn phân lớp và quên không cho nó biết cách isTileCleaned()hoặc, có lẽ nhiều khả năng hơn, đánh máy thành isTileCLeaned(). Sau đó, trong mã của bạn, bạn sẽ nhận được Nonekhi bạn gọi nó.

  • Bạn sẽ nhận được chức năng bị ghi đè mà bạn muốn? Chắc chắn không phải.
  • Noneđầu ra hợp lệ? Ai biết.
  • Đó có phải là hành vi dự định không? Gần như chắc chắn là không.
  • Bạn sẽ nhận được một lỗi? Nó phụ thuộc.

raise NotImplmentedError buộc bạn phải thực hiện nó, vì nó sẽ tạo ra một ngoại lệ khi bạn cố gắng chạy nó cho đến khi bạn làm như vậy. Điều này loại bỏ rất nhiều lỗi im lặng. Nó tương tự như lý do tại sao một ngoại trừ không bao giờ là một ý tưởng hay : bởi vì mọi người mắc sai lầm và điều này đảm bảo rằng họ không bị cuốn vào tấm thảm.

Lưu ý: Sử dụng một lớp cơ sở trừu tượng, như các câu trả lời khác đã đề cập, vẫn tốt hơn, vì khi đó các lỗi được tải trước và chương trình sẽ không chạy cho đến khi bạn triển khai chúng (với NotImplementedError, nó sẽ chỉ ném ra một ngoại lệ nếu thực sự được gọi).


11

Người ta cũng có thể thực hiện raise NotImplementedError() bên trong phương thức con của một @abstractmethodphương thức lớp cơ sở -decorated.


Hãy tưởng tượng viết một kịch bản điều khiển cho một họ mô-đun đo lường (thiết bị vật lý). Chức năng của mỗi mô-đun được định nghĩa hẹp, chỉ thực hiện một chức năng chuyên dụng: một chức năng có thể là một dãy rơ le, một bộ DAC hoặc ADC đa kênh, một ampe kế khác, v.v.

Phần lớn các lệnh cấp thấp được sử dụng sẽ được chia sẻ giữa các mô-đun, ví dụ như để đọc số ID của chúng hoặc để gửi lệnh cho chúng. Hãy xem những gì chúng ta có vào thời điểm này:

Lớp cơ sở

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

Động từ chia sẻ

Sau đó, chúng tôi nhận ra rằng phần lớn các động từ lệnh dành riêng cho mô-đun và do đó, logic của các giao diện của chúng cũng được chia sẻ. Dưới đây là 3 động từ khác nhau mà nghĩa của chúng sẽ tự giải thích được nếu xét theo một số mô-đun đích.

  • get(channel)

  • relay: nhận trạng thái bật / tắt của relaychannel

  • DAC: bật điện áp đầu rachannel

  • ADC: bật điện áp đầu vàochannel

  • enable(channel)

  • relay: cho phép sử dụng relay bậtchannel

  • DAC: cho phép sử dụng kênh đầu ra trênchannel

  • ADC: bật sử dụng kênh đầu vàochannel

  • set(channel)

  • relay:channel bật / tắt rơ le

  • DAC: bật điện áp đầu rachannel

  • ADC: hmm ... không có gì logic trong đầu.


Động từ chia sẻ trở thành động từ cưỡng bức

Tôi lập luận rằng có một trường hợp mạnh mẽ để các động từ trên được chia sẻ trong các mô-đun vì chúng ta đã thấy rằng ý nghĩa của chúng là hiển nhiên cho mỗi một trong số chúng. Tôi sẽ tiếp tục viết lớp cơ sở của mình Genericnhư vậy:

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

    @abstractmethod
    def enable(self, channel):
        pass

    @abstractmethod
    def set(self, channel):
        pass

Các lớp con

Bây giờ chúng ta biết rằng tất cả các lớp con của chúng ta sẽ phải xác định các phương thức này. Hãy xem nó có thể trông như thế nào đối với mô-đun 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'

Bây giờ bạn có thể tự hỏi:

Nhưng điều này sẽ không hoạt động đối với mô-đun ADCsetkhông có ý nghĩa gì ở đó như chúng ta vừa thấy ở trên!

Bạn nói đúng: không triển khai setkhông phải là một tùy chọn vì Python sau đó sẽ gây ra lỗi bên dưới khi bạn cố gắng khởi tạo đối tượng ADC của mình.

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

Vì vậy, bạn phải triển khai một cái gì đó, bởi vì chúng tôi đã tạo setmột động từ thực thi (hay còn gọi là '@abstractmethod'), được chia sẻ bởi hai mô-đun khác nhưng đồng thời, bạn cũng không được triển khai bất kỳ thứ gì setkhông có ý nghĩa đối với mô-đun cụ thể này.

NotImplementedError to the Rescue

Bằng cách hoàn thành lớp ADC như sau:

class ADC(Generic): # ...continued

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

Bạn đang làm ba điều rất tốt cùng một lúc:

  1. Bạn đang bảo vệ người dùng khỏi phát hành sai một lệnh ('set') không (và không nên!) Được triển khai cho mô-đun này.
  2. Bạn đang nói cho họ biết rõ ràng vấn đề là gì (xem liên kết của TemporalWolf về 'Không có ngoại lệ' để biết lý do tại sao điều này lại quan trọng)
  3. Bạn đang bảo vệ việc triển khai tất cả các mô-đun khác mà các động từ được thực thi có ý nghĩa. Tức là bạn đảm bảo rằng những mô-đun mà các động từ này có ý nghĩa sẽ triển khai các phương thức này và chúng sẽ làm như vậy bằng cách sử dụng chính xác các động từ này chứ không phải một số tên đặc biệt khác.

-1

Bạn có thể muốn bạn sử dụng trình @propertytrang trí,

>>> 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
Tôi không hiểu cách này giải quyết câu hỏi, đó là khi nào thì sử dụng nó .
TemporalWolf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.