I have a specific problem which might require a general solution. I am currently learning apache thrift. I used this guide.I followed all the steps and i am getting a import error as Cannot import module UserManager. So the question being
How does python import lookup take place. Which directory is checked first. How does it move upwards?
How does sys.path.append(») work?
I found out the answer for this here. I followed the same steps. But i am still facing the same issue. Any ideas why? Anything more i should put up that could help debug you guys. ?
Help is appreciated.
asked May 26, 2015 at 10:34
Saras AryaSaras Arya
2,9828 gold badges39 silver badges71 bronze badges
0
On windows, Python looks up modules from the Lib folder in the default python path, for example from «C:Python34Lib». You can add your Python libaries in a custom folder («my-lib» or sth.) in there, but you need a file in order to tell Python that you can import from there. This file is called __init__.py , and is totally empty. That data structure should look like this:
my-lib
(This is how every Python module works. For example urllib.request, it’s at «%PYTHONPATH%Liburllibrequest.py»)
You can import from the «mymodule.py» file by typing
import my-lib
and then using
mylib.mymodule.myfunction
or you can use
from my-lib import mymodule
And then just using the name of you function.
You can now use sys.path.append to append the path you pass into the function to the folders Python looks for the modules (Please note that thats not permanent). If the path of your modules should be static, you should consider putting these in the Lib folder. If that path is relative to your file you could look for the path of the file you execute from, and then append the sys.path relative to your file, but i reccomend using relative imports.
If you consider doing that, i recommend reading the docs, you can do that here: https://docs.python.org/3/reference/import.html#submodules
answered May 26, 2015 at 15:28
AgilixAgilix
3141 silver badge13 bronze badges
2
If I got you right, you’re using Python 3.3 from Blender but try to include the 3.2 standard library. This is bound to give you a flurry of issues, you should not do that. Find another way. It’s likely that Blender offers a way to use the 3.3 standard library (and that’s 99% compatible with 3.2). Pure-Python third party library can, of course, be included by fiddling with sys.path.
The specific issue you’re seeing now is likely caused by the version difference. As people have pointed out in the comments, Python 3.3 doesn’t find the _tkinter extension module. Although it is present (as it works from Python 3.2), it is most likely in a .so file with an ABI tag that is incompatible with Blender’s Python 3.3, hence it won’t even look at it (much like a module.txt is not considered for import module). This is a good thing. Extension modules are highly version-specific, slight ABI mismatches (such as between 3.2 and 3.3, or two 3.3 compiled with different options) can cause pretty much any kind of error, from crashes to memory leaks to silent data corruption or even something completely different.
You can verify whether this is the case via import _tkinter; print(_tkinter.file) in the 3.2 shell. Alternatively, _tkinter may live in a different directory entirely. Adding that directory won’t actually fix the real issue outlined above.
answered May 26, 2015 at 10:40
Ravinther MRavinther M
2632 silver badges13 bronze badges
For any new readers coming along that are still having issues, try the following. This is cleaner than using sys.path.append
if your app directory is structured with your .py files that contain functions for import underneath your script that imports those files. Let me illustrate.
Script that imports files: main.py
Function files named like: func1.py
main.py
/functionfolder
__init__.py
func1.py
func2.py
The import code in your main.py
file should look as follows:
from functionfolder import func1
from functionfolder import func2
As Agilix correctly stated, you must have an __init__.py
file in your «functionfolder» (see directory illustration above).
In addition, this solved my issue with Pylance not resolving the import, and showing me a nagging error constantly. After a rabbit-hole of sifting through GitHub issues, and trying too many comparatively complicated proposed solutions, this ever-so-simple solution worked for me.
answered Apr 13, 2021 at 1:34
You may try with declaring sys.path.append(‘/path/to/lib/python’) before including any IMPORT statements.
answered May 26, 2015 at 10:40
I just created a __init__.py
file inside my new folder, so the directory is initialised, and it worked (:
answered Feb 1, 2022 at 13:10
1
Jan 5, 2018 10:00:56 AM |
Python Exception Handling: ImportError and ModuleNotFoundError
A look into the ImportError and ModuleNotFoundError in Python, with code showing how to deal with failed imports in Python 2.7 and 3.6.
Making our way through our detailed Python Exception Handling series we arrive at the ImportError, along with its single child subclass of ModuleNotFoundError. The ImportError
is raised when an import
statement has trouble successfully importing the specified module. Typically, such a problem is due to an invalid or incorrect path, which will raise a ModuleNotFoundError
in Python 3.6 and newer versions.
Within this article we’ll explore the ImportError
and ModuleNotFoundError
in a bit more detail, beginning with where they sit in the overall Python Exception Class Hierarchy. We’ll also take a look at some simple code samples that illustrate the differences in import
statement failures across newer (3.6) and older (2.7) versions of Python, so let’s get started!
The Technical Rundown
All Python exceptions inherit from the BaseException
class, or extend from an inherited class therein. The full exception hierarchy of this error is:
BaseException
Exception
ImportError
ModuleNotFoundError
Full Code Sample
Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.
# outer_import_2.7.py
import sys
import gw_utility.Book
def main():
try:
print(sys.version)
except ImportError as error:
# Output expected ImportErrors.
print(error.__class__.__name__ + ": " + error.message)
except Exception as exception:
# Output unexpected Exceptions.
print(exception, False)
print(exception.__class__.__name__ + ": " + exception.message)
if __name__ == "__main__":
main()
# inner_import_2.7.py
import sys
def main():
try:
print(sys.version)
import gw_utility.Book
except ImportError as error:
# Output expected ImportErrors.
print(error.__class__.__name__ + ": " + error.message)
except Exception as exception:
# Output unexpected Exceptions.
print(exception, False)
print(exception.__class__.__name__ + ": " + exception.message)
if __name__ == "__main__":
main()
# outer_import_3.6.py
import sys
import gw_utility.Book
from gw_utility.logging import Logging
def main():
try:
Logging.log(sys.version)
except ImportError as error:
# Output expected ImportErrors.
Logging.log_exception(error)
# Include the name and path attributes in output.
Logging.log(f'error.name: {error.name}')
Logging.log(f'error.path: {error.path}')
except Exception as exception:
# Output unexpected Exceptions.
Logging.log_exception(exception, False)
if __name__ == "__main__":
main()
# inner_import_3.6.py
import sys
from gw_utility.logging import Logging
def main():
try:
Logging.log(sys.version)
import gw_utility.Book
except ImportError as error:
# Output expected ImportErrors.
Logging.log_exception(error)
# Include the name and path attributes in output.
Logging.log(f'error.name: {error.name}')
Logging.log(f'error.path: {error.path}')
except Exception as exception:
# Output unexpected Exceptions.
Logging.log_exception(exception, False)
if __name__ == "__main__":
main()
# logging.py
import math
import sys
import traceback
class Logging:
separator_character_default = '-'
separator_length_default = 40
@classmethod
def __output(cls, *args, sep: str = ' ', end: str = 'n', file=None):
"""Prints the passed value(s) to the console.
:param args: Values to output.
:param sep: String inserted between values, default a space.
:param end: String appended after the last value, default a newline.
:param file: A file-like object (stream); defaults to the current sys.stdout.
:return: None
"""
print(*args, sep=sep, end=end, file=file)
@classmethod
def line_separator(cls, value: str = None, length: int = separator_length_default,
char: str = separator_character_default):
"""Print a line separator with inserted text centered in the middle.
:param value: Inserted text to be centered.
:param length: Total separator length.
:param char: Separator character.
"""
output = value
# If no value passed, output separator of length.
if value == None or len(value) == 0:
output = f'{char * length}'
elif len(value) < length:
# Update length based on insert length, less a space for margin.
length -= len(value) + 2
# Halve the length and floor left side.
left = math.floor(length / 2)
right = left
# If odd number, add dropped remainder to right side.
if length % 2 != 0:
right += 1
# Surround insert with separators.
output = f'{char * left} {value} {char * right}'
cls.__output(output)
@classmethod
def log(cls, *args, sep: str = ' ', end: str = 'n', file=None):
"""Prints the passed value(s) to the console.
:param args: Values to output.
:param sep: String inserted between values, default a space.
:param end: String appended after the last value, default a newline.
:param file: A file-like object (stream); defaults to the current sys.stdout.
"""
cls.__output(*args, sep=sep, end=end, file=file)
@classmethod
def log_exception(cls, exception: BaseException, expected: bool = True):
"""Prints the passed BaseException to the console, including traceback.
:param exception: The BaseException to output.
:param expected: Determines if BaseException was expected.
"""
output = "[{}] {}: {}".format('EXPECTED' if expected else 'UNEXPECTED', type(exception).__name__, exception)
cls.__output(output)
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_tb(exc_traceback)
When Should You Use It?
The seemingly simple import
statement found in Python is actually rather complex when looking under the hood. At the most basic level an import
statement is used to perform two tasks. First, it attempts to find the module specified by name, then loads and initializes it, if necessary. It also automatically defines a name in the local namespace within the scope of the associated import
statement. This local name can then be used to reference the the accessed module throughout the following scoped code.
While the import
statement is the most common technique used to gain access to code from other modules, Python also provides other methods and functions that makeup the built-in import system. Developers can opt to use specific functions to have more fine-grained control over the import process.
For our code samples we’ll stick to the common import
statement that most of us are accustomed to. As mentioned in the introduction, behavior for failed imports
differs depending on the Python version. To illustrate we start with the outer_import_2.7.py
file:
# outer_import_2.7.py
import sys
import gw_utility.Book
def main():
try:
print(sys.version)
except ImportError as error:
# Output expected ImportErrors.
print(error.__class__.__name__ + ": " + error.message)
except Exception as exception:
# Output unexpected Exceptions.
print(exception, False)
print(exception.__class__.__name__ + ": " + exception.message)
if __name__ == "__main__":
main()
The outer
prefix for the file name indicates that we’re testing an «outer» or globally scoped import
statement of gw_utility.Book
. Executing this code produces the following output:
Traceback (most recent call last):
File "C:UsersGabeAppDataLocalJetBrainsToolboxappsPyCharm-Pch-0172.3968.37helperspydevpydevd.py", line 1599, in <module>
globals = debugger.run(setup['file'], None, None, is_module)
File "C:UsersGabeAppDataLocalJetBrainsToolboxappsPyCharm-Pch-0172.3968.37helperspydevpydevd.py", line 1026, in run
pydev_imports.execfile(file, globals, locals) # execute the script
File "D:/work/Airbrake.io/Exceptions/Python/BaseException/Exception/ImportError/outer_import_2.7.py", line 3, in <module>
import gw_utility.Book
ImportError: No module named Book
The overall issue here is that the gw_utility.Book
module doesn’t exist. In fact, the proper module is lowercase: gw_utility.book
. Since the import
statement is at the top of the file, it exists outside our try-except
block, so the ImportError
we get in the log is not caught — execution was terminated entirely when the error was raised.
Alternatively, let’s see what happens if we move the import
statement inside a try-except
block, as seen in inner_import_2.7.py
:
# inner_import_2.7.py
import sys
def main():
try:
print(sys.version)
import gw_utility.Book
except ImportError as error:
# Output expected ImportErrors.
print(error.__class__.__name__ + ": " + error.message)
except Exception as exception:
# Output unexpected Exceptions.
print(exception, False)
print(exception.__class__.__name__ + ": " + exception.message)
if __name__ == "__main__":
main()
Running this code — also using Python 2.7 — produces the same ImportError
, but we’re able to catch it and perform further processing of the caught ImportError
, if necessary:
2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:25:58) [MSC v.1500 64 bit (AMD64)]
ImportError: No module named Book
The ModuleNotFoundError
was added in Python 3.6 as a subclass of ImportError
and an explicit indication of the same kind of errors we’re seeing above in the 2.7 code. For example, let’s look at the outer import
example in Python 3.6 with outer_import_3.6.py
:
# outer_import_3.6.py
import sys
import gw_utility.Book
from gw_utility.logging import Logging
def main():
try:
Logging.log(sys.version)
except ImportError as error:
# Output expected ImportErrors.
Logging.log_exception(error)
# Include the name and path attributes in output.
Logging.log(f'error.name: {error.name}')
Logging.log(f'error.path: {error.path}')
except Exception as exception:
# Output unexpected Exceptions.
Logging.log_exception(exception, False)
if __name__ == "__main__":
main()
Once again, here we’re performing the import
outside the try-except
block, so running this code halts execution and produces the following output:
Traceback (most recent call last):
File "C:UsersGabeAppDataLocalJetBrainsToolboxappsPyCharm-Pch-0172.3968.37helperspydevpydevd.py", line 1599, in <module>
globals = debugger.run(setup['file'], None, None, is_module)
File "C:UsersGabeAppDataLocalJetBrainsToolboxappsPyCharm-Pch-0172.3968.37helperspydevpydevd.py", line 1026, in run
pydev_imports.execfile(file, globals, locals) # execute the script
File "C:UsersGabeAppDataLocalJetBrainsToolboxappsPyCharm-Pch-0172.3968.37helperspydev_pydev_imps_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"n", file, 'exec'), glob, loc)
File "D:/work/Airbrake.io/Exceptions/Python/BaseException/Exception/ImportError/outer_import_3.6.py", line 3, in <module>
import gw_utility.Book
ModuleNotFoundError: No module named 'gw_utility.Book'
The cause of this error is the exact same as the 2.7 version, but with 3.6+ the more specific ModuleNotFoundError
is now raised. Additionally, we can actually catch such errors if the import
is executed within a try-except
context:
# inner_import_3.6.py
import sys
from gw_utility.logging import Logging
def main():
try:
Logging.log(sys.version)
import gw_utility.Book
except ImportError as error:
# Output expected ImportErrors.
Logging.log_exception(error)
# Include the name and path attributes in output.
Logging.log(f'error.name: {error.name}')
Logging.log(f'error.path: {error.path}')
except Exception as exception:
# Output unexpected Exceptions.
Logging.log_exception(exception, False)
if __name__ == "__main__":
main()
This code allows us to output the Python version and process the error:
3.6.3 (v3.6.3:2c5fed8, Oct 3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
[EXPECTED] ModuleNotFoundError: No module named 'gw_utility.Book'
error.name: gw_utility.Book
error.path: None
We’re also outputting the name
and path
attributes of the ImportError
object, which were added in Python 3.3 to indicate the name of the module that was attempted to be imported, along with the path to the file that triggered the exception, if applicable. In this case our code is rather simple so, unfortunately, neither attribute is particularly useful.
Airbrake’s robust error monitoring software provides real-time error monitoring and automatic exception reporting for all your development projects. Airbrake’s state of the art web dashboard ensures you receive round-the-clock status updates on your application’s health and error rates. No matter what you’re working on, Airbrake easily integrates with all the most popular languages and frameworks. Plus, Airbrake makes it easy to customize exception parameters, while giving you complete control of the active error filter system, so you only gather the errors that matter most.
Check out Airbrake’s error monitoring software today and see for yourself why so many of the world’s best engineering teams use Airbrake to revolutionize their exception handling practices! Try Airbrake free with a 14-day free trial.
Сегодня мы поговорим о том, как импортировать пакеты и модули в Python (а заодно и о разнице между ними). К концу руководства вы получите структуру каталогов (для проекта Medium_Imports_Tutorial), где будет удобно импортировать любые скрипты из одного подкаталога в другой (стрелки синего цвета на картинке ниже).
Примечание. Если вы хотите поиграться с кодом, то вот репозиторий Github.
Итак, наша структура каталогов для изучения импорта в Python будет выглядеть следующим образом:
Прежде чем начать, давайте обсудим, в чем разница между пакетом и модулем, так как мы будем опираться на эти понятия на протяжении всей статьи.
- Модуль — один скрипт Python.
- Пакет — набор модулей.
Вот и вся разница! Просто, не правда ли?
Что ж, теперь давайте начинать!
[python_ad_block]
Импорт в рамках одного каталога
Структура каталогов на изображении выше выглядит немного сложной, но мы не будем создавать ее всю сразу.
Для простоты давайте сначала создадим один каталог scripts
в каталоге нашего проекта и добавим в него два модуля — example1.py
и example2.py
. Наша структура будет выглядеть так:
Идея состоит в том, чтобы любая функция/переменная/класс, определенные в example1.py
, были доступны в example2.py
. Содержимое у нас будет таким:
#example1.py MY_EX1_STRING = 'Welcome to Example1 module!' def yolo(x: int): print("You only LIve", x, "times.")
Чтобы импортировать эти элементы в example2.py
, сделаем следующее:
#example2.py import example1 # imported string print("The imported string is: ", example1.MY_EX1_STRING) # imported function example1.yolo(10)
Как видно, доступ к элементам в импортированном модуле можно получить с помощью записи через точку — например, example1.yolo()
или example1.MY_EX1_STRING
. Если вам покажется, что каждый раз писать example1.XXX
утомительно, можно воспользоваться псевдонимом (созданным при помощи as
) и переписать example2.py
следующим образом:
#example2.py import example1 as e1 # imported string print("The imported string is: ", e1.MY_EX1_STRING) # imported function e1.yolo(10)
Как вы правильно догадались, вывод останется прежним.
Но что именно происходит, когда мы пишем оператор import
?
Интерпретатор Python пытается найти в sys.path
каталог с модулем, который мы пытаемся импортировать. sys.path
— это список каталогов, в которых Python будет искать после просмотра кэшированных модулей и модулей стандартной библиотеки Python.
Давайте посмотрим, что в данный момент содержит наш системный путь sys.path
(предварительно закомментировав предыдущие строки кода в example2.py
).
#example2.py # import example1 # print("The imported string is: ", example1.MY_EX1_STRING) # example1.yolo(10) import sys print(sys.path)
Как видите, самый первый элемент в полученном списке sys.path
указывает на каталог Medium_Imports_Tutorial/scripts
, в котором находится наш импортированный модуль, т. е. example1.py
. Имейте в виду: то, что этот каталог волшебным образом присутствует в sys.path
, не случайно.
Вывод из sys.path
всегда будет содержать текущий каталог с индексом 0! Текущий каталог — это тот, в котором находится запускаемый скрипт.
Поэтому, когда и вызывающий, и вызываемый модули находятся в одном каталоге, импорт довольно прост.
Что делать, если нужно импортировать только определенные элементы из модуля?
В нашем примере в модуле example1.py
определены только переменная типа string
и функция. Важно помнить, что всякий раз, когда выполняется оператор импорта, будет запущен весь модуль. Чтобы доказать это, давайте немного изменим example1.py
:
#example1.py print("Thanks for importing Example1 module.") MY_EX1_STRING = 'Welcome to Example1 module!' def yolo(x: int): print("You only LIve", x, "times.") yolo(10000)
А теперь попробуйте запустить example2.py
. Вы увидите, что вместе с выводом оператора print()
также выведется yolo(10000)
.
Примечание. Существует обходной путь, с помощью которого мы можем контролировать, будет ли оператор выполняться при импорте.
#example1.py print("Thanks for importing Example1 module.") MY_EX1_STRING = 'Welcome to Example1 module!' def yolo(x: int): print("You only LIve", x, "times.") if __name__ == '__main__': yolo(10000)
Код внутри оператора if__name__ == '__main__'
не будет выполняться при импорте, но yolo()
и MY_EX1_STRING
, определенные снаружи, готовы к использованию через импорт. При этом, если бы мы запустили example1.py
как автономный модуль, код внутри оператора if
был бы выполнен.
Итак, мы увидели, что импорт модуля запускает все его содержимое (если не использовать if __name__ == «__main__»
). Теперь должно быть довольно понятно, почему импорт только интересующих элементов имеет смысл. Давайте посмотрим, как это сделать в example2.py
. Для этого импортируем из example1.py
только функцию yolo()
. Это также поможет нам избавиться от записи через точку, и мы сможем просто использовать функцию yolo()
.
#example2.py from example1 import yolo yolo(10)
Точно так же мы могли бы написать from example1 import yolo, MY_EX1_STRING
, чтобы импортировать оба объекта из example1.py
.
Примечание. В коде часто можно увидеть такие импорты, как from example1 import *
. По сути, это означает импорт всего. Но это считается плохой практикой, поскольку негативно влияет на читабельность кода.
Зачем нужен PYTHONPATH?
Возможно, вы замечали, что в структуре каталогов проектов на GitHub часто бывает каталог utils
. Он содержит служебные скрипты для распространенных задач, таких как предварительная обработка и очистка данных. Они хранятся отдельно от основных сценариев и предназначены для многократного использования.
Давайте создадим такой каталог для нашего проекта.
Пакет utils
будет содержать три модуля — length.py
, lower.py
и upper.py
для возврата длины строки и строк в нижнем и верхнем регистре соответственно.
Также создадим модуль example3_outer.py
в корне проекта. Сюда мы будем импортировать модули из пакета util
s.
Содержимое трех модулей будет следующим:
#utils/length.py def get_length(name: str): return len(name) #utils/lower.py def to_lower(name: str): return name.lower() #utils/upper.py def to_upper(name: str): return name.upper()
Теперь, чтобы импортировать модуль length.py
в example3_outer.py
, пишем следующее:
#example3_outer.py import utils.length res = utils.length.get_length("Hello") print("The length of the string is: ",res)
Важно отметить, что если бы мы выполнили импорт length
вместо импорта utils.length
, мы получили бы ModuleNotFoundError: No module named ‘length’
. Это связано с тем, что список sys.path
пока не содержит каталога ../Medium_Imports_Tutorial/utils
, который необходим для поиска модуля length.py
. Давайте посмотрим, как мы можем добавить его в этот список.
Есть два способа сделать это.
Способ 1: использование sys.path.append
#example3_outer.py import os import sys fpath = os.path.join(os.path.dirname(__file__), 'utils') sys.path.append(fpath) print(sys.path) import length txt = "Hello" res_len = length.get_length(txt) print("The length of the string is: ",res_len)
Несколько вещей, которые следует учесть:
1. Порядок импорта важен. Мы сможем выполнить import length
только после добавления пути к каталогу utils
с помощью sys.path.append
.
Короче говоря, не поддавайтесь искушению объединить import os
, import sys
и import length
в верхней части скрипта только для аккуратности!
2. os.path.dirname(__file__)
возвращает абсолютный путь к текущему рабочему каталогу. Мы используем os.path.join
, чтобы добавить каталог utils
к этому пути.
3. Как всегда, доступ к функциям, определенным в импортированном модуле, упрощается с помощью записи через точку, т.е. length.get_length()
.
Способ 2: использование переменной окружения PYTHONPATH
Однако зачастую проще изменить переменную PYTHONPATH
, чем возиться с добавлением каталогов, как мы это только что делали.
PYTHONPATH
— это переменная среды, которую вы можете настроить для добавления дополнительных каталогов, в которых Python будет искать модули и пакеты.
Прежде чем изменить PYTHONPATH
, давайте проверим его содержимое при помощи команды echo $PYTHONPATH
в терминале (чтобы случайно не перезаписать):
Похоже, сейчас PYTHONPATH пуст, но если это не так, лучше изменять его путем дополнения, а не перезаписи. Для этого нужно добавлять новый каталог в PYTHONPATH
, отделяя его двоеточием от существующего содержимого.
С установленной переменной PYTHONPATH
нам больше не нужно добавлять каталог к sys.path
в example3_outer.py
(мы закомментировали этот код в приведенном ниже фрагменте для ясности).
#example3_outer.py #import os #import sys #fpath = os.path.join(os.path.dirname(__file__), 'utils') #sys.path.append(fpath) #print(sys.path) import length txt = "Hello" res_len = length.get_length(txt) print("The length of the string is: ",res_len)
Примечание. Как только вы закроете python, список вернется к предыдущим значениям по умолчанию. Если вы хотите добавить каталог в PYTHONPATH
навсегда, добавьте команду экспорта (export PYTHONPATH=$PYTHONPATH:$(pwd)/utils
) в файл ~/.bashrc
. (подробнее можно почитать обсуждение на StackOverflow).
Наконец, познакомившись с обоими методами, давайте выберем один (в зависимости от ваших предпочтений и нужд) для импорта оставшихся двух модулей — upper.py
и lower.py
в example3_outer.py
.
P.S. Мы воспользуемся методом 1 просто для удовольствия 😉
#example3_outer.py import os import sys fpath = os.path.join(os.path.dirname(__file__), 'utils') sys.path.append(fpath) import length import upper import lower txt = "Hello" res_len = length.get_length(txt) print("The length of the string is: ",res_len) res_up = upper.to_upper(txt) print("Uppercase txt: ", res_up) res_low = lower.to_lower(txt) print("Uppercase txt: ", res_low)
Супер! Это выглядит потрясающе. Однако было бы здорово, если бы мы могли просто выполнить команду import utils
вместо того, чтобы импортировать все модули по отдельности. В конце концов, наш случай использования предполагает, что нам нужны все три функции. Итак, как это сделать?
Когда нам нужен __init__.py?
Во-первых, давайте попробуем импортировать каталог utils
в файле example3_outer.py
(предварительно закомментировав весь существующий код):
#example3_outer.py import utils
Запуск этого скрипта не вызовет никакой ошибки, и это правильно — интерпретатор заглянет внутрь sys.path
и найдет текущий каталог ../Medium_Imports_Tutorial
с индексом 0. Это все, что ему нужно, чтобы найти каталог utils
.
Теперь попробуем получить доступ к модулю length.py
из utils
:
#example3_outer.py import utils txt = "Hello" res = utils.length.get_length(txt)
Попытавшись запустить этот скрипт, вы увидите AttributeError: module ‘utils’ has no attribute ‘length’
. Это означает, что мы не сможем получить доступ к каким-либо скриптам Python внутри utils
просто потому, что интерпретатор еще не знает, что это пакет!
Мы можем превратить этот каталог в пакет, добавив файл __init__.py
в папку utils
следующим образом:
В __init__.py
мы импортируем все модули, которые, по нашему мнению, необходимы для нашего проекта.
# utils/__init__.py (incorrect way of importing) from length import get_length from lower import to_lower from upper import to_upper
И сделаем вызов в example3_outer.py
.
import utils txt = "Hello" res_low = utils.to_lower(txt) print(res_low)
Подождите секунду! Почему мы видим ошибку при запуске example3_outer.py
?
Ошибки импорта
То, как мы импортировали модули в __init__.py
, может показаться логичным. Ведь __init__.py
и length.py
(или lower.py
, upper.py
) находятся на одном уровне, так что нет ни одной причины, чтобы from lower import to_lower
не сработал. На самом деле, если вы запустите этот файл инициализации сам по себе, он будет выполняться безупречно (он не даст никаких результатов, но, тем не менее, будет успешно выполнен).
Но при этом мы не можем использовать описанный выше способ импорта, потому что, хотя length.py
и lower.py
находятся на том же уровне, что и __init__.py
, это не тот уровень, с которого будет вызываться init
.
В действительности, мы делаем вызов из example3_outer.py
, поэтому для поиска любого импорта в sys.path
будет только текущий каталог example3_outer.py
, т.е. ../Medium_Imports_Tutorial
.
Когда интерпретатор встречает команду import utils
в файле example3_outer.py
, даже если он переходит к __init__.py
внутри каталога utils
, sys.path
не обновляется автоматически, и, следовательно, интерпретатор не может узнать, где найти модуль с именем length
.
Мы должны как-то указать на расположение каталога utils
. Для этого мы можем использовать относительный или абсолютный импорт в __init__.py
(или установить переменную PYTHONPATH
, как это было описано выше).
Относительный импорт
Мы не рекомендуем использовать относительный импорт, но для общего развития давайте посмотрим, как он работает. Тут нужно указать путь относительно пути вызывающего скрипта. Выглядеть это будет так:
# utils/__init__.py from .lower import to_lower from .upper import to_upper from .length import get_length
При указании относительного импорта мы используем запись через точку (.
или ..
). Одиночная точка перед lower
указывает на тот же каталог, из которого вызывается импорт. Это можно представить как импорт функции to_lower()
из ./lower.py
. Пара точек перед названием модуля означает переход на два уровня вверх от текущего.
Абсолютный импорт
Абсолютный импорт более предпочтителен. Вы указываете абсолютный путь к импортируемому модулю из корня проекта (или любого другого каталога, к которому имеет доступ sys.path
):
# utils/__init__.py from utils.lower import to_lower from utils.upper import to_upper from utils.length import get_length
Теперь у программы есть гораздо больше информации по сравнению с относительным импортом. Более того, она менее подвержена взлому. sys.path
имеет доступ к корню проекта, т. е. ../Medium_Imports_Tutorial
, как описано выше, и оттуда он может легко найти каталог utils
. (Почему? Потому что это непосредственный дочерний каталог корня проекта).
Что происходит, когда мы импортируем пакет с определенным __init__.py?
Это работает как шаг инициализации. __init__.py
— первый файл, который будет выполняться при импорте пакета. Учитывая, что здесь мы делаем весь необходимый импорт, код в вызывающем скрипте становится намного чище. К примеру, example3_outer.py
будет выглядеть так:
#example3_outer.py import utils txt = "Hello" res_len = utils.get_length(txt) print(res_len) res_up = utils.to_upper(txt) print(res_up) res_low = utils.to_lower(txt) print(res_low)
Потрясающе! Мы преобразовали наш каталог utils
в пакет. Прелесть этого пакета в том, что его можно импортировать куда угодно и использовать практически сразу. Давайте посмотрим, как мы можем использовать этот пакет внутри каталога scripts
. Для этого создадим новый файл с именем example3.py
в scripts
.
# scripts/example3.py import os import sys PROJECT_ROOT = os.path.abspath(os.path.join( os.path.dirname(__file__), os.pardir) ) sys.path.append(PROJECT_ROOT) import utils print(utils.get_length("Hello")) ************** OUTPUT ********* 5
Несколько вещей, которые следует учитывать:
- Перед импортом пакета
utils
мы должны убедиться, что родительский каталогutils
, т.е. корень проекта, доступен для интерпретатора Python. Было бы неосмотрительно предполагать, что это произойдет по умолчанию, в основном потому, что мы сейчас находимся на один уровень ниже корневого каталога проекта (мы запускаем скрипт изscripts/example3.py
), и вsys.path
под индексом 0 будет../Medium/Imports_Tutorial/scripts
. os.path.dirname(__file__)
даст имя каталога для текущего скрипта, аos.pardir
даст путь к родительскому каталогу, используя точечную нотацию, т.е...
. В общем,os.path.abspath
будет предоставлять абсолютный путь к корню проекта.
Бонус: мы даже можем добавлять в наш __init__.py
модули из других каталогов. Например, давайте добавим yolo()
, определенный в scripts/example1.py
.
# utils/__init__.py from utils.lower import to_lower from utils.upper import to_upper from utils.length import get_length from scripts.example1 import yolo
Вызов этой функции в example3.py
будет выглядеть следующим образом:
# scripts/example3.py import os import sys PROJECT_ROOT = os.path.abspath(os.path.join( os.path.dirname(__file__), os.pardir) ) sys.path.append(PROJECT_ROOT) import utils print(utils.get_length("Hello")) utils.yolo(2) ************** OUTPUT ********* 5 You only LIve 2 times.
Заключение
Итак, сегодня мы обсудили, как импортировать в Python. Честно говоря, поначалу ошибки импорта действительно пугают, потому что это та область, о которой не так много говорят. Однако есть один полезный прием: какой бы пакет/модуль вы ни пытались импортировать с помощью import XYZ
, убедитесь, что интерпретатор Python имеет к нему доступ. Если нет, обновите sys.path
или, что еще лучше, добавьте соответствующий каталог в переменную PYTHONPATH
.
Надеемся, данная статья была полезна для вас! Успехов в написании кода!
Перевод статьи «Understanding Python imports, __init__.py and pythonpath — once and for all».
It is __init__.py
not init.py
. Make sure each of the directory in hierarchy contains it in order to be able to import.
EDIT: I managed to reproduce it.
Here’s the directory structure:
cesar@cesar-laptop:/tmp/asdasd$ tree . `-- myapp |-- __init__.py |-- models | |-- __init__.py | `-- models.py |-- scripts | |-- data.py | `-- __init__.py `-- tests |-- __init__.py `-- tests.py
I put the following code at the very beginning of the data.py
to narrow down the problem:
import sys
import pprint
pprint.pprint(sys.path)
from myapp.models.models import *
Running the data.py
the way OP indicated yeilds ImportError:
cesar@cesar-laptop:/tmp/asdasd$ python myapp/scripts/data.py ['/tmp/asdasd/myapp/scripts', '/usr/lib/python2.6', '/usr/lib/python2.6/plat-linux2', '/usr/lib/python2.6/lib-tk', -- Skipped -- '/usr/local/lib/python2.6/dist-packages'] Traceback (most recent call last): File "myapp/scripts/data.py", line 6, in from myapp.models.models import * ImportError: No module named myapp.models.models
But this way works like a charm:
cesar@cesar-laptop:/tmp/asdasd$ python -m myapp.scripts.data ['', '/usr/lib/python2.6', '/usr/lib/python2.6/plat-linux2', '/usr/lib/python2.6/lib-tk', -- Skipped -- '/usr/local/lib/python2.6/dist-packages']
Note the difference in the first entry of sys.path
.
When you try to import a module in a Python file, Python tries to resolve this module in several ways. Sometimes, Python throws the ModuleNotFoundError afterward. What does this error mean in Python?
As the name implies, this error occurs when you’re trying to access or use a module that cannot be found. In the case of the title, the «module named Python» cannot be found.
Python here can be any module. Here’s an error when I try to import a numpys
module that cannot be found:
import numpys as np
Here’s what the error looks like:
Here are a few reasons why a module may not be found:
- you do not have the module you tried importing installed on your computer
- you spelled a module incorrectly (which still links back to the previous point, that the misspelled module is not installed)…for example, spelling
numpy
asnumpys
during import - you use an incorrect casing for a module (which still links back to the first point)…for example, spelling
numpy
asNumPy
during import will throw the module not found error as both modules are «not the same» - you are importing a module using the wrong path
How to fix the ModuleNotFoundError in Python
As I mentioned in the previous section, there are a couple of reasons a module may not be found. Here are some solutions.
1. Make sure imported modules are installed
Take for example, numpy
. You use this module in your code in a file called «test.py» like this:
import numpy as np
arr = np.array([1, 2, 3])
print(arr)
If you try to run this code with python test.py
and you get this error:
ModuleNotFoundError: No module named "numpy"
Then it’s most likely possible that the numpy
module is not installed on your device. You can install the module like this:
python -m pip install numpy
When installed, the previous code will work correctly and you get the result printed in your terminal:
[1, 2, 3]
2. Make sure modules are spelled correctly
In some cases, you may have installed the module you need, but trying to use it still throws the ModuleNotFound error. In such cases, it could be that you spelled it incorrectly. Take, for example, this code:
import nompy as np
arr = np.array([1, 2, 3])
print(arr)
Here, you have installed numpy
but running the above code throws this error:
ModuleNotFoundError: No module named "nompy"
This error comes as a result of the misspelled numpy
module as nompy
(with the letter o instead of u). You can fix this error by spelling the module correctly.
3. Make sure modules are in the right casing
Similar to the misspelling issue for module not found errors, it could also be that you are spelling the module correctly, but in the wrong casing. Here’s an example:
import Numpy as np
arr = np.array([1, 2, 3])
print(arr)
For this code, you have numpy
installed but running the above code will throw this error:
ModuleNotFoundError: No module named 'Numpy'
Due to casing differences, numpy
and Numpy
are different modules. You can fix this error by spelling the module in the right casing.
4. Make sure you use the right paths
In Python, you can import modules from other files using absolute or relative paths. For this example, I’ll focus on absolute paths.
When you try to access a module from the wrong path, you will also get the module not found here. Here’s an example:
Let’s say you have a project folder called test. In it, you have two folders demoA and demoB.
demoA has an __init__.py
file (to show it’s a Python package) and a test1.py
module.
demoA also has an __init__.py
file and a test2.py
module.
Here’s the structure:
└── test
├── demoA
├── __init__.py
│ ├── test1.py
└── demoB
├── __init__.py
├── test2.py
Here are the contents of test1.py
:
def hello():
print("hello")
And let’s say you want to use this declared hello
function in test2.py
. The following code will throw a module not found error:
import demoA.test as test1
test1.hello()
This code will throw the following error:
ModuleNotFoundError: No module named 'demoA.test'
The reason for this is that we have used the wrong path to access the test1
module. The right path should be demoA.test1
. When you correct that, the code works:
import demoA.test1 as test1
test1.hello()
# hello
Wrapping up
For resolving an imported module, Python checks places like the inbuilt library, installed modules, and modules in the current project. If it’s unable to resolve that module, it throws the ModuleNotFoundError.
Sometimes you do not have that module installed, so you have to install it. Sometimes it’s a misspelled module, or the naming with the wrong casing, or a wrong path. In this article, I’ve shown four possible ways of fixing this error if you experience it.
I hope you learned from it
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started