'Dynamic Type Annotation and Adding a key-value-pair for pep8 [duplicate]

I'm currently working through "Python Crash Course" from No Starch and I'm trying to get used to pep8, by writing the code examples with flake8.

from typing import Any


def build_person(
        first_name: str,
        last_name: str,
        age: int = None) -> Any:   
             #Expression of type "None" cannot be assigned to parameter of type "int"
             #Type "None" cannot be assigned to type "int"
    """Return a dictionary of information about a person."""
    person = {"first": first_name, "last": last_name}
    if age:
        person["age"] = age
             #Argument of type "int" cannot be assigned to parameter "__v" of type "str"              
             #in function "__setitem__"
  "int" is incompatible with "str"

    return person


musician = build_person("Jimi", "Hendrix", 25)
print(musician)

In this piece of code I'm not really sure how I should do the type annotation for the age argument. I want it to be an int when used, but simply a Nonewhen not used, but None is obviously not an int. Naturally flake8 complains. Also I'm not sure how to annotate the dynamically changing return dict.

Is this simply a case where adherence to pep8 does not make sense, or is there an easy solution?

Secondly, flake8 seems to be unhappy about my way of adding a key-value-pair to the dictionary. I'm not sure what the corresponding message means though.



Solution 1:[1]

You probably want to use either

age: int | None = None

or

from typing import Optional
...

age: Optional[int] = None

or

from typing import Union
...

age: Union[int, None] = None

All expressions are equivalent.

Solution 2:[2]

A valid annotation for this function that avoids the use of Any would be:

from typing import Dict, Optional, Union


def build_person(
    first_name: str,
    last_name: str,
    age: Optional[int] = None
) -> Dict[str, Union[int, str]]:
    """Return a dictionary of information about a person."""
    person: Dict[str, Union[int, str]] = {
        "first": first_name,
        "last": last_name
    }
    if age:
        person["age"] = age
    return person


musician = build_person("Jimi", "Hendrix", 25)
print(musician)

A better option than Dict[str, Union[int, str]] IMO would be a TypedDict, which specifies the types of each key individually:

from typing import Optional, TypedDict


class _PersonRequired(TypedDict, total=True):
    # Required fields for Person.
    first: str
    last: str

class Person(_PersonRequired, total=False):
    age: int


def build_person(
    first_name: str,
    last_name: str,
    age: Optional[int] = None
) -> Person:
    """Return a dictionary of information about a person."""
    person = Person(first=first_name, last=last_name)
    if age:
        person["age"] = age
    return person


musician = build_person("Jimi", "Hendrix", 25)
print(musician)

but better still would be avoiding Dict entirely here and using a @dataclass, which is actually built for this exact type of use case:

from dataclasses import dataclass, field
from typing import Optional


@dataclass
class Person:
    first: str
    last: str
    age: Optional[int] = None


musician = Person("Jimi", "Hendrix", 25)
print(musician)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2