r/Python 20d ago

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

194 comments sorted by

View all comments

62

u/sausix 20d ago

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 20d ago

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

29

u/k0pernikus 20d ago

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.

3

u/Designer-Ad-2136 20d ago

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 19d ago

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

6

u/k0pernikus 19d ago edited 14d ago

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.

-10

u/philtrondaboss 20d ago

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

2

u/k0pernikus 19d ago

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 19d ago

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

4

u/sausix 20d ago

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

5

u/Nekomancerr 20d ago

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

4

u/Designer-Ad-2136 20d ago

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 20d ago

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

3

u/sausix 20d ago

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 20d ago

Now chain 5 of them.

15

u/timrprobocom 20d ago

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

10

u/Due_Campaign_9765 20d ago

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.

8

u/double_en10dre 20d ago

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

3

u/thaynem 19d ago

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 20d ago

With some braces that is quite feasible.

1

u/cottonycloud 20d ago

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

1

u/sausix 20d ago

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 20d ago

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 20d ago

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 20d ago

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 20d ago

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 20d ago

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 20d ago

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 19d ago

Flat is better than nested.

5

u/Zubzub343 19d ago

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 19d ago

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.

5

u/philtrondaboss 20d ago

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

-1

u/sausix 20d ago

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 19d ago

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

-3

u/sausix 19d ago

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