relative import in Python 3

2019-08-02 08:23发布

问题:

Say my project is structured like this:

myproject
├── calendar.py
├── foo.py
└── __init__.py

In foo.py, I have

from calendar import isleap

I thought in Python 3.x, without using the explicit .calendar the code above should load the build-in calendar module instead my own calendar module, but apparently my local calendar.py is still being imported and it throws an error because there's no 'isleap' in mypkg/calendar.py. Why was my local calendar module imported here?

I had to rename calendar.py to cal.py to get this work..

回答1:

from __future__ import absolute_import is the default on Python 3. Therefore from calendar import isleap statement imports the top-level module calendar.

If you see other results; it means either you are not using Python 3 or you are trying to run a python module from inside a package as a script (myproject directory itself is in sys.path). If the latter then your calendar.py becomes the top-level module and (due to the current directory comes before stdlib directories in sys.path) from calendar import isleap imports calendar.py from the current directory. "Never add a package directory, or any directory inside a package, directly to the Python path"

To avoid it, do not run modules from within python packages directly e.g., do not do this: cd myproject; python foo.py. Do this instead: python -mmyproject.foo (or you could define what scripts should be run in setup.py or create a similar script manually: from myproject import foo; foo.main()).

If you want to run a Python package as a script then create myproject/__main__.py then run python -mmyproject.


If you want to do a relative import in Python 3; do it explicitly e.g., in myproject/foo.py:

from .calendar import something

Or do an absolute import:

from myproject.calendar import something


回答2:

It looks like your path or directory structure is set up wrong.

Given the following structure the full name of your calendar module should be myproject.calendar. You can check this by printing out the __name__ attribute of your module. For this to be the case, the path that your program uses to import local modules must be the folder containing myproject.

myproject
├── calendar.py
├── foo.py
└── __init__.py

It seems like the path you are using is actually myproject. Meaning calendar.py is turned into the root level module calendar, rather than myproject.calendar. Python prefers local modules to builtin ones, and so imports your calendar module.

More typically you might do something like this.

MyProjectFolder
├── main.py
└── myproject
    ├── calendar.py
    ├── foo.py
    └── __init__.py

And then run your program like this:

#! /bin/bash
cd /path/to/MyProjectFolder
python main.py


回答3:

Python will check your local modules and load them firstly by import.

from calendar import isleap will search the module calendar in your locale package firstly. If not found, it will import from the builtin library calendar.

from .calendar import isleap will only import from your locale module calendar. If not found, raises a exception ImportError.

That is why you should use relative import in a package.

You can do a trick like that to import the builtin library without checking of the local modules. But it's only a trick. I will never use it in production. You should better rename your module calendar.

import imp, sys
f, pathname, desc = imp.find_module("calendar", sys.path[1:])
calendar = imp.load_module("calendar", f, pathname, desc)
f.close()

from calendar import isleap