Revisiting PEP 505

Also with pydantic you can define a field as Optional, so the None-aware operators are useful anyway.

1 Like

It wouldn’t be, but I think your use of the term “valid” is a bit misleading. No one is saying this is to work around invalid code. The syntax proposal is to deal with data structures where an attribute is defined to be optional. So in your hypothetical, the type checkers would require that you use getattr() or hasattr() as appropriate to avoid raising an AttributeError. With the proposed syntax the type checkers would still do the same checks, but the suggestion is the overall written code would be more succinct and easier to follow. So both scenarios lead to the same outcome, just with different approaches to the coding.

For me, it’s more about making scripting easier than correctness of code as the proposed syntax doesn’t enable some new coding abilities that Python didn’t have before. And for long-lasting production code, being more verbose in this specific instance isn’t the end of the world.

Hmm, don’t we need to tell them first that the attribute might be missing?

class Foo:
    def __init__(self, bar: int = None):
        if bar is not None:
            self.bar: NotRequired[int] = bar

foo = Foo()
foo.bar # Error
foo.bar? # OK

But there might be some problems with arbitrary attributes: Typing: `NotRequired`-like for potentially-absent class attributes?.

1 Like

Good example (even if NotRequired is only for TypedDicts, but it’s not important). Now I catch there could be attributes defined conditionally, so they can be present or not. I tried your code in my IDE (PyCharm) and actually its static type checker does not emit a warning if I try to access bar. So with this kind of classes, the Guido’s proposal is useful.

My question is: is this pattern commonly used? Guido posted a real world example in TypeScript, but not in Python.

Well, yes, you’re right, it’s only a syntactic sugar, but it’s quite handy. NULL checks are frequent when you code.

1 Like

I was able to track down the source of several dozen ? in a web application. It was used to protect against a single possible missing HTML element. While that’s quite rare, if the element was missing, the entire call tree would execute, short-circuiting at every ?.

The use of optional fields is not rare in Python. This could lead to entire call trees short-circuiting on ? quite often, unless proper guards were implemented.

1 Like

The postfix notation seems quite confusing to me (especially when switching between JS and Python).
I think event?["timeRange"]?["startTime"] is good.
And yes, it has to convert AttributeErrors and KeyErrors to None for it to be fully useful.
I’m also eagerly waiting for this feature, it’s tiresome having to put guards around most key accesses from a json api.

What I can tell about the opinions of people regarding this PEP from what I read :

  • ? is a meaningful character for “safe” operations.
  • Let refer ??, ?., ?[...] as “safe or”, “safe access”, “safe indexation”, these would be useful but are cryptic, and quantitative usage of them would decrease readability of the code.

One possibility could be to introduce a “family of safe operators” with this kind of explicit names :
?or, ?dot, ?at, …

So that one can write :

maybe_some_value ?or some_value  # safe or 
maybe_object ?dot maybe_attribute ?dot attribute  # safe access
some_list ?at maybe_index  # safe indexation

Also possibly more operators :

some_dict ?get maybe_key  # safe dictionary access
?exist "maybe_some_object"  # safe object reference

This may not be as concise as the OP syntax but might be more explicit and readable.

Also, from my usage cases, I use None checks mainly within class constructors.
One usage I frequently require is about mutually exclusive arguments for contructors, so maybe some ?mutex (following the “family of safe operators” syntax just above) function can be pertinent :

class Person:
    def __init__(self, name, age=None, birth_year=None):
        self.name = name
        match ?mutex [age, birth_year]:  # asserting exactly one list element is not None
            case 0 : self.age = age
            case 1 : self.age = current_year - birth_year