Hacker News new | ask | show | jobs
by aeturnum 686 days ago
My guess is that imports in python are just executing python. When you import a module you are (optionally) running code in that module. So a circular import generates infinite recursion.

Python does a lot when it evaluates an import statement. That is where a lot of the python magic happens. As soon as you try to limit the import statement somehow, a lot of python code needs to be re-written (and maybe doesn't work at all anymore). That's arguably a bad decision but it's one at the center of the language and it's unlikely to change.

3 comments

You're right. And same with the mutable default argument "trap". That's cause by `def foo` being a statement, not syntax. When a module is imported, the code in it is executed. Many of the statements in that will be `def something`, which, when executed, defines a function. And because that's code that gets executed,

  def foo(bar=[]):
      bar.append('lol')
      return bar
the `bar=[]` gets executed at that time. That is, Python doesn't treat `def foo` as some magic thing that gets special cased and squirreled away for later use.
Exactly, importing (like everything else in Python) is just a "syntax sugar" around loading the code on a file and running it instantly. If you do a circular reference it can pop the stack
> importing (like everything else in Python) is just a "syntax sugar" around loading the code on a file and running it instantly.

Not quite - it also checks whether the file has already been imported, and does nothing if so.

And it also checks if the file has been "partially imported" which is what causes it to fail during circular imports.

You could imagine a small change to Python which made the second case a no-op as well. This would allow you to use circular imports. You'd need to restrict yourself to imports like "import mymodule; mymodule.myfunction()" rather than "from mymodule import myfunction; myfunction()" but that's encouraged in popular style guides [0] anyway. By the time you run the function, mymodule.myfunction will be bound correctly and everything will work.

This would create hard-to-debug situations for people who rely on executing lots of side-effect code to set up state at import time, but 1) that would only happen in code that currently can't run at all, and 2) those people deserve it.

I think it would even be reasonable to make the "from mymodule" version work by binding those names later, but it wouldn't be a one-line change to the interpreter and it might break some legitimate use cases where a name got redefined.

[0] https://google.github.io/styleguide/pyguide.html

I don't doubt it's a tricky and complicated change to do.

But if it was viewed as a major language bug, I think they'd find a way.

IMO the way to think about it is that the turing-complete nature of python imports is one of the central design decisions of the language. Lots of python oddities make more sense once you realize that the module structure is determined at runtime - including the enormous capacity for the language to do its own setup (or fail to do its own setup in amazingly complex ways).

So it's not a bug, it's one of the central choices that makes python "pythonic."