I would like to create a class that looks something like ConfigAndPath
:
import pathlib
from typing import TypeVar, Generic
from dataclasses import dataclass, astuple
class ConfigBase:
pass
T = TypeVar("T", bound=ConfigBase)
@dataclass
class ConfigAndPath(Generic[T]):
path: pathlib.Path
config: T
I often have a list of these ConfigAndPath
, and so would want to destructure it in list comprehensions like this:
l: list[ConfigAndPath[MyConfig]] = ...
filenames = [_path.name for _path, _my_config in l]
So I added an __iter__
method to my class:
# In ConfigAndPath
def __iter__(self):
return iter(astuple(self))
However, I'm not sure how to make it so that my typechecker (Pyright) realizes that _path
is a pathlib.Path
and _my_config
is a MyConfig
. This works with NamedTuple
, however I can't appear to use generics with NamedTuple
due to it not allowing multiple inheritance. Is this possible to specify what I want?
I tried writing an as_tuple
method:
# In ConfigAndPath
def as_tuple(self) -> Tuple[pathlib.Path, T]:
return self.path, self.config
Which then allows me to write
filenames2 = [_path.name for _path, _my_config in [i.as_tuple() for i in l]]
Which gives me a type hint but is quite verbose and walks the list twice.
You can destructure a dataclass using a match
block (requires python ≥ 3.10). It is more verbose, but type checkers understand it. For example, using mypy:
@dataclass
class Foo(Generic[T]):
x: int
y: T
foo = Foo[float](1, 2.0)
match foo:
# NB. this case means must be an object, and have an attribute x and attribute y,
# which will be stored in the current scope as x and y respectively.
case object(x=x, y=y):
reveal_type(x) # note: Revealed type is "builtins.int"
reveal_type(y) # note: Revealed type is "builtins.float"
assert x + y == 3
case _:
raise RuntimeException('unreachable')
However, a match
block is a statement and not an expression. As such it cannot be used within a list comprehension.