Last active
November 4, 2025 04:43
-
-
Save xenophonf/05d40c18f2e16a238b1e045cb02f2c90 to your computer and use it in GitHub Desktop.
On using small classes with Python match-case statements
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 19:49 < dcb> so, I'm looking for opinions on a pattern I often end up using with python that I borrowed from erlang and haskell. The gist is to wrap some object inside a | |
| small class just so I can use it in a match-case statement. Here's a snippet should give you an idea: https://bpa.st/C6LQ | |
| 19:50 < dcb> others often react in one of two ways: (╭ರ_•́) or ¯\_(ツ)_/¯ | |
| 20:00 < phy1729> IMO Python's typing isn't algebraic enough for that to feel natural. Though TIL __match_args__ so maybe that'll change in my head. | |
| 20:03 < phy1729> dcb: though I'd be inclined to make Field generic https://mypy-play.net/?mypy=latest&python=3.12&gist=b4aa6967a03c0ac147130af73f8942db | |
| 20:17 < dcb> XenophonF: these patterns often end up appearing when I'm debugging branching logic early on development. Good call for making the base generic. And | |
| `__match_args__` is something I've started to use quite a bit recently | |
| 20:17 < dcb> phy1729 ^ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # approach 1: pattern-matching sub-classes | |
| from typing import Any | |
| from abc import ABC | |
| # the generic, parent class | |
| class Field(ABC): | |
| __match_args__ = ('data',) | |
| def __init__(self, data: Any): | |
| self.data = data | |
| def __repr__(self) -> str: | |
| return f'{self.__class__.__name__}(data={self.data!r})' | |
| def __str__(self) -> str: | |
| return str(self.data) | |
| # the more specific, concrete classes | |
| class UID(Field): | |
| def __init__(self, data: int): | |
| self.data = data | |
| class Name(Field): | |
| def __init__(self, data: str): | |
| self.data = data | |
| class Email(Field): | |
| def __init__(self, data: str): | |
| self.data = data | |
| def find_someone() -> tuple[UID, Name, Email]: | |
| return (UID(123), Name('Jimmy'), Email('[email protected]')) | |
| def do_something_with(field: Field): | |
| match field: | |
| case UID(data=uid): | |
| print(f'* field {field!r} matches an uid: {uid}') | |
| case Name(data=name): | |
| print(f'* field {field!r} matches a name: {name}') | |
| case Email(data=email): | |
| print(f'* field {field!r} matches an email: {email}') | |
| case _: | |
| print(f'* failed to match field {field!r} with existing patterns!') | |
| (uid, name, email) = find_someone() | |
| do_something_with(uid) | |
| do_something_with(name) | |
| do_something_with(email) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # approach 2: using enums as atoms | |
| from enum import StrEnum, auto | |
| from typing import Any | |
| class Field(StrEnum): | |
| UID = auto() | |
| Name = auto() | |
| Email = auto() | |
| type UID = int | |
| type Name = str | |
| type Email = str | |
| def find_someone() -> tuple[UID, Name, Email]: | |
| return (123, 'Jimmy', '[email protected]') | |
| def do_something_with(data: tuple[Field, Any]): | |
| match data: | |
| case (Field.UID, uid): | |
| print(f'* data {data} matches an uid: {uid}') | |
| case (Field.Name, name): | |
| print(f'* data {data} matches a name: {name}') | |
| case (Field.Email, email): | |
| print(f'* data {data} matches an email: {email}') | |
| case _: | |
| print(f"* failed to match data {data!r} with existing patterns!") | |
| (uid, name, email) = find_someone() | |
| do_something_with((Field.UID, uid)) | |
| do_something_with((Field.Name, name)) | |
| do_something_with((Field.Email, email)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from abc import ABC | |
| # the generic, parent class | |
| class Field[T](ABC): | |
| __match_args__ = ('data',) | |
| def __init__(self, data: T): | |
| self.data = data | |
| def __repr__(self) -> str: | |
| return f'{self.__class__.__name__}(data={self.data!r})' | |
| def __str__(self) -> str: | |
| return str(self.data) | |
| # the more specific, concrete classes | |
| class UID(Field[int]): | |
| pass | |
| class Name(Field[str]): | |
| pass | |
| class Email(Field[str]): | |
| pass | |
| def find_someone() -> tuple[UID, Name, Email]: | |
| return (UID(123), Name('Jimmy'), Email('[email protected]')) | |
| def do_something_with(field: Field): | |
| match field: | |
| case UID(data=uid): | |
| print(f'* field {field!r} matches an uid: {uid}') | |
| case Name(data=name): | |
| print(f'* field {field!r} matches a name: {name}') | |
| case Email(data=email): | |
| print(f'* field {field!r} matches an email: {email}') | |
| case _: | |
| print(f'* failed to match field {field!r} with existing patterns!') | |
| (uid, name, email) = find_someone() | |
| do_something_with(uid) | |
| do_something_with(name) | |
| do_something_with(email) |
Comments are disabled for this gist.