In a parser library I maintain, I have some classes that inherit from str
to manage parsed strings and parsed symbols. This has been working well for a long time, but with Python 3.10, someone requested being able to use match
and case
on these classes. I have constructed an example script that shows how this is failing:
class Binge(str):
def __eq__(self, other):
return self.__class__ == other.__class__ and str.__eq__(self, other)
def __ne__(self, other):
return not self == other
# These two asserts are important for how the class is used
assert Binge('a') == Binge('a')
assert Binge('a') != 'a'
# What does it take to then make this work?
matched = False
match Binge("asdf"):
case Binge("asdf"):
matched = True
assert matched
If I add:
print(self.__class__, other.__class__)
in the __eq__
function, I see this:
<class '__main__.Binge'> <class 'str'>
Class patterns are generally matched by attributes, key-value pairs, so e.g.:
match subject:
case Thing(foo="bar"):
...
becomes something like:
isinstance(subject, Thing) and subject.foo == "bar"
You can get a pattern like Thing("bar")
to work, but this requires a __match_args__
class attribute to map from positional arguments to the appropriate attribute, then the pattern becomes something like:
isinstance(subject, Thing) and getattr(subject, Thing.__match_args__[0]) == "bar"
So for the above patterns to both work correctly, Thing
would have to look something like:
from typing import ClassVar
class Thing:
__match_args__: ClassVar[tuple[str, ...]] = ("foo",)
foo: str
def __init__(self, foo: str):
self.foo = foo
However, as the documentation notes:
For a number of built-in types (specified below), a single positional subpattern is accepted which will match the entire subject...
One of those built-in types is str
so, because Binge
inherits from str
, the pattern:
match subject:
case Binge("asdf"):
...
becomes something like:
isinstance(subject, Binge) and subject == "asdf"
This explains what you're seeing: case Binge("asdf"):
→ Binge("asdf").__eq__("asdf")
→ False
, and the pattern does not match.
Therefore you cannot have all three of the following:
issubclass(Binge, str)
;Binge("foo") != "foo"
; andcase Binge("bar"):
matches Binge("bar")
.