Introducing a Safe Navigation Operator in Python

None. By definition. That is the only possible thing you could get.

Which means your two examples are NOT equivalent; however, you can simplify it to this:

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

Like all new pieces of syntax, it must be learned. But that’s true of all the syntax we already have in the language. The “thousand-yard stare” phenomenon could just as easily be caused by a list comprehension:

distances = [(a.x - b.x) ** 2 + (a.y - b.y) ** 2 for a, b in segments]

or a decorated function:

@app.route("/", methods=["GET"])
def home(): ...

or a regular expression:

if m := re.search("[a-z]+[0-9]+", text):
    print(m.groups(0))

or any of a number of other constructs. This is no different. So the question is not “do we all already know what this means”, but “can we learn it, and does it justify the complexity it adds to the language?”.

Personally, I believe that the answer is “yes and yes”.

10 Likes

To add onto this, I would assume the attribute not existing would raise an AttributeError. Since this is proposing to short-circuit on None, not missing attributes.

3 Likes

True, and identically so on all of the examples.

1 Like

These examples are not mine.

Ah, sorry. I should have said “those” examples. Point still stands: they are not equivalent, but the duplication can certainly be cut down somewhat.

2 Likes

The following code:

if obj and obj.attribute:
    print(obj.attribute)

Can be simplified to:

attribute = obj?.attribute
if attribute:
    print(attribute)

Or:

if attribute := obj?.attribute:
    print(attribute)

I don’t see any improvement.

1 Like

The simplification only really becomes obvious with long chains - obj?.attr1?.attr1a?.attr1a1. This is something you see in code that gets data out of JSON, but more often with the ?[] variant.

4 Likes

You still have to use a try/except block:

try:
    # attr1a1 = obj?.attr1?.attr1a?.attr1a1
    attr1a1 = obj.attr1.attr1a.attr1a1
except AttributeError:
    pass

This makes Safe Navigation Operator usage redundant.

1 Like

Personally, I most often wish for the simplification when handling optional parameters.

def frob(widget, template=None):
    setting = template.attrib if template is not None else None
    # setting = template?.attrib
2 Likes

Same here, or when handling optional attributes. Quite a common scenario when working with an ORM, for example:

client_category = project.client.category if project and project.client else None

would become

client_category = project?.client?.category
1 Like

My first reaction is visual discomfort. I am confused by the example. Please clarify which tokens do you see in the expression obj?.attribute. Is ?. one operator or two?

One, same as <= or := or any other two-character operator. Why is this a problem?

I disagree with a couple of the states benefits.

I find code which allows for nulls (Nones in Python) harder to read at a higher level. Now I have to consider many values may be null, instead of just the type they are in the positive case.

Consider the following:

a = b?.c?.d?.e

If a is null, was b, c, d or e null? I have run across this many times in JavaScript/Typescript (JS/TS), meaning I have to inspect each value to see which caused the null, ending up with the very if-statements this proposals tries to replace.

I find code with more nulls flying around less safe, with types more fluid. After years of use of both languages, JS/TS feel less reliable to me due to this, whereas Python I have more confidence in the type (or shape, when I’m duck-typing).

Making nulls more ubiquitous means every value must be considered as possibly null unless explicitly guarded. I find Python’s current idiom where shapes are stronger to be safer.


I don’t think the use of question mark ? is a problem in this proposal, as other languages have already applied it for this use, and the character evokes feelings of “maybe”.


In the case of ORMs having data-classes with optional attributes, I prefer to use ternaries or if-statements as they’re more explicit and readable, and short-circuiting for use:

if outfit.shirt:
    print(outfit.shirt.colour)

main_colour = (outfit.shirt or Shirt.default()).colour or Colour.default()

PS: sorry if I’m rehashing points already made in the previous discussions: no one has responded in this topic to all of the criticisms of the proposals in those discussions, so I don’t know if they’ve been considered.

One thing that has changed since those discussions is the maturity of the operator in JS/TS, meaning we can gather knowledge from there.

14 Likes

Ok, now I get it. Thanks

So, it’s a new binary operator for achieving the same as the expression: obj.attribute if hasattr(obj, "attribute") else None

2 Likes

I came to Python from C#, and I did at first miss the Null-conditional operators.

However, after learning the Python idoims, I’m only lukewarm on introducing a Python safe navigation operator, as I think Python makes is easier to deal with None. I think this because Python treats None somewhat more specially (e.g. is not None vs != null, != None is discouraged).

Although, many languages have a ‘safe navigation’ operator, and almost all that I know use ?..

Some ‘short-circuit’: i.e. stop and return the language’s equivalent of null as soon as null is encountered, and some do not. E.g.when short circuit-ing, in a?.b.c, b and c aren’t accessed if a is null.

It might be of interest to look at how other languages describe this (and related) operators:

As a side note, C# calls ?. the Elvis operator, while Groovy calls ?: (the shortened ternary operator) the Elvis operator.

7 Likes

I believe there are a lot of contexts in which you want to write something as simple as print(obj?.attribute) and yet either get “None” or something meaningful, but never an AttributeError. Python is not always something you use in production systems – sometimes you are just drafting something or improvising at the REPL, and I believe it’s ok to add syntax to the language that improves the “casual” experience of using Python.

I agree,but again – not all language features need to be tailored for use in high level complex systems.

Same here.

I’ll throw in Swift, which calls this “optional chaining”.

I didn’t know C# called theirs the Elvis operator and now I’m perturbed that they differed from Groovy. :stuck_out_tongue_winking_eye: Why does their Elvis only have one eye?!

Tangent about Groovy’s Elvis

Total tangent for the curious, groovy’s ?: is roughly this:

name = source ?: fallback
// same as:
name = source != null ? source : fallback

Which is very much not the same as C#’s ?., which does the same as what is proposed in the thread but with dotnet’s null.

1 Like

If a is null (or in Python, None), you shouldn’t care which one caused it. That’s the point. If you care about which one, you don’t use this style of indexing.

This is the same as many other constructs. If you write stuff.get("key") and get back None, you don’t care whether it was present and None or was absent - if you care about that distinction, you would check "key" in stuff first.

7 Likes

My final answer is given here: https://youtu.be/0m2Cy5X6lcE?feature=shared&t=1520

If anyone would like to summarise that into 505 as the reason for withdrawal, feel free.

9 Likes

This basically summarises my position, though in my case it’s the extra years of C# experience that have helped sway me (C# already had these operators for years before the Python or JS proposals, and the movement since then has been towards non-nullable types that can never be null).

Ensuring that functions always return correctly shaped objects (or raise if they cannot) is a better idiom. With ?., API designers will assume that their callers are happy to deal with the occasional None and will design lazy/slack APIs that return them instead of properly avoiding it.

(I’ve always gone back and forth on ??. Because empty lists fail the default argument def f(o=None): o = o or [] pattern, I’d kinda like o = o ?? [] to only coalesce None, but then, chances are that fixing default arguments would solve even more problems without creating new ways for API designers to slack off.)

Anyway, +1 to whatever Laurie says on this topic.

9 Likes