Turn inner classes into subclasses
Ever wanted to have inner classes in Python that are also subclasses of the outer class? I suppose you’re more likely to be wondering why anyone would want inner classes in Python in the first place. Actually, the combination is pretty neat to make sum types that support arguments and can be used in type hints and with instanceof
!
Normally, an inner class can’t be made a subclass of its outer class, since that name just isn’t in scope. I figured I’d just override the __base__
class as suggested on StackOverflow, however that code fails if you eliminate the ugly T
placeholder class.
So I put together something where you can hide all the ugliness in a file you never look at. All that’s left in your code, is a pretty decorator that makes all the inner classes also a subclass!
def inner_classes_are_subclasses(outercls):
"""
@author https://sr.ht/~midgard/
@license CC0-1.0
"""
for name, innercls in outercls.__dict__.items():
if name.startswith("_") or not isinstance(innercls, type):
continue
setattr(
outercls,
name,
type(
innercls.__name__,
tuple(filter(lambda x: x is not object, innercls.__bases__)) + (outercls,),
dict(innercls.__dict__)
)
)
return outercls
You can use it like so:
@inner_classes_are_subclasses
class Decision:
@dataclass
class Flag:
message: str
@dataclass
class KeepBefore:
pass
@dataclass
class KeepAfter:
pass
@dataclass
class ModifyElement:
tags: Dict[str, str]
@dataclass
class Delete:
pass
# Toy example function
def decide(before_edits: osm_api.Node, after_edits: osm_api.Node) -> Decision:
if before_edits.coords != after_edits.coords:
return Decision.KeepBefore()
return Decision.ModifyElement(tags={
k: v
for k, v in after_edits.tags
if not (has_obscenity(k) or has_obscenity(v))
})
inner_classes_are_subclasses
is not just a toy feature! I use this in production code for my OpenStreetMap selective reverting script.
If you’re going to use this, feel free to leave a comment in my public inbox.