|
|
Subscribe / Log in / New account

Reviving None-aware operators for Python

By Daroc Alden
January 17, 2025

The idea of adding None-aware operators to Python has sprung up once again. These would make traversing structures with None values in them easier, by short-circuiting lookups when a None is encountered. Almost exactly a year ago, LWN covered the previous attempt to bring the operators to Python, but there have been periodic discussions stretching back to 2015 and possibly before. This time Noah Kim has taken up the cause. After some debate, he eventually settled on redrafting the existing PEP to have a more limited scope, which might finally see it move past the cycle of debate, resurrection, and abandonment that it has been stuck in for most of the last decade.

Mark Haase and Steve Dower first proposed adding None-aware operators to Python in PEP 505 ("None-aware operators"). At the time, they suggested adding three different operators to Python to handle "None coalescing" (using a default option if something is None), None-aware attribute access (accessing an attribute of a potentially None object without raising an exception), and None-aware indexing (the same but for lists and dictionaries rather than objects). In support of these changes, they noted that many other languages have something similar, and that the operators can make code more concise and readable. The exact details of the translation into Python's existing semantics were the topic of some debate, but the simplest version would be something like this:

    # Current code:
    value = a if a is not None else b
    # Alternate current code that is subtly broken for false-y values:
    value = a or b
    # Proposed code:
    value = a ?? b

    # Current code:
    value = a.b if a is not None else None
    # Proposed code:
    value = a?.b

The original discussion of the PEP didn't end up leading to a decision, and neither did any of the follow up discussions in 2018 , 2022, or 2023. In mid-December 2024, Kim posted a topic in the Python discussion forum seeking to revive the PEP, hoping specifically to "solicit feedback on how I can get the process moving again." Emily Morehouse, a member of Python's Steering Council, was in favor of the idea, offering to sponsor the PEP. Guido van Rossum also approved of the PEP, noting that he had found the equivalent operators in TypeScript to be quite useful. He did raise the question of whether a.b?.c should be None when a.b is None, when b is missing from a, or (his preferred option) both.

Lucas Malor agreed that the ?. operator ought to work when an attribute was missing, but thought that the ?? operator should work differently, and raise an exception if its left argument is undefined. Kim disagreed with Van Rossum and partially agreed with Malor, saying that a.b?.c should raise an exception if a exists and is not None, but does not have an attribute b. Over the rest of the discussion, several people expressed support for all of those alternatives and more.

Python-specific concerns

Van Rossum shared his reasoning for the suggestion by comparing the proposed behavior to JavaScript. In that language, the use of undefined to represent missing attributes makes the behavior of the operators more consistent — and therefore useful for traversing JSON maps that have some ad-hoc structure.

Python works different – if you have a JSON dict a that may or may not have a key k, a["k"] raises, and if we want to have a["k"]?.f to return None, it looks like the ? operator would have to catch the KeyError from a["k"]. If we don't catch that exception, ?. would be much less useful than it is in JavaScript. The same reasoning applies to a.f?.g – we'd have to catch the AttributeError.

But he noted that approach was likely to be contentious, given that "there are a lot of people in the Python community (including core devs) who abhor operations that silence exceptions without an except clause". Kim proved to be one such person, saying that while he appreciated Van Rossum's point, he worries that suppressing exceptions "will introduce as many footguns as we've solved." For example, he pointed out that some developers wouldn't want such an operator to suppress all AttributeError exceptions, or it could potentially silently hide bugs in the code. He later said that he would not support the proposal if it suppressed exceptions — something that Brett Cannon wryly pointed out would put Kim in the same group as the original PEP authors, who don't like the idea of any None-aware operators anymore.

Cannon summarized the disagreement as being about safe traversal of structures versus handling None. Ultimately, he thought that no matter which design was picked, it would remain possible to write bad code using it: "You can't protect everyone from every conceivable issue and at some point have to lean on people being consenting adults." Dower thought that Cannon had identified "the key point".

"Sxderp" pointed out that everyone seemed to more or less agree about the meaning of ??, and that the other operators (?. and ?[]) were the ones causing disagreement. They proposed dropping the other operators from the PEP to focus on ??, an approach that Paul Moore agreed with.

At that point, the discussion changed to whether the ?? operator ought to only check for None, or whether there should be some way to make it work with other sentinel values. Cannon was of the opinion that the language should avoid making None "special", since Python is notable for its pervasive ability to let the user override operators and other language features. Van Rossum disagreed, saying that None was "special, intentionally". Many places in the standard library use None as a special case, and new syntax wouldn't change that.

More magic

Several people went back and forth proposing translations into Python's existing syntax before Van Rossum proposed an even more radical idea: introducing a postfix ? operator that generalizes the effects of the other operators to attribute access, indexing, and bare expressions. With this proposal, a single ? operator would handle short-circuiting an entire expression to be None if any part of it was None, or if there were any missing attributes or keys:

    # Using postfix ? on a lookup:
    value = a.b? # Same as original proposal's "a?.b"
    value = a['key1']['key2']?

Kim supported the idea, but it didn't gain many other supporters, since people were already leery about the existing proposal. Even the less-controversial option had a few detractors, though. Hugo van Kemenade quoted Dower's original explanation for why he initially withdrew PEP 505:

The reason I eventually kind of withdrew that is [I had] a really interesting discussion with Raymond Hettinger at one of our core dev sprints where he basically laid out: the features that go into the language actually change the way people program. And it has far more of a follow-on effect than just how that particular thing gets written. It's going to affect how the rest of the code is written.

At the time, Dower became convinced that adding features like None-coalescing would make Python programmers less likely to validate their data, which would cause verification logic to be spread throughout the code. Kim doesn't see that as a problem, but Dower still holds that position, going so far as to say that if Kim doesn't believe that adding these operators will change the way people use Python, then there's not much point in the proposal. So it's up to the proponents of the PEP to demonstrate that the resulting change would be positive.

Van Rossum shared a detailed example of the kinds of problems he was currently dealing with in TypeScript, and what the Python version would look like with and without the new syntax. Moore thought that kind of code was common, but remained unconvinced that this should motivate a change to the language, rather than a simple addition to the standard library to make traversing data with included None values easier. In fact, several people mentioned existing Python libraries designed to help with this use case, especially Pydantic. Supporters of the proposal were not convinced that external libraries were as useful as an extension to the language, however.

A major point of contention with Van Rossum's proposal was the fact that TypeScript effectively relies on its type checker to prevent accesses to undefined attributes. If Python adopted the TypeScript-flavored approach that Van Rossum suggested, a type checker would be all but mandatory in order to catch things that currently cause AttributeError exceptions in Python, once people start using None-aware operators everywhere. Michael H responded to one of Van Rossum's attempted explanations by saying:

Unless you're suggesting that static typing is required for a first-class experience with new features in Python, this isn't actually any different than getattr is, and I think this is actually an argument against the feature.

He went on to point out that, for all of the possible ideas being tossed around in the thread, several people had given examples that were actually incorrect, showing just how difficult a safe-traversal operator would be to understand and use correctly.

The future

By the end of the discussion, the only real conclusion that had been reached was that Kim intended to rewrite a more focused version of the PEP that dealt only with the ?? operator. Whether that less-controversial proposal will get anywhere remains to be seen, but PEP 505 is still provoking a lot of discussion. For example, Guillaume Desforges posted a separate topic pointing out that the discussion was really going around in circles, and asking whether that had happened before and how the community should address it.

The answer is that, while the community has spent a lot of time discussing the PEP, it has never actually been proposed to the Steering Council. They are the people who must ultimately accept or reject it, so until someone does officially propose it, the idea will never be definitively rejected. For the Steering Council to approve a proposal, however, usually requires the community discussion to have reached some kind of agreement.

Ultimately, with so many competing opinions, the Python community is unlikely to settle this particular discussion anytime soon. But if Kim does rewrite PEP 505, it may come closer than it has in a few years to seeing the matter settled.


Index entries for this article
PythonNone
PythonPython Enhancement Proposals (PEP)/PEP 505


to post comments

This belongs...

Posted Jan 17, 2025 16:50 UTC (Fri) by strombrg (subscriber, #2178) [Link] (8 responses)

...in a library function, not the language itself.

The complexity of a language varies with the square of its feature count, so wherever possible libraries should be preferred over additions to the core language.

This belongs...

Posted Jan 17, 2025 17:08 UTC (Fri) by JoeBuck (subscriber, #2330) [Link] (2 responses)

Certainly
def if_not_none(a,b):
   return a if a is not None else b
would be a functional representation. But I'm not convinced by the quadratic dependency of features argument in this case. If a call to the above function is the same as
(a ?? b)
then it doesn't seem that there any extra feature interactions: the internal representation would basically be the same.

This belongs...

Posted Jan 17, 2025 22:20 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (1 responses)

You can't replace it with a function, because ?? is a control flow operator. a ?? b does not evaluate b if a is None.

This belongs...

Posted Jan 17, 2025 22:21 UTC (Fri) by NYKevin (subscriber, #129325) [Link]

> a ?? b does not evaluate b if a is None.

Obviously, what I meant to say is that a ?? b *only* evaluates b if a is None.

Need more coffee.

This belongs...

Posted Jan 17, 2025 17:14 UTC (Fri) by Karellen (subscriber, #67644) [Link] (3 responses)

I'm having trouble imagining what a function that implements this would look like.

Can you give a rough example API or use case?

This belongs...

Posted Jan 17, 2025 17:18 UTC (Fri) by Karellen (subscriber, #67644) [Link] (2 responses)

In reference to the is_not_none() proposal posted before me, I was specifically thinking of the attribute/key lookup version(s) of this feature.

This belongs...

Posted Jan 17, 2025 18:29 UTC (Fri) by daroc (editor, #160859) [Link] (1 responses)

One could potentially write this:

    def none_aware_getattr(object, property):
        if object is None: return None
        return getattr(object, property, None)

Or, to replace a chained invocation, even something like:

    def traverse(object, *properties):
        q = list(properties)
        while q:
            if object is None: return None
            next = q.pop(0)
            object = getattr(object, next, None)
        return object

This belongs...

Posted Jan 22, 2025 22:13 UTC (Wed) by Karellen (subscriber, #67644) [Link]

I've been thinking about this, and about how it would be called. e.g.

traverse(a, "k", 3, "g")

and I'm not sure that reads very well. If it came to that, I think I'd prefer an xpath-like implementation (but with a pythonic selector) instead, along the lines of:

traverse(a, "k[3].g")

I dunno though. I'm not convinced that the language isn't the best place for this kind of feature. But also not convinced that it is :-) I get why the PEP's gone back and forth a bunch...

This belongs...

Posted Jan 22, 2025 11:54 UTC (Wed) by RGBCube (guest, #175223) [Link]

Due to Python not being lazily evaluated, you cannot add control flow operators/functions as easily. That would require a language feature that lets people tell the interpreter to evaluate arguments until they are accessed, so not happenning.

Haskell doesn't have this issue because it is inherently lazy, but in the case of Python this would be a pretty neat addition.

Periodic table of Python operators

Posted Jan 17, 2025 18:22 UTC (Fri) by Cyberax (✭ supporter ✭, #52523) [Link]

Is it time for the Periodic Table of Perlthon operators? Or do we need to wait for a couple more years?

better ergonomics for untyped dictionary access?

Posted Jan 17, 2025 20:26 UTC (Fri) by bentley (subscriber, #93468) [Link] (13 responses)

Personally, I often write code along the lines of

> x = parsed_json.get("key1", {}).get("key12", {}).get("key123")

when it's not guaranteed that the correct dict keys will exist. Something like

> x = parsed_json["key1"]["key12"]["key123"]?

would be a lot easier, or even be expanded to

> x = parsed_json["key1"]["key12"]["key123"]? ?? default_value

better ergonomics for untyped dictionary access?

Posted Jan 17, 2025 22:14 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (8 responses)

> x = parsed_json["key1"]["key12"]["key123"]?

The main objection I personally have to this sort of coalescing is that I can't understand how to parse the expression into simpler pieces.

When I read x = parsed_json["key1"]["key12"]["key123"], I understand it as follows:

1. First evaluate parsed_json.
2. Look up the 'key1' key in whatever we just evaluated.
3. Etc. for the remaining keys.
4. Store the result of the final lookup in x.

This is pretty elementary. It's practically the first thing we learn in introductory programming - complex expressions are made up of sub-expressions, each piece is evaluated, then replaced with its value, and this continues until there are no more sub-expressions to evaluate. In other words, to understand what parsed_json["key1"]["key12"]["key123"] means, you start by understanding parsed_json["key1"], which means exactly the same thing in this context as it would in isolation.

But a whole-expression question mark baffles me, because we're not doing that anymore. We're instead introducing a global context to the whole mess, where parsed_json["key1"] does not have the same meaning as it would in isolation. Instead, you have to know that it's inside of an expression guarded by ?, so that it suppresses KeyError and evaluates missing keys to None. This means that writing parsed_json["key1"] has two different meanings depending on where you write it.

Contrast this with e.g. contextlib.suppress():

x = None
with contextlib.suppress(KeyError): x = parsed_json["key1"]["key12"]["key123"]

In this case, parsed_json["key1"] still raises a KeyError, and still halts evaluation of the whole expression. It has the same meaning in that context as it does in any other context. We then catch the exception with an explicit control flow block (the with statement), but that doesn't change the meaning of parsed_json["key1"] in the first place.

The problem is not even with expression-level flow control. We already have expression-level control flow in Python and other languages, such as short-circuiting logical operators and the ternary expression. But that's merely deciding which operands get evaluated and in what order, not changing the semantics of those evaluations.

better ergonomics for untyped dictionary access?

Posted Jan 18, 2025 17:29 UTC (Sat) by marcH (subscriber, #57642) [Link] (6 responses)

> This is pretty elementary. It's practically the first thing we learn in introductory programming - complex expressions are made up of sub-expressions, each piece is evaluated, then replaced with its value, and this continues until there are no more sub-expressions to evaluate.

Just like... mathematics.

> But a whole-expression question mark baffles me, because we're not doing that anymore. We're instead introducing a global context to the whole mess, where parsed_json["key1"] does not have the same meaning as it would in isolation. [...]
> This means that writing parsed_json["key1"] has two different meanings depending on where you write it.

Thank you! For identifying and expressing so precisely what made me so uncomfortable. It's very funny that people spend a lot of time arguing about how "a.b?.c" should parse, when the real problem and red flag is: why is parsing so few characters so ambiguous in the first place? The mere fact that the question exists is wrong. If your sub-expression parser needs context, then many users will never be quite sure how to parse those sub-expressions either; many will be very often confused. And it's not because some other languages do it that it is a good idea, see below.

Granted: most parsers have operator precedence rules and these are not always easy to remember. But they are "just" precedence rules, they're only about "where the parentheses are by default" and they don't break the fundamental composition of sub-expressions.

If there is a way to make "?" useful then it should be non-ambiguous and composable.

I think one of the top reasons why Python was so successful is: "Programs are meant to be read by humans and only incidentally for computers to execute." I think this is also why Perl and Object-Obfuscation languages like C++ can get such a bad reputation: because you're constantly doubting yourself when reading brand new code. Which quickly escalates to an absolute nightmare when the system is down, the bosses are breathing down your neck and the author left the company 6 months ago. "Write-only" languages. I noticed it's often hard to understand for the l33t crowd on LWN but most of the code written in the world was never code reviewed at all. It's in Visual Basic by the way :-) I digress sorry.

better ergonomics for untyped dictionary access?

Posted Jan 18, 2025 19:16 UTC (Sat) by Wol (subscriber, #4433) [Link]

> but most of the code written in the world was never code reviewed at all. It's in Visual Basic by the way :-) I digress sorry.

Which is why (speaking as someone who has the misfortune to program mostly in VBA at the moment) I would describe much of our code as "write once crash many" :-)

Cheers,
Wol

better ergonomics for untyped dictionary access?

Posted Jan 18, 2025 20:49 UTC (Sat) by marcH (subscriber, #57642) [Link] (4 responses)

Conversely, I think "write-only" languages have been successful partly because they make programmers _feel smart_. Let's be honest: who has never experienced a shot of dopamine after understanding and/or solving an especially complex problem or bug? [*] If that problem was intrinsically complex, that's great. If that problem was complex only because obfuscated by write-only code, that's sick. I'm afraid both are unfortunately common.

That's probably the top reason why we have code reviews. Reviewers take very little credit and never have the time to pleasure themselves like this. Good reviewers will be very grateful in the former case, and merciless in the second.

[*] or: reading the corresponding LWN explanation ;-)

> ... was never code reviewed at all.

I meant "... was code never reviewed at all.", apologies.

better ergonomics for untyped dictionary access?

Posted Jan 20, 2025 16:20 UTC (Mon) by epa (subscriber, #39769) [Link] (3 responses)

I appreciate that the idea of a "write-only language" is humorous, but even so I wouldn't assume that a language with more subtle or complex features makes code harder to read. The most explicit programming language, where nothing is hidden and all is spelled out in plain sight, is assembly language. Yet typically a program written in assembler is harder to read and understand than a program to do the same task written in a higher-level language, with all its insidious implicit operations and complex syntax tree.

better ergonomics for untyped dictionary access?

Posted Jan 20, 2025 20:42 UTC (Mon) by marcH (subscriber, #57642) [Link] (2 responses)

You're confusing abstraction level and syntax, they're really different topics. The vast majority of developers has no idea what their code is being translated to and in the vast majority of cases they don't need to know anything about that either to write and read good code.

The C developers who think they have a clue have not read "C is not a low level language" yet (acm.org)

better ergonomics for untyped dictionary access?

Posted Jan 21, 2025 15:44 UTC (Tue) by epa (subscriber, #39769) [Link] (1 responses)

I didn't understand that your criticism of C++ was about syntax. I thought that the high level of abstraction and subtle semantics were the reason why you described it as write-only. An awkward syntax doesn't help, but in the end isn't the most important factor in whether code is understandable and maintainable. SQL has an amazingly primitive, clunky, and noncomposable syntax, yet despite that SQL code can be entirely readable.

better ergonomics for untyped dictionary access?

Posted Jan 21, 2025 18:44 UTC (Tue) by marcH (subscriber, #57642) [Link]

I agree there are multiple ways to make a "write-only" language and syntax is only one of them. Also what I think we were focusing on.

The main syntax problem with C++ is probably that anyone can redefine too many things?

I would not try to "rank" the various ways to make a programming language hard to read, they all have their... "strengths"! Syntax has the privilege to be the first one you bump into. Software is a relatively modern thing, yet it seems old enough that we should be able to just know and avoid ALL well known readability mistakes by now? I mean when creating how extending a language. So, no need to rank them?

I also agree that a language can be easy to read yet painful to work with in other ways (too limited, simplistic, other)

better ergonomics for untyped dictionary access?

Posted Jan 20, 2025 13:56 UTC (Mon) by paulj (subscriber, #341) [Link]

I didn't know about contexlib.suppress. What a neat little wrapper. Thanks :)

better ergonomics for untyped dictionary access?

Posted Jan 18, 2025 5:05 UTC (Sat) by xecycle (subscriber, #140261) [Link] (2 responses)

I'm for parsed_json.get("key1")?.get("key12")?.get("key123").

better ergonomics for untyped dictionary access?

Posted Jan 21, 2025 11:23 UTC (Tue) by ianmcc (subscriber, #88379) [Link] (1 responses)

Or continuing the theme of ? <operator>, parsed_json?["key1"]?["key12"?["key123"] perhaps?

better ergonomics for untyped dictionary access?

Posted Jan 21, 2025 23:45 UTC (Tue) by NYKevin (subscriber, #129325) [Link]

The (possibly overblown) concern I might have about this syntax is that it appears to do two things at once.

If you write parsed_json.get("key1")?.get("key12")?.get("key123"), then the get() method on dicts already returns None when there's no such key, so the only thing that ?. can actually be doing is None-coalescing self. That is, if you write None?.get("key12"), it evaluates to None and the get() method is never even looked up, let alone called (which is necessary, because None has no get() method to look up). But if self is not None and get() does get called, then it behaves exactly as it would with the regular foo.get() syntax. This is nice and predictable, and I see no obvious problem with it aside from the fact that it makes the language slightly more complicated.

If you write parsed_json?["key1"]?["key12"?["key123"], then we need foo?[...] to make two changes in semantics from foo[]. It has to do None-coalescing as in the previous paragraph, but it also has to return None instead of raising KeyError. The latter is not necessarily a problem by itself, because we could imagine splitting the __getitem__ magic method into two magic methods as was done with the division operator, but for it to also invoke None-coalescing at the callsite feels like a bridge too far to me. There's too much implicit behavior packed into one operator.

You could work around that problem by defining the new magic method on NoneType, so that None?[anything] always evaluates to None, but is still looked up and called like any other magic method. Then there wouldn't need to be any special None-coalescing behavior at the callsite. I'm not sure how I feel about that approach, but I suppose it would at least be logically coherent. The downside is that we usually expect None to have no special behavior, and this might be seen as breaking that rule.

better ergonomics for untyped dictionary access?

Posted Feb 3, 2025 5:55 UTC (Mon) by IkeTo (subscriber, #2122) [Link]

You are so lucky that all the aggregates are dict's without a single list mixed in.

Is Javascript a good example?

Posted Jan 17, 2025 20:58 UTC (Fri) by proski (subscriber, #104) [Link] (8 responses)

Maybe Python should learn from Rust rather than from Javascript. The Rust Option doesn't have any fancy operators, but it implements functions with descriptive names like is_none_or and unwrap_or_default. Besides, searching for ?? online is problematic.

Is Javascript a good example?

Posted Jan 18, 2025 8:51 UTC (Sat) by smurf (subscriber, #17840) [Link] (1 responses)

Searching for *any* operator is problematic, so that's hardly news.

The Optional datatype isn't the problem, that' easy enough to add. The problem is that you need new operators that generate it; given the current opposition against them on (part of) the People In Charge I doubt that'd fly, but you could try …

Is Javascript a good example?

Posted Jan 20, 2025 11:44 UTC (Mon) by taladar (subscriber, #68407) [Link]

That is why languages that heavily rely on operators have their own search engines for the docs that allow searching for operators (e.g. Haskell and Rust have them even though Rust does not rely on them as heavily as Haskell)

Is Javascript a good example?

Posted Jan 18, 2025 12:05 UTC (Sat) by DOT (subscriber, #58786) [Link] (1 responses)

Indeed, making the 'billion dollar mistake' less prevalent in Python would be a worthwhile (if multi-decade) effort. Instead, this coalescing null operator would probably make null *more* prevalent, because you can expect developers will try to turn exceptions into nulls, to make use of that operator.

Is Javascript a good example?

Posted Jan 18, 2025 15:28 UTC (Sat) by kleptog (subscriber, #1183) [Link]

The thing is that in certain contexts (especially REST APIs), optional data structures are super-common and so you have to deal with them on the client side. And that's just tedious currently in Python. That said, I'm sure entirely sure it's so tedious as to require a new operator. Sure, you have to do a.get('b', '{}').get('c') which is verbose, but not overly so.

Rust's "error propagation" operator

Posted Jan 18, 2025 17:38 UTC (Sat) by farnz (subscriber, #17727) [Link] (3 responses)

Note that the ? operator in Rust works on Option<T>, not just on Result<T, E>, and the ? operator is in the process of being made fully general via try_trait_v2.

A consequence of this is that it's perfectly acceptable (even normal) in Rust to write code like map.get(key)?.do_thing()? where do_thing returns an Option of its own. It's also not uncommon to use Result::ok to convert from a Result with a clear error type to an Option, or to use Option::ok_or and Option::ok_or_else to convert Option to Result, resulting in chains like do_the_thing().ok()?.get(key)?.do_something_else() (converting an error to Option::None and then returning it), or get(key).ok_or(Error::NotFound)?.do_the_thing() (converting Option::None to Result::Err(Error::NotFound) and then returning it).

Rust's "error propagation" operator

Posted Jan 19, 2025 13:53 UTC (Sun) by notriddle (subscriber, #130608) [Link] (1 responses)

As a casual user of Python and a more serious user of Rust, I don’t think Python should have this.

Van Rossum‘s remark about KeyError alludes to the biggest problem already. Python raises errors for many common forms of nonexistent value. Special-casing [] would be a mess (operator overloading). Having the ? operator blanket catch KeyError would be an excessively big hammer, also because of operator overloading—you don’t know if the error is actually intended for the collection you’re traversing, or if it was in some library code that __index__ happened to call.

But even if Python made special-cased [] work, that can’t help with all the traversals in libraries. If a missing value is a bug, you want to raise ASAP, to make the stack trace maximally useful. But, if you want `?`, basing it on exceptions opens up too much implicit control flow and potential to accidentally eat errors that were caused by bugs.

Rust's "error propagation" operator

Posted Jan 19, 2025 15:23 UTC (Sun) by farnz (subscriber, #17727) [Link]

There's a deep underlying cause here; Rust does not propagate errors implicitly, and thus if you call a fallible operation, you have to explicitly consider whether you want to handle any errors or propagate them. Python does propagate errors implicitly via exceptions, and so Python programmers expect to see something (try:, for example) at places where you intend to capture any error that occurs.

This means that while the ? operator is common in Rust code (you use it anywhere where something at a lower level can go wrong, and your only sensible course of action is to complain to higher levels), but its equivalent in Python code would be unusual (since Python's not about to change to require every statement that might throw to end with a ?). That makes it a harder thing to learn about, since you're not going to see it very often, nor are you writing complicated code (of the form try: thing(); except: raise in Python syntax) that causes your reviewers to tell you about the ? operator very often.

Rust's "error propagation" operator

Posted Jan 21, 2025 13:41 UTC (Tue) by tialaramex (subscriber, #21167) [Link]

Try also works on ControlFlow, which is one sense a no-op like the implementation of IntoIterator on Iterator (or the blanket implementation impl<T> From<T> for T) but since Try isn't stabilized this means if you want to expose the idea that you can stop early or not, you can write a method which gives back a ControlFlow and then people can write your_thing.some_method()? today

To me this was an important clarification in v2, what our operator does is actuate the ControlFlow type. We're leaving the function early (Break) or keeping going (Continue) based on a sum type with two possibilities. It's often about success or failure, but not always, and not necessarily in the most obvious way.

Firmly with Kim on this point.

Posted Jan 19, 2025 17:17 UTC (Sun) by Heretic_Blacksheep (guest, #169992) [Link] (2 responses)

An operator suppressing existing exceptions that would otherwise trigger is not a can of worms that should be opened. The response is weaksauce: "people are going to write bad code no matter what they do" is a cop out.

Eliminating and avoiding giving people a gun to shoot themselves in the foot is a worthwhile goal regardless of whether they then take an axe to their ankle and hack it off. The fewer methods one can *inadvertantly* make a fatal mistake has nothing to do with someone *intentionally* screwing up. Avoid giving a person a pistol with a hair trigger that will go off if its breathed on. But, it's perfectly acceptable to have a fire axe around to hack someone out of a stuck door when the room is on fire. You can't stop the deranged guy from hacking his foot off with said axe, but at the same time there are life saving reasons to having that tool available.

Silently suppressing a raised exception is the hair trigger pistol and people will *accidentally* shoot themselves or others with it. Having the operator handle the None case without changing existing expectations of the long form syntax is the fire axe.

I'm kinda ignoring the readability of the ? operand being an implicit check versus the explicit expanded if conditional. I'd go for the latter every time simply from a readability point of view whether fully awake or half asleep or hopped on caffeine. But that said, that's mostly a personal subjective stylistic choice and not necessarily a loaded foot gun in practice.

Firmly with Kim on this point.

Posted Jan 20, 2025 8:23 UTC (Mon) by kleptog (subscriber, #1183) [Link]

I think I also agree. The use case for the ?? is pretty obvious, I can think of places I would actually use that. Having a ? to suppress exceptions seems like a footgun. Since the only use case I'd have for it is traversing unpacked JSON objects, I'd actually be happier with a standard getpath() method on dict. Like:

obj.getpath("foo.0.bar", DEFAULT)

which feels more python-esque anyway. I find bigger projects tend to invent such a method anyway, just for the readability.

Firmly with Kim on this point.

Posted Jan 23, 2025 8:44 UTC (Thu) by marcH (subscriber, #57642) [Link]

> The response is weaksauce: "people are going to write bad code no matter what they do" is a cop out.

BTW I'm sick of this utterly pointless sentence. It's exactly as: "We all die in the end, so why bother with doctors and hospitals?"

I don't know; how about "to live healthier and for longer" maybe?


Copyright © 2025, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds