2

I want to create a class that has some nested class that defines some contract in Python. A tenable example is a typed config object. My attempt at this is below:

from typing import Mapping
from abc import ABCMeta, abstractmethod

class BaseClass(metaclass=ABCMeta):
    # If you want to implement BaseClass, you must also implement BaseConfig
    class BaseConfig(metaclass=ABCMeta):
        @abstractmethod
        def to_dict(self) -> Mapping:
            """Converts the config to a dictionary"""

But unfortunately I can instantiate a subclass of BaseClass without implementing BaseConfig:

class Foo(BaseClass):
    pass

if __name__ == "__main__":
    foo = Foo()

Is there some way to enforce that a subclass must implement an inner class, too?

erip
  • 16,374
  • 11
  • 66
  • 121
  • This isn't supported by `abc` as far as I know. Nested classes aren't a common pattern in Python. My advice would be to just pull the class out into the module level to begin with ... – juanpa.arrivillaga May 03 '20 at 23:56
  • 1
    That's sort of what I expected. Very disappointing. – erip May 03 '20 at 23:57
  • Is there any particular reason you want to use a nested class? – juanpa.arrivillaga May 04 '20 at 00:20
  • 1
    For the same reason you'd define methods operating on `abstractmethod`s in a base class: so you can operate at a higher level of abstraction. I can't do that if the, e.g., config is defined at the module level. – erip May 04 '20 at 00:37
  • Can you elaborate? What, exactly, can you not do with another class defined at the module level? It seems just as abstract as a class that exists in another classes' namespace... – juanpa.arrivillaga May 04 '20 at 00:40
  • Something like this: https://repl.it/repls/FriendlyItchyGravity. The semantics behind `self.Inner(...)` are certainly not possible at the module level, but likely aren't possible at all in Python. – erip May 04 '20 at 00:45

1 Answers1

0

It doesn't seem like this is currently possible. The closest thing is to create two abstract classes (corresponding to outer and inner classes) and to force the implementer to provide the cls constructor for the concrete inner class; e.g.:

from abc import ABCMeta, abstractmethod

class Inner(metaclass=ABCMeta):
    @abstractmethod
    def __str__(self):
        pass

class Outer(metaclass=ABCMeta):
    inner_cls = Inner

    def shout(self, *args, **kwargs):
        inner = self.inner_cls(*args, **kwargs)
        print(f"My inner is {inner}!!!")

class FooInner(Inner):
    def __str__(self):
        return "FooInner"

class FooOuter(Outer):
    inner_cls = FooInner

This requires Inner to have at least one abstractmethod otherwise it can be instantiated as a default inner_cls implementation.

erip
  • 16,374
  • 11
  • 66
  • 121