I've been using python for the past 10+ years, and I've got to say that the new Syntactic formalization of f-strings (PEP 701) has got to be one of the most "huh?" changes I've seen in a while.
Was this such a big problem?
In my experience, the GIL, faster start-up times are so much higher on the totem pole, why this now?
Because someone got annoyed enough to write a pep then implement it? Python is not a company and GvR is not a CEO handing out marching orders from on high.
Hell, personal testimony, I don't give a rat's ass about the GIL or start-up times, but f-string limitations are a daily pain in the ass. So I'm absolutely grateful for whoever worked on this and looking forward to it.
Two reasons seem obvious. F-strings are relatively new -- the code is maturing and the edge cases are being filled in. Second, everyone can't be working on the GIL and start-up times. There is a tipping point at a relatively low head count where adding developers decreases productivity.
It's an open source project. If a developer wants to go out of their way to make a really cool improvement, goes through the approval process, has a clean implementation that doesn't add any backwards incompatibilities, why do you care? This isn't some private company where management tells people what to do and they have to do it.
People are so out of touch with how OSS, and the volunteer maintainers that operate it, work. It's a shame. The sense of entitlement in a subset of the userbase ("Why aren't they fixing MY issues RIGHT NOW!") is mindblowing.
No. You’re missing one constituent part of a for-profit corporate-backed language platform. You cannot just step in the ring with Ali because you think you can box.
Politics, other forces, compellation will emerge as a stronghold barrier to your meritocratical hopes and dreams.
Performance improvement is obviously nice but I personally don't even care about it.
Being able to just use any quotes in f-strings is just godsend QOL change and probably would be the biggest reason I upgrade to 3.12 (for my pet project).
Makes the grammar simpler which makes new implementations to implement tokenizer much faster/cheaper. It's a simple problem with simple solution that has been causing a medium sized issue, so why not fix it?
Not everybody needs to work on the biggest problem, that's the "waterfall" approach to project management. If CPython was a single-developer project, that might be a valid criticism, but it's not.
For me that formalization is nice because they've brought f-strings into the PEG parser which can point at the precise location of a syntax error. I'm wary of the implications of arbitrarily nested f-strings for legibility... but oh well.
It's a nice quality of life improvement. Most other languages I've used with some form of string interpolation allow quotes in nested expressions to be the same as the quotes on the top-level string, and this has IMO been a weird wart in Python. I'm happy to see it fixed.
A few releases back they replaced the python parser with a more capable PEG parser. At the time nothing really changed, but it was done to enable this kind of improvement and make it practical to evolve the syntax over time.
This change isn't so much done for the sake of being able to next quotes in f-strings, it's done because there was a dedicated extra parser for the python syntax inside f-strings, but with the new parser this can be parsed without this special case extra parser.
As a data scientist using dataframes, I'll often do print(f"Total for X is {df["col"].sum()}"). It's annoying to have to remember to switch the type of quotation mark to avoid a crash.
I suspect, a lot of people that spend time in jupyter notebooks are in the same boat. You could argue I should be setting a variable but these are experimental scripts and it's annoying. I welcome it!
What I would love to see in a future version of python is being able to do `user["email"]` or `user.email` independently of the reason.
Sometimes both work, sometimes only one of the two and an error in throw for the other one. I don't care why, I just want it to work, it's such a basic feature.
Something even crazier would be to have an equivalent of `console.log` in python. It would be an amazing feature but I think I'm the only one wanting it. I know I can use `print` or different logger. But it's a lot more complicated to use and the output is a lot less navigable than in javascript. PHP also has `var_dump`. But we don't have any equivalent in python.
> What I would love to see in a future version of python is being able to do `user["email"]` or `user.email` independently of the reason. Sometimes both work, sometimes only one of the two and an error in throw for the other one. I don't care why, I just want it to work, it's such a basic feature.
It's an absolutely terrible idea and I'm thankful that there's so little chance it'll ever happen. I don't want random objects to become mappings, nor do I want mapping entries and their attributes to conflict. Javascript is a bad language and this is one of its worst features.
> Something even crazier would be to have an equivalent of `console.log` in python. It would be an amazing feature but I think I'm the only one wanting it. I know I can use `print` or different logger. But it's a lot more complicated to use and the output is a lot less navigable than in javascript.
You... can just call `logging.whatever()`, after having called `logging.basicConfig()` to set up your basic config?
I fail to see how that would change anything to navigability. `console.log` is not inherently navigable, it's the browser's console UI which provides some navigability.
If you want to write javascript, use javascript. There are ways to get what you're asking for depending on your use case. types.SimpleNamespace in the standard library provides one approach.
"There should be one-- and preferably only one --obvious way to do it." (zen of python)
I do agree that python logging is a weak point. It is too easy to do it wrong -- particularly when you are a few modules deep.
In terms of programming language construction making `x.y` and `x["y"]` equivalent looks appealing and, admittedly, cute but there are some problems:
* For new languages: It's not generic enough since there is no equivalent of `x[t]` if t is of a non-string type. E.g. there is no way to express `x[(1,2,3)]` or `x[3]` or `x[frozenset({1, 2, "foo"})]` this way.
* For existing languages like Python: this would be a breaking change since things that can do `x.y` and `x[t]` are structurally different in Python so they're typed differently. One are called "mappings" and the other are "objects", they're completely different things. Hence, you'll get cases where `x["foo"] == 5` but `x.foo == 4` so this will for sure break some programs. Too much pain for no gain.
In pandas for example that can happen often: `df["count"] == 5` and `df.count == 5` are logically different expressions that will give different answers
No, there is no confusion here at all (for a Python developer). I would consider it a code smell though as the whole problem is completely avoidable by better naming.
> Don't you think `x["foo"] == 5` but `x.foo == 4` is a hell of a lot confusing ?
No, having used lots of OO languages before JS and its “objects are dictionaries are arrays and member access and string indexing and integer indexing are all equivalent and can be used interchangeably, except you can't use member access where the key isn't a valid identifier” approach, which I find more confusing and error prone (though I’ve since had to use JS enough to be proficient in that, too.)
Indexing an object as an indexable collection and accessing a member (field, property, or method) of the object are fundamentally different things, and having a collection item with a particular string index isn’t the same thing as an object member with a similar identifier name.
This use of objects as also quasi-associative-arrays is so broken that JS’s actual associative-array type (Map) can’t use indexing notation because of it, and has to use .get() and .set() instead, unlike the associative array types of most other dynamic languages (and several statically-typed OO languages).
The JS way is less bad as a type-specific behavior (e.g., Ruby ActiveSupport’s HashWithIndifferentAccess), though.
> Don't you think `x["foo"] == 5` but `x.foo == 4` is a hell of a lot confusing ?
No. They're different notations; one means `x.__getitem__('foo')` and the other means `x.__getattribute__('foo')`. Why should they be the same? It isn't confusing that `5-4 == 1` but `5+4 == 9`, after all.
If we assume that the dict class was enhanced with your proposed equivalence, would you want `d['items']` to be the function `d.items`? Would that not make 'items' a forbidden key?
It is error prone. You're simply refusing to see an aberration.
That's how the language works, but it doesn't mean it's intuitive and easy to understand especially for a language known for being easy to use and understand.
It's not how the language "works", it's what the language offers.
When all you have is a hash table, or when all you have is an object, you get to refer to keys and properties uniformly - because they are the same thing. When you have both, you refer to them differently - because they're different. That's it. There are some languages where objects and hash tables use the same syntax for access even though they're different things, but... you probably never used any of them, and certainly none of them is in the Top 20 on TIOBE.
I'll kick the venerable HN guidelines aside for a second and mention this: you're being heavily downvoted, all your comments in this thread are in various shades of gray, and many people offer many different arguments as to why you're wrong. Yet, you're undaunted and continue posting - I don't want to break the guidelines that much, but honestly, it reads like trolling. You don't engage with the arguments, you're just repeating the same thesis over and over again, without citing evidence. Why?
I will admit to implementing `__getattr__` and `__setattr__` in such a way that they mimic object properties in dictionaries, for specific cases.
In general, the threshold for doing so should be IMHO fairly high. In my case,
- they are data-heavy classes but not @dataclass classes,
and
- there's enough attribute access that the `["` and `"]` become visually distracting,
and
- there are nested structures, so so you can write `x.foo.bar.baz` instead of `x["foo"]["bar"]["baz"]`
This is especially useful, in our case, in a system that intakes a lot of JSON with a LOT of nested dictionaries.
I think what you are looking for as an equivalent to the JS `console.log`/PHP `var_dump` feature set is available in f-string formats.
There's not one perfect format to rule them all but the "=" "self-documenting" debug format such as `f"{some_obj=}"` probably gives you a good starting point for what you are looking for most of the time. Sometimes you still want the `str()` or `repr()` representation specifically and would want something like `f"{some_obj=!s}"` or `f"{some_obj=!r}"` respectively. In some cases objects you want pretty-printed to a console might have custom `__format__()` representations as well and it might be something like `f"{some_obj=:some-custom-format}"`.
It's obviously all still differently useful than JS having a full object browser embedded in most common consoles today, but there is interesting power to explore in f-string formats if you need quick and dirty logs of object states.
> PHP also has `var_dump`. But we don't have any equivalent in python.
Maybe pprint is for you:
from pprint import pprint
# usage, but can do more as well and has more config stuff if needed
pprint({'a':'b'})
https://docs.python.org/3/library/pprint.html
PEP 695 is great. I've been using mypy every day at work in last couple years or so with very strict parameters (no any type etc) and I have experience writing real life programs with Rust, Agda, and some Haskell before, so I'm familiar with strict type systems. I'm sure many will disagree with me but these are my very honest opinions as a professional who uses Python types every day:
* Some types are better than no types. I love Python types, and I consider them required. Even if they're not type-checked they're better than no types. If they're type-checked it's even better. If things are typed properly (no any etc) and type-checked that's even better. And so on...
* Having said this, Python's type system as checked by mypy feels like a toy type system. It's very easy to fool it, and you need to be careful so that type-checking actually fails badly formed programs.
* The biggest issue I face are exceptions. Community discussed this many times [1] [2] and the overall consensus is to not check exceptions. I personally disagree as if you have a Python program that's meticulously typed and type-checked exceptions still cause bad states and since Python code uses exceptions liberally, it's pretty easy to accidentally go to a bad state. E.g. in the linked github issue JukkaL (developer) claims checking things like "KeyError" will create too many false positives, I strongly disagree. If a function can realistically raise a "KeyError" the program should be properly written to accept this at some level otherwise something that returns type T but 0.01% of the time raises "KeyError" should actually be typed "Raises[T, KeyError]".
* PEP 695 will help because typing things particularly is very helpful. Often you want to pass bunch of Ts around but since this is impractical some devs resort to passing "dict[str, Any]"s around and thus things type-check but you still get "KeyError" left and right. It's better to have "SomeStructure[T]" types with "T" as your custom data type (whether dataclass, or pydantic, or traditional class) so that type system has more opportunities to reject bad programs.
* Overall, I'm personally very optimistic about the future of types in Python!
> If a function can realistically raise a "KeyError" the program should be properly written to accept this at some level
By definition, it is, because there is defined behavior for unhandled exceptions.
If you want to—and this is a valid preference—wrap all exceptions thrown at a lower level and present an API where they are part of the return type that must be addressed by code to pass typechecking, then you do that and return actual values of an appropriate type (you can still use exception types, if you wish) instead of raising exceptions.
Exceptions which you force client code to handle to typecheck are not exceptions, they are return values and should be converted to explicitly that, rather than adding checked exceptions.
I think that this is a hard problem and you're right that spamming KeyError everywhere is against the exceptionality of exceptions. But the current completely unchecked model is also misleading. I don't have such a great solution in mind but I think mypy should be able to verify each exception type is handled at some point in the stack. I know that it sounds vague, but these are my current unorganized thoughts.
Exceptions are always handled: Python exits while telling you about the exceptional situation.
If you’re saying that I must handle an exception in my code rather than crash because my “fail early, fail often” design choices offend your religious preferences… then keep it to yourself and out of PEPs.
No I'm not saying that at all, I'm just saying that if you need a function to handle all exceptions, the type system should be powerful enough to express that. E.g. in a webserver if you have a handler that needs a wrapper like:
the type system needs to be able to determine that "f doesn't raise an exception". For example, one way to do this can be that all functions by default raise exception and we type:
Of course, here we need to specially handle `KeyboardInterrupt`, signals and exceptions returned by `logger` etc. I would personally recommend ignoring `KeyboardInterrupt`, and signals and type annotating `logger.exception` as `NeverRaises`. 95% is still better than 0%.
> I'm just saying that if you need a function to handle all exceptions, the type system should be powerful enough to express that.
I disagree, in part because “handles all exceptions” is a lie; exceptions can occur at any point, including in exception-hnadling code.
It is not possible to have a Python function which provides the guarantee you want, so it makes no sense to have a Python type system which expressed it: it will either be never used or a lie.
If you think you need this guarantee, you need to step back and ask what the functional requirement actually is and find a different way of meeting it.
Are there any other languages where typing is purely optional and yet so much effort is going into developing the typing system?
I started experimenting with Kotlin just when Python was starting to get type hints and after my experience with Kotlin I am totally sold on the benefits of typing. So to me it's great to see these recent developments. It just feels weird to me to have a language that is fundamentally dynamically typed (and committed to that paradigm) invest so much effort in developing this elaborate but optional typing system.
Typing is nice just for the documentation it provides. Refactoring old python scripts was a pain because you had no idea what the arguments and return values were.
I've been steadily finding the love for F# for sort-of-similar-but-not-quite-the-same reasons. I like Python. It's still my go-to language for most things. But I definitely feel a tail off in utility as code base size increases, and I find myself asking mentally "but what type is that thing?".
F# is sort of the opposite of Python. It's strongly, statically and soundly typed, based on Hindley-Milner. It has very good type inference, meaning that explicit type annotations are usually not required. But it's still type checked, and compilation will fail if there are type errors. So F# gives you type soundness without requiring type annotations (in most cases); Python now supports type annotations but doesn't (yet) give type soundness guarantees.
Of course, F# doesn't have the Python ecosystem, and that's a major issue. Whilst it sits on the .Net platform, it's very much Sunday League to C#'s premier division. Whilst most all .Net libs can be used in F# thanks the .Net CLR, very little is idiomatic: F# is a functional language, C# is object-oriented at heart.
I think my fantasy platform would be some child of F#-the-language and Python-the-ecosystem. Maybe Python's incremental support for typing will get me there someday.
Similar sentiment with Rust (love the ecosystem, and love the language, except for the many, many parts of it that I don't need). F# comes up a lot in discussions like this. From a thread a few days ago: https://news.ycombinator.com/item?id=37892666
> the time raises "KeyError" should actually be typed "Raises[T, KeyError]"
You can't do this in Python your bucket will always have hole you can't plug. Arbitrary exceptions can appear anywhere in your code thanks to signal handlers.
KeyboardInterrupt is the one you probably know without knowing.
Exceptions can even come from higher in the stack down to you with the .throw method on generators.
That'll be really helpful for nested method calls which proxy a lot of their arguments along. I can also see that helping out when creating stub libraries.
… but the Unpack[…] wrapping required is awful, required because they initially defined kwargs type annotations in a rather baffling way.
(I don’t know about others, but it was obvious to me from the start that the way the *args and **kwargs type annotations refer to the sequence values rather than the args/kwargs bindings was always going to cause confusion, and was very likely to cause problems down the line when they wanted more precise typing; and that day has arrived for kwargs, no idea if they have any plans for similar on args, which is sometimes useful. They should just have required `*args: List[T], **kwargs: Dict[str, T]` from the start.)
I suspect people will use these comments to share their wishlist of Python features, so let me add that I really wish Python had a safe navigation operator, for when you are dealing with nested objects that could be None. I have been trying to parse a lot of XML and JSON in Python lately and a feature like that could really help reduce boilerplate checks.
Of course this only works at one level and not arbitrarily deeply, but you still need to check for None with this code; it may actually be better to write `x.get("key") or {}` so that you always get an empty dict.
I write 'may' because the difference between None and an empty dict may be very subtle and rarely does the API specify with enough precision what are supposed to be the semantics of each case.
>> but you still need to check for None with this code
> Not sure what you mean here. You only have to check for None if you use `.get("key")` and don't provide a fallback value.
GP was talking about `{"foo": None}` and trying to drill deeper, which I misunderstood. Still, a simple try/except allows you to short-circuit the deeply nested access.
The issue is if the dictionary has a key defined and the value is None. Your get expression will return None, causing your next access to raise an error.
Ah, I see. Still, I'd just catch that exception and move on because it's obvious you won't have anything more deeply nested anyway. Blowing up on `None` is an easy short-circuit.
The thing being discussed is attempting to access deeply nested values, so short-circuiting here is a win-win. I.e., you wouldn't want to unnecessarily traverse tons of empty dictionaries using the `.get() or {}` trick.
This has the side effect of replacing falsy values like False or 0 with an empty dictionary instead of giving you the actual value. For the exact intended behaviour, you do need to explicitly check for None instead of using a short circuit trick with or.
Yeah, when trying to drill down into deeply nested structures, it'd be a waste to just blindly nullish-coalesce any None objects into empty dicts instead of just immediately short-circuiting out of the deep access. It's a cute trick but would not pass code review in my shop.
The problem was underspecified, but I suggested that under the assumption that the value would be an Optional[dict]. If you have another falsy value, it means either Optional[Any] or something like Optional[Union[dict, list]] (I put list as an example, but you get the idea).
I would say the only reasonable choice is Optional[dict], in which case it should be sufficient, but Python being Python, it could be anything. And in these cases you need to handle all cases way more carefully.
The syntax has a bit of a learning curve, but there's a library called glom that can be helpful in parsing heterogeneous JSON. Specifically it has a coalesce operator that let's you define multiple paths to get the data you are trying to get at, with a default value if none of the paths are valid.
You're right. Python is pass by reference and it's the reference being changed, not the object. This is what happens when you read code on the internet after drinks.
I did type it on my phone, so I’m sure it needs a little correction (particularly the choice of exceptions is not optimal, I think) but feel free to run it in any recent python interpreter and return back with any mutation you see.
That's something I've wished for a long time that it had. I know you can use exceptions to get similar behavior but it's really ugly and I just generally don't like raising exceptions for cases that aren't actually exceptional (even though I know Python uses exceptions for normal flow with StopIteration).
What's actually a bit odd about Python missing that operator is that its `or` operator acts as a rough equivalent of C#'s null-coalescing (`??`) operator. To me the safe navigation operator goes hand-in-hand with that.
> What's actually a bit odd about Python missing that operator is that its `or` operator acts as a rough equivalent of C#'s null-coalescing (`??`) operator.
Python `or` is C# `||` plus type coercion. It is not even roughly `??` because important non-None Python values are falsey (0, False, empty collections).
It evaluates to the left if that's truthy otherwise it evaluates to the right, similar to how `??` evaluates to the left if that's non-null otherwise the right.
In C# even if `||` automatically coerced the types, the output will always be a bool.
It's been stuck in discussions for a long time, unfortunately. The main reason that I remember gleaning from discussions is it's unclear just how "special" None should be treated as, and whether having special syntax for it is excessively "special".
I saw some suggestions for overridable behaviour in the data model for None-like objects, but didn't scrutinise them closely. Either way, it seems controversial. On the one hand, the ability to override operators is really key to Python's design, but on the other it could lead to rather surprising behaviour from overeager overriding. (But to be fair, this is true of other, more common operators too.)
So, for now we're stuck with `if _ is not None else`, I suppose!
I haven't even used Python for like four years now, but all of these suggestions to do try except and get.key() or {} seem to be ignoring that Python has had a defaultdict in the standard library for, I don't even know, at least the last decade and a half, as long as I've been paying attention. It's even written in C: https://github.com/python/cpython/blob/d9246c7b734b8958da034....
Shameless self-plug but with my project Willow[0] we have a management server implementation to deal with multiple devices, etc. We have a new feature called "Willow One Wake" that takes the incoming audio amplitude when wake word is detected and uses our Willow Application Server (python) to only activate wake on the device closest to the person speaking. Old and tired compared to the commercial stuff but a first in the open source space.
The asyncio improvements in Python 3.12 especially (plus perf generally) have been instrumental in enabling real world use of this. With Python 3.12 asyncio, uvloop, and FastAPI it works remarkably well[1]. As the demo video shows not only does it not delay responsiveness, it has granularity down to inches.
On the principle that it works best when you know your tools. The lament is about the features piling up at such fast pace--and of questionable utility, too--that it's exhausting to keep up/master over. You go for a vaccation, and the language you once knew, mastered, and loved, has left you behind.
On the utility of features, the arbitraty nesting of f-strings stands out. Why in tarnation would anyone need such nesting? Isn't it simpler and readable to keep non-trivial expressions out of the f-string itself?
> Isn't it simpler and readable to keep non-trivial expressions out of the f-string itself?
But then you have to define what non-trivial expressions are, and more importantly, people using the language have to learn that definition too. By having f-strings accept all valid expressions, the language actually becomes simpler.
This sounds like a personal problem, honestly. Do you read every diff too? If not, you can't deeply think through the performance implications of how existing features are changing. What about the rest of your userland? No one expects anyone to do that.
So many breaking changes promulgated with covert version numbers—an unfortunate truth for Python and why the writing on the wall is clear—Python is not dependable—Go is the safer choice for business applications and services.
Java and C# are much safer options, with almost 30 years of deployment production, and an ecosystem of programming languages across the JVM/ART and CLR, that aren't afraid to let programmers express themselves.
> So many breaking changes promulgated with covert version numbers
This has been the new strategy to avoid another python2-python3 debacle. As long as you're aware it's not semver and minor versions can have breaking changes it's no biggie.
119 comments
[ 3.6 ms ] story [ 179 ms ] threadWas this such a big problem?
In my experience, the GIL, faster start-up times are so much higher on the totem pole, why this now?
Hell, personal testimony, I don't give a rat's ass about the GIL or start-up times, but f-string limitations are a daily pain in the ass. So I'm absolutely grateful for whoever worked on this and looking forward to it.
People are so out of touch with how OSS, and the volunteer maintainers that operate it, work. It's a shame. The sense of entitlement in a subset of the userbase ("Why aren't they fixing MY issues RIGHT NOW!") is mindblowing.
Politics, other forces, compellation will emerge as a stronghold barrier to your meritocratical hopes and dreams.
Being able to just use any quotes in f-strings is just godsend QOL change and probably would be the biggest reason I upgrade to 3.12 (for my pet project).
For me that formalization is nice because they've brought f-strings into the PEG parser which can point at the precise location of a syntax error. I'm wary of the implications of arbitrarily nested f-strings for legibility... but oh well.
This change isn't so much done for the sake of being able to next quotes in f-strings, it's done because there was a dedicated extra parser for the python syntax inside f-strings, but with the new parser this can be parsed without this special case extra parser.
It's all to manage technical debt.
I suspect, a lot of people that spend time in jupyter notebooks are in the same boat. You could argue I should be setting a variable but these are experimental scripts and it's annoying. I welcome it!
Are you aware that more than one change can be worked on at a time?
>f"""{f'''{f'{f"{1+1}"}'}'''}"""
Something even crazier would be to have an equivalent of `console.log` in python. It would be an amazing feature but I think I'm the only one wanting it. I know I can use `print` or different logger. But it's a lot more complicated to use and the output is a lot less navigable than in javascript. PHP also has `var_dump`. But we don't have any equivalent in python.
It's an absolutely terrible idea and I'm thankful that there's so little chance it'll ever happen. I don't want random objects to become mappings, nor do I want mapping entries and their attributes to conflict. Javascript is a bad language and this is one of its worst features.
> Something even crazier would be to have an equivalent of `console.log` in python. It would be an amazing feature but I think I'm the only one wanting it. I know I can use `print` or different logger. But it's a lot more complicated to use and the output is a lot less navigable than in javascript.
You... can just call `logging.whatever()`, after having called `logging.basicConfig()` to set up your basic config?
I fail to see how that would change anything to navigability. `console.log` is not inherently navigable, it's the browser's console UI which provides some navigability.
"There should be one-- and preferably only one --obvious way to do it." (zen of python)
I do agree that python logging is a weak point. It is too easy to do it wrong -- particularly when you are a few modules deep.
> "There should be one-- and preferably only one --obvious way to do it." (zen of python)
Look at all the other possibilities to do `f"hello {name}`. That are more than one obvious way to do it.
* For new languages: It's not generic enough since there is no equivalent of `x[t]` if t is of a non-string type. E.g. there is no way to express `x[(1,2,3)]` or `x[3]` or `x[frozenset({1, 2, "foo"})]` this way.
* For existing languages like Python: this would be a breaking change since things that can do `x.y` and `x[t]` are structurally different in Python so they're typed differently. One are called "mappings" and the other are "objects", they're completely different things. Hence, you'll get cases where `x["foo"] == 5` but `x.foo == 4` so this will for sure break some programs. Too much pain for no gain.
Don't you think it should not be possible to have such a thing ? To me it's so prone to error and there is absolutely too much gain to fix this.
By the way, there is a vulnerability (Prototype Pollution) that is only possible due to this behaviour in JS: https://portswigger.net/web-security/prototype-pollution
No, having used lots of OO languages before JS and its “objects are dictionaries are arrays and member access and string indexing and integer indexing are all equivalent and can be used interchangeably, except you can't use member access where the key isn't a valid identifier” approach, which I find more confusing and error prone (though I’ve since had to use JS enough to be proficient in that, too.)
Indexing an object as an indexable collection and accessing a member (field, property, or method) of the object are fundamentally different things, and having a collection item with a particular string index isn’t the same thing as an object member with a similar identifier name.
This use of objects as also quasi-associative-arrays is so broken that JS’s actual associative-array type (Map) can’t use indexing notation because of it, and has to use .get() and .set() instead, unlike the associative array types of most other dynamic languages (and several statically-typed OO languages).
The JS way is less bad as a type-specific behavior (e.g., Ruby ActiveSupport’s HashWithIndifferentAccess), though.
No. They're different notations; one means `x.__getitem__('foo')` and the other means `x.__getattribute__('foo')`. Why should they be the same? It isn't confusing that `5-4 == 1` but `5+4 == 9`, after all.
If we assume that the dict class was enhanced with your proposed equivalence, would you want `d['items']` to be the function `d.items`? Would that not make 'items' a forbidden key?
Most languages that have both objects and associative arrays (so not Lua or JavaScript) make this distinction.
It's not particularly error prone. You're simply refusing ("don't care why") to learn how to use the tool you're given.
user instance from a django model => user.email
It is error prone. You're simply refusing to see an aberration.
That's how the language works, but it doesn't mean it's intuitive and easy to understand especially for a language known for being easy to use and understand.
When all you have is a hash table, or when all you have is an object, you get to refer to keys and properties uniformly - because they are the same thing. When you have both, you refer to them differently - because they're different. That's it. There are some languages where objects and hash tables use the same syntax for access even though they're different things, but... you probably never used any of them, and certainly none of them is in the Top 20 on TIOBE.
I'll kick the venerable HN guidelines aside for a second and mention this: you're being heavily downvoted, all your comments in this thread are in various shades of gray, and many people offer many different arguments as to why you're wrong. Yet, you're undaunted and continue posting - I don't want to break the guidelines that much, but honestly, it reads like trolling. You don't engage with the arguments, you're just repeating the same thesis over and over again, without citing evidence. Why?
There's not one perfect format to rule them all but the "=" "self-documenting" debug format such as `f"{some_obj=}"` probably gives you a good starting point for what you are looking for most of the time. Sometimes you still want the `str()` or `repr()` representation specifically and would want something like `f"{some_obj=!s}"` or `f"{some_obj=!r}"` respectively. In some cases objects you want pretty-printed to a console might have custom `__format__()` representations as well and it might be something like `f"{some_obj=:some-custom-format}"`.
It's obviously all still differently useful than JS having a full object browser embedded in most common consoles today, but there is interesting power to explore in f-string formats if you need quick and dirty logs of object states.
https://docs.python.org/3/whatsnew/3.8.html#bpo-36817-whatsn...
Maybe pprint is for you:
Although it'd be nice if it worked with slotted classes instead of just blowing up.
Dataclasses have asdict which work on both, but it's annoying there's nothing for regular classes.
1. Write a class with custom __getitem__ and __getattr__ methods
2. Use a template system like Django or Jinja that implements this in certain situations
3. Use a library like https://pypi.org/project/python-box/
* Some types are better than no types. I love Python types, and I consider them required. Even if they're not type-checked they're better than no types. If they're type-checked it's even better. If things are typed properly (no any etc) and type-checked that's even better. And so on...
* Having said this, Python's type system as checked by mypy feels like a toy type system. It's very easy to fool it, and you need to be careful so that type-checking actually fails badly formed programs.
* The biggest issue I face are exceptions. Community discussed this many times [1] [2] and the overall consensus is to not check exceptions. I personally disagree as if you have a Python program that's meticulously typed and type-checked exceptions still cause bad states and since Python code uses exceptions liberally, it's pretty easy to accidentally go to a bad state. E.g. in the linked github issue JukkaL (developer) claims checking things like "KeyError" will create too many false positives, I strongly disagree. If a function can realistically raise a "KeyError" the program should be properly written to accept this at some level otherwise something that returns type T but 0.01% of the time raises "KeyError" should actually be typed "Raises[T, KeyError]".
* PEP 695 will help because typing things particularly is very helpful. Often you want to pass bunch of Ts around but since this is impractical some devs resort to passing "dict[str, Any]"s around and thus things type-check but you still get "KeyError" left and right. It's better to have "SomeStructure[T]" types with "T" as your custom data type (whether dataclass, or pydantic, or traditional class) so that type system has more opportunities to reject bad programs.
* Overall, I'm personally very optimistic about the future of types in Python!
[1] https://github.com/python/mypy/issues/1773 [2] https://discuss.python.org/t/extend-type-hints-to-cover-exce...
By definition, it is, because there is defined behavior for unhandled exceptions.
If you want to—and this is a valid preference—wrap all exceptions thrown at a lower level and present an API where they are part of the return type that must be addressed by code to pass typechecking, then you do that and return actual values of an appropriate type (you can still use exception types, if you wish) instead of raising exceptions.
Exceptions which you force client code to handle to typecheck are not exceptions, they are return values and should be converted to explicitly that, rather than adding checked exceptions.
If you’re saying that I must handle an exception in my code rather than crash because my “fail early, fail often” design choices offend your religious preferences… then keep it to yourself and out of PEPs.
Of course, here we need to specially handle `KeyboardInterrupt`, signals and exceptions returned by `logger` etc. I would personally recommend ignoring `KeyboardInterrupt`, and signals and type annotating `logger.exception` as `NeverRaises`. 95% is still better than 0%.
I disagree, in part because “handles all exceptions” is a lie; exceptions can occur at any point, including in exception-hnadling code.
It is not possible to have a Python function which provides the guarantee you want, so it makes no sense to have a Python type system which expressed it: it will either be never used or a lie.
If you think you need this guarantee, you need to step back and ask what the functional requirement actually is and find a different way of meeting it.
I started experimenting with Kotlin just when Python was starting to get type hints and after my experience with Kotlin I am totally sold on the benefits of typing. So to me it's great to see these recent developments. It just feels weird to me to have a language that is fundamentally dynamically typed (and committed to that paradigm) invest so much effort in developing this elaborate but optional typing system.
Is date a string or an object? It gets tiresome.
F# is sort of the opposite of Python. It's strongly, statically and soundly typed, based on Hindley-Milner. It has very good type inference, meaning that explicit type annotations are usually not required. But it's still type checked, and compilation will fail if there are type errors. So F# gives you type soundness without requiring type annotations (in most cases); Python now supports type annotations but doesn't (yet) give type soundness guarantees.
Of course, F# doesn't have the Python ecosystem, and that's a major issue. Whilst it sits on the .Net platform, it's very much Sunday League to C#'s premier division. Whilst most all .Net libs can be used in F# thanks the .Net CLR, very little is idiomatic: F# is a functional language, C# is object-oriented at heart.
I think my fantasy platform would be some child of F#-the-language and Python-the-ecosystem. Maybe Python's incremental support for typing will get me there someday.
You can't do this in Python your bucket will always have hole you can't plug. Arbitrary exceptions can appear anywhere in your code thanks to signal handlers.
KeyboardInterrupt is the one you probably know without knowing.
Exceptions can even come from higher in the stack down to you with the .throw method on generators.
That'll be really helpful for nested method calls which proxy a lot of their arguments along. I can also see that helping out when creating stub libraries.
(I don’t know about others, but it was obvious to me from the start that the way the *args and **kwargs type annotations refer to the sequence values rather than the args/kwargs bindings was always going to cause confusion, and was very likely to cause problems down the line when they wanted more precise typing; and that day has arrived for kwargs, no idea if they have any plans for similar on args, which is sometimes useful. They should just have required `*args: List[T], **kwargs: Dict[str, T]` from the start.)
https://news.ycombinator.com/item?id=37737519
https://en.wikipedia.org/wiki/Safe_navigation_operator
I write 'may' because the difference between None and an empty dict may be very subtle and rarely does the API specify with enough precision what are supposed to be the semantics of each case.
It does though, you can chain as many of these as you want:
edit:>> but you still need to check for None with this code
> Not sure what you mean here. You only have to check for None if you use `.get("key")` and don't provide a fallback value.
GP was talking about `{"foo": None}` and trying to drill deeper, which I misunderstood. Still, a simple try/except allows you to short-circuit the deeply nested access.
> Blowing up on `None` is an easy short-circuit.
The thing being discussed is attempting to access deeply nested values, so short-circuiting here is a win-win. I.e., you wouldn't want to unnecessarily traverse tons of empty dictionaries using the `.get() or {}` trick.
This has the side effect of replacing falsy values like False or 0 with an empty dictionary instead of giving you the actual value. For the exact intended behaviour, you do need to explicitly check for None instead of using a short circuit trick with or.
I would say the only reasonable choice is Optional[dict], in which case it should be sufficient, but Python being Python, it could be anything. And in these cases you need to handle all cases way more carefully.
https://glom.readthedocs.io/en/latest/api.html#defaults-with...
e.g.
https://peps.python.org/pep-0505/
But really, how do you know if your path is missing or the object at the end of the path is None, without using exceptions for communicating this?
But I really don't like having this much `Any`; it's generally a sign of poor design.
This isn't like C++ with its intrusive `=`.
https://github.com/jmespath/jmespath.py
What's actually a bit odd about Python missing that operator is that its `or` operator acts as a rough equivalent of C#'s null-coalescing (`??`) operator. To me the safe navigation operator goes hand-in-hand with that.
Python `or` is C# `||` plus type coercion. It is not even roughly `??` because important non-None Python values are falsey (0, False, empty collections).
In C# even if `||` automatically coerced the types, the output will always be a bool.
I saw some suggestions for overridable behaviour in the data model for None-like objects, but didn't scrutinise them closely. Either way, it seems controversial. On the one hand, the ability to override operators is really key to Python's design, but on the other it could lead to rather surprising behaviour from overeager overriding. (But to be fair, this is true of other, more common operators too.)
So, for now we're stuck with `if _ is not None else`, I suppose!
The asyncio improvements in Python 3.12 especially (plus perf generally) have been instrumental in enabling real world use of this. With Python 3.12 asyncio, uvloop, and FastAPI it works remarkably well[1]. As the demo video shows not only does it not delay responsiveness, it has granularity down to inches.
[0] - https://heywillow.io/
[1] - https://youtu.be/qlhSEeWJ4gs
It has become an unpleasant chore to keep up with PEP squabbles and accepted changes.
On the utility of features, the arbitraty nesting of f-strings stands out. Why in tarnation would anyone need such nesting? Isn't it simpler and readable to keep non-trivial expressions out of the f-string itself?
But then you have to define what non-trivial expressions are, and more importantly, people using the language have to learn that definition too. By having f-strings accept all valid expressions, the language actually becomes simpler.
This has been the new strategy to avoid another python2-python3 debacle. As long as you're aware it's not semver and minor versions can have breaking changes it's no biggie.
How is this not a major version change if a module is removed from the standard library? Other breaking changes too. This is confusing to me.