Things I don't like about the `from package import module` that we're using everywhere:
1. Makes it hard to grep the codebase for all uses of a module. Can't just grep for "foo.bar.baz", now it might be from foo.bar import baz, from .bar import baz, from . import baz, from foo.bar import gar, dar, baz, jaz, etc.
2. Increases "travel" when trying to read code. I just see `baz`, I have to go up to the top of the file to see that that is in fact `foo.bar.baz`. Multiply that across everything we import in every file, makes code much harder to understand imo.
3. Pollutes namespaces, a module with lots of `from X import Y` has a high chance of some function, local variable, etc name clashing with something imported.
4. Understanding the meaning of an import statement requires taking into account the location of the file, hurts readability imo.
5. "from package.subpackage import module" is too close to the even worse "from package.module import object", encourages it. It's simpler to just say "no 'from' imports".
If I had my way we would always use absolute imports, just always:
import h.foo.bar
And always one import per line. Each module has only one canonical way to be imported. You can grep for "import foo.bar" to find all imports, or just "foo.bar" to find all uses not just imports. You can see right away in the code whenever h.foo.bar is being used because it says h.foo.bar right there.
To keep verbosity under control name packages and modules well and don't have too deep package hierarchies.
Hi Sean,
I think you raise some great points. I'm all ears for ways we can make our code more readable, so thank you for sending this.
TL;DR: For the reasons below, and taking account of Randall's email which has arrived since I started writing this, I suggest we agree to eliminate relative imports, but stick with using from foo.bar import baz
as an aid to readability.
- Makes it hard to grep the codebase for all uses of a module.
I don't think it makes it hard, although it might make it slightly harder. Any "foo.py" anywhere can only be imported by a line that includes the string "foo", so grepping for that will always find all imports of that module. Sure, it may also find some other stuff, but in my experience this has never really been a problem. Even
grep -r foo . | grep import
almost always reduces the number of false positives to a level so low that it's not a problem.
- Increases "travel" when trying to read code. I just see
baz
, I have to go up to the top of the file...
I think you have a fair point here, but this is heavily context sensitive. A common pattern is having a set of "adapter" modules which are all supposed to conform to the same interface, say,
foo.renderers.csv
foo.renderers.json
foo.renderers.xml
each of which is expected to contain a render
function. In such a case, if people are in the habit of doing
from foo.renderers.csv import render
(... many lines omitted...)
render(data)
then I'd agree that it's confusing. But that confusion can be just as easily reduced by doing
from foo.renderers import csv
(...)
csv.render(data)
So I'd agree insofar as not all uses of this kind of import are good for code readability, but they're not all bad, either.
- Pollutes namespaces, a module with lots of
from X import Y
has a high chance of some function, local variable, etc name clashing with something imported.
In practice I don't think I've run into this frequently. If your module is importing All The Things, then chances are it's a bootstrap or other glue code module, and probably shouldn't be defining lots of its own names. If your module is defining lots of its own names and also importing All The Things, that sounds to me like bad factoring, and the fact that you end up running into namespace collisions is probably a good thing in disguise -- it serves as a hint that your module is doing too much.
Either way, most editors/linting tools will flag this kind of error immediately, and if the worst comes to the worst, imports can be renamed.
- Understanding the meaning of an import statement requires taking into account the location of the file, hurts readability imo.
This only applies to relative imports, and not to absolute from foo import bar
imports.
And I think it's probably your strongest argument. When I'm jumping around source files I'm not always immediately aware of where I am in the package hierarchy.
That said, there is one consideration which to my mind argues in favour of relative imports: modules which are orthogonal to one another shouldn't frequently be importing code from levels above them in the package hierarchy -- it implies, in fact, that they're not orthogonal. Orthogonal modules would usually only import layers below them in the package hierarchy, and in that sense lots of from ..
or from ...
can serve as a code smell.
- "from package.subpackage import module" is too close to the even worse "from package.module import object", encourages it. It's simpler to just say "no 'from' imports".
A fair point, but this isn't a dichotomy. We can say importing module members is not a good idea without also banning from package import module
.
That said, I would happily agree that from package.module import foo, bar, baz, ...
is a code smell, and should usually be replace with from package import module
.
So, in summary, I think I accept that maybe relative imports aren't a great idea, but I'm afraid I do think that using exclusively the import foo.bar.baz
style actually hurts readability, if anything. Which is more readable:
import foo.db
import foo.auth.permits
import foo.renderers.csv
def render(request, data):
conn = foo.db.connect()
if foo.auth.permits(request.user, 'thingamijig'):
foo.renderers.csv.render(data)
or
from foo import db
from foo import auth
from foo.renderers import csv
def render(request, data):
conn = db.connect()
if auth.permits(request.user, 'thingamijig'):
csv.render(data)
I certainly know which I prefer!
In order to inform the discussion with the practices of other Python projects, I checked out a couple of the big ones. For each, I've counted how many of each style of import they use within the project (i.e. not counting external dependency imports):
import foo.bar.baz
from foo.bar import baz
from . import baz
Counting with:
for pkg in boto django jinja2 requests; do
printf "%10s import %4d\n" $pkg $(ag --python '^import '$pkg $pkg | wc -l)
printf "%10s from %4d\n" $pkg $(ag --python '^from '$pkg $pkg | wc -l)
printf "%10s from . %4d\n" $pkg $(ag --python '^from \.' $pkg | wc -l)
done
The results:
boto import 215
boto from 1304
boto from . 0
django import 23
django from 5156
django from . 569
jinja2 import 2
jinja2 from 148
jinja2 from . 0
requests import 2
requests from 11
requests from . 213
So:
import foo.bar.baz
formfrom django.foo import bar
formSee the top for my conclusion. Sorry for the mammoth email!
-N
Analysis FTW.
FWIW I still like the relative form. I wonder if Django predates the PEP for explicit relative imports. I'll concede this, though, so absolute it is.
Should we also add "from future import absolute_imports" or whatever, too? I think it's probably a good practice to guarantee not to accidentally import relative modules.
TL;DR: For the reasons below, and taking account of Randall's email which has arrived since I started writing this, I suggest we agree to eliminate relative imports, but stick with usingfrom foo.bar import baz
as an aid to readability.
import foo.db as db
import foo.auth as auth
import foo.renderers.csv as csv
def render(request, data):
conn = db.connect()
if auth.permits(request.user, 'thingamijig'):
csv.render(data)
I see that the "from" style seems to be very popular in successful
projects but I don't really get what it gives us other than
disadvantages, over "import ..." and "import ... as ...".It's readability, IMO. No need to repeat a namespace all over the place, when that's not necessarily very descriptive of what it's doing on any given line, whereas the deepest module is likely to be the meat/purpose of the line.
<shrug> that's how I see it.
--
You received this message because you are subscribed to the Google Groups "Hypothesis Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dev+uns...@list.hypothes.is.
To post to this group, send email to d...@list.hypothes.is.
To view this discussion on the web visit https://groups.google.com/a/list.hypothes.is/d/msgid/dev/550FEEAB.6060604%40hypothes.is.
On 23 Mar 2015, at 11:44, Sean Hammond wrote:
On 20/03/15 19:10, Nick Stenning wrote:
TL;DR: For the reasons below, and taking account of Randall's email
which has arrived since I started writing this, I suggest we agree to
eliminate relative imports, but stick with using |from foo.bar import
baz| as an aid to readability.Can't we just use "import foo.bar.baz as baz" then?
I think for now we stick with the common pattern: preferring from absolute.package.path import module
Very happy to revisit this some time in the future, but for now I think sticking to "least surprising for other Python programmers" is probably the right thing to do here.
-N