[Python-ideas] abc.optionalabstractmethod

Nick Coghlan ncoghlan at gmail.com
Fri Aug 3 06:45:02 CEST 2012


On Fri, Aug 3, 2012 at 1:26 AM, Eric Snow <ericsnowcurrently at gmail.com> wrote:
> Sometimes you want to specific an optional method in an abstract base
> class.  Currently we don't have a consistent way of doing so, instead
> having to mix in the old way of defining "abstract" methods:
>
> class MyABC(metaclass=ABCMeta):
>     ...
>
>     def do_something_optional(self):
>         """An optional method for doing something."""
>         raise NotImplementedError

Really, the phrase "optional abstract method" is a bit of an oxymoron.
The point of an abstract method is that subclasses *must* implement
it, either so that concrete method implementations provided by the
base class can rely on it, or so that users of the class can rely on
it. For interface methods that aren't mandatory, there are already a
few different signalling methods currently in use within the language
core and standard library:

1. Attribute checks
   Call method X if it exists, call method Y otherwise (e.g. the
iterator protocol, which falls back to the sequence iterator if
__iter__ doesn't exist, but __getitem__ does, and the backwards
compatibility in the path entry finder protocol, which tries
find_loader first, then falls back to find_module)

2. Setting attributes to None
  Call method X if it is not None (e.g. the hash protocol, where we
added support for __hash__ = None to cope with the fact that object
instances are hashable, but instances of subclasses that override
__eq__ without also overriding __hash__ are not)

3. Implementing the method, but returning NotImplemented
  Call method if defined, treat a result of NotImplemented as if the
method did not exist at all. (e.g. the binary operator protocols)

4. Throwing a particular exception (typically NotImplementedError)
  Call method if defined, treat the designated exception as if the
method does not exist at all (e.g. optional methods in the IO stack)

Now, the interesting point here is that these are all things that
can't easily be defined in a way that is open to *programmatic*
introspection. Here's how the four of them would currently look in a
base class:

  # optional_method(appropriate_signature) can be defined by X instances
  # See the docs for details (users should handle the case where this
method is not defined)

  # optional_method(appropriate_signature) can be defined by X instances
  # See the docs for details (users should handle the case where this
method is set to None)
  optional_method = None

  def optional_method(appropriate_signature):
    """Details on the significance of optional_method

    Users should handle the case where this method returns NotImplemented
    """
    return NotImplemented

  def optional_method(appropriate_signature):
    """Details on the significance of optional_method

    Users should handle the case where this method raises NotImplementedError
    """
    raise NotImplementedError

Expanding the ABC descriptor lexicon to accurately describe those 4
protocol variants (or at least some of them) in a base class may be
worthwhile, but -1 on merely adding yet another variant.

For example (this is rather ugly and I don't actually like it, but I
wanted to illustrate the general point that the descriptor protocol
allows all 4 cases to be distinguished):

    @optional(raise_on_instance_get=AttributeError)
    def optional_method(appropriate_signature):
        ...

    @optional(value_on_instance=None)
    def optional_method(appropriate_signature):
        ...

    @optional(result_on_method_call=NotImplemented)
    def optional_method(appropriate_signature):
        ...

    @optional(raise_on_method_call=NotImplementedError)
    def optional_method(appropriate_signature):
        ...

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia



More information about the Python-ideas mailing list