How should we mark up multiple types in a type field?

When using the :type: and :rtype: directives, how do we want to mark up multiple types?

Currently, we’re using a bar (|), similar to how you’d annotate a union of types:

:param p:
   A parameter that takes an int or a float argument.
:type p: int | float

:param other:
   Possibly a path.
:type other: path-like object | None

However, the Sphinx docs says to use the word or, like this:

:param p:
   A parameter that takes an int or a float argument.
:type p: int or float

:param other:
   Possibly a path.
:type other: path-like object or None

The rendered docs look the same[1], so it is a matter of style. In PR #116389, @alexwaygood pointed out that using or could be more readable for the casual docs reader.

It would be nice to settle on a single style and document it in the devguide.

  • Typing-like: int | float
  • The Sphinx way: int or float
0 voters

  1. other than the | or or separator ↩︎

3 Likes

Which “casual docs reader” is reading the raw ReST and not the rendered docs?

1 Like

Oh, the rendered docs look the same? Are they rendered “typing-style” or “Sphinx-style”? Can you edit a screenshot into your original post?

1 Like

Yeah, FWIW, I’d prefer they be rendered Sphinx style, but am quite happy to type them in using vertical bars.

The rendered docs look like the source.

This:

   :type timeout: float or None

renders like this:

And this:

   :type timeout: float | None

renders like this:

2 Likes

If that were the case I’d agree, but I believe what @erlendaasland was referring to was everything rendering the same, other than the “or”/"| character—I see how that could be unclear, as I had to go double-check myself.

With that being the case, I’d have to go with “or” as being clearer for readers, despite having used | before myself in the same context, as the meaning may not be obvious to newer users who may not be familiar with the syntax (particularly since the bar is currently italicized, which looks rather strange to my eye).

4 Likes

Ah, I see. Consider my vote changed, then

2 Likes

float | None is what Python needs in the .py file, and float or None is a valid sequence of Python tokens that produce a SyntaxError. float or None could lead people to an misleading readability trap.

So, “or” is more readable in English, but these are short annotation, and I think it would be better to parallel the actual syntax.

3 Likes

No, it evaluates to float.


Also, as a comment on that blog post points out, the trap isn’t readability, it’s writability. I am honestly not too concerned with beginners trying to copy over float or None into their projects. If they try to use it as an annotation, it’s not going to do anything, unless they start using a type checker, in which case they have to learn a lot of stuff anyway (including the difference between | and or). The docs should be concerned with readability of the docs only, which is potentially relevant for people who haven’t learned anything about the actual typing parts of python ecosystem, or even the bitwise operator that is being “misused”.

1 Like

This is also why, I feel, displaying the type names as literals (monospace) would help add clarity, as it would make clear that float and None are Python literals, while “or” is just a normal prose word.

3 Likes

Sorry, you are right, it evaluates to float but it is an error for a type checker.

I don’t think monospace/proportional/monospace is a clear enough indicator (especially to beginners) that this isn’t valid Python code. The more the docs mirror Python syntax, the better.

I agree with the first part, but disagree with the second :slight_smile:

The less it looks like something unique to Python, the less likely it’ll be copied over or imitated. Using the vertical bar seems like something special, rather than prose, and if copied it’ll be very non-obvious that it isn’t doing anything (or is doing something unexpected).

Would be great if we can find some formatting that makes the “or” look like prose, though. Maybe an Oxford comma?

timeout (float, or None) …

2 Likes

Sorry, I’m confused. You say that using monospace for code literals and plain/italic for prose won’t be clear enough that the result isn’t literal Python code, particularly for beginners, but then go on the state “the more the docs mirror Python syntax, the better”, when that is the very concern motivating the former.

Neither form is intended to be literal Python code nor be used as such, but rather to make clear to readers what types functions accept and return. The types shown are presented for maximum clarity to readers, and may not necessary match the programmatic typeshed type annotations used under the hood for various reasons (the more complex of which can be challenging to comprehend for a non-expert, much less a beginner who’s never heard of type annotations), leading to subtle bugs if a users were ever to try to use them in code. Others use glossary terms or protocol descriptions (file-like object) to accurately describe the accepted types, such as e.g. code object in the linked PR. Using syntax and formatting that is more clearly not incorrectly confused with literal code makes that, as well as the actual meaning, more clear.

I can certainly I sympathize with the desire to mirror actual type annotation syntax, and as mentioned previously did so in the number of callables I documented types for in this way, for that reason. However, after considering the points raised by Alex and others, the increased clarity and avoidance of confusion for readers, particularly newer ones (which is the overriding consideration for reference documentation) with “or” seems to be a decisive advantage over mirroring the type annotation syntax, particularly for something that’s not actually intended to be a literal programmatic type hint.

4 Likes

I see now that I failed the 101 on how to present a poll: I did not state how I intended to use the poll results. To be frank, I’m not sure how I intend to use the poll result :slight_smile: I propose we discuss this at the next docs meeting.

1 Like

Maybe there can be hidden text that’s apparent when copied, that has the explanation that the or isn’t python and this text shouldn’t be copied for use in code?

timeout (float, or None) timeout (float, <or> None) - NB this or is prosaic only; this text shouldn't be copied as correct python syntax

It looks to me that the sub-discussion about C&P from :type: fields is based on the premise that all :type: fields are marked up with typing compatible names[1]. However, this is not always the case. A lot of functions and methods take path-like and file-like objects[2]. In order to be more fair towards these, I expanded the example in the OP with this case[3]:

:param other:
   Possibly a path.
:type other: path-like object | None

vs.

:param other:
   Possibly a path.
:type other: path-like object or None

Take this into account when discussing the pros and cons of each style.


  1. int, None, etc. ↩︎

  2. other examples would be callables, and other non-basic types, often explicitly marked up using :term: or :ref: ↩︎

  3. note that path-like and file-like refs would normally be marked up using :term:, but I omitted this, since IMO it is not relevant for the discussion ↩︎

2 Likes

I see the point about some types not being actual Python types that can be used. Perhaps an approach would be: either the types should be actual Python types with pipes for alternation, so the entire annotation is Python code in monospace; or the types should be presented prosaically in proportional type. It’s the authors’ choice which to use.

Switching typefaces word by word is not a strong enough signal to be clear.

1 Like

My experience on a third-party project matches that of @erlendaasland above: type descriptions in docstrings are very likely not valid type annotations, as they use informal prose such as “bytes-like” or “buffer-like”.

We should probably not conflate documentation, meant for human readers and tailored for maximal comprehension by them, and type annotations, meant for machine checkers and optimized for correctness and accuracy (selectivity + specificity). They do not need to share the same conventions.

3 Likes

Or, rather, with an appropriate glossary term cross-reference to clarify what this actually means, as such prose terms should generally be (of some form):

:param other:
   Possibly a path.
:type other: :term:`path-like object` | None

vs.

:param other:
   Possibly a path.
:type other: :term:`path-like object` or None

Right, but as described above even if the types presented are literal Python types, they quite often will not literally correspond to the function’s actual programmatic annotations in typeshed, which this would imply, and users should not be induced to treat them as such (rather than what they truly are, as many here have emphasized: a clear, concise human-readable reference of the accepted/returned types).

Furthermore, this leads to inconsistencies in how types are presented between and possibly even within functions, leading to even more potential for reader confusion and being harder for other writers to emulate (a point which you aptly made in support of other efforts to make similar such things consistent).

Strongly agreed with everything there, though I do note it is important to define and cross-reference those terms and use them consistently (as the Python docs generally do), so that readers are not left guessing or assuming the author’s intent. While it needn’t be as rigorously precise as a programmatic type specification, as reference documentation it should still clearly and unambiguously communicate to readers what types are accepted/returned, if not directly then by reference.

2 Likes

Sure, hence the last footnote in my post.

1 Like