In argparse, what distinguishes a positional argument from an optional one?
answer
- Look at the first character
- Order versus name
- One of them is required by default
- Where the parsed values land
- nargs="?" softens a positional
basics
~20 sThe leading dashes decide. add_argument("dataset") defines a positional argument: required, matched by its place on the command line. add_argument("--dataset") defines an optional argument: matched by name, and when absent it takes its default. Both land on the Namespace.
solid answer
~40 s`ArgumentParser.add_argument()` branches on the first string you give it. No leading dash means a **positional** argument: it is matched by order, it is required, and the name you gave becomes the attribute name. A leading dash means an **optional** argument: it is matched by name anywhere on the line, it is not required unless you pass `required=True`, and when it is missing argparse stores its `default` (`None` if you gave none). `action="store_true"` builds a valueless flag whose default is `False`. `parse_args()` returns one `argparse.Namespace`; read it with attribute access, or `vars(args)` for a plain dict. A positional can be softened with `nargs="?"` or `nargs="*"`, and the generated `-h` lists the two kinds in separate groups, `positional arguments:` and `options:`.
code
python · 11 linesimport argparse
parser = argparse.ArgumentParser(prog="report")
parser.add_argument("dataset") # positional: required
parser.add_argument("--rows", type=int, default=6800) # optional: has a default
parser.add_argument("--verbose", action="store_true") # flag: default False
args = parser.parse_args(["sales", "--verbose"])
print(args)
print(args.dataset, args.rows, args.verbose)
print(vars(args))go deeper
Be ready to write a five-line parser from memory: one positional, one --option with a default, one store_true flag, then read them off the object parse_args() returns. Knowing that dashes make the difference is the whole answer here.
Explain the mechanics: how the dest is derived for each kind, why a positional is required unless nargs relaxes it, and what argparse actually stores when an optional is absent. Mention that vars() turns the Namespace into a dict.
Show judgement about the interface users will type. Positionals for the subject, options for modifiers, sane defaults so the common invocation is short, and help text that survives being read by someone at 3am debugging a failed job.
Own the consistency story across a team's tools: which shape is a positional everywhere, whether abbreviations are allowed, and how flag names stay stable once scripts and schedulers depend on them. Renaming an option is a breaking change to every caller.
### The switch is the dash, not a keyword `argparse.ArgumentParser.add_argument()` inspects the first string you hand it. If that string begins with one of the parser's prefix characters — `-`, unless you changed `prefix_chars` — argparse builds an *optional* argument. If it does not, argparse builds a *positional* argument. Nothing else selects between the two: there is no `positional=True` keyword, and passing `required=` to a positional raises `TypeError: 'required' is an invalid argument for positionals`. Everything else about the two kinds follows from that one branch. ### What each kind implies A positional is matched by **place**. The parser consumes the non-flag words of the command line in the order the positionals were declared, and a missing one is a parse error: argparse prints the usage line plus `error: the following arguments are required: dataset` to standard error and exits with status 2. A positional is therefore *required by default*, and a `default=` on one is only reachable if you also relax `nargs`. An optional is matched by **name**, and may appear anywhere on the line, in any order relative to other optionals. Long options accept both `--rows 500` and `--rows=500`; single-letter options accept `-r500`, `-r 500` and bundling such as `-ab`. An optional is *not required by default*; when the user omits it, argparse stores whatever you passed as `default`, and `None` when you passed nothing. `required=True` is available but reads as a contradiction in the help text, where the argument still appears in the `options:` group. ### Where the value lands `parse_args()` returns a single `argparse.Namespace` — a plain attribute bag holding one entry per declared argument. You read `args.rows`, and `vars(args)` converts it to a dict, which is the idiomatic way to log the whole parse or to splat the values into a function call. The attribute name (argparse calls it the `dest`) is derived differently for the two kinds. For an optional, argparse takes the first long option string, strips the leading dashes and replaces internal hyphens with underscores, so `--dry-run` becomes `args.dry_run`. For a positional, the name you wrote **is** the dest verbatim: `add_argument("input-file")` produces a Namespace key `'input-file'`, which attribute syntax cannot reach at all. Name positionals with underscores, or pass an explicit `dest=`. ### Flags are just optionals with an action `action="store_true"` declares an optional that takes no value: present means `True`, absent means `False`, and you never write `default=False` yourself. Its mirror is `action="store_false"` (absent means `True`). `action="count"` gives `-vvv` semantics, `action="append"` collects repeats into a list, and `argparse.BooleanOptionalAction` (added in 3.9) generates the `--cache` / `--no-cache` pair from one declaration. ### Softening a positional `nargs` is how a positional stops being mandatory. `nargs="?"` accepts zero or one value and falls back to `default`; `nargs="*"` accepts any number and yields a list; `nargs="+"` accepts one or more and still errors on none. Those are the supported ways to make a positional omissible — `required=False` is rejected outright. ### Choosing between them when you design a CLI The convention users already know is: the positional is the *thing the command acts on*, and optionals are *modifiers of how it acts*. A report generator takes the dataset as a positional and `--rows`, `--since` and `--verbose` as optionals. Two or three positionals is usually the limit before order becomes a memory test; past that, name them. ### Generated help and two pitfalls Unless you pass `add_help=False`, argparse installs `-h/--help` for you and groups the two kinds under their own headings. The heading for optionals was renamed from `optional arguments:` to `options:` in Python 3.10, which is the difference you will notice comparing help output across versions. Two behaviours surprise people. First, `allow_abbrev` defaults to `True`, so any unambiguous prefix of a long option is accepted: `--verb` works when `--verbose` is the only match. Pass `allow_abbrev=False` if you want strict names. Second, a value that itself starts with a dash confuses the matcher — `--rows -1` looks like two options — so write `--rows=-1`. When you need the leftovers rather than an error on unknown words, `parse_known_args()` returns a `(namespace, remaining_list)` tuple instead of failing.
- How do you make a positional argument that the user may omit?Relax `nargs`, not `required`. `nargs="?"` accepts zero or one value and falls back to `default`; `nargs="*"` accepts any number and yields a list, so `default=[]` is the sane pairing. Passing `required=False` to a positional is not supported — argparse raises `TypeError: 'required' is an invalid argument for positionals`.
- Where does the Namespace attribute name for --dry-run come from, and when does that rule not apply?For an optional, argparse takes the first long option string, strips the leading dashes and replaces internal hyphens with underscores, so `--dry-run` becomes `args.dry_run`. Positionals are different: the name you passed is the dest verbatim, so `add_argument("input-file")` yields the key `'input-file'`, unreachable by attribute access. Name positionals with underscores or pass `dest=`.
- When is required=True on an optional argument a design smell?When the argument is really the subject of the command. `report --dataset sales` with `required=True` forces users to type a name that carries no choice, and `-h` still shows it under `options:`, which reads as untrue. Make it a positional instead. `required=True` earns its place only for a genuinely modal input, such as an output target that has no safe default.
saying these in an interview costs you the question
- Says a required= keyword is what makes an argument positional
- Thinks a positional can never be omitted, ignoring nargs
- Expects args.dry-run to work as attribute access
- Believes a store_true flag defaults to None
- Claims optional arguments must be given in declaration order
- Reads sys.argv by hand after calling parse_args