全面解析python当前路径和导包路径问题

引言

python中的模块、库、包有什么区别?

module:一个 .py 文件就是个 module

lib:抽象概念,和另外两个不是一类,只要你喜欢,什么都是 lib,就算只有个 hello world

package:就是个带 __init__.py 的文件夹,并不在乎里面有什么,不过一般来讲会包含一些 packages/modules

如何你有以下的疑问的话,那这个文章很适合你!

  • 子疑问:为什么在 pycharm 中运行单元测试是正常的?但还是在终端运行就出现了导包错误?
  • 子疑问:Pycharm 中运行正常,但是终端运行出现错误:ModuleNotFoundError: No module named
  • 子疑问:为什么 python 在 vscode 运行的路径和 pycharm 不一致
  • 子疑问:VSCode找不到相对路径文件

何为当前路径?

所谓的当前路径到底是输入命令的路径还是 py 脚本文件所在的路径?
插一句: linux 等系统中查看当前路径的命令是 pwd, python 中查看当前路径是 os.getcwd()

❓ 疑问一 python 程序的当前路径是执行 python 脚本等路径还是 python 脚本说在的路径?

即执行下面的命令的时候,所谓的当前路径是 testing 文件夹所在的路径还是 main.py 文件所在路径。

  1. python testing/main.py

✅ 答案:当前路径是输入运行命令的路径,而不是 py 文件所在的路径。

对于下面的命令,是无所谓区分这两个路径的,但是上面的路径就不一样了

  1. python main.py

导包路径和当前路径的关系?

知道这个知识对写程序避坑有什么帮助?,接下往下看吧!

❓ 疑问二 :如何查看 Python 的导包路径?

  • 子疑问:python 的导包顺序是什么?
  • 子疑问:python 导包的时候会去哪些文件夹下查找 package?
  • 子疑问:python 导包的时候会去哪些路径下查找 package?

我在 /Users/bot/Desktop/code/ideaboom 新建一个名为 testing 的文件夹,并在 testing 文件夹下新建一个 main.py 的文件。

main.py 文件的内容如下所示:

  1. import os
  2. import sys
  3. print(‘当前工作路径: ‘, os.getcwd())
  4. print(‘导包路径为: ‘)
  5. for p in sys.path:
  6.      print(p)

并在 /Users/bot/Desktop/code/ideaboom 处运行命令:python testing/main.py

程序输出如下:

当前工作路径:  /Users/bot/Desktop/code/ideaboom

导包路径为:

/Users/bot/Desktop/code/ideaboom/testing
/Library/Frameworks/Python.framework/Versions/3.9/lib/python39.zip
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload
/Users/bot/.local/share/virtualenvs/ideaboom-8ZWsq-JB/lib/python3.9/site-packages

  • 现象一 :我们可以看到 导包路径 有好多,sys.path 返回的是一个列表对象,搜包的时候,会先从列表的第一个元素开始早起,比如 import django 就会先去 /Users/bot/Desktop/code/ideaboom/testing 查看有没有叫做 django 的包或者 django.py 文件。再去 /Library/Frameworks/Python.framework/Versions/3.9/lib/python39.zip 等依次查找。

python 中的包就是包含 __init__.py 文件的文件夹

  • 现象二 :可以看到,当前路径是执行 python testing/main.py 命令的路径,但是导包路径就不是用执行命令的路径,而是 main.py 文件所在的路径。
  • 现象三 :sys.path 排第一的是 main.py 文件所在的路径。系统路径都往后稍稍。

首要导包路径不是当前路径有什么问题?

这是一个很典型的问题,我们往往会在项目的根目录下面建一个 testing 文件夹,把需要单元测试相关的文件放在。

但是当我们输入命令 python testing/main.py 的时候,就会出现 ModuleNotFoundError: No module named xxx ,出现的原因就是上面提到的:首要导包路径不是当前路径

本来 xxx 和 testing 文件夹是在项目的根目录下面,sys.path 中的首要导包路径就是项目的根目录,但是当我们 python testing/main.py 的时候,首要导包路变成了 testing 而不是项目根目录了!这还是 main.py 中的 import xxx 当然找不到了。

知道了问题如何解决呢?

解决什么?当然是运行 tetsing 文件夹下面的 main.py 文件报错 ModuleNotFoundError: No module named xxx 的问题。

❓ 疑问三:如何改变 Python 程序的首要导包路径?

python 首要导包路径就是 sys.path 列表中的第一个元素,即被运行的 py 文件所在的文件夹路径

方案一:动态修改 sys.path

最常见的方式就是:

把当前路径添加到 sys.path 中,且为了避免命名冲突,最好添加到列表的头部,而不是用 append 添加到尾部。至于本来的(不期望的)首要导包路径 /Users/bot/Desktop/code/ideaboom/testing 可以删除,也可以保留。

  1. import os
  2. import sys
  3. print(‘当前工作路径: ‘, os.getcwd())
  4. print(‘导包路径为: ‘)
  5. sys.path.insert(0,os.getcwd()) # 把当前路径添加到 sys.path 中
  6. for p in sys.path:
  7.      print(p)

程序输出:

当前工作路径:  /Users/bot/Desktop/code/ideaboom
导包路径为:
/Users/bot/Desktop/code/ideaboom
/Users/bot/Desktop/code/ideaboom/testing
/Library/Frameworks/Python.framework/Versions/3.9/lib/python39.zip
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload
/Users/bot/.local/share/virtualenvs/ideaboom-8ZWsq-JB/lib/python3.9/site-packages

但是这个方案不太好,有一些缺点,比如下面的代码,看起来就很不优雅,因为按照 python 的代码规范,导包相关的代码应该写在最前面,这种 导包+代码+导包 的方式破坏了 pythonic

  1. import os
  2. import sys
  3. import time
  4. import schedule
  5. from pathlib import Path
  6. import os
  7. import sys
  8. import time
  9. import schedule
  10. from pathlib import Path
  11. BASE_DIR = Path(__file__).resolve().parent.parent.parent
  12. sys.path.insert(0, str(BASE_DIR))
  13. import django
  14. django.setup()

方案二:使用环境变量 PYTHONPATH

更好的方案

因为首要导包路径的设定是 python 解释器的默认执行,那我们能不能在 python 解释器启动之前就指定好我们需要的首要导包路径呢?

通过查看 python –help 命令,我们可以到以下内容:

  1. usage: /opt/homebrew/Cellar/python@3.8/3.8.12/bin/python3 [option]  [-c cmd | m mod | file | -] [arg] 
  2. Options and arguments (and corresponding environment variables):
  3. : issue warnings about str(bytes_instance), str(bytearray_instance)
  4.              and comparing bytes/bytearray with str. (-bb: issue errors)
  5. : don‘t write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x
  6. -c cmd : program passed in as string (terminates option list)
  7. -d : debug output from parser; also PYTHONDEBUG=x
  8. -E : ignore PYTHON* environment variables (such as PYTHONPATH)
  9. -h : print this help message and exit (also –help)
  10. -i : inspect interactively after running script; forces a prompt even
  11.              if stdin does not appear to be a terminal; also PYTHONINSPECT=x
  12. -I : isolate Python from the user’s environment (implies and s)
  13. m mod : run library module as a script (terminates option list)
  14. : remove assert and __debug__dependent statements; add .opt1 before
  15.              .pyc extension; also PYTHONOPTIMIZE=x
  16. OO : do O changes and also discard docstrings; add .opt2 before
  17.              .pyc extension
  18. : don‘t print version and copyright messages on interactive startup
  19. -s : don’t add user site directory to sys.path; also PYTHONNOUSERSITE
  20. : don‘t imply ‘import site‘ on initialization
  21. -u : force the stdout and stderr streams to be unbuffered;
  22.              this option has no effect on stdin; also PYTHONUNBUFFERED=x
  23. -v : verbose (trace import statements); also PYTHONVERBOSE=x
  24.              can be supplied multiple times to increase verbosity
  25. -V : print the Python version number and exit (also –version)
  26.              when given twice, print more information about the build
  27. -W arg : warning control; arg is action:message:category:module:lineno
  28.              also PYTHONWARNINGS=arg
  29. -x : skip first line of source, allowing use of non-Unix forms of #!cmd
  30. -X opt : set implementation-specific option. The following options are available:
  31.              -X faulthandler: enable faulthandler
  32.              -X showrefcount: output the total reference count and number of used
  33.                  memory blocks when the program finishes or after each statement in the
  34.                  interactive interpreter. This only works on debug builds
  35.              -X tracemalloc: start tracing Python memory allocations using the
  36.                  tracemalloc module. By default, only the most recent frame is stored in a
  37.                  traceback of a trace. Use -X tracemalloc=NFRAME to start tracing with a
  38.                  traceback limit of NFRAME frames
  39.              -X showalloccount: output the total count of allocated objects for each
  40.                  type when the program finishes. This only works when Python was built with
  41.                  COUNT_ALLOCS defined
  42.              -X importtime: show how long each import takes. It shows module name,
  43.                  cumulative time (including nested imports) and self time (excluding
  44.                  nested imports). Note that its output may be broken in multi-threaded
  45.                  application. Typical usage is python3 -X importtime -c ‘import asyncio
  46.              -X dev: enable CPython’“development mode”, introducing additional runtime
  47.                  checks which are too expensive to be enabled by default. Effect of the
  48.                  developer mode:
  49.                  * Add default warning filter, as default
  50.                  * Install debug hooks on memory allocators: see the PyMem_SetupDebugHooks() C function
  51.                  * Enable the faulthandler module to dump the Python traceback on a crash
  52.                  * Enable asyncio debug mode
  53.                  * Set the dev_mode attribute of sys.flags to True
  54.                  * io.IOBase destructor logs close() exceptions
  55.              X utf8: enable UTF8 mode for operating system interfaces, overriding the default
  56.                  localeaware mode. X utf8=0 explicitly disables UTF8 mode (even when it would
  57.                  otherwise activate automatically)
  58.              X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the
  59.                  given directory instead of to the code tree
  60. checkhashbasedpycs always|default|never:
  61.      control how Python invalidates hashbased .pyc files
  62. file : program read from script file
  63.  : program read from stdin (default; interactive mode if a tty)
  64. arg …: arguments passed to program in sys.argv[1:]
  65. Other environment variables:
  66. PYTHONSTARTUP: file executed on interactive startup (no default)
  67. PYTHONPATH : ‘:’separated list of directories prefixed to the
  68.                  default module search path. The result is sys.path.
  69. PYTHONHOME : alternate <prefix> directory (or <prefix>:<exec_prefix>).
  70.                  The default module search path uses <prefix>/lib/pythonX.X.
  71. PYTHONCAseoK : ignore case in ‘import’ statements (Windows).
  72. PYTHONUTF8: if set to 1, enable the UTF8 mode.
  73. PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.
  74. PYTHONFAULTHANDLER: dump the Python traceback on fatal errors.
  75. PYTHONHASHSEED: if this variable is set to ‘random’, a random value is used
  76.      to seed the hashes of str and bytes objects. It can also be set to an
  77.      integer in the range [0,4294967295] to get hash values with a
  78.      predictable seed.
  79. PYTHONMALLOC: set the Python memory allocators and/or install debug hooks
  80.      on Python memory allocators. Use PYTHONMALLOC=debug to install debug
  81.      hooks.
  82. PYTHONCOERCECLOCALE: if this variable is set to 0, it disables the locale
  83.      coercion behavior. Use PYTHONCOERCECLOCALE=warn to request display of
  84.      locale coercion and locale compatibility warnings on stderr.
  85. PYTHONBREAKPOINT: if this variable is set to 0, it disables the default
  86.      debugger. It can be set to the callable of your debugger of choice.
  87. PYTHONDEVMODE: enable the development mode.
  88. PYTHONPYCACHEPREFIX: root directory for bytecode cache (pyc) files.

看了一圈下来,感觉这个 PYTHONHOME 是救星,但实际上不是哦,恰恰是那个不起眼的 PYTHONPATH

testing/main.py

  1. import os
  2. import sys
  3. print(‘当前工作路径: ‘, os.getcwd())
  4. print(‘导包路径为: ‘)
  5. for p in sys.path:
  6.      print(p)

我们可以使用下面的方式来启动程序:

  1. PYTHONPATH=$(pwd) python testing/main.py

此时程序的输出变成了:

(ideaboom) ╭─bot@mbp13m1.local ~/Desktop/code/ideaboom  ‹master*›
╰─➤  PYTHONPATH=$(pwd)  python testing/main.py
当前工作路径:  /Users/bot/Desktop/code/ideaboom
导包路径为:
/Users/bot/Desktop/code/ideaboom/testing
/Users/bot/Desktop/code/ideaboom
/Library/Frameworks/Python.framework/Versions/3.9/lib/python39.zip
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/lib-dynload
/Users/bot/.local/share/virtualenvs/ideaboom-8ZWsq-JB/lib/python3.9/site-packages

让我们再来看看 PYTHONPATH=$(pwd) python testing/main.py, 它等效于 PYTHONPATH=/Users/bot/Desktop/code/ideaboom python testing/main.py.

在 python testing/main.py 添加 PYTHONPATH=$(pwd) 的环境变量的作用域仅限于本次命令的运行,不会扩散到当前的 shell 环境中。

以上就是全面解析python当前路径和导包路径问题的详细内容,更多关于python当前路径导包路径的资料请关注我们其它相关文章!

标签

发表评论