Leading dots in class references – why and why not?

Looking at Doc/library/datetime.rst I see this:

:class:`date`, :class:`.datetime`, and :class:`.time` objects all support a
``strftime(format)`` method, to create a string representing the time under the
control of an explicit format string.

What does the leading dot in front of the datetime and time classes mean, and why isn’t the date class referenced so adorned?

See The Python Domain — Sphinx documentation

Normally, names in these roles are searched first without any further qualification, then with the current module name prepended, then with the current module and class name (if any) prepended. If you prefix the name with a dot, this order is reversed. For example, in the documentation of Python’s codecs module, :py:func:`open` always refers to the built-in function, while :py:func:`.open` refers to codecs.open().

This is to avoid :class:`time` making a reference to the time module, and :class:`datetime` to the datetime module (instead of the class). TBH, I’m not sure why Sphinx is happy to insert a reference to a Python module for a :class: role.

3 Likes

That’s because mod, class, func, meth, data are all really «look this name up in the names index, make a link and display as code»! In other words not type-checked or limited. :func:`os` will work.

So, no difference, not even in indexes? :func:`os` wouldn’t cause os to turn up as a function in a generated function index? (I don’t know if that’s a thing in Sphinx…)

That example was for a reference. At the definition point, markup like .. module:: os will be used, so that the index correctly calls it a module.

IMO, we should instead spell these out:

:func:`~module.function`  # <= like this
:func:`.function`         # <= not like this

It is too easy to not see the leading dot; I’ve reworked docs where I’ve accidentally introduced the wrong link, because I did not see the leading dot in the first place. The explicit variant is IMO better.

4 Likes

Agreed, that’s just for the small number of experienced folks who know the trick; its potentially even more confusing and non-obvious to folks who don’t (as was the case here); even I didn’t know about it until relatively recently, well after I knew pretty much all the other ins and outs of the xref roles.

FWIW I get 302 hits for the use of . in this manner (:(class|func|meth|attr|data|const|mod):.[a-zA-Z]) in .rstfiles underDoc/ (a good deal more than I expected, actually), ; by contrast I get 4778 hits for the much more common use of ~ in this manner (:(class|func|meth|attr|data|const|mod):~[a-zA-Z])

1 Like

It does not work if you want to refer to classname.methname. You need to write more verbose :meth:`classname.methname <module.classname.methname>` if you do not want to write :meth:`.classname.methname`. I do not think that the former is much clearer, especially if you use several such references per paragraph.

. is a shortcut, which allows you to to use more laconic markup if it is applicable. It is can only be used in limited cases, but when it can be used, it makes a difference.

Yes, so a devguide recommendation would be welcome; one for the general case (spell it out), and one for the special case (use the shortcut).

Sounds like there’s more than one way to do it. Not too Pythonic. Maybe the problematic construction could be discouraged/deprecated? (Take this with a grain of salt. I have essentially no experience with Sphinx, and not much more with ReST.)

1 Like

To my surprise testing it just now, a leading . doesn’t just trigger xref lookup to search local names before global ones, but actually does something arguably a lot more interesting, theoretically useful, yet unintuitive: it searches under all modules for objects that match.

This mean that in e.g. the What’s New you can do :meth:.ClassName.method_name without using .. currentmodule, and the lookup will actually work (which is used a handful of places in the 3.4 What’s New)—if there happens to be only one ClassName.method_name in the entire docs, which is certainly not always the case. This even works with, e.g., :meth:`.methodname`—it will match a method named methodname under any class in any module (though of course, not terribly useful given the ambiguity and likelihood of multiple matches).

However, mo’ heuristics, mo’ problems—this capability actually has more issues than the previous, despite being nominally more powerful:

  • This is arguably even more surprising (as apparently no one here, including myself, was even aware of it until now)
  • A user intending to link to something in the current module may silently end up linking something completely different with no warning, if they mistype a name, it doesn’t exist/isn’t documented in the current module, or they misunderstand the complex lookup rules around this feature, and the name exists anywhere else in the docs.
  • It only works if there happens to be exactly one class of that name with that method anywhere in any module, in the present and the future, or it may unpredictably link to the wrong method (although a warning will also be emitted in this case, if nitpicky mode is enabled).

Therefore, given that one-off instances of needing this can be replaced with the slightly more verbose but explicit and clear form, and the use cases with multiple that really benefit (e.g. What’s New) can just use .. currentmodule, which has the same conciseness benefit while being more explicit and without all the caveats and complexity, I’m still not sure its worth using for us.

Yeah, but this is only relevant for the extremely narrow corner case where all of the following are true:

  • A class has the same name as its own module
  • That class has a method with the same name as a top-level function of that module
  • The documentation referencing the method is inside the module but outside the class
  • It wishes to reference the class’ method rather than module’s, AND
  • The writer wishes the text to display the (potentially confusing) classname.methodname, rather than either methodname or modulename.classname.methodname.

And of course, it is for those few cases that clarity and explicitness is most beneficial for both the reader and the writer, and for which the total verbosity cost is small by comparison.

In total, there are only 12 hits in .rst files under /Docs for :(class|func|meth|attr|data|const|mod):`(\.\w+){2,}`, which matches all xref roles that use a leading . for names with two or more dotted components. 8 of those are the aforementioned usages in the 3.4 What’s New (which could be replaced by currentmodule, with a substantial net increase to overall conciseness while improving explicitness), and in any case don’t fall under the specific case quoted.

The remaining four are all in datetime.rst, and of them:

  • In :meth:`.date.strftime` the . isn’t actually necessary, since there is no date module (much less one with a top-level strftime function), so :meth:`date.strftime` will resolve as intended to the class method.
  • In :meth:`.datetime.timetuple`, there is no top-level timetuple in the datetime module, so likewise :meth:`datetime.timetuple` resolves as intended.
  • In :meth:`.datetime.strftime` , again there’s no top-level strftime in the datetime module, so :meth:`datetime.strftime` resolves to the class method.
  • In :meth:`.time.strftime`, there is a time module with a strftime method, so without the prepended ., this would resolve to the wrong object (unless the lookup takes into account the specified role, which AFAIK it currently does not. Of course, it can be replaced with the more verbose but explicit :meth:`time.strftime <datetime.time.strftime>`, which would also avoid

So, in total, there’s only a single instance in the entire Python documentation where this corner case actually occurs, so it doesn’t seem worth using potentially confusing syntax over the more explicit form just for that reason.

Per agreed convention, normally only the first cross reference to an object in a paragraph (or equivalent information unit) need be resolved, so this would only occur at most once per paragraph; the rest would simply be :meth:!classname.methname regardless.

To be fair, at least conceptually, .spam is really more of a modifier to the implicit lookup behavior than a shortcut—particularly given the above findings. And it actually does have some advantages in terms of being more explicit and avoiding some silent mistakes than the unqualified un-.-prefixed versions, when used inside a module to refer to, e.g., a method of a class of the same name—in the datetime case, using e.g. .date.strftime and friends instead of just date.starftime, etc., which will always match the class method and never any module-level function, and makes it clear that is what is intended.

This would be a clear improvement, except that as a direct result of the above behavior, if no date.strftime is present under the datetime module, the .-prefixed version will search for and match any date.strftime method under any module in the docs, instead of simply stopping resolution and giving a clear warning. Therefore, its a mixed bag as to whether that’s really better, whereas only the explicit, fully-qualified version avoids both these sets of issues.

If there is enough consensus we could maybe consider add an opt-in sphinx-lint rule for it, due to the confusion, ambiguity and possible unexpected behavior. But I’m not sure how high on the list that would be, compared to other priorities.

1 Like

Totally agree. Let’s instead focus on unambiguous markup syntax.

Is there a “Best Sphinx Practices” or “Established Sphinx Conventions” document which highlights the best way to do stuff? Your reference is to a GitHub issue, not the most obvious place people will look.

There’s a dedicated pages over in the devguide.

And also the Style Guide page for general style. To note, not quite everything has made it over there yet—I’m not sure exactly where we formally added that specific suggestion (though I seem to recall it somewhere. PRs welcome!

1 Like