[HN Gopher] How the Python import system works ___________________________________________________________________ How the Python import system works Author : zikohh Score : 315 points Date : 2021-07-24 14:28 UTC (8 hours ago) (HTM) web link (tenthousandmeters.com) (TXT) w3m dump (tenthousandmeters.com) | Bukhmanizer wrote: | This is so needed. I feel like a lot of people know enough about | python imports, but everyone has pains with it. | dheera wrote: | I created this fun hack that taught me a LOT about the import | system. Basically it allows you to import anything you want, even | specify a version, and it will fetch it from PyPI live. Might be | interesting to flesh this out in a way that's deployable. | | Basically instead of import tornado | | and hoping and praying that the user has read you README and pip | installed the right version of tornado, you can do | from magicimport import magicimport tornado = | magicimport("tornado", version = "4.5") | | and you have exactly what you need, as long as there is an | internet connection. | | https://github.com/dheera/magicimport.py | kristjansson wrote: | Cool! But why not just throw the code a package with a version | dependency on tornado? | dheera wrote: | Because then you'd still have to install the package. | | It's nice to have something that "just works" without having | to install it. I like to call it Level 5 autonomous software | -- software that can figure out how to run itself with zero | complaints. | | I actually use this for a lot of personal non-production | scripts, I can just clone the script on any system and just | run it, it will figure itself out. | | Also, packages with version dependencies fuck up other | packages with other version dependencies, unless you set up | virtualenvs or dockers or condas for them, and those take | additional steps. | | magicimport.py uses virtualenv behind the scenes, so when it | imports tornado 4.5 because some script wants 4.5, it won't | mess up the 6.0 that's already on your system. In some sense | it just automates the virtualenv and pip install process on- | the-fly, to give the effect that the script "just works". | tyingq wrote: | There's a very cool (and succinct) blog post[1] showing how to | abuse this in an interesting way where you can put the code of a | module into a string and load it that way. | | Not something I'd use in production, but it's a very clear way to | see how both "finding a module" and "loading a module" works | under the covers. | | [1] https://cprohm.de/blog/python-packages-in-a-single-file/ | | Edit: As an aside, I much prefer the way Perl does things in this | space. It's much easier to define multiple packages either within | a single file or across different files, and much clearer about | what's happening underneath. | yongjik wrote: | Probably half of the commenters here know this, but since we're | here, this is my go-to boilerplate for starting a python script. | (Probably won't work on Windows.) #!/bin/sh | # Run the interpreter with -u so that stdout isn't buffered. | "exec" "python3" "-u" "$0" "$@" import os | import sys curdir = | os.path.dirname(os.path.realpath(sys.argv[0])) # Add | enough .. to point to the top-level project directory. | sys.path.insert(0, '%s/../..' % curdir) Your main | program starts here ... | kbenson wrote: | There's no well known module to do most this for you? In perl, | the recent canonical way is to use the FindBin module to find | the current binary's running dir, and the the local::lib module | to set the import path (or just use lib for older style library | dirs). That always seemed cumbersome to me at 2-3 lines that | weren't very clean looking. | | Also, say what you will about Perl and esoteric global | variables, but it's kinda nice to be able to toggle buffered | output on and off on the fly. Is there really no way to do this | in python without re-executing the script like that? | icegreentea2 wrote: | Ya... if you're trying to get the path of the script, you can | use `__file__` special variable (instead of loading it from | bash $0 and grabbing sys.argv[0]). | | For adding current directory to front of path, the | sys.path.insert() call is a pretty sound way of doing it. | yongjik wrote: | Yeah, I think you're right - didn't know about __file__. | | (To clarify, using "$0" with bash is just standard method | to invoke the same script with an interpreter - sys.argv[0] | will work with or without bash exec part.) | dagmx wrote: | Why not just add a shebang , chmod +x and then you're done? | yongjik wrote: | #!/usr/bin/python | | This is sometimes what you want, but it will always look at | this exact path, and won't play nicely with virtualenv/conda. | #!/usr/bin/env python | | This works - it will use Python found in $PATH. Unfortunately | you can't add any more parameters to the interpreter. | | The contraption I wrote allows adding arbitrary parameters - | I was burnt one too many times by Python silently buffering | my debug messages, so I use it to always add "-u". | danpalmer wrote: | > # Add enough .. to point to the top-level project directory. | | This suggests that there is more than one entry point to the | Python project? | | While I'm sure there are good reasons for this, and while I'm | not criticising your instance of this specifically, as a | general point of advice I've found this sort of thing to be a | bit of an anti-pattern. | | Having one entry that handles things like path setup and other | global concerns, before delegating out to subcommands or | whatever construct works best makes it much easier to keep the | whole codebase aligned in many ways. | | Django has a system for this and while it has its flaws, it is | nice to have it. Using this, on our main Python codebase of | ~400k lines, we have a single manual entry point, plus one | server entry point. Coordinating things like configuration, | importing, and the application startup process, are therefore | essentially non-issues for us for almost all development, even | though we have a hundred different operations that a dev can | run, each of which could have been a separate tool like this. | mdaniel wrote: | That's crafty; it reminds me of the suggested similar tactic | with tclsh since tcl honors backslashes in comment strings(!): | https://wiki.tcl-lang.org/page/exec+magic | random_upvoter wrote: | One of the reasons I gradually fell out of love with Python. To | get Python right you need to remember more protocol than Queen | Victoria's master of tea. And it is truly protocol in the sense | that there is always this arbitrariness hanging around it. | danuker wrote: | What do you use now? | moonbug wrote: | "badly" | abhisuri97 wrote: | Despite using python for the past 4 years, it still takes me | several tries to set up packages and imports correctly when I | make them myself. Honestly, I wish that python had an import | system similar to JS (where you can just say "I want this file" | <insert path to file> and specify the exports yourself). For me, | it just feels more intuitive and less "magic"-like when dealing | with custom scripts you want to import. | josephorjoe wrote: | I have seen way too many `ModuleNotFoundError`s. It is | moderately infuriating when the two files are in the same | directory and python can't find the module. | | Honestly that error is misnamed. It should be | `ModuleImportRefusedError`. | | And the frustration caused by getting PyTest to work in a | project is likely responsible for a large percentage of the | untested python projects in the world... | captainmuon wrote: | There are two things that are annoying about Python's import | system. | | Number one is that relative imports are weird. My intuition about | imports is good enough that I never bothered to learn all the | rules explicitly, but sometimes something simple is just not | possible and it bites me. I think the case is importing files | relative to a script (and not running with python -m ...). | | Number two is, in order to do package management, you have to | create a fake python installation and bend PYTHONPATH. | Virtualenvs are the canonical way to do it, but to me it feels | like a hack - the core language seems to wants all packages | installed in /usr. So now I have all these virtualenvs lying | around and they are detached from the scripts. | | Why couldn't the import system resolve versions, too? You could | say `import foo >= 1.0` and it would download it to some global | cache, and the import the correct versions from the cache. | blakesley wrote: | Earnest question: why are you all trying to use relative | imports? What problem is that solving for you? I've never even | bothered to try it out because it seems potentially problematic | in the way all relative references can be, e.g., relative file | paths. | rocqua wrote: | People have a script run with python, and want to use code in | other files. | | This is not supported in python. For reasons beyond my | understanding, you are supposed to put the script with | python, or with the shebang, in a different directory. | | Alternatively, you can always use `python -m` to run your | code. | scaryclam wrote: | So much this. There's no good reason to use relative imports. | They're less readable, more dangerous and don't solve a | single problem. | zarzavat wrote: | In JS land everything is a relative import. It's nice | because you can move a whole directory of code from one | project to another (or around in the same project) and it | still works because all the imports pointing to other files | inside that directory were relative, you only have to fix | imports that go outside the directory. | | Also the way that JS imports are just relative paths is | _very_ nice because it means that the imports are | statically determinable, your editor can understand them | and _fix them automatically_ and you can trust that | refactoring. Python has turing complete imports because | there 's so much dynamic messing about with sys.path that | goes on in Python due to inadequacies of the import system. | noptd wrote: | Given most editors handle absolute imports as good as if | not better than relative imports, I don't see any real | benefit to using the latter. | whoknowswhat11 wrote: | I never followed the changes around imports in 3 ... but def | still run into issues especially trying to move code from dev | to deploy | foresto wrote: | > Number two is, in order to do package management, you have to | create a fake python installation and bend PYTHONPATH. | | If I understand what you mean by package management in this | context, I wonder if editable installs will help you. | | https://www.python.org/dev/peps/pep-0660/#abstract | | https://discuss.python.org/t/pronouncement-on-peps-660-and-6... | Denvercoder9 wrote: | _> the core language seems to wants all packages installed in | /usr._ | | There's also ~/.local/lib/python3/site-packages (or whatever | your distribution made of that). Virtualenvs are only necessary | if you want to isolate dependencies between environments. | That's useful if you have projects with conflicting | dependencies, because Python doesn't allow you to install | multiple versions of the same package, for better or worse. | However, if you've written some simple scripts that don't care | much about the exact version of their dependencies, it's | perfectly fine to install their dependencies glboally. | chriswarbo wrote: | Quick shout-out to nix-shell shebangs, which allow a script | to specify exact versions of all dependencies, Python or | otherwise, which will be cached into a temporary sandbox. | | https://nixos.org/manual/nix/stable/#use-as-a-interpreter | | I wrote a Python script yesterday which calls out to a couple | of external commands (`mlr` and `xq`), with a shebang like | this: #!/usr/bin/env nix-shell | #!nix-shell -i python3 -p python3 -p miller -p yq | rcfox wrote: | > Why couldn't the import system resolve versions, too? You | could say `import foo >= 1.0` and it would download it to some | global cache, and the import the correct versions from the | cache. | | What do you do about conflicts? Or say you have `import foo >= | 1.0` in a file, and `import foo == 2.4`, but the latest version | is 2.5, so the first import grabbed the latest version, and you | later realize you need 2.4? | | Imagine running a report generator for 5 hours, only to have | the formatting module require a conflicting version of | something and erroring out at run time... | kristaps wrote: | Yeah, saner relative imports might be one of the few things | that I envy the JS ecosystem for. | robochat wrote: | Relative imports used to work much more naturally IMHO in | python2 but then they broke it in python3 because Guido wanted | scripts and modules to always be separate codebases. So, | whereas it used to be easy to have a module that could also be | run as a script inside a package, this is now very difficult to | implement. To the extent that any python2 code that does this, | should probably be refactored when being ported to python3. | rocqua wrote: | This decision has bit me in the ass so often. | | I want to organize my code logically in directories. As a | script grows, I want the ability to spin out parts of that | file to separate files. | | In order to do that in python, I need separate directories | between the script and the spun-out functionality. This ends | with a script that says "do function from module" and all | code being in the module. | | Having code in different directories for no reason except | "the import system" sucks. How is this supposed to go? | bobbylarrybobby wrote: | Always an informative (and fun) read: | | https://stackoverflow.com/questions/14132789/relative-import... | Xavdidtheshadow wrote: | Once I wrapped my head around when you can and can't use | relative imports, I've been pretty ok with them. The think that | irks me is whether they work changes based on where you've | invoked Python from. `./bin/my_script.py` behaves differently | from `./my_script.py`. | | Coming from JS, that was a pretty frustrating realization. | jgwil2 wrote: | Related: https://instagram-engineering.com/python-at-scale- | strict-mod... | shockfist wrote: | Badly | maest wrote: | Slightly off-topic: | | One of the simplest import systems I've seen were in q/kdb (like | with most things in that language, everything is as simple as | possible) | | Imports work by simply calling `\l my_script.q` which is similar | to simply taking the file `my_script.q` and running it line by | line (iirc, it does so eagerly, so it reruns the entire file | whenever you do `\l my_script.q`, even if the file has been | loaded before, which may affect you state. By contrast, Python | `import` statements are no-ops if the module has already been | imported). | | The main disadvantage is that you risk your imported script | overwriting your global variables. This is solved by following | the strong (unenforced) conversion that scripts should only | affect their own namespaces (which works by having the script | write declare \d .my_namespace at the top of the script) | | I never found this system limiting and always appreciated its | simplicity - whenever things go wrong debugging is fairly easy. | | What does Python gain by having a more sophisticated import | system? | btilly wrote: | Python avoids having to rerun the import over and over again. | | Suppose that you are importing a larger project. Where your one | import (say of your standard database interface) pulls in a | group of modules (say one for each table in your database), all | of which import a couple of base libraries (to make your | database objects provide a common interface) that themselves | pull in common Python modules (like psychopg2) which themselves | do further imports of standard Python modules. | | The combinatorial explosion of ways to get to those standard | Python modules would make the redundant work of parsing them | into a significant time commitment without a caching scheme. | | From the point of view of a small script, this kind of design | may seem like overkill. But in a large project, it is both | reasonable and common. | AlphaSite wrote: | Yep. Rerunning things is how you end up with C/C++ style | headers and all the (performance and otherwise) problems | there in. | wizzwizz4 wrote: | Everything is an object, and it "just works" for most purposes. | (Not nearly as many as I would like, but still; it has | backwards-compatibility to consider, so I'll cut it some | slack.) | | If you need to start installing your own user packages, you | need `pip` and then `venv` and then things get ugly, but for | the usual case where the sysadmin deals with all that (or | you're on Windows), it works quite well. | __float wrote: | Python imports aren't necessarily always importing Python | source code -- they can be pyc (bytecode) files, or C API | extensions, etc. | | These are slightly more complicated than "load the script at | this path". | | There's probably a more detailed answer, in that historically, | decisions were made that we're now stuck with. Python packages | can and sometimes intentionally have import-time side effects, | for example. They _must_ be only run once, without relying on | convention, or we break existing code. | [deleted] | GregJJ wrote: | Python import system is by far the worst one I dealt with. Using | Setup.py and regular or namespace packages, relative import, | having complex sub packages and cross importing, running a script | from somewhere inside one of your sub packages, and many more | craps like these. Import system must be intuitive and easy to | use! | CapmCrackaWaka wrote: | Yeah it really tripped me up as a beginner. I think the hardest | part to get used to was that the import syntax actually changes | based on how, and from where, you are running your code. So | depending on how you call your code, your imports might or | might not work. This is ESPECIALLY painful when you are | building a distribution. There is no syntax that works for all | situations, which seems like it would be pretty important for | an import system. I had to bookmark this tab, and still refer | to it often. | | https://stackoverflow.com/questions/14132789/relative-import... | t-writescode wrote: | ... have you tried Rails' autoloading? Especially when running | rake tasks? | grawprog wrote: | Years ago, I thought I'd try learning Python, I'd heard it was | supposed to be easy, good for beginners and everything. I read | one of those beginner Python type books and followed along with a | roguelike tutorial. Everything was going pretty alright, until I | started trying to split everything into different files and use | imports. | | I ended up just giving up. I read programming in lua, and rewrote | my entire project in lua and actually finished it. | | Some day I'd like to go back and maybe learn Python but I really | didn't enjoy my experience with it. I even found C headers easier | to figure out than Python imports. | toxik wrote: | Strange, Python's import is not very difficult to use. | 3pt14159 wrote: | It's on of those things people using Python for so long | forget about: Some people try to run individual files and cd | around the place. I never do that anymore. I have a test | suite and breakpoints and that's it. But before you've | learned those tools it feels natural to "run that file there" | and then say "oh hey why doesn't it work any more?" | grawprog wrote: | It was probably something I did. The original tutorial I | followed had everything in one file and didn't get into | anything about imports. I started splitting everything up | arbitrarily and started tossing imports into the files that | complained about missing dependencies and ended up getting | overwhelmed because nothing worked. | | I'm sure if I'd taken the time to try and fix it I eventually | could have and at this point i've had more experience with a | bunch of different languages, so I'm sure it's not as bad as | I remember. | | I imagine it's one of those cases where if i were to go back | and laugh about how stupid I was, but ya know, those first | impressions. | trulyme wrote: | No, your impression was right. Reading this blog post made | me realize how little I know about the python import system | (and I use python daily), and at the same time how little I | _want_ to learn it. It is completely unintuitive and | probably one of the worst aspects of otherwise beautiful | and useful language. Fortunately, sys.path hack works | reliably - one can just add that one line and imports work | as expected. | rcxdude wrote: | It has some wildly frustratingly unintuitive behaviours in | precisely the wrong place for beginners: in between having | everything in a single script and building a proper package, | especially when you are invoking your script with 'python | script.py' as opposed to say 'python -m scripts.script'. | roenxi wrote: | But _despite_ being terrible the Python import system is | remarkably easy to get started in. And generally easy for | beginners to work with (put all the code in the same folder or | "pip install"). | | There are some lessons here that other languages would do well to | learn. Trouble importing 3rd party libraries must be a kiss of | death for beginner engagement. | tasogare wrote: | No. Python is a language I have to use infrequently, but I give | up half of the time using a project found on GitHub because of | missing depencies, the need to install a package manager to | install another package manager to install dependencies, etc. | The other day I spent some times fixing a docker image that was | working fine few months ago but which was now failing because | some Python package install returned an error. | | On the contrary, C projects tends to build with 3 commands and | C# (often way bigger) with a single command, and without having | to do magic things around "virtual environments". | simlevesque wrote: | I disagree too... you can't even refer to a file in the same | folder without using some magic (adding current folder to the | path), which is a huge barrier when starting. | awestroke wrote: | I disagree. I find it hard to get started in python. There are | loads of package managers, so I don't know which one to pick. | There are multiple different rules for for how imports work | with local files. The standard library is full of of functions | that one should not use any more, and you need to know which is | which. Defining a main entrypoint consists of checking a magic | __NAME__ constant. | | You have internalised all these quirks, and know how to work | with/around them. Beginners haven't. | The_Colonel wrote: | I vividly remember the frustration when trying to make some | very simple application imports working. There's a couple of | gotchas, the biggest one being that you need to write imports | based on where you expect to run your applications from. | Perhaps obvious for experienced python devs, very much | surprising for newcomers. | | And then I was quite shocked by the state of package managers | in python. You need to learn pip, venv (with references to | "virtualenv"), these are too low level, so you find pipenv, | which is unbelievably slow (installing a single dependency can | take 10 minutes), so you need to learn to use it with "--skip- | lock", but then you lose locking ... | | I've never appreciated node's bundled "npm" so much before | which mostly "just works". | AlphaSite wrote: | Poetry is pretty great comparatively, since it handles both | dependencies, locking and virtual environments for you, but | it has slow resolution just like pip (since the new update), | pyenv, pipenv, etc. | johncena33 wrote: | I've been programming in Python professionally for more than five | years. I consider myself quite a good programmer. I still don't | have good grasp on Python's import system. Does anyone else have | similar experience? | jlund-molfese wrote: | I've only been writing Python for about a year, but I've found | it much harder to grasp how dependency resolution and imports | work than other languages I've picked up (JVM, Node, Go, C). | agumonkey wrote: | I thought not knowing that made me a noob. | isatty wrote: | The average python programmer does not really need to deal with | pythons import system that much (just be aware of how it does | its module loading and that you can conditionally do stuff | sometimes with __import__ etc). As someone who has messed | around with it a lot (dynamically loading/unloading modules, | modifying on the fly etc) I would NOT recommend doing that | stuff for anything in production. | wpietri wrote: | Same. Occasionally I will get into some sort of mess, learn how | it works under the hood enough to get myself out, and the | promptly forget everything. | | And I think that's for the best. I'd much rather have a happy | path that I stay on than use some sort of dark magic that | nobody who comes after me will understand. | benrbray wrote: | When in doubt, put some more dots in front of your import | statements. Or remove them? Maybe I need an extra __init__.py | somewhere? Oh I'm importing from a subfolder, what do I need to | do to get that work again? I can't remember. | captaincaveman wrote: | This | fullstop wrote: | I sometimes fall into the trap of using pip to install | dependencies and then things break after an os update. That is, | my python version has changed from 3.8 to 3.9 and my | dependencies are sitting in the wrong directory. I never know | if I should use pip and requirements.txt or rely on Ubuntu's | packaged versions. | geofft wrote: | I used to have a rule of, never use pip and only use | Ubuntu's/Debian's packaged versions. That works pretty well | if you're happy with the packaged versions and you don't need | unpackaged libraries. | | I now have the rule of, only ever use pip _inside a venv_. If | your venv is more than a little bit complex, write a | requirements.txt file so you can generate it. So it 's | something like $ cat > requirements.txt << | EOF tensorflow Django==3.2.5 | cryptography EOF $ echo venv > .gitignore | $ python3 -m venv venv $ venv/bin/pip install -r | requirements.txt $ venv/bin/python | | or, if you prefer, $ . venv/bin/activate | (venv)$ pip install -r requirements.txt (venv)$ | python | | Then when your Python version changes, or you get confused | about what's installed, or whatever, you can just blow away | the entire venv and recreate it: $ rm -r | venv $ python3 -m venv venv $ venv/bin/pip | install -r requirements.txt | | and you're in a known-good place. | | Either of these rules works fine. The thing that works poorly | is using pip install outside of a venv (with or without | root). | trulyme wrote: | For me the rule is to always use pipenv locally and pip + | requirements.txt (generated by pipenv) for production (in | docker container usually). No complaints. | skindoe wrote: | If you dig into importlib and try to write extensions for it | then the underlying concepts of modules and packages will make | a lot more sense. | | For a project I was working on where you could dynamically call | distributed tasks (we we're using ecs) I added a subclass of | module and package that dynamically created package structures | and modules from a database call. | | So the new modules and packages would load from the database | instead of from a python file and python the class would be | dynamically generated to be something like albiet more | advanced. | | Class MyDistributedTask: Def run(self, input, | execution_engine=ecs): Run task | | So users of our package could import directly import their | package_name.TaskName | | And run it directly as long as they imported our package which | contained a custom module loader to our db to discover. | | Learned a ton about importing. | abledon wrote: | same! I've always been shielded from it with Django's | conventions. (the ecosystem I mainly work in). I used a lot of | '.' and '..' imports but I think something changed in python3 | that made that strategy a lot less forgiving... now I _really_ | should read the entirety of this article! | BozeWolf wrote: | Always use absolute imports, do not use relative imports. | Solves most problems. Also is recommended by pep8. | | Skip the relative imports in the article. No need to read it | entirely anymore ;-) | brilee wrote: | I'm a python readability approver at Google and I don't | understand how the import system works | trulyme wrote: | Interesting... If someone wants to know more about what a | readability approver does: | https://www.pullrequest.com/blog/google-code-review- | readabil... | joshuamorton wrote: | To be fair, Google's python avoids ~99% of the complexity of | Python's import system by making all imports absolute and | doing most things through blaze/bazel. | MeteorMarc wrote: | Python importing quirks can be time consuming. Things beginners | will encounter: | | - three modules cannot depend on each other in a circular way | | - relative imports are fragile ("module not found") | | - the __all__ definitions in the __init__ file make modules | available under different full names | | - how to reload a module in a jupyter notebook if edited | | and so on. | chrisseaton wrote: | > three modules cannot depend on each other in a circular way | | What would the purpose of circular modules like this be? You | may as well collapse into a single module and the situation | would not be any different would it? | btilly wrote: | _What would the purpose of circular modules like this be? You | may as well collapse into a single module and the situation | would not be any different would it?_ | | What is the purpose of modules? You may as well collapse into | a single script and the situation would not be any different, | would it? | | I'm not being facetious here. The answer to the second is the | answer to the first. | | A common example might go like this. You have a module for | each kind of thing you have in the database. But now if | someone loads a Company, they need to get to Employees. And | if someone loads Employee they need to get to Accounting for | the salary, payments, etc. And Accounting needs to be able to | get to Company. | | Those are all large and complicated enough that it makes | sense to make them modules. But you just created a circular | dependency! | | The standard solution is to load a base library that loads | all kinds of objects so they all can assume that all the | others already exist and don't need a circular dependency. | But of course someone won't like wasting all that memory for | things you don't need and ... | chrisseaton wrote: | > What is the purpose of modules? | | So parts of the system can be managed independently. | | > The answer to the second is the answer to the first. | | Clearly not - since circular modules cannot be managed | independently! | wpietri wrote: | To me the purpose of models is to help humans manage | code. Our brains don't hold much at once, so the more we | can forget about in a given circumstance, the easier it | is. So I think btilly is correct: the reason I want | modules, ignoring things I don't care about, is the same | reason I want them to deal reasonably with circular | references. | btilly wrote: | Exactly. | | In the example that I gave, the design described will | handle complex edge cases such as a part time employee | working for multiple companies. And will do so without | programmers having to think through the whole system at | all points. | | Independence of modules has no importance in a codebase | that ships as a whole. But modularity does. | [deleted] | dragonwriter wrote: | > But you just created a circular dependency! | | Only if those things not only need to be able to "get to" | each other, but also need to know, at compile time, about | the concrete implementation of the others. | | That's _possible_ to be a real need, but its also something | that often happens because of excessive and unnecessary | coupling. | btilly wrote: | The coupling that I described needs to be in the software | because it exists in the real world that the software is | trying to describe. | | However your "compile time" point is important. There is | another solution, which is to implement lazy loading of | those classes. | | So you put your import in the method of each that needs | to know the other. This breaks the circular dependency | and needs more up front memory. However it can also | become a maintenance issue where a forgotten import in | one function is masked by a successful import in another, | until something changes the call and previously working | code mysteriously goes boom. | | It's all tradeoffs. | lp251 wrote: | Suppose you have two classes, A and B. They are sufficiently | complex to merit their own modules. | | Suppose you have some method of A which does something | special if it gets an instance of B, and vice versa. Now you | have a circular import problem; glhf | chrisseaton wrote: | > Now you have a circular import problem; glhf | | But why not collapse into a single module at this point if | you can't avoid dependency? What are the separate modules | adding at this stage forward? | lp251 wrote: | You can, but sometimes it's not ideal. | | I ran into this when A and B had many derived classes. I | wanted to put A and it's derived classes in one module, | and B and it's derived classes in another. It was messy. | | I wound up putting A and B in a single module and having | a separate one for the derived classes. Not ideal. | nemetroid wrote: | It _does_ sound ideal to me, or at least better than the | initial proposal. | | A and B both need to know about the other's base | definition, neither cares about the details about the | other's derived classes. Splitting it into three modules | shares as little surface area as possible. | yongjik wrote: | IMHO that's code smell. Modules shouldn't depend on each | other, because that creates a web of tangled dependency | where you have to understand _everything_ before you can | understand one of them. Circular dependency is to modules | what goto is to control flow. | | Besides, if you are in a "well, fuck it, deadline is | tomorrow" mode, you can always do something horrible like: | if 'classB' in type(obj).__name__: ... | linspace wrote: | I think bad code gives raise to more dependencies in | general and so circular dependencies. | | But the truth is sometimes it has happened to me and the | only solution I found was creating an small module with | maybe one or two functions which is not exactly ideal. | 0xC0ncord wrote: | I generally solve this problem by having a module | specifically containing the abstract base classes of each | of the classes I will be working with that implements | either no or bare minimum functionality for these objects. | That way, any other module can import this module and have | visibility of every other class I will be working with. | scbrg wrote: | Won't this work just fine if you instead of writing: | from a import A | | write import a | | and in the code (which is presumably not at module level) | check against isinstance(obj, a.A) | ? | dragonwriter wrote: | > Suppose you have some method of A which does something | special if it gets an instance of B. | | While that's in rare circumstances the right thing to do, | it's mostly an anti-pattern--you should be taking an object | supporting a protocol, with the behavior difference | depending on a field or method of (or actually implemented | in a method of) that protocol. If you do that, you don't | create a dependency on a concrete class that happens to | require the special behavior. | perlgeek wrote: | A Customer has a BillingContact, which references a Person, | which has primary Customer. | | Boom, circular dependency. | | Happens in basically all corporate code bases that grow over | the years, with varying path lengths. | | Throwing all potentially circular types into one big module | isn't a great solution. | | (In practice, we tend to rely on run-time imports to make it | work. Not really great, but better than throwing several 10k | or 100k lines of code into a single module). | [deleted] | chrisseaton wrote: | I guess I don't get why so you want to use separate modules | if you aren't getting the benefits of modularisation? | perlgeek wrote: | I still get _some_ benefits of modularisation, even if | there are some cross-dependencies. | gchamonlive wrote: | I don't think purposeful circular dependency, but you can end | up with circular imports after refactoring for instance. | | The common approach to solving this is pulling everything | that is used by all the modules into leaf libraries, | effectively creating a directed acyclic graph, but this is | not obvious nor easy to do the first time. | jvolkman wrote: | In my experience they creep in over time as the system grows. | Coupling between parts of the system that was previously | unnecessary is added, and the cycles form. | BurningFrog wrote: | The purpose is you want to use code in other modules. | | If you keep doing that for a while, a circular dependency | will happen. | | This is the dumbest thing thing in Python. All other | languages I know have solved it. | chrisseaton wrote: | > The purpose is you want to use code in other modules. | | So put them in the same module? Circular modules don't give | you the benefits of modules, do they? Not an expert in | modularity. | BurningFrog wrote: | Putting all your code in one file does indeed solve all | import problems, but creates far bigger ones. | | In case you didn't know, each source code file is a | module in Python. | nemetroid wrote: | In my experience, typically a codebase that has grown | organically in a different direction than the original | design, and where the cost of refactoring is not deemed worth | it. | Waterluvian wrote: | I'm always amazed just how tolerant javascript's import system | is when I have circular imports. I guess maybe because it | doesn't care about modules and just cares about specific | elements that are being imported/exported. | | When I do have a nasty circular dependency Webpack usually does | a bad job telling me what's wrong. | | Though I should still treat circular imports as, at the very | least, an organization code smell. | zarzavat wrote: | Circular imports in JS are fine and not a code smell, for | instance in typescript you may have two classes that | reference each other's types - obviously this is not a real | import from JS's perspective but the point is that you should | not have to care whether it is real or not. That's a world we | don't want to live in. | | Circular imports are only ever a problem when you have code | running when the module loads. Then you run into module load | ordering issues. So avoid any side effects on module load and | make all setup explicit. | brundolf wrote: | Circular imports in JS matter when the imported code is being | called immediately at import time. If a file defines | functions that _later_ call functions from another file (and | vice-versa), but those symbols will be populated before the | function actually gets called, there 's no problem | Waterluvian wrote: | Yeah. It's so flexible. I get frustrated at Python (Django) | serializers that legitimately need to depend on each other. | And the answer on the forums is to create a near duplicate | class. | nyrikki wrote: | JavaScript does not provide namespaces by default which | allows this. | | Python is built upon namespaces and cycles in | dependencies, either viewed as graph theory or kSAT | introduce that fun NP-complete problem of version hell. | | Using `need` in Javascript maintains the directed acyclic | graph structure, but if you get fancy you will run into | the same problems with circular depends in Python. | | Karp's 21 and/or SAT will catch up with you at some point | if you don't respect the constraints that make the | problem tractable. | | Note I am not saying I prefer or like pythons | choices...but that they had to make one. | BadInformatics wrote: | IIRC an explicit design goal was to enable circular deps | (hence why imported bindings are considered "live". It's | interesting to see this works in practice though, I've never | tried using them myself. | maxnoe wrote: | > __all__ | | No it doesn't. __all__ just defines which objects are imported | when doing a star import. | nooorofe wrote: | probably, you may add to the list | | - understand difference between Python in IDE and Python in | shell | | So many times people do `pip install <>` and still not able to | use in IDE | blooalien wrote: | I usually deal with this by defining a cell with the actual | contents of the class/module code so that I can just re-execute | that cell any time I make changes to it. Then I simply | copy/paste all the code back into the module.py file once I'm | done tweaking it and playing with it. Thus for me Jupyter sort | of operates as almost an IDE of sorts. | AdrianoKF wrote: | One interesting use case of overwriting `builtins.__import__` | I've encountered was the automatic hooking by ClearML [0] | (experiment tracking, ...) into all sorts of common libraries | like Matplotlib, Tensorflow, Pytorch, and friends. | | The implementation is surprisingly straightforward, once you've | come to terms with the basic idea, see [1] and the rest of the | `clearml.binding` package. | | [0]: https://clear.ml [1]: | https://github.com/allegroai/clearml/blob/master/clearml/bin... | simonw wrote: | This is great: I'm learning so much reading this. | | It lead me to read the source of the Python "types" standard | library module, which really does just create a bunch of | different Python objects and then use type() to extract their | types: https://github.com/python/cpython/blob/v3.9.6/Lib/types.py | | Some examples from that file: async def _ag(): | yield _ag = _ag() AsyncGeneratorType = type(_ag) | class _C: def _m(self): pass MethodType = | type(_C()._m) BuiltinFunctionType = type(len) | BuiltinMethodType = type([].append) # Same as | BuiltinFunctionType | hirowan wrote: | I'm just grateful I'm not a core Python dev after reading this | thread. I've never seen so much negativity concentrated in one | place in quite some time, for a feature of a programming language | which is fairly innocuous. | ledauphin wrote: | Python is the language everyone at HN loves to hate. One | presumes it has something to do with the fact that it's Y | Combinator's recommendation for most startups. | ASalazarMX wrote: | I love Python, but to be fair, the dependency/import system has | not aged well, and the various projects trying to fix it are | proof of that. | [deleted] | jvolkman wrote: | My least favorite is that import ordering matters in some | situations. Like if I just run "organize imports", all of a | sudden dependency cycles pop up and everything is broken. | Certainly a sign of things being misimported/misorganized, but | stuff happens has systems grow fast. And solving these issues is | always incredibly time consuming. | | Unless anyone knows of magical tools to help solve import issues? | matheusmoreira wrote: | > Unless anyone knows of magical tools to help solve import | issues? | | Topological sorting? Always wondered why programming languages | can't do what package managers do. | sltkr wrote: | The breakage described can only occur if there are dependency | cycles, so topological sorting can't fix it. If there are no | cycles, then the order of imports doesn't matter. | | _edit_ : Actually I'm not even sure what kind of error we're | talking about here. If two modules import each other and they | both need access to the other's contents upon initialization, | there is no ordering that will work. And if at most one needs | access to the other, it will always work, no matter in which | order they are imported. So I don't really know what the OP | was talking about. | colonwqbang wrote: | Yes, it is unfortunate. Loading modules can have side effects | as the loaded module is allowed to execute arbitrary code at | load time. This is also a source of ordering issues. | | Maybe some think this is only a theoretical problem and doesn't | happen with "well-written" libraries. Well, here is one example | which bit me in the past: | https://stackoverflow.com/a/4706614/767442 | matheusmoreira wrote: | It's certainly a lot better than Ruby's require which just | executes code and alters global virtual machine state. Not too | different from C's #include. | | My favorite is Javascript's. Modules are objects containing | functions and data, require returns such objects. Simple, | elegant. Completely reifies the behind-the-scenes complexity of | Python's import, making it easily understandable. | brundolf wrote: | Though that's not how JS's new module system works, which I | would say is also elegant in its own ways, but juggling two | different systems is less so | osmarks wrote: | Lua does the same thing as JS. It's basically "dofile" with | some indirection and extra path resolution logic. | sillysaurusx wrote: | Fun fact: you can overload the Python import system to work with | _other languages_ that _you create_. | | I use this for my Python-based Lisp: | https://github.com/shawwn/pymen/blob/ml/importer.py | import foo | | checks for "foo.l", and if found, compiles it to foo.py on the | fly and imports _that_ instead. | | It's so cursed. I love it. | catlifeonmars wrote: | NodeJS allows this as well. I think this is pretty much a must- | have feature for any serious dynamic language. | | Edit: A must have for any prolific dynamic language. But now | I'm not sure that's true, because even though it apparently | works in Python, it's certainly not widely used. In NodeJS this | feature is used quite heavily for typescript, coffeescript | (etcetera) interop. | aasasd wrote: | It's pretty much the exact feature that's behind the saying | that 'to parse Perl you must have the Perl interpreter'. | Because Perl allows some kind of language/imports handlers, | as exhibited by tricks like Lingua::Romana::Perligata. | dragonwriter wrote: | > But now I'm not sure that's true, because even though it | apparently works in Python, it's certainly not widely used. | | I mean, it is used--even in the standard library--but often | for alternative packaging (e.g., loading python modules in | zip files) rather than alternative languages. It may be used | less prominently than in Node, but it definitely is used for | a variety of things. | toomuchtodo wrote: | A master of the dark arts I presume. | bloopernova wrote: | Can I just say that the introduction to Lisp you made in your | README.md is really good! | | I keep trying to get into Lisp (and JavaScript, and TypeScript, | etc etc) but I've been a sysadmin my whole professional life | and also a chronic pain sufferer. That translates into mostly | having energy only for work and that's it, not much motivation | to learn after work or on the weekend. | | In my DevOps job, I write Terraform, plus read javascript and | cloudformation yaml. I do wish I could convert my current stuff | to AWS CDK, but I don't want to fragment the multiple projects | that are using Terraform. (I haven't looked into tf-cdk much at | all yet) | sillysaurusx wrote: | Not mine! That was all Scott Bell. It's forked from Lumen: | https://github.com/sctb/lumen | | But, I did make an interactive tutorial here: | https://docs.ycombinator.lol/ | | If you have any questions about it, I'd be happy to answer. | This stuff is pure fun mixed with a shot of professionalism. | | For what it's worth, as someone with narcolepsy, I relate | quite a lot to your chronic pain. | (https://twitter.com/theshawwn/status/1392213804684038150) | For me, it mostly translated into wandering aimlessly from | job to job, since I thought no one would have me. I hope that | you find your way -- there's nothing wrong at all with taking | it slow and spending years on something that takes others a | few months. Everyone is different, and it's all about the | fun. | markrages wrote: | Does "-5e4" really evaluate to "-5000" in this language? | sillysaurusx wrote: | Hah! Good catch! That readme typo has been in there since | Lumen's inception. | | It evaluates to -500000, as you'd expect. | | (Just kidding, it's -50000. Amusingly, the | https://docs.ycombinator.lol version gets it right, since | it has to; every expression is actually evaluated in the | browser.) | mh- wrote: | Thanks for sharing this. I didn't expect to read that whole | gist but I did and I'm glad I did. Happy for you. | gilch wrote: | Hylang also does this. Macropy too. | andrewaylett wrote: | Lots of things you _can_ do with Python but probably shouldn 't | and people typically don't. That's one reason I prefer it to | Ruby, or even Node, where monkey-patching or otherwise exposing | bad magical behaviours is common and even encouraged -- the | power is all there, but the ecosystem encourages you to use it | for good, not evil. | | This sounds very much like the good kind of magic, though. | th0ma5 wrote: | JPype does this for importing Java classes. | Fordec wrote: | Wait, what? | | > that you create | | As in existing language supplied eg. Perl, Java, etc., or | _literally_ anything? Like bootstrapping your own home made | language from scratch? | sillysaurusx wrote: | Literally anything. 'Tis a homemade homegrown lisp, grown by | Scott Bell for several years till I took it all for myself. | Nom nom. | | It starts with reader.l: | https://github.com/shawwn/pymen/blob/ml/reader.l where the | raw character stream is turned into a bunch of nested arrays. | E.g. (+ 1 2) becomes ["+", 1, 2] | | Then it's punted over to compiler.l | https://github.com/shawwn/pymen/blob/ml/compiler.l where it's | passed through `expand`, which does a `macroexpand` followed | by a `lower`. E.g. (do (do (do (print 'hi)))) becomes | ["print", "\"hi\""] | | Then the final thing is thrown to the compile function, which | spits out print("hi") -- the final valid Python code that | gets passed into the standard python `exec` function. | | Works with all the standard python things, like async | functions and `with` contexts. Been screwing around with it | for a few years. | Fordec wrote: | That's absolutely disgusting. But in a Web Assembly sort of | way... I don't know whether to spit on it or give it a | medal. | masklinn wrote: | > As in existing language supplied eg. Perl, Java, etc., or | literally anything? Like bootstrapping your own home made | language from scratch? | | Should work with anything as long as you can ultimately | generate Python bytecode (and provide a module object). The | import system is not simple, but it's really open. | aasasd wrote: | If the import system allows your code to run instead of the | 'import' statement, and to produce the module however you | want, then of course you can do whatever: load code from | Google or StackOverflow results, if you wish. | amelius wrote: | You mean that you can override the import mechanism, which | means that it allows you to do just about anything, including | making it work with other languages. | | That doesn't sound cursed to me, just flexible. | cobbal wrote: | What's flexible for a dynamic scripting language is often | cursed from a static perspective. Knowing what imports | resolve to statically can be nice. | dheera wrote: | I don't get it. Where do you define which Lisp-to-Python | translator to use? It certainly doesn't seem to know on its | own. $ touch foo.l $ python3 | >>> import foo ModuleNotFoundError: No module named | 'foo' | petters wrote: | I'm guessing something else needs to be imported first. | sillysaurusx wrote: | Close! git clone | https://github.com/shawwn/pymen -b ml cd pymen | touch foo.l bin/pymen (import foo) | | It's a bit of a WIP (notice this is on the `ml` branch, not | mainline), but it does work. >:) | | You need nodejs to be installed too, ha. ___________________________________________________________________ (page generated 2021-07-24 23:00 UTC)