[Tutor] Type annotation errors

Peter Otten __peter__ at web.de
Thu Jun 4 04:10:41 EDT 2020


boB Stepp wrote:

> As I continue to integrate type annotations with David Beazley's
> recently released course, "Practical Python Programming", I ran into
> another issue.  I have pared down a larger function to just what seems
> to be giving the type annotation complaint:
> 
> def parse_csv(data_rows: List, has_headers: bool = True) -> List:
>     """Parse a CSV file into a list of records."""
>     data_rows_iter = iter(data_rows)
>     if has_headers:
>         headers = next(data_rows_iter)
>     records = []
>     for row in data_rows_iter:
>         if has_headers:
>             # Make a dictionary
>             record = dict(zip(headers, row))
>         else:
>             # Otherwise make a tuple
>             record = tuple(row)
>     records.append(record)
> 
>     return records
> 
> The complaint is:  test.py|19 col 22 error| Incompatible types in
> assignment (expression has type "Tuple[Any, ...]", variable has type
> "Dict[Any, Any]")
> 
> One of the exercises is to return a list of dictionaries if a csv file
> has headers, but if not to return a list of tuples.  mypy apparently
> does not like this.  In the "Type hints cheat sheet..." (at
> https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html) in the
> section "When you're puzzled or when things are complicated" it
> discusses the use of "Any":
> 
> "Use Any if you don't know the type of something or it's too dynamic
> to write a type for..."
> 
> This does not seem particularly complicated to me, so I'm probably
> doing something bad or screwy. Any thoughts?

Let me note that I do not have any experience with mypy.
However, the following

>         if has_headers:
>             # Make a dictionary
>             record = dict(zip(headers, row))
>         else:
>             # Otherwise make a tuple
>             record = tuple(row)

makes every user of classical statically typed languages cringe.
What's the type of record after the above? It's either the type of

dict(zip(headers, row))

or the type of

tuple(row)

I don't know if mypy is not smart enough or just refuses to infer the type 
-- in any case you can help it with a declaration.
The most generic would be

record: Any

but making a union from the types in the error message should work too.

record: Union[Tuple[Any, ...], Dict[Any, Any]]

You might try to tighten that a bit -- e. g. requiring the dict keys to be 
strings. 

There are other things that you may know about the types 

- all records of one function run will be either tuples or dicts, never a
  mixture
- all iterables in the data_rows list should have the same "length"
- the first item in the data_rows list should be an iterable of strings if
  has_headers is true

where I have no idea how it might be explained to a type checker. I fear 
that a moderately clean typed soluton would require two separate functions

parse_csv()
parse_csv_with_headers()

> BTW, the above function works.

Hooray to Python's ducktyping ;)

> If I run it with:
> 
> with_headers = [["a", "b", "c"], [1, 2, 3]]
> without_headers = [[1, 2, 3]]
> 
> my_dict = parse_csv(with_headers)
> my_tuple = parse_csv(without_headers, has_headers=False)
> 
> print("My dict =", my_dict)
> print()
> print("My tuple =", my_tuple)
> 
> I get my expected result:
> 
> bob at Dream-Machine1:~/practical-python/Work$ python3 test.py
> My dict = [{'a': 1, 'b': 2, 'c': 3}]
> 
> My tuple = [(1, 2, 3)]
> 




More information about the Tutor mailing list