Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Review Suggested Edit

You can't approve or reject suggested edits because you haven't yet earned the Edit Posts ability.

Approved.
This suggested edit was approved and applied to the post 5 months ago by Alexei‭.

33 / 255
  • One of the usecases I have found very useful is to do a `raise NotImplementedError()` _inside_ the child method of an `@abstractmethod`-decorated base class method. Yes, it's a mouthful but what it **really means** is **graceful simplicity**.
  • ----------
  • # Example
  • Imagine writing a control script for a family of electronic measurement modules (i.e. physical devices).
  • The functionality of each module is narrowly-defined, implementing just one dedicated function:
  • one could be an array of relays, another a multi-channel DAC or ADC, another an ammeter etc.
  • Many of the low-level commands would be shared between the modules for example to read
  • their ID numbers or to send a command to them. Let's see what we have at this point:
  • ## Base Class
  • 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
  • ----------
  • ### Shared Verbs
  • We then realise that much of the module-specific command verbs and, therefore, the
  • logic of their interfaces is also shared. Here are 3 different verbs whose meaning
  • would be self-explanatory considering a number of target modules.
  • **`get(channel)`:**
  • - **relay:** get the on/off status of the relay on `channel`
  • - **DAC:** get the _output_ voltage on `channel`
  • - **ADC:** get the _input_ voltage on `channel`
  • **`enable(channel)`:**
  • - **relay:** enable the use of the relay on `channel`
  • - **DAC:** enable the use of the _output_ channel on `channel`
  • - **ADC:** enable the use of the _input_ channel on `channel`
  • **`set(channel)`:**
  • - **relay:** set the relay on `channel` on/off
  • - **DAC:** set the _output_ voltage on `channel`
  • - **ADC:** hmm... nothing _logical_ comes to mind.
  • ----------
  • ### Shared Verbs Become Enforced Verbs
  • I'd argue that there is a strong case for the above verbs to be shared across the modules
  • as we saw that their meaning is evident for each one of them. I'd continue writing my
  • base class `Generic` like so:
  • class Generic(ABC): # ...continued
  • @abstractmethod
  • def get(self, channel):
  • pass
  • @abstractmethod
  • def enable(self, channel):
  • pass
  • @abstractmethod
  • def set(self, channel):
  • pass
  • ----------
  • ## Subclasses
  • We now know that our subclasses will all have to define these methods. Let's see what it
  • could look like for the ADC module:
  • 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 value measured on the given 'channel'
  • def enable(self, channel):
  • # enables accessing the given 'channel'
  • You may now be wondering:
  • > But this won't work for the **ADC** module as **`set`** makes no sense there!
  • You're right: NOT implementing `set` is not an option as Python would then fire the error below at you
  • as soon as your as you tried instantiating your ADC object.
  • TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'
  • So you must implement `set`, because we made it an _enforced verb_ (aka `@abstractmethod`),
  • which is shared by two other modules. But, what should you implement if `set` does not make sense for this particular module? What makes the interface (for users) and the code-base (for future maintenance) as clean as possible?
  • ## NotImplementedError to the Rescue
  • By completing the ADC class like this:
  • class ADC(Generic): # ...continued
  • def set(self, channel):
  • raise NotImplementedError("Can't use 'set' on an ADC!")
  • You are doing three very good things at once:
  • 1. You are protecting a user from erroneously issuing a command ('set') that is
  • not (and shouldn't!) be implemented for this module.
  • 2. You are telling them **explicitly** what the problem is (see TemporalWolf's link about
  • 'Bare exceptions' for why this is important)
  • 3. You are protecting the implementation of all the other modules for which the _enforced verbs_
  • do make sense. I.e. you ensure that those modules for which these verbs _do_ make sense will
  • implement these methods and that **they will do so using exactly these verbs** and not some
  • other ad-hoc names.
  • One of the usecases I have found very useful is to do a `raise NotImplementedError()` _inside_ the child method of an `@abstractmethod`-decorated base class method. Yes, it's a mouthful but what it **really means** is **graceful simplicity**.
  • ----------
  • # Example
  • Imagine writing a control script for a family of electronic measurement modules (i.e. physical devices).
  • The functionality of each module is narrowly-defined, implementing just one dedicated function:
  • one could be an array of relays, another a multi-channel DAC or ADC, another an ammeter etc.
  • Many of the low-level commands would be shared between the modules for example to read
  • their ID numbers or to send a command to them. Let's see what we have at this point:
  • ## Base Class
  • 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
  • ----------
  • ### Shared Verbs
  • We then realise that much of the module-specific command verbs and, therefore, the
  • logic of their interfaces is also shared. Here are 3 different verbs whose meaning
  • would be self-explanatory considering a number of target modules.
  • **`get(channel)`:**
  • - **relay:** get the on/off status of the relay on `channel`
  • - **DAC:** get the _output_ voltage on `channel`
  • - **ADC:** get the _input_ voltage on `channel`
  • **`enable(channel)`:**
  • - **relay:** enable the use of the relay on `channel`
  • - **DAC:** enable the use of the _output_ channel on `channel`
  • - **ADC:** enable the use of the _input_ channel on `channel`
  • **`set(channel)`:**
  • - **relay:** set the relay on `channel` on/off
  • - **DAC:** set the _output_ voltage on `channel`
  • - **ADC:** hmm... nothing _logical_ comes to mind.
  • ----------
  • ### Shared Verbs Become Enforced Verbs
  • I'd argue that there is a strong case for the above verbs to be shared across the modules
  • as we saw that their meaning is evident for each one of them. I'd continue writing my
  • base class `Generic` like so:
  • class Generic(ABC): # ...continued
  • @abstractmethod
  • def get(self, channel):
  • pass
  • @abstractmethod
  • def enable(self, channel):
  • pass
  • @abstractmethod
  • def set(self, channel):
  • pass
  • ----------
  • ## Subclasses
  • We now know that our subclasses will all have to define these methods. Let's see what it
  • could look like for the ADC module:
  • 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 value measured on the given 'channel'
  • def enable(self, channel):
  • # enables accessing the given 'channel'
  • You may now be wondering:
  • > But this won't work for the **ADC** module as **`set`** makes no sense there!
  • You're right: NOT implementing `set` is not an option as Python would then fire the error below at you
  • as soon as your as you tried instantiating your ADC object.
  • ```none
  • TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'
  • ```
  • So you must implement `set`, because we made it an _enforced verb_ (aka `@abstractmethod`),
  • which is shared by two other modules. But, what should you implement if `set` does not make sense for this particular module? What makes the interface (for users) and the code-base (for future maintenance) as clean as possible?
  • ## `NotImplementedError` to the Rescue
  • By completing the ADC class like this:
  • class ADC(Generic): # ...continued
  • def set(self, channel):
  • raise NotImplementedError("Can't use 'set' on an ADC!")
  • You are doing three very good things at once:
  • 1. You are protecting a user from erroneously issuing a command ('set') that is
  • not (and shouldn't!) be implemented for this module.
  • 2. You are telling them **explicitly** what the problem is (see TemporalWolf's link about
  • 'Bare exceptions' for why this is important)
  • 3. You are protecting the implementation of all the other modules for which the _enforced verbs_
  • do make sense. I.e. you ensure that those modules for which these verbs _do_ make sense will
  • implement these methods and that **they will do so using exactly these verbs** and not some
  • other ad-hoc names.

Suggested 5 months ago by Michael‭