piglet
piglet

Reputation: 93

`__subclasshook__` on inherited abstract classes in Python?

I'm wondering how best I can define __subclasshook__ on a hierarchy of abstract classes in Python. Let's say I have an interface, and an ABC that inherits from that interface. Is something like this okay?

import abc
from typing import IO

class IFileHandler(metaclass=abc.ABCMeta):
  def import(self) -> bytes:
    raise NotImplementedError

  def export(self) -> IO:
    raise NotImplementedError

  @classmethod
  def __subclasshook__(cls, subclass):
    return all((
      hastattr(subclass, 'import'),
      callable(getattr(subclass, 'import', None)),
      hasattr(subclass, 'export'),
      callable(getattr(subclass, 'export', None))
    )) or NotImplemented


class FileBase(IFileHandler, metaclass=abc.ABCMeta):
  def __init__(self):
    ... 

  def import(self):
    ...

  def export(self):
    raise NotImplementedError

  def another_method(self):
    ...

  @classmethod
  def __subclasshook__(cls, subclass):
    if super().__subclasshook(subclass) is NotImplemented:
      return NotImplemented
    return all((
      hasattr(subclass, '__init__'),
      callable(getattr(subclass, '__init__', None)),
      hasattr(subclass, 'another_method'),
      callable(getattr(subclass, 'another_method', None)),
    )) or NotImplemented


class ConcreteFileClass(FileBase):
  def export(self):
    ...

Is there a more graceful way for calling the superclass' __subclasshook__ and dealing with its result? Have I barked up completely the wrong tree here? Any insight would be much appreciated, thanks!

Upvotes: 2

Views: 372

Answers (1)

Mad Physicist
Mad Physicist

Reputation: 114330

You might consider using the abstractmethod decorator instead of reinventing the whole machinery of abc:

from abc import ABCMeta, abstractmethod
from typing import IO

class IFileHandler(metaclass=ABCMeta):
    @abstractmethod
    def import(self) -> bytes:
        pass

    @abstractmethod
    def export(self) -> IO:
        pass

class FileBase(IFileHandler):
    @abstractmethod
    def __init__(self):
        pass

    def import(self):
        ...

    def export(self):
        ...

    @abstractmethod
    def another_method(self):
        pass

You don't need to explicitly supply metaclass to FileBase, since inheritance already takes care of it. abstractmethod will not allow a subclass to be instantiated without a concrete implementation. Rather than raising NotImplementedError in your stub methods, use Ellipsis (...) or pass. That way, subclasses will be able to correctly call super().whatever without errors.

Upvotes: 2

Related Questions