python / python/typeshed

Implications of Union in argparse.Action.__call__ parameter types

Open
#7,559 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
5.1k
Forks
2.1k
Avg merge
1d 19h
Merged PRs (30d)
82

Description

https://github.com/python/typeshed/blob/c41034c3540c5abda10008dd6c17d8e6ff6cc634/stdlib/argparse.pyi#L364-L366
I'm wondering if the reasoning behind this choice has been discussed before. Return types of library functions generally avoid Union (python/mypy#1693). To me, the parameters of a user-provided callback are similar to the return type of a library function—the annotations reflect objects passed to user code, and the user may have runtime control of their types. It is, however, more removed from the runtime context because the class must be first defined and then referenced.

If I override __call__ with an implementation that will only ever see str:

import argparse

class CSVCallback(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, list(values.split(',')))

parser = argparse.ArgumentParser()
parser.add_argument('-o', action=CSVCallback, default=[])

args = parser.parse_args()

pyright complains, based on what appears to be a correct interpretation of the stub:

  repr.py:5:51 - error: Cannot access member "split" for type "Sequence[Any]"
    Member "split" is unknown (reportGeneralTypeIssues)
  repr.py:5:51 - error: "split" is not a known member of "None" (reportOptionalMemberAccess)

Would the appropriate solution be to add assert isinstance(values, str) at the beginning of every callback? Or is this actually abuse of action=..., which should be replaced with type=..., like this?

def csv_callback(value: str) -> list[str]:
    return list(value.split(','))

parser.add_argument('-o', type=csv_callback, default=[])

Since these examples are based on 10-year-old optparse code that was ported 1:1 to argparse 2 years ago, I'm not considering reimplementing them like that—they aren't broken, and the less trivial examples reference parser.error and the previous value of self.dest. But I'd rather avoid the asserts, so for new code I would probably prefer that pattern.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review stdlib/argparse.pyi at lines 364-366 and the linked discussion about Union in library interfaces first. Compare the Action callback example with the type= example and determine whether the stub should change or the existing annotation should be documented; done requires a maintainer-approved resolution.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
tooling
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.