I am not against the proposal, or I wouldn’t bother discussing it. The Python community is very diverse, and there are many features I don’t use. My only concern is the potential after-effects that could make the code ambiguous and hard to follow.
Hmm, I’m very much in agreement with Noah on this.
We don’t need a["k"]? to not raise if K doesn’t exist. Just use a.get("k")?.
This makes the intention clearer and avoids conflating the two cases - whether to capture “k” existing and being None, or it not existing at all.
I agree. It’s a question of whether this is about traversal or None. I consider it the former and if you make it the latter it waters it down the usefulness enough I would probably be -0 on this.
There’s code in pydoc that could be nicely cleaned up if AttributeError was caught:
And I came across that code a decade ago, which shows you how triggering it was to have to work around code like that.
Or you could default to True instead. Or you could say you shouldn’t chain in that instance. You can’t protect people from every possible error; at some point you have to let linters and code reviews do some of the work.
If you view it more as hasattr() than capturing AttributeError then certainly; pretty much every binary operation has to look up it an attribute exists to determine what special method to ultimately call (e.g., Unravelling binary arithmetic operations in Python).
No, but that’s for a style guide of code review to decide. There is so much “ugly” code you can write in Python using very nice operators. You can’t protect everyone from every conceivable issue and at some point have to lean on people being consenting adults.
I’m intrigued by the common interest in exception-aware accessors (and appreciate everyone weighing in on this), but I remain on the side of the original PEP writers: suppressing exceptions is too heavy a tradeoff and makes me -1 on the feature overall.
This is splitting hairs and completely aside from the discussion at hand, but I don’t see how either implementation would help here since name is dynamically assigned.
Addendum: I’d also like to float paring the PEP down to just the ?? and ?= operators, deferring the decision on ?. and ?[]. I suspect the first two alone are enough to cover upwards of 70% of my use cases.
Personally I like ?? and ??= and think it would probably be easy to specify their semantics, and would probably play well with Sentinel Values. They also seem to be specifiable without causing too much confusion or changes to the grammar (they are more or less syntactic sugar). The ?. operator seems to introduce a lot of contention, and that’s probably indicative of something. As far as I can tell, what most people want from ?. is the behavior that you get from something like powershell’s HashTable.
Here is some code that works with HashTable:
# creates a HashTable
$foo = @{}
# "invalid" access returns something *like* null that can be "invalidly" accessed over again.
$foo.bar
# null-like thing
$foo.bar.moo.cow
# null-like thing again
# setting values behaves reasonably
$foo.bar = @{abc='def'}
$foo.bar.abc
# abc
# this one throws:
$foo.bar.moo.cow = 'ab'
# InvalidOperation: The property 'cow' cannot be found on this object. Verify that the property exists and can be set.
Now if I had some collection that would take a json-like object and give me something that behaves this way I would just continue to sing my praises of python instead of cocking an eyebrow. Plus, you don’t have to worry about how the semantics of ?. might interact with every single object that might ever be created…
This is the key point. When I was working on this PEP, it was very much about None, and traversal of unvalidated[1] objects was just something that gets a bit nicer with get().
I’m sure most people are aware, but I was turned to -1 on the entire proposal, and remain there
For traversal tasks, I now prefer some kind of metalanguage - glom is a great example, and if I could ever remember the syntax for more than five minutes at a time I’d probably like XPath too. A traversal helper - possibly aligned with match statements? - would be a welcome addition.
For None handling, my code now relies more heavily on preventing None at the boundaries. So my code is usually going to raise rather than return None, and it’s going to replace or exclude incoming None (often with a no-op object instance that matches all the others - e.g. a None in a list of file objects gets replaced with open(os.devnull)).
I’d still probably use ?? if it were added (for None), just for the slight improvement over using or. But I think ?., ?[ and ?([2] are best avoided entirely.
In the sense that we haven’t already checked the entire schema. This is a less common scenario now, as tools like Pydantic make it easier to be pedantic about the object before you even start traversing it. ↩︎
All of which have been used incorrectly in multiple examples in this thread already… that’s a bit of a warning sign. ↩︎
I don’t have strong feelings about this proposal, I feel pattern matching and if-checks works well enough, but I’m sure I would reach for this if it was part of Python.
However, you didn’t mention this version of None-checks
def foo(bar: list=None):
bar = bar if bar is not None else []
...
which is the one I reach for when I want to make sure bar is None and not any falsey value. Although with type checking I tend to just use or.
In either case, I really hope we don’t go this far. Any operator that translates to getattr(a.b, "c", None) makes the program silently continue when the attribute name is typed wrong. This reduces the utility of any null-coalescing operator by turning it into a dangerous footgun.
It seems like there’s fair support for ?? while the others are more controversial. I think ?. might have its use case but understanding the behavior (when it raises and doesn’t) could be confusing. I probably wouldn’t use it in my code.
Perhaps scoping the PEP to just ?? ? A great use-case is function arguments. Instead of two lines of boilerplate we have one! I’m not fond of the ??= though.
def somefn(action_list=None):
if action_list is None:
action_list = ["copy"]
do_some_stuff(action_list)
def somefn(action_list=None):
action_list = action_list ?? ["copy"]
do_some_stuff(action_list)
I agree. Every time .? and []? come up, people seem to have different unstated opinions about how they should work.
Making a proposal that only included ?? and ??= would be a lot less controversial. The big problem is that it might turn out to not be useful enough to justify the change, as the use cases are more limited. The additional problem is that if it gets rejected, trying to propose .? and []? when ?? was rejected is almost certainly going to be a lot harder. I’m OK with this, as I’m a lot less keen on those operators, but others might not want to take that risk.
If we’re focusing on ??, have a suggestion/some bikeshedding. I’ve been coding quite a bit of Zig lately, and it spells ?? as orelse, which I quite prefer over using questionmarks, maybe because it fits in better with Python’s current design. I looked through the pep and couldn’t see this or similar spellings being rejected, only that the Maybe monad was rejected.
What do you all think about spelling ?? as orelse?
I like this idea but I’m hesitant to use terminology that already denotes boolean logic (and consequently, truthiness). I saw someone suggest otherwise, and although that’s a bit verbose, I think I would prefer something like that.
orelse sounds like it is related to or or booleans in general - this is not true, it doesn’t check for truthyness but for None[1]. If it did that, it would be equivalent to or
This would add a new hard keyword making the cost a lot higher.
This would prevent us from adding the augment assignment variant imo.
I would prefer the ability to also check for other sentinels ↩︎
I think it’s fair to say that for many programmers it does feel very boolish to have or in an operator, but orelse and various spellings thereof is used in many other languages for the operators and functions that does “use the value unless it’s null then use this instead”. From the top of my head Rust, C++, and Zig does it, probably more. That is not to say that there’s not an association with or and booleans, but orelse has another meaning that is commonly used. I’ll spell it otherwise for now.
I don’t think another keyword would be necessary, I’m pretty sure the grammar supports this being a soft keyword, but people who know the grammar better can correct me on that[1].
Having it spelled in English could prevent us from having an assignment variant, but I’m also fairly certain (if my understanding of soft keywords is correct) there’s nothing stopping us from having otherwise= be a thing, except that it looks funky. I do not see a big value in the assignment version, I think some amount of repetition is ok, but even then we could do something a bit silly and have otherwiseas be the assignment version (or maybe otherwise as if we want to be less silly).
In the end I just see this as a matter of taste, given that it can be implemented in the grammar/parser.
I think you could write a language using a PEG grammar that does not require any keywords. Python might have further restrictions though. ↩︎
Even if it can be implemented in the PEG parser anf there aren’t some edge cases where it does cause issues, I don’t think we should allow otherwise otherwise= otherwise otherwise otherwise. Ofcourse, this a contrived example, but especially if we choose something like otherwise I can very much see found otherwise otherwise happening at least a bit.
And yes, “looking funky” is exactly my issue with the augmented assignment variant. I also wouldn’t want to not have = in there (and as specifically would be confusing because it would clash with all other users of that keyword). But I agree that this is just an opinion, I don’t really have more arguments here.
Worth noting in Javascript land, nullish coalescing aka null safe was the most popular / most used new Javascript feature according to this years State of JS: State of JavaScript 2024: Features