เมื่อใดควรใช้ 'เพิ่ม NotImplementedError'


109

เป็นการเตือนตัวเองและทีมของคุณให้ใช้ชั้นเรียนอย่างถูกต้องหรือไม่? ฉันไม่ได้รับการใช้คลาสนามธรรมอย่างเต็มที่เช่นนี้:

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

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

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

5
การหลอกลวงข้ามไซต์: softwareengineering.stackexchange.com/q/231397/110531
jonrsharpe

2
ผมจะบอกว่า: เมื่อมันพึงพอใจ"หลักการน้อยมหัศจรรย์"
MSeifert

3
นี่เป็นคำถามที่มีประโยชน์ซึ่งเห็นได้จากคำตอบที่ชัดเจนของ Uriel ตามเอกสารและคำตอบที่เพิ่มมูลค่าเกี่ยวกับคลาสพื้นฐานนามธรรมโดยJérôme
ดาวอส

นอกจากนี้ยังสามารถใช้เพื่อระบุว่าคลาสที่ได้รับมานั้นไม่ได้ใช้เมธอดนามธรรมคลาสพื้นฐานซึ่งสามารถให้การป้องกันแบบสองทางได้ ดูด้านล่าง
pfabri

คำตอบ:


81

ในฐานะที่เป็นรัฐเอกสาร[เอกสาร] ,

ในคลาสพื้นฐานที่ผู้ใช้กำหนดเมธอดนามธรรมควรเพิ่มข้อยกเว้นนี้เมื่อต้องการคลาสที่ได้รับมาเพื่อแทนที่เมธอดหรือในขณะที่กำลังพัฒนาคลาสเพื่อระบุว่ายังต้องเพิ่มการนำไปใช้จริง

โปรดทราบว่าแม้ว่ากรณีการใช้งานหลักที่ระบุไว้ข้อผิดพลาดนี้เป็นข้อบ่งชี้ของวิธีการเชิงนามธรรมที่ควรนำไปใช้กับคลาสที่สืบทอดมา แต่คุณสามารถใช้วิธีใดก็ได้ที่คุณต้องการเช่นการระบุTODOเครื่องหมาย


6
สำหรับวิธีนามธรรมฉันชอบใช้abc(ดูคำตอบของฉัน )
Jérôme

@ Jérômeทำไมไม่ใช้ทั้งสองอย่าง? ตกแต่งด้วยและปล่อยให้มันเพิ่มabstractmethod NotImplementedErrorสิ่งนี้ห้ามsuper().method()ในการนำไปใช้methodในคลาสที่ได้รับ
timgeb

@timgeg ฉันไม่รู้สึกว่าจำเป็นต้องห้าม super () วิธีการ ()
Jérôme

52

ตามที่Uriel กล่าวไว้มันมีไว้สำหรับวิธีการในคลาสนามธรรมที่ควรนำไปใช้ในคลาสย่อย แต่สามารถใช้เพื่อระบุสิ่งที่ต้องทำเช่นกัน

มีทางเลือกสำหรับกรณีการใช้งานครั้งแรกคือการเรียนบทคัดย่อฐาน สิ่งเหล่านี้ช่วยสร้างคลาสนามธรรม

นี่คือตัวอย่าง Python 3:

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

เมื่อสร้างอินสแตนซ์Cคุณจะได้รับข้อผิดพลาดเนื่องจากmy_abstract_methodเป็นนามธรรม คุณต้องนำไปใช้ในคลาสย่อย

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

ซับคลาสและดำเนินการCmy_abstract_method

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

ตอนนี้คุณสามารถสร้างอินสแตนซ์Dได้แล้ว

C.my_abstract_methodไม่จำเป็นต้องว่างเปล่า สามารถเรียกได้จากการDใช้super().

ข้อดีของสิ่งนี้NotImplementedErrorคือคุณจะได้รับความชัดเจนExceptionในเวลาสร้างอินสแตนซ์ไม่ใช่ในเวลาเรียกใช้


2
มีใน Python 2.6+ เช่นกัน เพียงและกำหนดเอบีซีของคุณด้วยfrom abc import ABCMeta, abstractmethod __metaclass__ = ABCMetaเอกสาร: docs.python.org/2/library/abc.html
BoltzmannBrain

หากคุณต้องการใช้สิ่งนี้กับคลาสที่กำหนด metaclass คุณต้องสร้าง metaclass ใหม่ที่สืบทอดทั้ง metaclass ดั้งเดิมของคลาสและ ABCMeta
rabbit.aaron

27

พิจารณาว่ามันเป็น:

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

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

และคุณ subclass และลืมที่จะบอกว่าวิธีการหรือบางทีอาจจะมากกว่าน่าจะพิมพ์ผิดเป็นisTileCleaned() isTileCLeaned()จากนั้นในรหัสของคุณคุณจะได้รับNoneเมื่อคุณเรียกมัน

  • คุณจะได้รับฟังก์ชันที่ถูกแทนที่ที่คุณต้องการหรือไม่? ไม่อย่างแน่นอน.
  • คือNoneการส่งออกที่ถูกต้อง? ใครจะรู้.
  • เป็นพฤติกรรมที่ตั้งใจหรือไม่? เกือบจะไม่แน่นอน
  • คุณจะได้รับข้อผิดพลาดหรือไม่? มันขึ้นอยู่กับ.

raise NotImplmentedError บังคับให้คุณนำไปใช้เนื่องจากจะทำให้เกิดข้อยกเว้นเมื่อคุณพยายามเรียกใช้จนกว่าคุณจะดำเนินการดังกล่าว สิ่งนี้จะลบข้อผิดพลาดที่ไม่มีการโต้ตอบจำนวนมาก คล้ายกับว่าทำไมการเปลือยเปล่าแทบจะไม่เป็นความคิดที่ดีเลยเพราะคนเราทำผิดพลาดและทำให้แน่ใจว่าพวกเขาจะไม่ถูกกวาดไปใต้พรม

หมายเหตุ: การใช้คลาสพื้นฐานที่เป็นนามธรรมตามที่คำตอบอื่น ๆ ได้กล่าวไว้นั้นยังดีกว่าเนื่องจากข้อผิดพลาดจะถูกโหลดไว้ล่วงหน้าและโปรแกรมจะไม่ทำงานจนกว่าคุณจะนำไปใช้ (ด้วย NotImplementedError จะทำให้เกิดข้อยกเว้นหากถูกเรียกจริงเท่านั้น)


11

นอกจากนี้เรายังสามารถทำraise NotImplementedError() ภายในเมธอดลูกของ @abstractmethodเมธอดคลาสฐานที่ได้รับการตกแต่ง


ลองนึกภาพการเขียนสคริปต์ควบคุมสำหรับกลุ่มโมดูลการวัด (อุปกรณ์ทางกายภาพ) ฟังก์ชันการทำงานของแต่ละโมดูลมีการกำหนดไว้อย่างแคบโดยใช้ฟังก์ชันเฉพาะหนึ่งฟังก์ชัน: หนึ่งอาจเป็นอาร์เรย์ของรีเลย์อีกอันคือ DAC หรือ ADC แบบหลายช่องสัญญาณแอมป์มิเตอร์อื่น ๆ เป็นต้น

คำสั่งระดับต่ำที่ใช้อยู่ส่วนใหญ่จะใช้ร่วมกันระหว่างโมดูลเช่นเพื่ออ่านหมายเลข 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ไม่ใช่ตัวเลือกเนื่องจาก Python จะเริ่มต้นข้อผิดพลาดด้านล่างเมื่อคุณพยายามสร้างอินสแตนซ์วัตถุ ADC ของคุณ

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

ดังนั้นคุณจะต้องดำเนินการบางสิ่งบางอย่างเพราะเราทำบังคับใช้คำกริยา (aka '@abstractmethod') ซึ่งจะใช้ร่วมกันโดยสองโมดูลอื่น ๆ แต่ในเวลาเดียวกันคุณต้องยังไม่ได้ดำเนินการอะไร ไม่ได้ทำให้รู้สึกสำหรับโมดูลนี้โดยเฉพาะsetset

NotImplementedError เพื่อช่วยเหลือ

เมื่อจบคลาส ADC ดังนี้:

class ADC(Generic): # ...continued

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

คุณกำลังทำสิ่งที่ดีสามอย่างพร้อมกัน:

  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.