# Should \`None\` defaults for optional arguments be discouraged?

**URL:** <https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554>\
**Category:** Core Development\
**Created:** [February 5, 2023, 1:04pm UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554 "2023-02-05T13:04:56Z")\
**Posts on this page:** 8\
**Page:** 2

<div class="post-metadata">

**Author:** ![steven.daprano](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steven.daprano/32/1083_2.png) [@steven.daprano](https://discuss.python.org/u/steven.daprano)\
**Post date:** [February 8, 2023, 1:36am UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/22 "2023-02-08T01:36:00Z")

</div>

> [@](#):
>
> I don’t see any reason that the stdlib should _in general_ discourage the use of `None` to indicate an optional argument, but there may be individual cases where it muddies the waters.

Well said!

None is often a perfectly fine default.

But sometimes a function is more aesthetically pleasing if the default is not None. Then we get to argue about subjective judgements of beauty in code, which is always fun 😉

And Raymond is right that we should make AC and Signature objects smarter and better able to deal with alternative APIs, and so reduce the pressure that every API must use defaults.

---

<div class="post-metadata">

**Author:** ![fancidev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/fancidev/32/9983_2.png) [@fancidev](https://discuss.python.org/u/fancidev)\
**Post date:** [February 8, 2023, 11:05pm UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/23 "2023-02-08T23:05:52Z")

</div>

Seems an ok compromise here.

VB/COM had a sentinel for “missing” argument. But it sounds an overkill to introduce such a sentinel to Python given the ecosystem has grown well without it.

---

<div class="post-metadata">

**Author:** ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)\
**Post date:** [April 5, 2023, 3:32pm UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/24 "2023-04-05T15:32:28Z")

</div>

The problem is how to **not** document a default value in the documentation, especially in introspection APIs like `inspect.signature()`. I don’t think that accepting a surprising but _valid_ default value like `-1` or `None` is a big deal: as Guido explained, it’s just convenient and practical.

When APIs were documented manually, we used syntax like `math.log(value[, base])` for functions accepting 1 or 2 arguments. I never liked this syntax, it’s barely readable with many arguments, but so far, no one came with a better syntax 🙂

Right now, the `inspect.signature()` API has no way to express “the default value should not be documented”. See also [PEP 661 Sentinel Values](https://peps.python.org/pep-0661/): many functions use sentinel values which _should not_ be documented.

---

<div class="post-metadata">

**Author:** ![larry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/larry/32/936_2.png) [@larry](https://discuss.python.org/u/larry)\
**Post date:** [April 10, 2023, 2:05pm UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/25 "2023-04-10T14:05:18Z")

</div>

Personally I’ve never been a fan of functions with mystery internal-only defaults. I see this as an anachronism when Python function declaration syntax wasn’t as rich, and a lot of functions were written in C with handmade argument parsing or PyArg\_ParseTuple argument parsing, and maybe from before signature introspection. If I could wave my magic wand and change the world, I’d change Python so that optional arguments always had default values visible in the function’s signature.

Occasionally I stub my toe on this. For example, I might want to call a function both with an explicit value or with the default value of a parameter, but because the function has this mystery default value, I have to work a little harder–conditionally passing in the argument by splatting it in using `*args` or \*\*kwargs, or maybe just writing two function calls with an if`. I admit I haven’t run into this recently enough for me to remember an example.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [April 11, 2023, 2:14am UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/26 "2023-04-11T02:14:50Z")

</div>

> [@larry](#):
>
> Personally I’ve never been a fan of functions with mystery internal-only defaults. I see this as an anachronism when Python function declaration syntax wasn’t as rich, and a lot of functions were written in C with handmade argument parsing or PyArg\_ParseTuple argument parsing, and maybe from before signature introspection. If I could wave my magic wand and change the world, I’d change Python so that optional arguments always had default values visible in the function’s signature.

PEP 671 works in the same kind of field, trying to make defaults more visible even when they aren’t simple values. So I definitely agree with the principle (although with C-implemented functions, there’s probably no single approach that would work for all of them).

---

<div class="post-metadata">

**Author:** ![kknechtel](https://avatars.discourse-cdn.com/v4/letter/k/e47c2d/32.png) [@kknechtel](https://discuss.python.org/u/kknechtel)\
**Post date:** [April 11, 2023, 4:58am UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/27 "2023-04-11T04:58:37Z")

</div>

> [@larry](#):
>
> If I could wave my magic wand and change the world, I’d change Python so that optional arguments always had default values visible in the function’s signature.

The issue is, this breaks for builtins. It seems pretty awkward trying to explain, e.g. the `range` constructor in these terms. What’s clearer:

> `range. __init__ (start, stop, step=1)` separately allows you to call it with only one argument, as `range. __init__ (stop)`, in which case `start` defaults to `0` and `step` to `1`

or

> `range. __init__ (start, stop=NOT_PROVIDED, step=1)` will check if `stop is NOT_PROVIDED` and, in this case, reassign the provided `start` value to `stop` and assign `0` for `start`

? Granted, the latter is still clearer than showing the defaults as `None`. But it’s still quite awkward, and then it turns out that there isn’t an actual sentinel `NOT_PROVIDED` value you could use explicitly (even if you had access to it), and the internals [actually take more of a \*args-equivalent approach](https://github.com/python/cpython/blob/main/Objects/rangeobject.c#L75) anyway, and in fact you can’t pass arguments by keyword at all…

Granted, if we had a time machine we could make it so that the built-ins only ever have Python-styled function signatures. (Maybe we could incorporate PEP 570 from the start while we’re at it.) But then it seems like you’re either stuck with the `NOT_PROVIDED` hack and the weird explanation in the doc (and then, do you actually want people to be able to write it with the sentinel explicitly?) Or maybe you don’t get a 1-arg form at all. Or you document the start/stop parameters as `*endpoints` and have to explain that actually it can only be either 1 or 2 elements etc.

On the other hand, being able to write `range(10, step=2)` does seem kinda neat…

---

<div class="post-metadata">

**Author:** ![larry](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/larry/32/936_2.png) [@larry](https://discuss.python.org/u/larry)\
**Post date:** [April 11, 2023, 11:17am UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/28 "2023-04-11T11:17:01Z")

</div>

> [@kknechtel](#):
>
> The issue is, this breaks for builtins. It seems pretty awkward trying to explain, e.g. the `range` constructor in these terms.

I think this reflects more on `range` than on my position. As you point out, the signature of `range` can’t be easily expressed with a Python signature–and yet it’s a Python function. I regard the behavior of `range` as a historical artifact, the result of an early experiment in alternative approaches to argument processing from the early prehistory of Python. I assert that if we added `range` to the language today its signature would look very different.

Of course, I don’t have my magic wand, and I can’t retroactively change things, so my opinion there isn’t really actionable. We need to live with the situation as it exists. In order to non-awkwardly express the signature of `range` in Python, either we’d need to change the signature of `range`, or change the definiton of a Python function signature so it encompasses the existing behavior of `range`. Since the former is a non-starter, our only remaining choices are “enhance Python function signatures to encompass `range`” or “live with it being awkward”.

---

<div class="post-metadata">

**Author:** ![davidism](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/davidism/32/3295_2.png) [@davidism](https://discuss.python.org/u/davidism)\
**Post date:** [April 28, 2024, 8:08pm UTC](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554/30 "2024-04-28T20:08:35Z")

</div>

Closed on April 28, 2024, 8:08pm UTC

Don’t resurrect old topics.

[Previous page](https://discuss.python.org/t/should-none-defaults-for-optional-arguments-be-discouraged/23554.md?page=1)
