Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I feel the same, but about more subtle features like the walrus operator, the dictionary merge operator, or proposals that fortunately weren't accepted [1] which I feel do not add much to the language.

Type annotations, imho, are actually useful and not really hard to read.

[1] https://lwn.net/Articles/888945/



For me, type annotations has been the single biggest ergonomics improvement to come to python. It's a very simple type system compared to many other languages (definitely simpler than rust, haskell, scala, java, and c++, I find it easier than go's generics but I am not practiced). I don't see how they add complexity to the language. In fact I find it limiting if anything.

Walrus is...fine. I don't use it much, but I defo see how it's one more thing.

Python's biggest complexity, imho, is its almost unrivaled dynamicism. There's just too many degrees of freedom at times.

The import system continues to be a nuclear footgun. I consider myself a python expert with 15 years under my belt and today I was still fighting with imports in a pytest suite. Like wtf.


my main issue with the typing is circular imports, and how ultimately for a lot of configurations the best solution is to just skip the typing that one time. Even "if type_checking:" often simply can't cut it

I want to be able to use typing everywhere if I'm going to use it, it really grinds when I have to selectively not use it.

That being said, im glad typing is in the language :)


Configuration hoisting and library layout to avoid circulars is tricky. The way I structure my libs, lets say ./foo is my main lib, I'll have ./foo/types/.py with most of my primary schema models (I use Pydantic heavily). Then ./foo/config/.py manages application config. Basically everything is pulled in with env vars / config files - I rarely use CLI flags for application-level global configuration - the rest are passed in to entry point functions.

What this means is I can import my AppConfig class anywhere (except types and config submodules) without circular imports, construct them, and they will be initialized with the configs from the env.

Occasionally I have to bust out ForwardRef annotations and model.update_forward_refs() but that's pretty rare. Basically, the imports are structured so that it's as close to a DAG as possible.

Definitely check out pydantic, it makes it really easy to do 12-factor style application config.

I haven't done a lot with CLI-flag global config, but if I had to, I'd use something like dependency-injectors to wire it all together.


The official solution to this is to just use strings. I.e instead of Type, use "Type" where Type can't be in scope because of circularity.


> my main issue with the typing is circular imports

FYI: You can use strings as a workaround, or the

    from __future__ import annotations
trick.


Dictionary merge is a weird one to single out to me, it's an existing operator (|) and functions the same way as a set union over the keys (which uses that operator) in concept, it's a very natural thing that doesn't feel like an expansion of the language, just behaviour you'd expect from the datatype.


A problem with dictionary merge that doesn’t exist with sets is what to do with the values if you have a key in both dictionaries.

Do you keep the value on the left, the value on the right, error out, try merging the values (if so: how?), add a parameter allowing the user to make that choice, require the user to pass a lambda?

I don’t think there’s a ‘best’ answer here, but if you want to make one of them easier to write as a|b, you can only support one.


At the end of the day people were doing {**a, **b} or dict(d1.items() | d2.items()) (which is subtly different in that set order is arbitrary so which "wins" in the duplicate key case is arbitrary) in the wild, so I think supporting a natural dict-level union is the better option, even if it can't be all things to all people.


Agree. More complex stuff where precedence is tricky can be done in a dictionary comprehension.


That problem also exists in sets because some values that aren't interchangeable still compare equal:

  >>> {1} | {1.0} | {True}
  {1}
But the behavior for dicts is surprising even in that light:

  >>> {1: 1} | {1.0: 1.0} | {True: True}
  {1: True}


But that is not really the `|` operators fault. You get the exact same behaviour when you create a dictionary.

    >>> {1: 1, 1.0: 1.0, True: True}
    {1: True}
So `|` between dictionaries behaves just as you'd expect, if your expectations about creating a dictionary are correct. I think that's very reasonable.


bool as int is sometimes useful and often infuriating


Honestly the set operators too are just screwed: I end up using `set` surprisingly much yet I'm always going back to the docs to figure out what the "or" symbol was, etc.


I love types. But as first class citizens. I started in Pascal and C. And they’re one of the reasons I’m enjoying Go for the kind of work I did with Python. Usually, if it compiles, its right.

I’d be cool with a statically typed flavor that wouldn’t allow for the type gymnastics.

But that’s gross with the None situation where you’d have to fall back to magic values vs ‘nil’ or similar concepts. None was one of my favorite things back when other languages didn’t address it.

And a lot of my opinion is probably based on the fact that I came to the language in the late 90s from a Pascal/C background. Probably just getting to old :).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: