If one of the registered functions of a multimethod targets a certain type A and another function targets the Union of several subclasses of A, e.g., B and C, the former will always be preferred (and the latter ignored), even if calling the multimethod with instances of B or C. I would have expected the latter to take priority in this case, since I think of the union function as several functions, each targeting one type in the union, but all with the same code (so I use Union to avoid repeating myself).
Example
from typing import Union
class A: pass
class B(A): pass
class C(A): pass
@multimethod
def f(x: A): print("A")
@f.register
def f(x: Union[B, C]): print("B, C")
Expected
>>> f(A())
A
>>> f(B())
B, C
>>> f(C())
B, C
Actual
>>> f(A())
A
>>> f(B())
A
>>> f(C())
A
Cause
From what I understood from the docs, a Union is checked directly with isinstance, and a tie such as in the last two examples above is resolved using __mro__, but since the __mro__ of the Union will be completely different this won't work as expected.
Workarounds
Keep the implementation in a function that targets a single type in the union, and make a separate function for each remaining type that explicitly calls the former:
from typing import Union
class A: pass
class B(A): pass
class C(A): pass
@multimethod
def f(x: A): print("A")
@f.register
def f(x: B): print("B, C")
@f.register
def f(x: C): f[B](x)
Define a common base class for all the types of the union (this requires being able to modify those subclasses):
from typing import Union
class A: pass
class Intermediate: pass
class B(Intermediate): pass
class C(Intermediate): pass
@multimethod
def f(x: A): print("A")
@f.register
def f(x: Intermediate): print("B, C")
If one of the registered functions of a multimethod targets a certain type
A
and another function targets theUnion
of several subclasses ofA
, e.g.,B
andC
, the former will always be preferred (and the latter ignored), even if calling the multimethod with instances ofB
orC
. I would have expected the latter to take priority in this case, since I think of the union function as several functions, each targeting one type in the union, but all with the same code (so I useUnion
to avoid repeating myself).Example
Expected
Actual
Cause
From what I understood from the docs, a
Union
is checked directly withisinstance
, and a tie such as in the last two examples above is resolved using__mro__
, but since the__mro__
of theUnion
will be completely different this won't work as expected.Workarounds
Keep the implementation in a function that targets a single type in the union, and make a separate function for each remaining type that explicitly calls the former:
Define a common base class for all the types of the union (this requires being able to modify those subclasses):