Introducing a Safe Navigation Operator in Python

I’ve been considering the idea of proposing a new feature in Python - a Safe Navigation Operator, similar to what’s available in languages like JavaScript, Ruby, and C#. Before proceeding with writing a formal PEP, I wanted to bring this up here to gather initial feedback, insights, and ascertain if such an idea has been proposed or discussed before :raised_hands:

Motivation

In Python, attempting to access an attribute or method of a NoneType object raises an AttributeError exception. This often requires writing boilerplate code to check if an object is None before accessing its attributes or methods. The Safe Navigation Operator would mitigate this by returning None if the object is None, rather than raising an exception.

For example, consider the following code:

if obj is not None and obj.attribute is not None:
    print(obj.attribute)

With the Safe Navigation Operator, this can be simplified to:

print(obj?.attribute)

Specification

The Safe Navigation Operator (?.) would be a new operator allowing attribute and method access to be short-circuited when the base object is None. If the left-hand operand is None, the expression evaluates to None; otherwise, it evaluates normally.

Benefits

  1. Readability: The code becomes cleaner and more readable, reducing the cognitive load required to understand the null checks.
  2. Conciseness: It reduces the amount of boilerplate code, leading to more concise code representation.
  3. Safety: Helps in avoiding AttributeError exceptions caused by attempting to access attributes or methods on a NoneType object.

Rationale

Languages like JavaScript, Ruby, and C# have already adopted similar operators, resulting in cleaner, more concise code. Introducing the Safe Navigation Operator in Python would offer similar benefits, enhancing code readability and maintainability.

Backward Compatibility

This change is backward compatible as the ?. operator is not currently used in Python, and its introduction would not break existing code.

I believe this operator can be a valuable addition to the language, especially for developers coming from other languages where this is a standard feature. However, I’m interested in hearing the community’s thoughts, concerns, and feedback on this.

Is this something that Python developers would find beneficial? Are there any potential issues or conflicts that I might not have considered?

21 Likes
3 Likes

Hello Chris,

Thank you for pointing me to that PEP. I wasn’t aware of its existence. However, I noticed that the PEP is in a deferred status. I believe that by revisiting and possibly enhancing this PEP, we can make strides towards making Python even more user-friendly, efficient, and safe.

5 Likes

You’ll need to look through the objections to this proposal and answer them. Why are the problems no longer problems? Are there new advantages that weren’t known about when that was written?

10 Likes

You’ll need to look through the objections to this proposal and answer them. Why are the problems no longer problems? Are there new advantages that weren’t known about when that was written?

I think this is quite an unfair burden to OP. The PEP lists no objections, the commit changing the status to “Deferred” lists no reasons. The PEP doesn’t even have a “Discussions-To” header. After five years it seems appropriate to start a new discussion, instead of digging through three years worth of mailing list archives. (The time between the creation of the PEP and the change to “Deferred”.)

20 Likes

Agreed the PEP lacks any real explanation of what happened. Maybe the OP could reach out to the authors, @steve.dower and @mehaase, to ask what happened to the PEP and whether there is any benefit in restarting the discussion, though?

Otherwise, it’s quite likely that many people here will simply think “oh, not this again” and not comment[1], so not following up on the previous PEP is likely to give a misleading impression of the community feeling on the proposal.


  1. I know that’s what I was going to do until you prompted me to make this comment. ↩︎

7 Likes

There has been quite a bit of more recent discussion.

7 Likes

I actually hadn’t checked the PEP to see if it listed any objections, but the point still somewhat stands: what do we know now that hasn’t already been gone over? What new information do we have? If you look through all of the previous discussions, they all petered out, partly because one of its original authors is no longer in favour of it. (Unless that’s changed, in which case, that would indeed be new information.)

So, why did all those discussions falter and fail? What is different this time? Is there a compelling argument that we haven’t heard yet?

3 Likes

As far as I understand, one of the main sticking points is always that ? is one of the last ASCII characters available for new syntax, and PEP505 usage is merely a small quality-of-life addition to the language. But I wonder if that ship hasn’t sailed, given how common the ? operators have become in other widely-used languages, since that fact might immediately shoot down any non-PEP505 proposal to use ?.

3 Likes

I have reviewed PEP 505 and noticed that the document doesn’t provide explicit reasons for its deferral. However, I can make some educated guesses as to why it might have been deferred.

In terms of complexity,

  • The PEP proposes the introduction of new operators (??, ?., and ?[]), which could increase the language’s complexity.
  • The implementation of these operators would require substantial changes to Python’s grammar and could potentially introduce unforeseen issues or edge cases.

I think another reason might be that it proposes several changes to the Python language at once as follows;

  • None Coalescing Operator (??)
  • None-Aware Attribute Access Operator (?.)
  • None-Aware Indexing Operator (?)
  • Augmented Assignment for None Coalescing Operator (??=)

Introducing multiple new operators at once can add a level of complexity to the language. Each operator needs to be evaluated for its individual merit and how it integrates with existing features.

5 Likes

Agreed, but I would consider ?. and ?[] to be sufficiently closely related that they can be discussed as a pair. Those are also the ones I personally would be making the most use of; whether my situation is representative or not is another question though.

3 Likes

From what I remember, one of the main problems was whether None was “special” enough. Why aren’t we coalescing other sentinels?

1 Like

Trust me, None is special enough. No doubt about it.

27 Likes

It’s been deferred for so long, just consider it withdrawn (which means the PEP author never sent it to the SC and doesn’t plan to at this point).

Steve can correctly me if I’m wrong, but I believe he couldn’t get enough consensus around the idea to muster the desire to keep pushing it forward.

1 Like

There is a comment in this thread linking the many discussions bringing up this topic. Please read them so you don’t have to guess why people are hesitant too add this feature.

2 Likes

For what it’s worth, I didn’t know about PEP 505 until it was too late to cast my vote in favour of it.

I dearly wish python had some kind of safe navigation mechanism around dealing with None, but I also understand the hesitance around adding ? operators. The big problem for me with that syntax is that although it will be familiar to programmers coming from other languages which have that operator, it will likely be confusing and scary for new programmers who have no idea what to make of it and lack the proper programming vocabulary to know what to look up to make sense of it.

6 Likes

I have an alternative proposal, one that would allow people to try out safe navigation of None objects without making any (IMO) drastic changes to python:

from __future__ import safe_none

# This import alters the behaviour of NoneType such that attribute lookups on None and indexing operations on None and calling None return None

my_nested_data: dict[str, list[dict[str, str]]] | None

my_nested_data = {
    "sub": [
        {"otherfield": "hello"}
    ]
}

assert my_nested_data['sub'][0]['otherfeild'].upper() == "HELLO"

my_nested_data = None

assert my_nested_data['sub'][0]['otherfeild'].upper() == None

thoughts?

1 Like

No, for several reasons.

  1. Errors should not pass silently, unless explicitly silenced. Python’s normal approach is, 99.999% of the time, correct: bomb immediately on error. Even JavaScript wouldn’t have this massive infection of None (or as it would be there, undefined), as chaining another step after an undefined would give you an error.
  2. Future directives are an often-misunderstood feature of the language. They are compiler flags and they generally apply to a single module, allowing you to change the behaviour of one module independently of another. Generally, this means syntax changes, since you can’t really test those out any other way (for example, if print is a keyword, it’s a SyntaxError to say def print(), so you need a directive to say “print isn’t a keyword in this module”). While it’s possible for the compilation directive to apply to objects defined in that module (see eg generator_stop), there’s no clear way for that to make sense with this behaviour.
  3. You still have all the same problems of defining the end of the expression, but now, instead of locally requesting this behaviour, it’s done globally.

So, no, absolutely not. This is a much MORE drastic change than introducing new syntax.

I am in favour of having some sort of None-aware operators, although not necessarily with exactly PEP 505 semantics. Personally, I’m fine with consuming the question mark in this way, but I’m aware not everyone is.

8 Likes

Thank you for weighing in, Guido. Your perspective is always highly valued.

I absolutely agree that None holds a unique and significant place in Python, serving as a universal sentinel value denoting “nothing” or “undefined”. It’s in the light of this special status that we find potential utility in operators designed to work with None efficiently.

The aim of reintroducing the discussion around PEP 505 is not to challenge None’s distinctiveness, but to explore ways we might make Python even more robust and user-friendly. If None is indeed special enough, could there be merit in having explicit operators that help manage it effectively? In scenarios where users deal with None frequently, such operators could enhance code readability and maintainability, promoting Python’s philosophy of readability and simplicity.

I’m eager to understand if there are specific technical or philosophical challenges to adopting such operators, aside from the increased complexity they might introduce. Could there be a middle ground or a simplified proposal that captures the essence of easing operations involving None, while mitigating concerns about complexity and integration with existing features?

3 Likes

@bsavas The following code:

Cannot be simplified to:

What should the print() function output if obj is None or if the attribute attribute does not exist?

That is the first question I ask my friends about the usage of the ‘Safe Navigation Operator.’ They just give me a thousand-yard stare.

2 Likes