Simplifing a Nested List Comprehension

Viewed 24

I have the following code

def parse_names(tex_names: list[str]) -> set[str]:
    # the list comprehension in question
    return set(parsed_name for parsed_name in [_parse_name(tex_name) for tex_name in tex_names] if parsed_name)


def _parse_name(tex_name: str) -> Optional[str]:
    tex_regex = re.compile(r"^%? ?([\w() äöüÄÖÜß]+)( \((ab|bis) \d{2}:\d{2} Uhr\))?,")
    match = tex_regex.match(tex_name)
    if match:
        return match.groups()[0]
    return None

with an example usage

def test_parse_names():
    raw_names = [
        "% Valid Name,",
        " Without percent,",
        "Without space,",
        "% Another (valid) name,",
        "% Max Mustermann (ab 13:00 Uhr),",
        "% Maxi Mustermann (bis 14:00 Uhr),",
        "% Mara Musterfrau (von 13:00 Uhr),",  # invalid
        "% Lara Musterfrau von 13:00 Uhr,",  # invalid
        "12345",  # invalid
        "%% some message",  # invalid
        "Missing comma",  # invalid
    ]

    expected = {
        "Valid Name",
        "Without percent",
        "Without space",
        "Another (valid) name",
        "Max Mustermann",
        "Maxi Mustermann"
    }
    actual = parse_names(raw_names)
    assert actual == expected

Is it possible to simplify the list comprehension while maintaining correct typing? E.g.

def parse_names(tex_names: list[str]) -> set[str]:
    return set(_parse_name(tex_name) for tex_name in tex_names if _parse_name(tex_name))

triggers the mypy error

error: List comprehension has incompatible type List[Optional[str]]; expected List[str]
1 Answers

A friend found an answer for me:

def parse_names(tex_names: list[str]) -> set[str]:
        return set(filter(_is_valid_name, map(_parse_name, tex_names)))


def _is_valid_name(name: Optional[str]) -> TypeGuard[str]:
    return bool(name)

works perfectly well. I could however not get around TypeGuard[str] instead of just using bool.

Related