Open
Description
Consider these two similar python programs, which have identical behavior:
#! /usr/bin/env/python
import configparser
import re
import sys
if __name__ == "__main__":
for filename in sys.argv[1:]:
cparser = configparser.ConfigParser(interpolation=None)
cparser.read(filename)
changed = False
for _ignored, section in cparser.items():
if section.name == "DEFAULT":
continue
for k, v in section.items():
if re.fullmatch(r"\d+", v):
section[k] = str(int(v) + 1)
changed = True
if changed:
with open(filename, "wt", encoding="utf-8") as fp:
cparser.write(fp)
and
#! /usr/bin/env/python
import configparser
import re
import sys
if __name__ == "__main__":
for filename in sys.argv[1:]:
cparser = configparser.ConfigParser(interpolation=None)
cparser.read(filename)
changed = False
for section in cparser.values():
if section.name == "DEFAULT":
continue
for k, v in section.items():
if re.fullmatch(r"\d+", v):
section[k] = str(int(v) + 1)
changed = True
if changed:
with open(filename, "wt", encoding="utf-8") as fp:
cparser.write(fp)
(The only difference between the two programs is invoking cparser.items()
or cparser.values()
)
The first program type-checks in mypy
without any issues, but the second program:
/tmp/incints.py:13: error: "Mapping[str, str]" has no attribute "name" [attr-defined]
/tmp/incints.py:17: error: Unsupported target for indexed assignment ("Mapping[str, str]") [index]
Found 2 errors in 1 file (checked 1 source file)
The problem is that ConfigParser
objects accept assignment of arbitrary str
-> str
mappings but when you pull a value back out of a ConfigParser
, the value pulled out is always a configparser.SectionProxy
. (see the __getitem__
and __setitem__
methods on configparser.RawConfigParser
)
Note that this affects not only the return type of values
but also the return type of __getitem__
; it should be perfectly well-typed to write cparser[some_str_value].name
.