Reputation: 100
I'm trying to define an inner class in a hierarchy of classes and I can't figure out the right way to make sure that the inner class correctly subclasses the parents' corresponding inner classes without throwing an exception in the case where one or more of the immediate parent classes has not themselves subclassed that inner class.
I've also tried many times to write this question in a more approachable way, so I apologise if it's a bit of a headscratcher!
Hopefully this example will clarify things:
(assume for this question that we don't know which, if any, of B or C define subclasses of A.Inner - obviously in this example neither do, but that's not the point.)
Cheers.
class A:
class Inner:
...
class B(A):
...
class C(A):
...
class D(B, C):
class Inner(B.Inner, C.Inner):
...
>>>
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-163-6f592c573c6f> in <module>
6 class C(A):
7 ...
----> 8 class D(B, C):
9 class Inner(B.Inner, C.Inner):
10 ...
<ipython-input-163-6f592c573c6f> in D()
7 ...
8 class D(B, C):
----> 9 class Inner(B.Inner, C.Inner):
10 ...
TypeError: duplicate base class Inner
Upvotes: 1
Views: 107
Reputation: 100
Alright folks, going on from g.d.d.c's partial answer, I think we have a solution here that works cleanly and simply for arbitrary class hierarchies.
I'll do some testing on it for a couple of days and see if it does the job. I'm still not happy with the requirement to specify the bases manually - I feel like some sort of introspection inside the wrapper could handle this automatically. Please do chime in with more suggestions.
Thanks heaps again to g.d.d.c for kicking me in the right direction!
def inner_class(*bases):
def _inner_class(cls):
bs = []
for b in bases:
try: bs.append(getattr(b, cls.__name__))
except AttributeError: pass
bs = sorted(set(bs), key = bs.index)
return type(cls)(
cls.__name__,
(cls, *bs),
{}
)
return _inner_class
class A:
class Inner:
...
class B(A):
class Inner(A.Inner):
...
class C(A):
...
class D(A):
...
class E(B, C, D):
@inner_class(B, C, D)
class Inner:
...
print(E.Inner.mro())
>>> [<class '__main__.Inner'>, <class '__main__.E.Inner'>, <class '__main__.B.Inner'>, <class '__main__.A.Inner'>, <class 'object'>]
Upvotes: 0
Reputation: 47988
You can use the fact that if A.inner
is a class, and B
or C
do not explicitly subclass the inner class, then B.inner
and C.inner
are the same object -> A.inner
:
>>> class A:
... class inner: pass
...
>>> class B(A): pass
...
>>> class C(A): pass
...
>>> B.inner is C.inner
True
>>> C.inner is A.inner
True
We're leveraging a dictionary to ensure uniqueness and order (use collections.OrderedDict
or some other ordered_set implementation if you are on a version that does not yet guarantee dict order). We can determine which classes we need to subclass on D.inner
like so:
inner_classes = {getattr(klass, 'inner'): True for klass in [C, B] # control your MRO here.
if getattr(klass, 'inner') is not A.inner}
# Ensure that we also extend A.inner, in case neither B nor C subclasses A.inner
inner_classes[A.inner] = True
class D(A):
class inner(*inner_classes.keys()): pass
In this way we get consistent MRO, it doesn't matter which class (if any) subclasses A.inner
, and D.inner
works.
Upvotes: 1