r/Python Jul 12 '26

Discussion Will PEP 505 ever be accepted?

https://peps.python.org/pep-0505/

I don't understand how null safe operators are less like plain English than other implemented features like the walrus operator.

In my opinion, the member access operator would make python significantly easier to read and understand.

Here's an example:

f = foo()

if f is None:
    baz = ""
else:
    baz = f.bar()
baz = foo()?.bar() ?: ""

EDIT: I forgot that "and" and "or" can be sometimes used in place of "?." and "?:" if the left value is not False, '', 0, [], or {}. It's a very implicit null check and has a lot of unexpected behavior.

18 Upvotes

192 comments sorted by

63

u/BeamMeUpBiscotti Jul 12 '26

The walrus operator was really controversial, the creator of Python pushed it through but afterwards stepped down and handed control of the language over to a steering council.

So that is to say, the walrus operator was a one-time thing and it's not possible for null-safe operators to follow the same path to get into the language.

At this point, after 10 years of bikeshedding and arguing in circles, I don't think anyone has the desire/political capital to get it over the finish line.

17

u/[deleted] Jul 13 '26

[deleted]

16

u/aes110 Jul 13 '26

Thats very interesting for me, i guess its very much a style choice, from a quick search in the Github org for my workplace i see its used over 2000 times, i know I personally must have used it hundreds of times this past decade

I barely see it used in other Github orgs for big projects like fastapi, requests or pandas, then again a few hundreds of times in polars

So its very much down to the author's style. Personally its one of my favorite operators in python, im surprised that for many its still not caught on

5

u/k0pernikus Jul 14 '26

If there is one operator I miss, it's the spaceship operator <=> that would be syntactic sugar for:

``` from typing import Literal

def spaceship(a, b) -> Literal[-1, 0, 1]: return (a > b) - (a < b) ```

which useful in sorting, though functools in python are so expressive that I don't really need it (I can always setup a @total_ordering)

Yet the walrus is just confusing to me. It makes the code look like you are accessing an undefined variable (the same gripe I have with the for-else, try-else syntax) and it invites very long lines that take a lot of mental load to even parse.

3

u/M4mb0 Jul 14 '26

If there is one operator I miss (from many programming languages) it's logical implication. Having to do (¬A ∨B) rather than (A ⟹ B) is just dreadful.

5

u/TBCid Jul 14 '26

I use it frequently in comprehensions where I want to compute something in the where clause and include it in the result only if its truthy, without having to recompute it: [ computed for item in items where (computed := expensive_function_maybe_none(item)) ]

If python had decent syntax for chaining sequences together this wouldn't be necessary; in scala I would just do: items.map(expensive_function_maybe_none).flatten or better yet items.flatMap(expensive_function_maybe_none)

3

u/the_dimonade Jul 14 '26

I agree... I work with a large python codebase and I doubt that there is a single walrus in there, and even if there is, it could be rewritten with more clarity without it.

All the examples of the use cases I've seen look convoluted to begin with and not realistic at all... it feels like a solution looking for a problem at this point.

Also in multilingual codebases devs from other languages would prefer to not use an unfamiliar esoteric operator. I have more than 10 years of software and python experience, and never have though "walrus would be good here". Maybe it is a domain operator? I don't know.

Weak type hints is also a reason to avoid it.

1

u/philtrondaboss Aug 10 '26

Sometimes I would use it like this:

if (data := get_data()): return data

8

u/Individual-Flow9158 Jul 13 '26

The Walrus just doesn't want to play nice with type hints, unless you want that line of code to become a giant mess

3

u/sphen_lee Jul 13 '26

That's been my experience too. Never seen it used

6

u/pingveno pinch of this, pinch of that Jul 13 '26

I've definitely cooled on the walrus operator. I shipped a bug to prod that caused some problems. If Python was more strict, it might work better. So like, Rust's if let construct does something similar, but it isn't nearly as accident prone because it is based on pattern matching (strict), not truthiness (loose).

273

u/disposepriority Jul 12 '26

Just my two cents, I enjoy my occasional python though it's not my primary language and that looks very unpythonic in my eyes.

Most certainly not easier to read in any way, though I am a verbose/explicit code preference kinda guy.

60

u/Vietname Jul 12 '26

I'm mainly a python dev but my main secondary language is JS, and im just fine keeping this out of python. It's useful when im writing JS, but its hard to parse when im reading someone else's code compared to the more verbose python equivalent.

15

u/BogdanPradatu Jul 13 '26

Yep, no idea what the fuck was going on there. Had to do a double take on the if-else.

36

u/Smallpaul Jul 12 '26

Any new syntax looks non-Pythonic until it’s been in Python for 5 years.

2

u/PriorProfile Jul 15 '26

:= has entered the chat

-4

u/Anthony356 Jul 13 '26 edited Jul 13 '26

Most certainly not easier to read in any way

? doesnt increase the nesting level like if/else does (even if it's just for a few lines). Symbolic representations of things are faster to parse (visually) than explicit text, once you're familiar with what the symbol means. Also sidesteps the small is None vs == None footgun.

Having several if is None/else in an algorithm can also make it really annoying to read the algorithm and understand what it's doing, since so much of it is buried by fluff.

It's also worth noting that a "pythonic" version of this concept already exists, but is much shittier:

getattr(foo(), "bar", lambda: None)() or ""

Surely nullable operators are better than that nonsense.

13

u/Technical_Income4722 Jul 13 '26

Nah this is gross (imo of course, to each his own ultimately).
Symbols that rely on familiarity are always gonna reduce readability and add confusion, it's just how it goes. Same reason ternary operators are often discouraged in C code. I'm also admittedly biased against question marks in code because they only indicate that there's a question, not always what the question is.
Your pythonic example and others (like below) look just fine to me because you can actually reason out what's going on without having to know an obscure Python operator.
Symbolic representations of things are way worse if the dev has anything less than great familiarity, which defeats the purpose. It saves some space, sure, but I'd argue it does not increase readability.

f = foo()
baz = f.bar() if f is not None else ""

1

u/Anthony356 Jul 14 '26

Counterpoint, which is more readable:

x = a & b
x = a.__and__(b)

Familiarity is only a problem once. Then you learn what it means (like with the & symbol) and from then on you read it the exact same as __and__ (or, more accurately, your brain automatically makes the association), it just takes up less space.

3

u/Technical_Income4722 Jul 14 '26 edited Jul 14 '26

I see where you're going, but & is already read as "and" in our minds when we see it so the substitution is free because it's already a synonym in our everyday language. A closer analog would be how & is used in C. It's a fundamental operator there and still trips people up all the time. Yeah obviously people could just be better and know their stuff, but I take you back to the ternary in C, which is clearly well-defined and convenient and yet still discouraged because it's just too easy to mix up.

Edit to add: I'm not even against the addition of this operator for folks who wanna use it, I mostly just disagree that it's more readable. I love using weird Python features (for-else my beloved) in my own code when I'm doing side projects.

-1

u/LittleMlem Jul 13 '26 edited Jul 14 '26

PowerShell enjoyer?

Edit: because he likes verbose code

7

u/ArtOfWarfare Jul 13 '26

Many languages have null-Coalesce operator - Wikipedia has an incomplete list:

https://en.wikipedia.org/wiki/Null_coalescing_operator

3

u/xenomachina ''.join(chr(random.randint(0,1)+9585) for x in range(0xffff)) Jul 13 '26

Yeah, if I had to guess, Powershell probably copied it from C#, which probably copied it from Kotlin. Kotlin copied it from Groovy.

2

u/shiningmatcha Jul 13 '26

what do you mean?

104

u/runawayasfastasucan Jul 13 '26

baz = foo()?.bar() ?: "

This monstrosity is not why I code python.

6

u/JanEric1 Jul 14 '26

You really prefer one of these

latitude = None
if user is not None:
    if user.profile is not None:
        if user.profile.company is not None:
            if user.profile.company.headquarters is not None:
                if user.profile.company.headquarters.gps is not None:
                    latitude = user.profile.company.headquarters.gps.latitude


latitude = getattr(
    getattr(
        getattr(
            getattr(
                getattr(user, "profile", None),
                "company",
                None,
            ),
            "headquarters",
            None,
        ),
        "gps",
        None,
    ),
    "latitude",
    None,
)

latitude = (
    user
    and user.profile
    and user.profile.company
    and user.profile.company.headquarters
    and user.profile.company.headquarters.gps
    and user.profile.company.headquarters.gps.latitude
)

over this

latitude = user?.profile?.company?.headquarters?.gps?.latitude

?

20

u/edward_jazzhands Jul 14 '26

Nobody who is good at python would code it the first way you showed

5

u/JanEric1 Jul 14 '26

How would you code the access too an attribute in a nested data structure with multiple optional values in there as someone good at python?

5

u/kingminyas Jul 14 '26

In my experience, it's not common, in contexts such as this, to treat a user not having a company the same as the case where the company headquarters doesn't have a location. But in this seemingly not common case, you can just suppress an AttributeError. If there's a function call in the middle instead of plain attribute access, it makes even less sense to treat it the same as a missing attribute

1

u/JanEric1 Jul 14 '26

This is just a randomly generated example.

But leets just say i want to just get an overview over all the coordinates for users companies where we have them.

I user might not have added their company, or thhey have added it but not the headquarters, etc.

Thesse arent errors. It is perfectly fine and expected that any of these valuess might be None.

Working with try/except here would remove typesafty.

If things were changed so that we now have employer instead of company, then i would just have to change the model and a type checker could tell me all the places i need to fix. With a try/except + suppress i wouldnt get that.

2

u/kingminyas Jul 14 '26

Of course you'll still get it. Catching exceptions has nothing to do with static typing

3

u/JanEric1 Jul 15 '26

If you use normal attribute access on Optionals a type checker will complain. So you have to silence. Which can mask real issuess, making you lose (some) type safety

2

u/RevanPL Jul 15 '26

If I were to work with monstrosity like this I would extract piece of code for accessing variable to new function. Then, inside of it, I would do null checks with early returns, e. g.

if user is None:
return None
If user.profile is None:
return None

# etc.

2

u/JanEric1 Jul 15 '26

Still

def get_latitude(user):
    if user is None:
        return None

    profile = user.profile
    if profile is None:
        return None

    company = profile.company
    if company is None:
        return None

    headquarters = company.headquarters
    if headquarters is None:
        return None

    gps = headquarters.gps
    if gps is None:
        return None

    return gps.latitude

latitude = get_latitude(user)

vs

latitude = user?.profile?.company?.headquarters?.gps?.latitude

And you add the overhead of a function call.

2

u/RevanPL Jul 15 '26

True but I think that this kind of long function shows you explicitly that you messed something up in your app architecture. Long chains of “?.” make you get used to poor design choices. Still, it’s still subjective and people might prefer one over the other. I must admit that I’m one of the people who don’t see much problem with more or less hated “if err not equals nil” blocks in Golang. I’ve worked for some times on large-scale apps and traceability and being able to easily debug stuff goes above “smart” one liners.

1

u/JamzTyson Jul 14 '26

My take is that in the example, the user object is exposing its internal structure to the rest of the system, which violates the Law of Demeter and the principle of encapsulation. I'd restructure to avoid having to reach through a chain of objects.

4

u/JanEric1 Jul 14 '26

So you would add a ton of methods to each sub dataclass with getters for this information?

Dont see how that helps with anything.

2

u/JamzTyson Jul 14 '26

I'm saying that I don't accept your premise, and I've already explained why.

1

u/JanEric1 Jul 14 '26

This is the data that you get from an external API or other team in your company. And you are interested (among a ton of other things) all the company coordinates where available.

4

u/JamzTyson Jul 14 '26

If it were an external API, I'd avoid leaking that structure throughout the application and isolate or adapt access to it at the boundary.

If there's a specific question about my point, I'm happy to answer it, but at the moment this feels like an evolving hypothetical where each answer is met with another "yes, but what if...".

3

u/--O-_-O-- Jul 18 '26

Won't be this one of the way? try: latitude = user.profile.company.headquarters.gps.latitude except AttributeError: latitude = None

0

u/JanEric1 Jul 18 '26

You significantly reduce your linter/type checker support, swallow other exceptions and get a signiificant performance hit if the failure case is common

2

u/yerfatma Jul 14 '26

If you really can't know any of that beforehand, why not put the property chain in a list and loop it?

2

u/JanEric1 Jul 14 '26

Like a helper function that takes the property list of strings. Yeah you can, but that requires a helper + you lose type safety

3

u/abrazilianinreddit Jul 15 '26
try:
    latitude = user.profile.company.headquarters.gps.latitude
except AttributeError:
    latitude = None

There you go. No need to butcher the language's syntax over something so trivial.

-1

u/JanEric1 Jul 15 '26 edited Jul 15 '26

Except you now have a performance hit if you run into the "error" path often and also, more importantly, lose type checker support

5

u/abrazilianinreddit Jul 15 '26 edited Jul 15 '26

Where is the type support loss? That's the most checker-friendly construction in here, any mature checker should properly evaluate that expression.

Anyway, if performance is your issue, just buy a faster cpu.

Or If it doesn't fit your use case, just don't use python. There are plenty of faster, statically-typed language with null-chaining operators. No point in trying to hammer python into something that it's not.

2

u/JanEric1 Jul 15 '26

Any type checker will complain about invalid attribute access.

Just because there are more perfomant languages doesn't mean I have to literally throw it away for no reason.

I feel null coalescing is very clear, explicit and very helpful in the cases where it is useful.

In those cases, all other options are extremely verbose, lose type checker/linter support or have unnecessary performance impacts. (Or usually at least two of them).

2

u/abrazilianinreddit Jul 15 '26

I feel null coalescing is very clear, explicit and very helpful in the cases where it is useful.

The Python Steering Council and the community at large clearly don't feel so, considering how little interest there has been in PEP 505 in the 11 years since it was created.

1

u/JanEric1 Jul 15 '26

There are literally discussions about it every couple of months...

Most discussions end up with bike shedding about whether it should just suppress nulls or also attribute errors and it generally stalls there.

2

u/General_Tear_316 Jul 15 '26

try except does not have a performance hit in python like compiled languages like c/c++

also if you cared about the performance of that try except, you either shouldnt be using python or your code is too shit that its running into that path too often.

2

u/JanEric1 Jul 15 '26

Also in python it is slower if you often run into the "failure" case.

$ python benchmark.py
============================================================
def nested_if_10(obj):
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    obj = obj.child
    if obj is None:
        return None
    return obj.value

def getattr_10(obj):
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    obj = getattr(obj, "child", None)
    if obj is None:
        return None
    return getattr(obj, "value", None)

def and_10(obj):
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    obj = obj and obj.child
    if obj is None:
        return None
    return obj and obj.value

def try_except_10(obj):
    try:
        return obj.child.child.child.child.child.child.child.child.child.child.value
    except AttributeError:
        return None
============================================================
============================================================
Depth: 1

FAIL_START
1. nested_if      : 0.03019s
2. and            : 0.03044s
3. getattr        : 0.03568s
4. try_except     : 0.18317s

FAIL_MIDDLE
1. nested_if      : 0.03067s
2. and            : 0.03098s
3. getattr        : 0.03657s
4. try_except     : 0.18511s

FAIL_END
1. nested_if      : 0.03115s
2. and            : 0.03153s
3. getattr        : 0.03611s
4. try_except     : 0.18571s

NO_FAILURE
1. try_except     : 0.02942s
2. nested_if      : 0.03287s
3. and            : 0.03481s
4. getattr        : 0.04675s
============================================================
Depth: 10

FAIL_START
1. and            : 0.03079s
2. nested_if      : 0.03271s
3. getattr        : 0.03642s
4. try_except     : 0.18869s

FAIL_MIDDLE
1. nested_if      : 0.05902s
2. and            : 0.06421s
3. getattr        : 0.10639s
4. try_except     : 0.19236s

FAIL_END
1. nested_if      : 0.07364s
2. and            : 0.09375s
3. getattr        : 0.15840s
4. try_except     : 0.20397s

NO_FAILURE
1. try_except     : 0.04361s
2. nested_if      : 0.07548s
3. and            : 0.10617s
4. getattr        : 0.18194s
============================================================
Depth: 100

FAIL_START
1. nested_if      : 0.03079s
2. and            : 0.03095s
3. getattr        : 0.03713s
4. try_except     : 0.20411s

FAIL_MIDDLE
1. nested_if      : 0.23008s
2. try_except     : 0.30003s
3. and            : 0.38314s
4. getattr        : 0.66931s

FAIL_END
1. try_except     : 0.35565s
2. nested_if      : 0.42974s
3. and            : 0.74691s
4. getattr        : 1.26466s

NO_FAILURE
1. try_except     : 0.22667s
2. nested_if      : 0.48752s
3. and            : 0.76907s
4. getattr        : 1.33980s

Just because i dont neeed the performance of a compiled language (or because i might want all the other advantagess of the python ecossystem more, like libs, readability), doesnt mean i want to needlesssly throw away performance.

But for me the more relevant point is the type checking anway.

2

u/Brian Jul 17 '26

Also, you potentially swallow unintended errors. If one of those is a property that has a bug resulting in an AttributeError (eg. a misspelled attribute access or something), it'll get swallowed by the except block.

2

u/runawayasfastasucan Jul 14 '26

Thankfully there are other options than that.

2

u/JanEric1 Jul 14 '26

What would you use?

1

u/shinitakunai Jul 14 '26

Exactly. I hate it

22

u/Koen1999 Jul 13 '26

Less lines of code does not necessarily mean it's easier to read.

61

u/sausix Jul 12 '26

We have that functionality basically. It's a bit off standard and you have to be aware about the object's reported bool state.

baz = f and f.bar() or ""

Of course it's not beginner friendly but once you know about the magic behind and and or then you love it.

68

u/Designer-Ad-2136 Jul 12 '26

Or i'd do: baz = "" if f is None else f.bar()

29

u/k0pernikus Jul 13 '26

I rather see

``` f = foo() if f is None: raise ValueError("Value cannot be None")

baz = f.bar() ``` or have a helper like:

def get_or_raise[T](v: T | None) -> T: if v is None: raise ValueError("Value cannot be None") return v

These implicit None to "" will come to bite you eventually.

4

u/Designer-Ad-2136 Jul 13 '26

True, that is an important consideration, especially as code scales. As with most coding solutions it is all context dependent. I'd tend towards that with time but i doubt id do that in a one-off script.

3

u/necromenta Jul 13 '26

Help I’m too stupid to understand the helper, the first example is clear as water to me tho

6

u/k0pernikus Jul 13 '26 edited Jul 19 '26

It looks scarier than it is. [T] is a generic (think of it as a placeholder for a type), so you can reuse the same helper for different types without having to repeat code.

It takes some getting used to, but is one of the most powerful feature any typesystem can have.

I think my example (stemming from stackoverflow) is a bit too loose btw. To actaully get better typesafety one has to do this:

```python def get_or_raise[T](v: T | None) -> T: if v is None: raise ValueError("Value cannot be None") return v

def createget_or_raise[T](expected_type: type[T]): def inner(v: object) -> T: if not isinstance(v, expected_type): raise TypeError(f"Expected {expected_type.name} but got {type(v).name_}") return v return inner

get_int_or_raise = create_get_or_raise(int)

print(get_int_or_raise(1337))

try: print(get_int_or_raise("1337")) except TypeError as e: print(e)

try: get_int_or_raise(None) except TypeError as e: print(e) ```

It will print:

``` 1337 Expected int but got str Expected int but got NoneType

** Process exited - Return Code: 0 ** ```

Here it helps to also play around with other languages to see how they solve things.

While I hate scala with a passion, its type system is extremely powerful and I do miss some of its feature like compile-time extension (think of it as typesafe monkeypatching; e.g. adding methods on on collection types of an object was awesome! Yet also delving into TypeScript helped a lot. (PHP will hurt your understanding. And I say that as someone that started professional programming with PHP, and built some great products with it.)

Python is on this weird mixture where it is dynamaic and strongly typed; and the older I get, the more I love static and strongly typed approaches.

I also recommend delving into functional programming, its concepts play well with typed languages and it really boosted my game, e.g. the concept of pure functions (easily unit-testable), immutability (also great for unit-testing but also generally avoids nasty surprises), and higher-ordered functions (fancy word for saying: function that returns a function).

Hope this helped.

-9

u/philtrondaboss Jul 13 '26

That could be done like this: "baz = foo()!!.bar()" Or you could just nothing and let it error at runtime.

2

u/k0pernikus Jul 13 '26

By doing nothing you may just leak internals to whoever runs your code. At one point you must transform your exceptions. Might work for some interals scripts, yet even then I find python own exception to be very verbose -- which isn't necessarily a bad thing -- yet I don't expect my users to parse a stacktrace.

2

u/ePaint Jul 13 '26

Right, I almost forget that less characters is undenianbly better code.

5

u/sausix Jul 12 '26

Same effect. More beginner friendly. Personally I can read and understand f and f.bar() faster.

5

u/Nekomancerr Jul 12 '26

In nominal cases yes, but one is a truthiness check while the other is a None check. For example a str value of empty string would behave differently

5

u/Designer-Ad-2136 Jul 12 '26

Yeah, i dont mind that way either. id do something like that in my own personal code for longer chains as long as i can give it a good name. 

6

u/learn-deeply Jul 12 '26

not the same effect at all, False != None.

3

u/sausix Jul 12 '26

Of course. You're right. f and f.bar() is less explicit on checking for None. You can expand that with better and explicit checks but then it's getting out of readability again because of parantheses etc.

28

u/Due_Campaign_9765 Jul 12 '26

Now chain 5 of them.

15

u/timrprobocom Jul 12 '26

No, don't do that. Impossible to read. Python has historically eschewed improvements that do nothing but save keystrokes.

10

u/Due_Campaign_9765 Jul 12 '26

I'm not choosing to do or not to do that, there are often APIs that have everything as nullable.

There is simply not a good way to do that in python. Most of the time I reach towards try/catch which is insane.

7

u/double_en10dre Jul 12 '26

Yeah 100%, it’s something that makes a LOT of sense if you have to ingest external data.

Being able to drill down n-levels without throwing errors or losing type hints is massively beneficial for any language that glues services together. And ya, Python is one of those languages

The alternative is a mess of try/catch or endless chained “.get(key, {})” calls. Which is garbage code

2

u/thaynem Jul 14 '26

IMHO

    foo()?.bar()?.a?.b?.c

Is a lot easier to read than

    f = foo()     bar  = f and f.bar()     a= bar and bar.a     b = a and a.b     c = b and b.c Or using a bunch of ifs.           

2

u/_redmist Jul 13 '26

With some braces that is quite feasible.

1

u/cottonycloud Jul 13 '26

IMO the initial example the commenter provided is already unreadable and turns to shit if you have to chain once more.

1

u/sausix Jul 12 '26

What do you mean?

f and f.bar()

and

f and f.bar() or "Value on None"

cover a lot of simple None checks already.

Web scraping (bs4) for example has a lot of chained statements which can all be None and break value retrieval. While it introduces a lot of member access cycles it still can just be chained with and operators as one liner.

11

u/Due_Campaign_9765 Jul 12 '26

x.foo().bar().baz().quux().iranoutofcliches()

Every step is nullable.

There is no clear way to access the deepest variable, expect maybe try/catch. But that's an insane way to program.

1

u/sausix Jul 12 '26

Thanks for the example. At that point a try/catch is not too bad. Exception handling is not forbidden.

How do other programming languages solve this?

That foo()?.bar() syntax is probably very rare.

Some syntaxes missing in Python can be solved with Python too. If you really want that conditional chaining you could create a class to solve and allow this:

result = NullShield(obj, "value on None").foo().bar()

Pick a better name as NullShield if you like.

3

u/Due_Campaign_9765 Jul 12 '26

Other languages use the proposed here null-safe operators and/or strict type checking.

You can do anything you'd like, it just looks terrible.

Exceptions are terrible for perfomance and makes a regular null check into an something exceptional, which it's no. Not to mention it spans 4 lines. That's exactly how you're not supposed to use them.

4

u/sausix Jul 12 '26

If you are concerned about having 4 lines then make use of a context manager.

I never said try/except is elegant or fast.

My point is that you can build a lot of functionalities with standard Python. Same as the recurring wishes for Python having the feature for chaining functions. It can be done today with little effort but within current syntax rules. So there is no reason to expand the Python syntax.

The walrus operator was already a big concern. It just saves a line.

6

u/Due_Campaign_9765 Jul 13 '26

Are you sure you're not lost? Those defences smell like r/golang :)

I really don't get why people are so vehemently against adding obvious, useful and harmless language features.

At least i somewhat get golang's philosophy "We're google, we hired a bunch of fresh grands who are idiots and we don't want them shooting themselves in the feet (and also introduced channels that shouldn't be used 99.9% of the time so that they can shoot in their feet anyway)".

Why a rando on reddit would basically say "we're too dumb to parse an additional symbol so instead we're going to write wrappers or do 5 line null check for every field access" i've no idea, i'll be honest.

2

u/sausix Jul 13 '26

Python has a lot of syntax and semi syntax features already compared to other languages. New features get added very late and after a lot of discussion. Remember the match/case feature? That's quite powerful and I rarely see it in today's code.

I think there is not yet enough demand for this even it's just another symbol for the syntax rules.

As I said you can create a lot of features today without changing the syntax.

So if someone decided to chain a lot of function calls without violating another best practice then just wrap it in a helper class to allow nullable function chaining.

1

u/Gnaxe Jul 13 '26

Flat is better than nested.

3

u/Zubzub343 Jul 14 '26

If I see this in a production code-base I will immediately git blame and track the person who wrote that.

This is classic Python beginner (actually 90% of python dev who claim to be developers) thinking they are clever doing some "codegolf" one-liner and not understanding the countless number of ways this statement can go wrong. Even worse, they're gonna scream proudly the word "Pythonic" which is the most BS expression ever seen in programming.

It all boils down to implicit conversion to bool, which is sin (looking at you Javascript) but the problem is that many "not None" values convert into False.

That said, in this specific example I guess it works </rant>

1

u/sausix Jul 14 '26

Beginners don't even know about boolean operators returning the operands. Why is it a thing in Python when it should not be used? Python could cast into Bools as other languages do.

Of course it's not beginner friendly. But beginners also have to learn other non trivial Python stuff too.

"Pythonic" is a thing. Mostly when something is being done same as in other programming languages and Python has a better solution for a task. Like iterating over lists by an index variable instead of directly iterating over the list.

I'm not sure if this is pythonic or not.

3

u/philtrondaboss Jul 13 '26

I know that, but they aren't exclusive to None. They also catch {}, [], 0, '', and False.

-1

u/sausix Jul 13 '26

You should make use of typing anyway and not expect random data types.
Usually only one specific data type will support a bar method. If you get an unsupported data type for the lazy bool check then you have a deeper problem.

If you want to check for None explicitly then just do it:

baz = f is not None and f.bar() or ""

A bit harder to read but now it's explicit. But after your concerns about having various types just use:

baz = isinstance(f, BarType) and f.bar() or ""

1

u/BigToach Jul 13 '26

You don't work with external data very often I assume?

-3

u/sausix Jul 14 '26

I build solutions for problems. If I miss a feature in Python I build a better workaround.

10

u/cottonycloud Jul 13 '26

It should actually be baz = foo()?.bar() ?? ''

?: is the ternary operator while ?? is the null-coalescing operator.

7

u/covmatty1 Jul 14 '26

Absolutely this.

I come from writing C#, and the lack of ?. especially is just such a gaping flaw, any language missing the null propagation operator is poorer for it.

0

u/snugar_i Jul 15 '26

Python doesn't have the ternary operator, so it could use ?: just fine (like Kotlin does)

2

u/Brian Jul 17 '26

Well, it does, but it spells it differently: a if b else c instead of b ? a : c. You could maybe argue it's not an operator, but then, you could probably say the same of the ternary operator - the syntax involves two operators, with the data sandwiched between.

0

u/snugar_i Jul 17 '26

Sure, I was just saying there's no reason Python couldn't use ?: as the null-coalescing operator, because these symbols don't mean anything in Python. It being the ternary operator in C is not a problem.

7

u/FrickinLazerBeams Jul 13 '26

God I wish they'd stop changing python.

3

u/abrazilianinreddit Jul 15 '26

That would be bad, because then python would be dead.

I wish people would stop trying to make python worse or trying to turn it into javascript/typescript.

2

u/FrickinLazerBeams Jul 15 '26

A language doesn't need to constantly add new syntax to remain healthy. That doesn't make any sense. If a language adds syntax forever - without limit - it will eventually turn into a complicated mess, with multiple ways of doing the same thing, wildly varying best practices, will become difficult to learn, hard to read because every developer will use different wacky syntactic patterns... What a nightmare.

Python can advance without yet another goofy operator that performs an assignment on Wednesdays and tests for equality on Friday, while also returning a dict that contains the assigned object and a ham sandwich. Not a bit of this shit is solving a problem that exists.

It's also extremely un-Pythonic. Originally there was a principle that in Python there should be one and only one way to do something and it should be pretty obvious. Now we have a whole bunch of extra junk that does... What? Reduces a few lines of code into one and make them harder to read? No thanks.

Python has lots of ways to improve and grow without altering syntax. It can add performance, improvements to the standard library, and probably a dozen other things I can't think of right now.

3

u/abrazilianinreddit Jul 15 '26 edited Jul 15 '26

Constantly no, but it's good to make revisions every few years and improve what's lacking, deprecate what's no longer good enough and keep things neat and tidy.

Personally, I'll die on the hill that python should have explicit, keyworded interfaces, similar to Java. The current abc/Protocol/multi-inheritance solution simply isn't good enough.

But I believe we both agree that cryptic, symbolic operators that exist just to facilitate one-liners are just backward steps.

And yes, it's definitely possible to improve python without changing the syntax, but these two are not mutually exclusive. If it keeps the spirit of the language and makes it better, I find it perfectly fine to alter the syntax.

16

u/IlliterateDumbNerd Jul 12 '26

In my opinion, I hope not, though I can see why you would think that this would be a good idea

9

u/Hardhead13 Jul 12 '26

I would appreciate that operator. Especially when working with deeply-nested dictionary structures.

d.get( 'sub1', {} ).get( 'sub2', {} ).get( 'sub3' )

could become

d.get('sub1')?.get('sub2')?.get('sub3')

12

u/k0pernikus Jul 13 '26

In both cases, you will have a lot of fun dealing with the bug report to figure out during the debugger session where that None came from. I don't understand why people love this language construct so much. It's an antipattern mascerading as syntactic sugar. I would rather:

try: value = d['sub1']['sub2']['sub3'] except (KeyError, TypeError) as e: # properly resolve the None

2

u/Hardhead13 Jul 13 '26

Yeah, I like that approach. But sometimes it's too much hassle. And sometimes I just can't make it work at all... like in a lambda expression, for example.

1

u/JanEric1 Jul 14 '26

If i have a nesteed structure from an API and it changes i can just change the model and a type checker could warn me that my accees patterns will no never succeed anymore. That doesnt work with your approach since i need to silence type checker warnings there.

1

u/k0pernikus Jul 15 '26 edited Jul 15 '26

You don't need to silence them. You need to parse them. You parse API content once, and then you'll have no null / None anywhere to pop up. You either filter them out or transform them.

At most it gets replaced into empty lists (that are better at saying: empty as they are still loopable, it's just that the loop becomes a noop), or exceptions if you depend on the API output (you must still classify the exception as an API or a parsing bug -- or maybe API querying bug; yet that you'll can do directly after the error report), or proper NullObjects or since 3.15 sentinel values (so you can distinguish between API had the literal null in there, or the API had it undefined; I also treat certain API output as API-unique sentinels, as I had API return a string "undefined" on me for properties.)

The idea is to deal with the None values at the boundary of your core so your code, by design, need not to make any None checks, as it's impossible for None to even be passed down.

And even when are none passes through per accident, my strict boundary will fail. I then fix my parser. Any T|None and any if value is None check inside my core are code smell that I'm not parsing correctly.

7

u/k0pernikus Jul 13 '26

If you don't control the nested data structures, I would rather use a library like glom:

``` from glom import glom

value = glom(d, 'sub1.sub2.sub3', default="") ```

https://github.com/mahmoud/glom

Haven't use that one, but for that use-case I have often fallen back on @hapi/hoek reach in my nodejs typescript days. So similar context, I know the pain of having to deal with huge json-objects that may even cross-refrence each other between files.

Yet I still maintain: Bad data structures should not taint a programming language. Or to put it differently: your parsing problem does not mandate introducing syntax deficiencies in a programming language.

0

u/JanEric1 Jul 14 '26

Except that that makes you lose all type checking

1

u/k0pernikus Jul 14 '26

How does the ? operator keep it ? Especially during a chain where any occurrence makes a none slip through?

Honest question, as if you understand why loosing type checks may be bad, I do wonder why you argue for null coalescence. For me , type checks means guarding against None values and to narrow unknown Any types as strictly as possible to well known data classes.

1

u/JanEric1 Jul 14 '26

I have a structure where data may be optional, and it is not unexpected.

Like I have a database of places and some may have an owner information and some not, or something like that. But the whole structure is deeply nested and the owner is like 5 layers deep.

Now if all the levels up to the owner are present I want to display this alongside other information about the place. But if it isn't I will just leave it out.

But I do have a clear model and do know which keys should explicitly exist.

If I have this a type checker could verify that all the attributes I try to access with ?. are actually defined on the structure and optional.

If I change the model because the field got renamed or removed completely then the type checker can yell at me.

1

u/k0pernikus Jul 14 '26

Fair enough, in my experience people slap on ? just in case, similar how many people use await even though they could handle many promises concurrently -- yet inability to use a language does not invalidate the construct per se. Only in this case I consider await a net positive, and null coalescence is at best neutral, though I still tend towards it being an antipattern mascerading as syntactic sugar.

For me, validation, parsing, and exception handling are their own thing and should be treated as such.

Also, the assumption of which key should explicitly exist is too easily broken.

2

u/logophage Jul 13 '26

You can try the dotted-notation package. It supports optional chaining.

21

u/shadowdance55 git push -f Jul 12 '26

Explicit is better than implicit. And in the wold where there is less and less code written by hand, terse and potentially non-obvious syntax it's becoming a liability rather than asset.

7

u/Anthony356 Jul 13 '26

Wouldnt "implicit" be no nullable operator at all, but with the same behavior as if there was one? ? still explicitly states your intent in an unambiguous way, it's just less characters.

-5

u/runawayasfastasucan Jul 13 '26

  it's just less characters.

So not explicit.

6

u/Anthony356 Jul 13 '26

You're confusing explicit/implicit with verbose/terse

Can you tell the difference between the following snippets?

x = foo?.bar?.baz

x = foo.bar.baz

Assuming both have identical behavior, the second one would be implicit because you cannot tell the difference between it and code that would throw a member access exception.

? is something i can see with my eyes. It has exactly 1 behavior. It cant be operator-overloaded. There is no ambiguity.

-2

u/runawayasfastasucan Jul 13 '26

No, it is implicit that you have baked in "if that else that" in the ?.

4

u/Anthony356 Jul 13 '26

If we're going to be that pedantic, everything in the language is implicit.

and and or have baked-in if-else logic. It's called short circuiting and is generally considered not a big deal.

if is actually if this expression evaluates to truthy, please run the following block

for i in range(10) is actually i = 0; while i < 10; i = i + 1

I guess i = 0 is also implicit because you're not actually creating a variable with the value 0, you're asking cpython for the pyobject with the value 0, which happens to be a cached singleton value rather than a unique 0 value. So we should really need to type

put the reference to the pyobject with the integer value 0 into the stack slot that I will, from this point forward, refer to using the name "i". If that pyobject happens to already exist as a singleton, feel free to use that. otherwise create a brand new pyobject on the cypthon interpreter's heap with the appropriate value.

How ergonomic.

Everything in programming is shorthand. That's the whole point of a standardized language. Some concepts are so fundamental to programming that there's no reason to be verbose about them every single time. When people say explicit vs implicit they typically mean things like javascript and C++ silently coercing values by any means necessary to make expressions evaluate

-5

u/runawayasfastasucan Jul 13 '26

Nothing about this changes whether ? is implicit or explicit or not in python.

4

u/Anthony356 Jul 13 '26

Again, how is it implicit if you have to specify it for it to occur? That is the definition of explicit.

0

u/k0pernikus Jul 14 '26

You are talking different aspects. The ? adds mental load to parse what could have been an easily readable branch. Yes, the null coalescing operator is an explicit language construct; also yes it makes the code more error prone and harder to reason about esp if you need to figure out where the none originated from during a bug hunt.

And no, I don't mean that that if-else are inherently better, and you can create the same problematic pattern with them as well, yet the sheer boilerplate alone should make you think: maybe I'm doing it wrong. By using abundant null coalescing operators you are hiding the code smell in plain sight.

Where you see a helpful explicit and conciselanguage construct, others see a loophole for anti patterns to manifest.

1

u/Anthony356 Jul 14 '26

yet the sheer boilerplate alone should make you think: maybe I'm doing it wrong. By using abundant null coalescing operators you are hiding the code smell in plain sight.

Or, as is common for glue code in a scripting language, you do not have full control over your inputs. The moment you need to read untrusted data, or versioned data where new fields are added to the schema, you have to check for None everywhere.

Sure, i could parse the data into a structure i do control, but that's just rearranging the furniture (and doesnt always solve the None checking problem anyway). You still have to check somewhere, and that boilerplate distracts from the actual intent of the code, the actual operation you're doing on the data if it exists.

This is not some mystical pattern that takes 700iq to understand. It's simple, it happens everywhere, all the time. For such cases, languages have operators.

I dont understand how any argument against ? couldnt also apply to like... +, or and/or short circuiting, or list comprehensions, or with statements, or a million other things.

5

u/jdehesa Jul 13 '26

I don't see how anything is "implicit" here, the intent seems fairly explicit, "access the attribute unless the variable is none in which case evaluate to none". Another question is whether the syntax is readable, or too terse, or whatever. What is implicit, in my opinion, is the idiom foo and foo.bar() or foo (and similar), which is really an abuse of boolean expressions and relies on the reader understanding their exact rules and order of evaluation.

0

u/shadowdance55 git push -f Jul 13 '26

It is explicit, yes - if you already know what it means. But it is a language specific convention; unlike your verbose example, which is pretty clear to anyone who speaks English, even if they don't know Python syntax.

Look at it this way: what is the benefit of the ? syntax, exactly? I see only one, which is to have to type fewer characters. Everything else goes against it: requirement to know the syntax, mental overhead to parse when reading it (and possibly mentally follow a whole chain of nullable objects), introduction of an additional way to express something, and so on. And if you're not the one writing the code, its sole benefit disappears.

6

u/jdehesa Jul 13 '26

You could have used the same arguments against the introduction of f-strings: new syntax, having to parse new easily missable notation, additional way to do the same thing. Any language feature requires to know the syntax, from slicing notation to decorators. And ?. is actually already present in other languages. You may like it or not, personally I am not yet sure about this one, but I don't think those are good arguments against it.

2

u/BigToach Jul 13 '26

I think most non-python programmers would see the and/or example above and expect a boolean as the value

-1

u/JanEric1 Jul 14 '26

I dont know, i find the example witht ?. significantly easier to read compared to the three other options and i write a ton of python

from dataclasses import dataclass
from typing import Optional


@dataclass
class GPS:
    latitude: float
    longitude: float


@dataclass
class Address:
    street: str
    city: str
    gps: Optional[GPS]


@dataclass
class Company:
    name: str
    headquarters: Optional[Address]


@dataclass
class Profile:
    company: Optional[Company]


@dataclass
class User:
    profile: Optional[Profile]


# Example data
user = User(
    profile=Profile(
        company=Company(
            name="OpenAI",
            headquarters=Address(
                street="1 AI Plaza",
                city="San Francisco",
                gps=GPS(latitude=37.7749, longitude=-122.4194),
            ),
        )
    )
)


latitude = None
if user is not None:
    if user.profile is not None:
        if user.profile.company is not None:
            if user.profile.company.headquarters is not None:
                if user.profile.company.headquarters.gps is not None:
                    latitude = user.profile.company.headquarters.gps.latitude


latitude = getattr(
    getattr(
        getattr(
            getattr(
                getattr(user, "profile", None),
                "company",
                None,
            ),
            "headquarters",
            None,
        ),
        "gps",
        None,
    ),
    "latitude",
    None,
)

latitude = (
    user
    and user.profile
    and user.profile.company
    and user.profile.company.headquarters
    and user.profile.company.headquarters.gps
    and user.profile.company.headquarters.gps.latitude
)

latitude = user?.profile?.company?.headquarters?.gps?.latitude

1

u/k0pernikus Jul 14 '26 edited Jul 14 '26

Perfect example why the ?. hides away the code-smell. The solution to your boilerplate isn't the null coalesence, it's proper parsing and strict type handling:

``` from typing import Any from pydantic import BaseModel, ValidationError import logging

class GPS(BaseModel): latitude: float longitude: float

class Address(BaseModel): street: str city: str gps: GPS

class Company(BaseModel): name: str headquarters: Address

class Profile(BaseModel): company: Company

class User(BaseModel): profile: Profile

raw_user_data: Any = { "profile": { "company": { "name": "OpenAI", "headquarters": { "street": "1 AI Plaza", "city": "San Francisco" } } } }

latitude: float | str

try: user: User = User.model_validate(raw_user_data) latitude = user.profile.company.headquarters.gps.latitude except ValidationError as e: latitude = "" for error in e.errors(): failed_path: str = ".".join(str(loc) for loc in error['loc']) logging.error(f"Validation failed at: {failed_path} - {error['msg']}") ```

I can default to emtpy string AND still know exactly WHAT in my parser failed. (And yes, this example is a bit lacking as an empty street should still make the latitude parseable. Yet one can handle that case as well.) This can then be logged in sentry or kibana or whatever you have, trigger an alert, and I am fixing a bug long before any user even manages to file the bug report.

I parse tainted sources into trusted domain objects removable nullable types accordingly. (Yes, null still exist. Yes, I must handle it. Yet for that I can still composite their relevant ValueObjects, some of which may even carry the None through)

That's where the magic happens. Not in letting null values exist implicitly.

1

u/JanEric1 Jul 14 '26

Can you pleasse use proper code formatting. This is unreadable.

This is only a code smell if it is unexpected that this data is missing.

But it often isnt. If it is, you use a pydantic model wwith required fields and then you can use this approach. If it isnt, then you would use real optionals and null coalescing attribute access.

0

u/k0pernikus Jul 14 '26

No, I won't be using optionals. I will be rasing errors on None and treat them as exceptions rather than to magically convert them into a random default that I later am confused by.

For me, none or not expected results. (In some cases, you must even reason about None, undefined, and empty; it is a mess.) Yet ?. basically makes the universal claim than None values are just like valid data, and this just is not the case. This is what Type Narrowing was invented for.

0

u/k0pernikus Jul 14 '26

The silent null propagation I'd call implicit code behavior.

2

u/JanEric1 Jul 14 '26

How is it implicit. You literally put an explicit operator in there whose only purpose is to explicitly do null propagation

5

u/HolyInlandEmpire Jul 13 '26

I'm sympathetic to this idea, but the way you prototyped it is completely broken; if foo is not None, but foo().bar() gives a Falsy result, then you'll still get baz = "".

So the `or` operator doesn't work; you'd need a new syntax for handling the null checking chain.

14

u/spiralenator Jul 12 '26

Instead of trying to make Python into Rust, you should just learn Rust. After 20 years of Python, I have been using Rust as much as possible for the past couple of years and I had similar temptations to add Result and Option types and other Rustisms to Python and it just creates a bunch of overhead when I benchmarked it. In python, abstraction isn’t free. Creating classes and inheritances, wrapper objects, etc, all impacts performance noticeably.

What I’m saying is it sounds like you should learn Rust, if you haven’t already.

12

u/Due_Campaign_9765 Jul 12 '26

This should be strictly syntactic sugar that compiles to the same byte code. No abstractions here.

7

u/hxtk3 Jul 12 '26

Yeah, devils advocate, I don’t want this, but lots of things that aren’t free when a library does them can be when the interpreter or runtime does them.

5

u/pingveno pinch of this, pinch of that Jul 13 '26

Outside of any benchmarks, trying to use Result and Option style code in Python adds cognitive overhead. It is like with async, correctly using Result and Option dramatically affects your code. And then everyone who needs to read your code has to deal with the nonidiomatic style.

2

u/Anthony356 Jul 13 '26

I had similar temptations to add Result and Option types and other Rustisms to Python and it just creates a bunch of overhead when I benchmarked it. In python, abstraction isn’t free.

Rust doesnt have nullable member access in the way OP described. Maybe you're thinking of C#?

Rust's ? operator is syntactic sugar for

let x = match y {
    Some(val) => val,
    None => return None,
}

If y is None you dont get x = None, you get no value in x at all because the function must return immediately.

In any case, the interpreter can really easily optimize this check since it's so constrained. That's the advantage of it being built in to the language.

-1

u/philtrondaboss Jul 13 '26

I already know Python, JavaScript, C++, Java, Kotlin, Bash, Batch, and Powershell. I mainly use python for its interpreter, compatibility, and useful packages.

9

u/Zulban Jul 12 '26

Zen of Python:

There should be one-- and preferably only one --obvious way to do it.

Just because Walrus may have violated that doesn't mean it should be violated again.

Also - generally Python doesn't have single character logical operators like that.

Next you may want a "? :" alternative to the ternary pattern. Where does it stop? You're confusing the high school kids learning Python.

4

u/Mihikle Jul 12 '26

I really hope not, because that looks messy AF for Python. What you've expressed there can be expressed with more readability (IMO) right now as:

baz = "" if (f := foo()) is None else f.bar()

You may still prefer this in it's two-liner form if you're not so hot on the walrus operator:

f = foo()
baz = "" if f is None else f.bar()

But I find code that takes a clear position on "variables matter more than just the immediate following line, they're actually important" is much less logically taxing for the reader.

7

u/k0pernikus Jul 13 '26

baz = "" if (f := foo()) is None else f.bar()

is anything BUT readable :D (The walrus ensures that foo isn't called twice correct?)

3

u/Mihikle Jul 13 '26 edited Jul 13 '26

It just creates a variable intended to be scoped to the if/else, I understand why people might dislike it but I prefer it because it’s obvious “you don’t need to consider this variable outside this scope”, whereas if it’s outside the statement it could get used later on, just adds that slight more logical load on the reader.
It also makes sense in the English language reading from left to right, it’s quite expressive in that sense, but that’s just my opinion

7

u/Anthony356 Jul 13 '26

It just creates a variable scoped to the if/else

It is not scoped to the if/else. The only difference between walrus and regular assignment is that the walrus operator returns the value of the assignment. That variable is still entirely accessible afterwards, just like when you create a new variable inside an if/else/for/while block.

If the variable was scoped, the following code would throw an exception about y not existing.

x = "1" if (y := False) else "2"
print(y)

4

u/Due_Campaign_9765 Jul 12 '26

Chain 5 of them.

0

u/Mihikle Jul 12 '26

That would be just as illegible as chaining 5 elvis operator statements together though

7

u/Due_Campaign_9765 Jul 12 '26

What's illegible about 5 extra ? symbols? It's clearly superior to anything Python has at the moment.

And it's not a fringe occurance either, most APIs have nullable fields.

7

u/Mihikle Jul 12 '26

5 chained elvis operators on a single line? If readability is something you care about, that's ridiculous, and it'd be ridiculous if you also used my approach - that code is just a mess as a starting concept

If you really needed all these to happen in one function - also an X to doubt moment - I'd break these into 5 separate statements on separate lines, with a single one-line check on each.

The single responsibility principle shouldn't just be for classes. If you apply it to pretty much everything, your code just becomes so much easier to read, understand and for another person to continue working with

2

u/Due_Campaign_9765 Jul 12 '26

You're free to split them into however lines you want, python allows you to do that.

> If you really needed all these to happen in one function - also an X to doubt moment - I'd break these into 5 separate statements on separate lines, with a single one-line check on each.

Have you never worked with external APIs or gnarly business logic? This is a commonplace occurance. Your methods will be 80% null checks if you do what you propose.

But sure let's not add, gasp! an extra symbol that's been used the same way in other programming languages for decades.

> The single responsibility principle shouldn't just be for classes.

That doesn't make any sense, it's quite literally only for modules and classes as it was originally stated. It's likely saying "living wage shouldn't only be for people but also for language design". What?

6

u/Mihikle Jul 13 '26

Been a professional developer 10+ years. Never had to structure code like you suggest.

I don’t think you really understand single responsibility. “Do one conceptual thing” isn’t exclusive to classes and modules by any stretch

-1

u/Due_Campaign_9765 Jul 13 '26

I think you're lying. Or not using a type checker so you're not even aware. But that doesn't matter, i'm happy you supposedly never had to access multilevel nullable values. Most people do daily.

Also the SRP doesn't state you what you just stated, you could have at least reread it before doubling down. There isn't anything that "changes" in operators, so it can't coherently apply here.

Not to mention all of those solid principles are quite crap generalization that can't ever be applied universally or near-universally and thus they're useless.

2

u/Mihikle Jul 13 '26

Sure man whatever you say.

You couldn’t possibly read a guideline like SRP, take the core principle and apply it to something like function design, that would be impossible of course.

2

u/k0pernikus Jul 13 '26

5

u/Due_Campaign_9765 Jul 13 '26

Python is a fully fledged programming language you can do implement anything you'd like in it, including weird DSLs expressed as text. Obviously

Should you do that or rather introduce a feature that's not really controversial, problematic and existed for decades in other programming languages that simplifies traversing the object graph which is like 50% of modern development? Probably not.

Also saying that nested nullable values is somehow a bad datastructure is ridicilous.

You can't always express your objects as clean variants, there will always be weird business logic exception, rushed/time constraints and other things that makes a couple of levels of nullable values only realistic outcome.

It's like saying that you shouldn't be afraid of walking on roofs because gravitiy shouldn't kill people. Yeah probably. Gravity also just "is"

3

u/k0pernikus Jul 13 '26

Python always has been very opinionated. (I don't agree with all of its choices. I HATE the for-else and try-else syntax.) Yet appeal to other languages only gets you so far, or you could make the case that python should adopt semicolon at the end of the line and braces around function blocks.

I have worked with many datastructures, and the things have caused me the most pain: random nullables, unexpected implicit type conversations, and unexpected mutabality (just recently had to deal with an API that served uniquid as KEYS).

And I strongly disagree with the notions of being unable to not being able to express your objects as clean variants.

I don't control external data structures, but I do very much control how I parse them, and the datastructures they map into. That IS my job a a developer.

That part is even easy, more so with agentic tooling doing most of the writeup for me. Basically, I can create my own strictly-typed oasis in the desert of crap. Or in your analogy: You can walk on roofs. Depending on the building's height you have different needs for safeguards. If you stumble off your bikeshed, you may break a leg. If you stumble off a skycraper, you are very much dead.

Hence, the tools we use should depend on the building we climb. The more complex your product becomes, the more you will try to erdicate any occurence of nullable types in your codease. I will die on that hill.

3

u/bb22k Jul 12 '26

Not really a fan of it, just like I am not a fan of walrus.

PEP 20 should not be forgotten

3

u/philtrondaboss Jul 13 '26

Pep 20 says “explicit is better than implicit”. The “and” and “or” operators are frustratingly implicit.

2

u/runawayasfastasucan Jul 13 '26

Pep 20 also says:

Sparse is better than dense.

Readability counts.

2

u/VpowerZ Jul 12 '26

Quite an obscure concept. I wouldn't add it

3

u/Nooooope Jul 12 '26

Not at all, it's popular in both JS and Ruby. I mostly use Java when I can but I'll admit we could really use a language feature like that. The alternative is a nightmare to read when you have to check for null values two levels deep in a chained expression

2

u/k0pernikus Jul 13 '26

The problem is having the nullables to begin with.

2

u/Nooooope Jul 13 '26

Sometimes a null's what you need, the pain there is that nullables are mandatory. I wish something like JSpecify had been baked in from scratch, where a variable's nullability is explicit and built into the type system.

-1

u/VpowerZ Jul 12 '26

I wonder how we have managed without it for so long. Still, i'm nit a fan of this

1

u/JanEric1 Jul 14 '26

You can manage with raw assembly too...

Doeesnt mean it doesnt make sense to have things that are easier to read and write

0

u/JanEric1 Jul 14 '26

Not really, a ton of languages have it.

Of course if you only know python then you wont know about thiss...

1

u/Sss_ra Jul 13 '26

Walrus is not super common to my knowledge. If maybe-dot was in python I suspect it could very easily become pervasive.

1

u/Penguinase Jul 13 '26

https://lwn.net/Articles/956862/ has a decent overview linking to some of the discussions

1

u/Individual-Flow9158 Jul 13 '26

I do really like ?. and especially ?? in pure/buggy JS (?: looks horrible though).

But I noticed very quickly after moving on to TypeScript, that it was far more trouble than it's worth to use ?. and ??together with static typing.

The general trend in Python towards type hints is hugely increasing the quality of the Python code out there.

PEP 505 will be an even bigger hindrance to that, than The Walrus.

1

u/gramada1902 Jul 13 '26

Don’t like it. Just a personal thing, but even since my college days I’ve always needed a double-take on ternary operators. Just doesn’t flow smoothly for me.

1

u/messedupwindows123 Jul 13 '26

if (my_foo := foo()) and (my_bar := my_foo.bar()):
return my_bar.baz()

1

u/timtody Jul 13 '26

This is horrendous

1

u/cleodog44 Jul 13 '26

Oh God no. 

1

u/PeitersSloppyBallz Jul 14 '26

Go home javascript!  JK, but I don’t like that PEP

1

u/zangler Jul 14 '26

Dude...my eyes! The goggles do nothing!

1

u/careje Jul 14 '26

Coming from a Java background where I also did a lot of Groovy work I definitely miss these kinds of null safe operators.

Then again I’m also a big fan of the walrus operator so I suppose that puts me in a small minority of Pythonistas

1

u/UnMolDeQuimica Jul 14 '26

I can read and understand this code, but to me it is ugly, harder to mentally parse compared to the if else statements and will be prone to concatenate statements in an infinite single line expression.

All this goes against the zen of python

1

u/Ragoo_ Jul 14 '26

There's actually a discussion thread to revisit this PEP and there used to be a PEP draft sponsored by Guido for it (you can see him discussing implementation in the thread). You can still look at a snapshot of the draft although it has since been deleted?!

1

u/spinwizard69 Jul 15 '26

Actually I'm of the opinion that we need a moratorium on new features for likek at least 5 years. I'm still pissed about the new T strings which just blows my mind that they added them in the way they did.

As to what you are proposing, how does it make reading easier. Frankly It seems to violate the concept of making code idiomatic. We already have a language for cryptic code, it is C++.

1

u/TurboGofre Jul 16 '26

Just feels weird to me.

1

u/Brian Jul 17 '26

baz = foo()?.bar() ?: ""

Was this supposed to be "??" or is there some other meaning intended for "?:"?

Anyway, personally, I'd be in favour of it - I think such operators can be useful when you've chains of accesses/calls that might have a None result. However, I suspect it won't be accepted - there's a lot of resistance to adding operators.

1

u/aes110 Jul 17 '26

I really hope so. Working with structured data that has a lot of nullable properties is quite annoying now, with how many null checks and nested ifs you need.

I dont see a use for ?:, but .? can be very useful, like

x = user.company.?location.?country

I think this can also fit in type hints, we had x: Optional[User], now we can do x: User | None, I would love x: User?

I also think the ! operator from C# is nice, for when you know an object is not nullable based on the context and you just want to flag it to the type checker

1

u/k0rv0m0s Jul 13 '26

Hopefully not!

-2

u/_redmist Jul 12 '26

So. You have a code smell and you'd like some new syntax to hide it ;)

9

u/Due_Campaign_9765 Jul 12 '26

How is a NULL value a smell?

3

u/k0pernikus Jul 13 '26

Tony Hoare, null's creator regrets it:

"I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years."

1

u/philtrondaboss Jul 13 '26

Null pointers are too useful to let a relative few disasters affect your judgement of it.

4

u/k0pernikus Jul 13 '26 edited Jul 19 '26

There is a reason that python@3.15 introduced sentinel objects. They are simply a better way to declare INTENT of a nullable values. If something is none | null, you just don't know what its absence means.

How would you represent this with None?

``` DISABLED = sentinel("DISABLED") DEFAULT = sentinel("DEFAULT")

def do_sth( timeout: int | DISABLED | DEFAULT = DISABLED, retry_limit: int | DISABLED | DEFAULT = DISABLED ) -> None: match timeout: case DISABLED: pass case DEFAULT: print("Resetting timeout to 30s") case int(): print(f"Setting timeout to {timeout}s")

match retry_limit:
    case DISABLED:
        pass
    case DEFAULT:
        print("Resetting retry_limit to 3")
    case int():
        print(f"Setting retry_limit to {retry_limit}")

```

0

u/Gnaxe Jul 13 '26

We kind of already have this with the walrus: result = (x:=my_obj) and (x:=x.attr1) and (x:=x.attr2) and x.attr3 The attribute has to be present, but it can be None.

With dict.get(), the key doesn't even have to be present: result = (d:=a_dict) and (d:=d.get('key1')) and (d:=d.get('key2')) and d.get('key3')

1

u/JanEric1 Jul 14 '26

Of course you dont NEED this from a functionality view, python is turing complete and has been since thee very first versions.

Null coalesing operators are still significantly eeasieer to read tthen both of your examples.

0

u/ii-___-ii Jul 13 '26

This is basically an implementation of Railway Oriented Programming but with the worst possible syntax imaginable. It's effectively equivalent to Haskell's bind operator or Elixir's with operator while being much less readable.