What's the type of csv.Dictreader.fieldnames in python?

25 Views Asked by At

I am trying to read a csv file and read the field names (top row of csv file) from it. I used csv.Dictreader to create a reader object and extracted the top row using the reader.fieldnames object. I know that reader.fieldnames will be a list of strings for the kind of csv files I will be using. However, pylance considers the possibility that reader.fieldnames will be None and considers reader.fieldnames to be non-subscriptable.

Here's my code

import sys
import tabulate
import csv
import typing

def main() -> None:
    menu: list[list[str]] = list()
    
    try:
        file: typing.TextIO
        with open(sys.argv[1]) as file:
            reader: csv.DictReader = csv.DictReader(file)
            headings: list[str] = reader.fieldnames
            row: dict[str,str]
            for row in reader:
                menu.append([row[headings[0]],row[headings[1]],row[headings[2]]])            
    except IOError:
        sys.exit("CSV file does not exist")
    else:
        print(tabulate.tabulate(menu, headers=headings, tablefmt="grid"))

if __name__ == "__main__":
    main()

If I use the list[str] type for headings, pylance complaints that

Expression of type "Sequence[Unknown] | None" cannot be assigned to declared type "list[str]"

However, if I skip the type hint for headings, pylance complaints with headings[0] that

Object of type "None" is not subscriptable

How do I deal with this problem? I could ignore pylance warnings, but I do not want to do so, unless there is no solution to this problem.

2

There are 2 best solutions below

0
Zach Young On BEST ANSWER

I'd say your best bet is to do a simple assert that heading is not None:

try:
    with open("inpu.csv", newline="") as f:
        reader = csv.DictReader(f, skipinitialspace=True)
        headings = reader.fieldnames
        assert headings is not None
        assert len(headings) == 3

        menu: list[list[str]] = []
        for row in reader:
            menu.append([row[headings[0]], row[headings[1]], row[headings[2]]])
except IOError as e:
    print(f"couldn't open CSV: {e}")
except AssertionError:
    print(
        f"couldn't get fieldnames, check CSV and ensure first line is a properly encoded row"
    )
else:
    print(menu)

If it fails, you'll know to look at the CSV file for any kind of issues.

  • Before the assert, VSCode shows that headings could be Sequence[str] | None.
  • After the assert, the type checker realizes that it cannot have been None, so headings must be Sequence[str].

Also, look at Barmar's and Mark Tolonen's comments, you can do what you much more simply by just reading the CSV as a list of strings:

reader = csv.reader(f)
menu = list(reader)[1:]

print(menu)
[
    ["foo", "1", "a"],
    ["bar", "2", "b"],
    ["baz", "3", "c"],
]
0
Barmar On

fieldnames can potentially be None if the CSV file is empty.

You can solve your problem by providing a default value.

headings: list[str] = reader.fieldnames or ["", "", ""]