In typestubs for the Python standard library I noticed a peculiar type called MaybeNone
pop up, usually in the form of NormalType | MaybeNone
. For example, in the sqlite3-Cursor class I find this:
class Cursor:
# May be None, but using `| MaybeNone` (`| Any`) instead to avoid slightly annoying false positives.
@property
def description(self) -> tuple[tuple[str, None, None, None, None, None, None], ...] | MaybeNone: ...
The definition of this MaybeNone
is given as:
# Marker for return types that include None, but where forcing the user to
# check for None can be detrimental. Sometimes called "the Any trick". See
# CONTRIBUTING.md for more information.
MaybeNone: TypeAlias = Any # stable
(I could not find additional information in the CONTRIBUTING.md
, which I assume to be this one.)
I understand the intention of marking a return type in such a way that a user is not forced to null check in cases where the null is more of a theoretical problem for most users. But how does this achieve the goal?
SomeType | Any
seems to imply that the return type could be anything, when what I want to say is that it can be SomeType
or in weird cases None
, so this doesn't seem to express the intent.
MyPy already allows superfluous null-checks on variables that can be proven not to be None
even with --strict
(at least with my configuration?) so what does the special typing even accomplish as compared to simply doing nothing?
A nice summary can be found in this comment explaining the "Any Trick" of typeshed.
We tend to use it whenever something can be None, but requiring users to check for
None
would be more painful than helpful.
As background they talk about xml.etree.ElementTree.getroot
which in some case returns None
(Happens when the tree is initialized without a root).
To reflect this, getroot
was updated to def getroot(self) -> Element | Any: ...
with the possible return types(Element
) and additionally | Any
.
The different possibilities and effects are summarized as:
-> Any
means "please do not complain" to type checkers. Ifroot
has typeAny
, you will no error for this.
-> Element
means "will always be anElement
", which is wrong, and would cause type checkers to emit errors for code likeif root is None
.
-> Element | None
means "you must check for None", which is correct but can get annoying. [..., it could be possible used] to do things likeET.parse("file.xml").getroot().iter("whatever")
.
-> Element | Any
means "must be prepared to handle an Element". You will get an error forroot.tagg
, because it is not valid when root is an Element. But type checkers are happy withif root is None checks
, because we're saying it can also be something else than an Element.
I did slightly modify the quotes by adding italics and -> type