0

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.

anthony sottile
  • 61,815
  • 15
  • 148
  • 207
  • Changing the annotation of ``age`` to ``Any`` does help calm down the linter, but it also has pretty diffuse meaning. – HymnsPrimo Apr 16 '22 at 20:26
  • Correct. Avoid `Any` where possible. – sarema Apr 16 '22 at 20:27
  • For the second part of your question, please post the complete traceback to allow us to help you. – sarema Apr 16 '22 at 20:28
  • this probably isn't flake8 telling you this, but a type checker (mypy, pyright, etc.) – anthony sottile Apr 16 '22 at 21:41
  • `mypy` is clever enough to automatically widen the type of a parameter to include the default value if needed (i.e. it'll automatically infer `age: int = None` as `age: int | None = None`). – Samwise Apr 16 '22 at 21:44

2 Answers2

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.

sarema
  • 695
  • 5
  • 18
0

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)
Samwise
  • 68,105
  • 3
  • 30
  • 44
  • Instead of declaring the type as `Dict[str, Union[int, str]]` (very strict and verbose), you can just use `dict` as type (more dynamic and less verbose). Obviously, this depends on how strict you want to be. – Lucas Vazquez Apr 16 '22 at 21:08
  • 1
    Using plain `dict` is equivalent to `Dict[Any, Any]`, which isn't much better than just using `Any`. The fact that it's *implicit* is worse in some ways -- I've chased down *so many* bugs that were due to type errors that were "hidden" by a stealthy `Any`. :P – Samwise Apr 16 '22 at 21:38