1ncunabula 2 minutes ago

Python sure has become bloated in recent years...

robviren an hour ago

Every time I try to use Python I get this mixed feeling of liking how few guard rails there are between me and the logic I am making and this lingering worry that my code looks like fools attempt to use Python. So much exists as convention or loosely followed rules. Whenever I read articles like this I am wowed by the depth of things I didn't know about Python or how much has changed. It makes something like Go feel like a comfort blanket because I have reasonable certainty code I write won't feel deprecated or left behind a year or two later. Excellent article showing off even more I didn't know.

  • pydry an hour ago

    There are plenty of language features which let you re-use or build your own guardrails in python.

    I do with the concept of truthiness and falsiness would be taken out and shot though. It's been responsible for far too many nasty bugs IME and it only cuts out a few extra characters. Not a great trade off.

    • brookst 25 minutes ago

      +1 — and it only saves characters when someone really knows what they’re doing. Most of is end up with `if (not result) or (result == “”)` and worse.

djoldman 22 minutes ago

This is wild and something I didn't know about:

https://blog.edward-li.com/tech/advanced-python-features/#2-...

   def bar(a, /, b):
       ...

   # == ALLOWED ==
   bar(1, 2)  # All positional
   bar(1, b=2)  # Half positional, half keyword

   # == NOT ALLOWED ==
   bar(a=1, b=2)  # Cannot use keyword for positional-only parameter
lordnacho 2 hours ago

For me, the biggest benefit to python is that it feels like executable pseudocode. The language gets out of the way of your domain level instructions. This is probably why most non-programmers have the easiest time with python.

The more fancy stuff you add to it, the less attractive it becomes. Sure, most of these things have some sort of use, but I reckon most people do not get deep enough into python to understand all these little things.

  • 7bit an hour ago

    I disagree. Adding "fancy stuff" by definition does not remove what made it attractive to you in the first place. You even acknowledge this in your second sentence. Something you don't know of is not capable to influence your opinion in any way. And when you know of it -- and dislike it -- just keep doing it the way you did before knowing it.

    • pansa2 an hour ago

      > And when you know of it -- and dislike it -- just keep doing it the way you did before knowing it.

      That breaks down as soon as you need to work with anyone else's code that uses the "fancy stuff".

hiichbindermax 2 hours ago

Nitpick about 9.3 Short Circuit Evaluation: both things evaluate differently if you have empty strings. The if-else clause treats empty strings as valid while the or operator will treat them equivalent with None.

steinuil an hour ago

Turns out I managed to use almost all of these during a refactor of a project at work, even metaclasses... (Metaclass usage is justified in my case: we have a sort of language evaluator and using a metaclass lets us define function arguments with their types and validators in a very coincise and obvious way similar to Pydantic.)

I think this list should also include descriptors[0]: it's another metaprogramming feature that allows running code when accessing or setting class attributes similar to @property but more powerful. (edit: nvm, I saw that they are covered in the proxy properties section!)

I think the type system is quite good actually, even if you end up having to sidestep it when doing this kind of meta-programming. The errors I do get are generally the library's fault (old versions of SQLAlchemy make it impossible to assign types anywhere...) and there's a few gotchas (like mutable collections being invariant, so if you take a list as an argument you may have to type it as `Sequence[]` or you'll get type errors) but it's functional and makes the language usable for me.

I stopped using Ruby because upstream would not commit on type checking (yes I know you have a few choices if you want typing, but they're a bit too much overhead for what I usually use Ruby for, which is writing scripts), and I'm glad Python is committing here.

[0]: https://docs.python.org/3/howto/descriptor.html

William_BB 27 minutes ago

I enjoyed reading the article. I'm far from a Python expert, but as an observation, most of these features are actually just typing module features. In particular, I wasn't sold on Generics or Protocols as I would have just used duck typing in both cases... Does modern, production-level python code use types everywhere? Is duck typing frowned upon?

Loranubi 4 hours ago

I would argue that most of these features (basically everything except metaclasses) are not advanced features. These are simple, but for some reason less well known or less used features.

Metaclasses are however quite complex (or at least lead to complex behavior) and I mostly avoid them for this reason.

And 'Proxy Properties' are not really a feature at all. Just a specific usage of dunder methods.

krelian 2 hours ago

Python has evolved into quite a complex language considering the amount of features that come built-in. The documentation, while complete, does not facilitate discovery of many of those features.

  • lenkite 30 minutes ago

    Just like Microsoft maintains the Typescript handbook, I think the Python Foundation needs a "Idiomatic Modern Python Handbook" which always shows the most modern and idiomatic way to use Python features.

  • zahlman 2 hours ago

    The quality of documentation is a known, highlighted concern in the community. There is particular interest in adopting Diataxis principles to improve documentation, both for Python itself and for packaging (see e.g. the user guide at https://packaging.python.org/en/latest/ , or the PyPA main site https://www.pypa.io/en/latest/ ).

    If you want to help, there's a section on the Python forum (https://discuss.python.org/c/documentation/26) and a Discord server, and issues with documentation can also be reported on the main Python GitHub issue tracker (https://github.com/python/cpython/labels/docs).

  • sakesun an hour ago

    It's more complex that decade ago, but still a relatively simple language. I can understand the article without much effort, while I scratch my head really hard when read about advance feature of Typescript or Scala.

    • pansa2 an hour ago

      > still a relatively simple language

      If only. I suspect very few Python programmers can even fully explain what `a + b` does.

      If `a` and `b` are instances of classes, many would say it's equivalent to `a.__add__(b)` or `type(a).__add__(a, b)`, but in fact it's much more complex.

      • cl3misch 19 minutes ago

        Care to elaborate? Where is the complexity hidden?

TekMol 4 hours ago

TFA's use-case for for/else does not convince me:

    for server in servers:
        if server.check_availability():
            primary_server = server  
            break
    else:
        primary_server = backup_server
    deploy_application(primary_server)
As it is shorter to do this:

    primary_server = backup_server
    for server in servers:
        if server.check_availability():
            primary_server = server  
            break
    deploy_application(primary_server)
  • HelloNurse 7 minutes ago

    The shorter version that saves the else: line assigns primary_server twice: it is OK for an assignment but a potential problem if doing something less trivial, and very ugly style in any case.

  • hk__2 3 hours ago

    What about this?

        for server in servers:
            if server.check_availability():
                primary_server = server  
                break
        else:
            logger.warning("Cannot find a valid server")  # <---
            primary_server = backup_server
        deploy_application(primary_server)
    • TekMol an hour ago

      Yes, that might be a better example.

  • paolosimone 37 minutes ago

    If the team is familiar with Functional Programming concepts, I'd actually suggest

      available_servers = (server for server in servers if server.check_availability())
      primary_server = next(available_servers, backup_server)
      deploy_application(primary_server)
    
    which IMHO better conveys the intent to the reader (i.e. focus on "what to do" over "how to do it")
  • wesselbindt 4 hours ago

    If you replace the assignment in the else clause with something side-effectful it does make sense. But even then it harms the readability of the code to such a ridiculous extent. I've never not regretted adding an else clause to a for loop. Any cognitive benefits from Python's approach to for loops closely mirroring natural language just go out the window.

  • zahlman 2 hours ago

    This kind of search can be done a variety of different ways, and is worth abstracting, e.g.:

      def first(candidates, predicate, default):
          try:
              return next(c for c in candidates if predicate(c))
          except StopIteration:
              return default
    
      deploy_application(first(servers, Server.check_availability, backup_server))
    • sowhat25 an hour ago

      Great, first() comes part of more_itertools !

  • macleginn 4 hours ago

    I would say that the main benefit of for/else is readability and ease of writing since nothing needs to be front-loaded. (The downside is an arguably redundant addition to the basic syntax, of course.)

  • Loranubi 4 hours ago

    I do like the else clause with for loops. However most people are not familiar with it, and also `else:` as a keyword is confusing. I always remember it as `no break:`.

davnn 3 hours ago

Working in the ML field, I can't hate Python. But the type system (pre-3.12, of course) cost me a lot of nerves. Hoping for a better post-3.12 experience once all libraries are usable in 3.12+. After that experience, I’ve come to truly appreciate TypeScript’s type system. Never thought I’d say that.

  • srean 2 hours ago

    One of the most frustrating bugs I had encountered was when I was using memory mapped CSC format sparse arrays.

    I needed the array indices to be int64 and specified them as such during initialization.

    Downstreams, however, it would look at the actual index values and dynamically cast them to int32 if it judged there would be no loss in precision. This would completely screw up the roundtrip through a module implemented in C.

    Being an intermittent bug it was quite a hell.

  • drumnerd 3 hours ago

    Hey kiddo… did you ever try something nastier? I’ve got something that will blow your mind and you’ll keep coming back

    You don’t know but you are addicted to types

    Come to the light - Haskell!

    • hyperbrainer an hour ago

      Which is despite, a decade of attempts, still missing dependent types. Time to embrace Idris.

      Or embrace logic + functional programming: Curry. https://curry-language.org/

  • hk__2 3 hours ago

    Same experience here, Python’s typing experience is awful compared to TypeScript, even post-3.12. Mypy’s type inference is so dumb you have to write arguments like `i: int = 0`; `TypedDict`s seems promisable at first and then end up as a nightmare where you have to `cast` everything. I miss TypeScript’s `unknown` as well.

    • NeutralForest 2 hours ago

      I'm just waiting for the astral's people (uv, ruff) type checker at this point. On large projects mypy is often unreliable and slow.

    • davidatbu 2 hours ago

      You really should check out pyright/pylance/basedpyright. Just an all around better type checker. Even has the "unknown" from typescript (kinda).

    • drumnerd 3 hours ago

      0 can be inferred as a float too, so doesn’t it make sense to type numbers?

      • hk__2 2 hours ago

        Try:

            def f(i=0) -> None:
                reveal_type(i)
        
        The inferred type is not `float` nor `int`, but `Any`. Mypy will happily let you call `f("some string")`.
        • rfoo 20 minutes ago

          `Any` is the correct call.

          It could be:

            def f(i=0) -> None:
              if i is None:
                do_something()
              else:
                do_something_else()
          
          Yeah, I know it's retarded. I don't expect high quality code in a code base missing type annotation like that. Assuming `i` is `int` or `float` just makes incrementally adoption of a type checker harder.
        • mzl 36 minutes ago

          How would a typing system know if the right type is `int` or `Optional[int]` or `Union[int, str]` or something else? The only right thing is to type the argument as `Any` in the absence of a type declaration.

        • jsjohnst 42 minutes ago

          So you want strong typing, but then are to lazy to properly type your function definitions?

        • veber-alex 2 hours ago

          That's a mypy issue.

          Pyright correctly deduces the type as int.

          In any case it's a bad example as function signatures should always be typed.

      • porridgeraisin 2 hours ago

        I believe mypy infers i as an integer in i = 0. I remember I had to do i = 0.0 to make it accept i += someFloat later on. Or of course i:float = 0 but I preferred the former.

        • hk__2 2 hours ago

          Yes, but not in arguments:

              def f(i=0) -> None:
                  j = i + 1
                  k = 1
                  reveal_type(i)
                  reveal_type(j)
                  reveal_type(k)
          
          Output:

              Revealed type is "Any"
              Revealed type is "Any"
              Revealed type is "builtins.int"
          • jsjohnst 4 minutes ago

            Because it shouldn’t in function arguments. The one defining the function should be responsible enough to know what input they want and actually properly type it. Assuming an int or number type here is wrong (it could be optional int for example).

    • globular-toast an hour ago

      Mypy was designed to enable gradual adoption. There is definitely Python code out there with `def f(i=0)` where `i` could be any numeric type including floats, complex, numpy etc.. This is called duck typing. It's wrong for a type checker to assume `i: int` in such a case.

      Pyright probably works if you use it for a new project from the start or invest a lot of time "fixing" an existing project. But it's a totally different tool and it's silly to criticise mypy without understanding its use case.

      • hk__2 an hour ago

        I’d be very happy if `def f(i=0)` would type i as a number-like, but right now it’s not typed at all: mypy "infers" its type as `Any`.

        I tried Pyright but as you say on an existing project you need a looot of time to "fix" it.

macleginn 4 hours ago

The way the language is evolving, it seems likely to me that people in the applications camp (ML, simple web-dev, etc.) will soon need a "simple Python" fork or at least an agreed-upon subset of the language that doesn't have most of these complications (f-strings are a major success, though).

  • globular-toast 4 hours ago

    The only newish thing on this list appears to be structural pattern matching. Other than that it's all typing stuff which is all optional.

mvATM99 3 hours ago

Good list. Some of these i knew already, but the typing overloading and keyword/positional-only arguments were new to me.

One personal favorite of mine is __all__ for use in __init__.py files. It specifies which items are imported whenever uses from x import *. Especially useful when you have other people working on your codebase with the tendency to always import everything, which is rarely a good idea.

  • hk__2 2 hours ago

    It’s never a good idea. I use `__all__` to explicitely list my exports in libraries, so that when one write `from mylib import `, the IDE auto-completes only the public classes and functions.

lyu07282 2 hours ago

Good list, its one of these things you either never heard of, or used for years and think everyone knew that. I'll add a few:

* did you know __init__.py is optional nowadays?

* you can do relative imports with things like "from ..other import foo"

* since 3.13 there is a @deprecated decorator that does what you think it does

* the new generics syntax also works on methods/functions: "def method[T](...)" very cool

* you can type kwargs with typeddicts and unpack: "def fn(*kwargs: Unpack[MyKwargs])"

* dataclasses (and pydantic) support immutable objects with: "class MyModel(BaseModel, frozen=True)" or "@dataclass(frozen=True)"

* class attributes on dataclasses, etc. can be defined with "MY_STATIC: ClassVar[int] = 42" this also supports abstract base classes (ABC)

* TypeVar supports binding to enforce subtypes: "TypeVar['T', bound=X]", and also a default since 3.13: "TypeVar['T', bound=X, default=int]"

* @overload is especially useful for get() methods to express that the return can't be none if the default isn't None

* instead of Union[a, b] or Optional[a] you can write "a | b" or "a | None" nowadays

* with match you can use assert_never() to ensure exhaustive matching in a "case _:" block

* typing has reveal_type() which lets mypy print the type it thinks something is

* typing's "Self" allows you to more properly annotate class method return types

* the time package has functions for monotonic clocks and others not just time()

anyone know more things?

  • zahlman an hour ago

    > did you know __init__.py is optional nowadays?

    It has an effect, and is usually worth including anyway. I used to omit it by default; now I include it by default. Also, you say "nowadays" but it's been almost 13 years now (https://peps.python.org/pep-0420/).

    > since 3.13 there is a @deprecated decorator that does what you think it does

    Nice find. Probably worth mentioning it comes from the `warnings` standard library.

    > the time package has functions for monotonic clocks and others not just time()

    There's quite a bit in there, but I question how many people need it.

    Anyway, it's always surprising to me how when other people make these lists, such a large fraction is taken up by tricks with type annotations. I was skeptical of the functionality when the `typing` standard library was introduced; I've only grown more and more wary of it, even as people continue to insist to me that it's somehow necessary.

globular-toast 3 hours ago

This is a nice list of "things you might not know" that is worth skimming to add to your toolkit.

If you are really interested in "advanced Python", though, I would recommend the book Fluent Python by Ramalho. I have the first edition which is still highly relevant, including the async bits (you just have to translate the coroutines into async syntax). There is a second edition which is more up to date.

I would also recommend checking out the functools[0] and itertools[1] modules in the standard library. Just go and read the docs on them top to bottom.

It's also worth reading the first few sections of Python Data Model[2] and then bookmarking this page.

[0] https://docs.python.org/3/library/functools.html

[1] https://docs.python.org/3/library/itertools.html

[2] https://docs.python.org/3/reference/datamodel.html

fxj 3 hours ago

This is all fun and games unless you have to debug someone elses code and they use a new feature that you didnt know about. Speaking for myself, I would be glad if there were a python_light version of the interpreter that has a simple syntax only like the early 3.x versions.

just my 2 ct

  • hk__2 2 hours ago

    > This is all fun and games unless you have to debug someone elses code and they use a new feature that you didnt know about.

    What’s wrong about learning things by looking at code from more experienced people?

Starlevel004 3 hours ago

If context managers are considered advanced I despair at the code you're writing.

  • lesser-shadow 3 hours ago

    As a UD, I didn't know what a context manager was before reading this article, so you are not really far from the truth, tbh.

shaunpud 2 hours ago

Another blog with no feed :(

red_admiral 3 hours ago

This sounds fun if you have 10x programmers or at least IQ > 140 programmers in charge. Last place I worked, I was told never use "smart" tricks if you can do the same thing in a simpler way. For-else and f-strings and := sound like harmless enough (though the last one is controversial); "with" is useful for resources that need to be deallocated but "yield"? Really? "more readable" when you slap a decorator on the thing? Why are we packing the enter and exit operations into a single function?

Playing with the typesystem and generics like this makes me worry I'm about to have a panic attack.

Give me code that I can understand and debug easily, even when I didn't write it; don't do implicit magical control flow changes unless you have a very good excuse, and then document both the how and the why - and you'll get a product that launches earlier and has fewer bugs.

Sometimes, a few more if statements here and there make code that is easier to understand, even if there's a clever hack that could cut a line or two here and there.

  • tomn an hour ago

    The simple contextlib.contextmanager example doesn't really sell the benefits.

    The main one is that it makes error handling and clean-up simpler, because you can just wrap the yield with a normal try/catch/finally, whereas to do this with __enter__ and __exit__ you have to work out what to do with the exception information in __exit__, which is easy to get wrong:

    https://docs.python.org/3/reference/datamodel.html#object.__...

    Suppressing or passing the exception is also mysterious, whereas with contextlib you just raise it as normal.

    Another is that it makes managing state more obvious. If data is passed into the context manager, and needs to be saved between __enter__ and __exit__, that ends up in instance variables, whereas with contextlib you just use function parameters and local variables.

    Finally, it makes it much easier to use other context managers, which also makes it look more like normal code.

    Here's a more real-world-like example in both styles:

    https://gist.github.com/tomjnixon/e84c9254ab6d00542a22b7d799...

    I think the first is much more obvious.

    You can describe it in english as "open a file, then try to write a header, run the code inside the with statement, write a footer, and if anything fails truncate the file and pass the exception to the caller". This maps exactly to the lines in the contextlib version, whereas this logic is all spread out in the other one.

    It's also more correct, as the file will be closed if any operation on it fails -- you'd need to add two more try/catch/finally blocks to the second example to make it as good.

  • hk__2 2 hours ago

    I don’t really see "smart tricks" here, just features of the language that you may not be aware of. I use most of them in my daily work without thinking about it: `@overload` and other typing features are useful to document the code, prevent mistakes, and help IDEs generate more helpful suggestions; keyword-only arguments force users to pass options as keywords, so you avoid calls like `foo(x, z, z, true, true, false, true)` and ensure the code is more explicit; for-else can sometimes clarify some complicated code; the walrus operator, short-circuiting with `or`, and chaining operators are just normal Python.

    • mpeg 2 hours ago

      overload is very useful, but I find some people mistakenly think it actually modifies the runtime function signature to do multiple dispatch, so I do try to avoid it in favour of simpler functions whenever possible

      too many overloads can be a code smell IMHO, it's ok when you are implementing a very common pattern and want proper types (decorators come to mind, so that both @decorator and @decorator() work, where the decorator might also have optional args) but I think in cases like the example in the article it should almost always be two separate functions

  • mpeg 2 hours ago

    I don't fully disagree, but the yield context manager approach is very common when writing test fixtures, you do:

      # setup  
      yield resource. 
      # teardown
    
    Not that it makes it any less magical, but at least it's a consistent python pattern
  • at_a_remove 2 hours ago

    I strongly agree. The walrus operator ... argh.

    When I code, I try to make everything I write not clever. I am not saving a byte here and there because I am not typing in a program from a magazine in the 1980s (I was there, I am not going back). No code golf. I did my time on the Timex-Sinclair with its miserable two kilobytes of memory and reserved keywords as special-function strokes at a character each.

    Each line should do one thing, in general, and it ought to be obvious. Cleverness is held in reserve for when it is truly needed, namely data structures and algorithms.

    One of my accomplishments which seems to have mattered only to me is when my apartment-finding/renting system, written entirely in Perl, was transferred into the hands of students, new programming students. Perl is famously "write-once, read-never" and seems to have a culture favoring code golf and executable line noise. Still, the students got back to me and told me how easily-ported everything was, because I had done just one thing per line, avoided $_ and other such shortcuts, and other practices. They were very happy to take it over because I had avoided being cryptic and terse.

hrtk 2 hours ago

Trying so hard to make it a typed language

drumnerd 3 hours ago

Those are basic, you can decompile a function and change its bytecode, you can completely revamp the parser using codecs, etc

  • padjo 2 hours ago

    Yeah I only worked professionally with python for about 6 months and I knew about most of them.