乐趣区

关于python:Python解决python3中关于import的疑难杂症

python 中 import 与包治理

概念:模块与包

  • 模块 module:个别是以.py 为后缀的文件,也包含 .pyo.pyc.pyd.so.dll后缀的文件,模块内定义了函数、类以及变量
  • package:包是含有若干个模块的文件夹,在工程项目用包治理模块能够防止模块名抵触

\_\_init\_\_.py

在 Python 工程项目中,如果一个文件夹下有 __init__.py 文件就会认为该文件夹是一个包package,这样能够不便组织工程文件,防止模块名抵触。

  • __init__.py为空时仅用于标识以后这个文件夹是一个包package
  • __all__变量指明当该包被 import * 时,哪些模块 module 会被导入
  • 能够利用 __init__.py 对外提供类型、变量及接口,对用户暗藏各个子模块的实现细节
  • 当咱们 import 一个包时,会主动加载该包对应的__init__.py,因而如果在其中做太简单的运算会造成不必要的开销

sys.modules

sys.modules保护了一个已加载 module 的字典,第二次加载该 module 时能够间接从字典中查找,放慢执行速度。

import sys
print(sys.modules)

// 输入:
{'random': <module 'random' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/random.pyc'>, 'subprocess': <module 'subprocess' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.pyc'>, 'sysconfig': <module 'sysconfig' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sysconfig.pyc'>, 'gc': <module 'gc' (built-in)>}

namespace

  • local namespace:函数的命名空间,记录函数的变量
  • global namespace:模块的命名空间,记录模块的变量(函数、类、导入的模块、模块级别的变量和常量)
  • build-in namespace:蕴含 build-in functionexceptions,可被任意模块拜访

import形式影响咱们应用包的形式正是 namespace 作用的体现:

from foo import bar  # 将模块 foo 中的函数 / 变量 bar 导入到以后模块的命名空间, 能够间接拜访 bar
import foo  # 导入模块 foo 同时保留它本人的命名空间, 须要通过 foo.bar 的形式来拜访 bar

模块外部属性

  • __doc__:文件正文
  • __file__:以后文件门路
  • __package__:导入文件的门路
  • __cached__:导入文件的缓存门路
  • __name__:导入文件的门路加文件名称
  • __builtins__:蕴含内置函数

python 内置模块

  • os:提供文件和目录等的零碎级操作
  • sys:提供对解释器相干的操作
  • hashlib:提供加密相干的操作,代替了 md5sha模块
  • shutil:提供文件、文件夹和压缩包等解决模块
  • configparser:提供对特定配置的操作
  • logging:提供日志性能
  • time datetime:提供工夫相干操作
  • random:提供随机数操作
  • jsonpickle:提供序列化操作
  • shelve:提供简略 kv 将内存数据通过文件长久化的性能

import 形式

1. 简介

在 Python 中 import 的罕用操作为:

import somemodule  # 导入整个模块
from somemodule import somefunction  # 从模块中导入单个函数
from somemodule import firstfunc, secondfunc, thirdfunc  # 从模块中导入多个函数
from somemodule import *  # 从模块中导入所有函数

2. 执行 import 的步骤

  1. 创立一个新的 module 对象
  2. 将该 module 对象插入sys.modules
  3. 装载 module 的代码
  4. 执行新的 module 中对应的代码

3. import 的搜寻包程序

留神第三步装载 module 代码时 python 解释器须要先搜寻到对应的 .py 文件,搜寻程序为:

  • sys.path:蕴含了 以后脚本的门路 和其余查找包(零碎库、第三方库等)的门路,你也能够在代码中通过 sys.path.append() 动静增加搜寻门路
  • PYTHONPATH
  • 查看默认门路,比方 Linux 下为/usr/local/lib/python/

4. 相对导入与绝对导入

相对导入和绝对导入的概念只针对于包内模块导入包内模块,留神如果 foo.pybar.py在同一个非包(没有 __init__.py 文件)的目录下,那么它们之间能够相互import,不存在相对导入和绝对导入的问题。

在 Python3 中倡议应用相对导入。

举个例子:

$ tree
mypackage
├── __init__.py
├── module_bar.py
└── module_foo.py

在包 mypackage 内,如果 module_bar 要导入module_foo,那么有三种形式:

# 办法一: 
import module_foo

# 办法二:
# 如果是下层文件夹写.., 上下层文件夹写..., 以此类推
from . import module_foo

# 办法三:
from mypackage import module_foo
import mypackage.module_foo
  • 对于 python2 而言,办法一和办法二都是绝对导入,成果一样,然而前者被称为隐式绝对导入,后者被称为显式绝对导入,办法三是相对导入(会在 sys.path 中的门路搜寻)
  • 对于 python3 而言,办法二是绝对导入,办法一和办法三都是相对导入,官网更举荐办法三

5. 包导入

包的导入和模块导入基本一致,只不过导入包时会执行 __init__.py。如果只是导入一个包import package 而不指名任何模块,且包中的 __init__.py 没有其余的初始化操作,那么包上面的模块是无奈被主动导入的。

6. 间接运行与模块运行

以上面的我的项目为例:

$ tree
.
└── mypackage
    ├── __init__.py
    └── module_foo.py
    
# module_foo.py 内容如下:
import sys
print(sys.path)

咱们有两种形式运行module_foo.py

-m参数示意 run library module as a script,即以脚本的形式执行模块。

# 间接运行: 第一个目录是模块 module_foo 所在的
$ python3 -B mypackage/module_foo.py    
['/Users/didi/Desktop/MyProject/mypackage', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Users/didi/Library/Python/3.7/lib/python/site-packages', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']

# 模块运行: 第一个目录是以后门路
$ python3 -B -m mypackage.module_foo
['/Users/didi/Desktop/MyProject', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Users/didi/Library/Python/3.7/lib/python/site-packages', '/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages']

实例:包之间模块援用的疑难杂症

1. 我的项目 demo

假如以后你的工程文件目录如下(仅针对 python3):

留神这里我的文件夹下并没有__init__.py,严格来讲它们并不是包,只是将分割严密的模块放在同一个文件夹下不便工程项目管理。

.
└── src
    ├── bar_package
    │   └── module_1.py
    ├── foo_package
    │   ├── module_2.py
    │   └── module_3.py
    └── main.py
# 留神
# 1) 所有模块都以 src 为根目录, 包含 main.py(当然这只是我集体习惯)
# 2) 引入形式都是相对引入(python3 举荐应用)

"""module_1.py: 空文件"""

"""module_2.py: import 同个包内的 module_3"""
from foo_package import module_3  # 援用同个包的模块

"""module_3.py: import 另一个包内的 module_1"""
from bar_package import module_1  # 跨包援用模块

if __name__ == "__main__":
    print("module_3 exec successfully!")

"""main.py: import 所有模块"""
from foo_package import module_3, module_2
from bar_package import module_1

下面就是通常我的项目文件包治理的形式,执行整个程序:

python3 -B src/main.py

2. 问题:独自执行某个模块

如果要独自执行module_3.py,这时候会报错:

$ python3 -B src/foo_package/module_3.py 
Traceback (most recent call last):
  File "src/foo_package/module_3.py", line 1, in <module>
    from bar_package import module_1  # 跨包援用模块
ModuleNotFoundError: No module named 'bar_package'

回顾一下之前提到的 import 查找包的门路,咱们有两种办法能够解决这个问题。

3. 办法一:通过模块运行的形式解决(举荐)

实质上咱们是心愿将 module_3.py 这个模块作为脚本运行,所以咱们能够带上 -m 参数:

$ cd src  # 代码中是以 src 为根目录的, 所以须要进入到 src 下
$ python3 -B -m foo_package.module_3   
module_3 exec successfully!

4. 办法二:在 sys.path 中增加查找门路

后面的报错是找不到 bar_package 的模块名,因为间接运行的话 sys.path 第一个门路就是 module_3.py 的门路,天然找不到它下层的 bar_package,咱们能够通过sys.path.append(..) 将它的下层目录也退出 sys.path,批改后的module_3.py 文件内容为:

"""module_3.py
实质上就是将 module_3.py 的下级目录退出到 sys.path 中, 这样就能够找到 bar_package 了
"""
import os
import sys
parent_path = os.path.dirname(sys.path[0])
if parent_path not in sys.path:
    sys.path.append(parent_path)
from bar_package import module_1  # 跨包援用模块


if __name__ == "__main__":
    print("module_3 exec successfully!")

须要留神的是,如果你应用的是如下这种写法还是可能呈现问题:

"""module_3.py"""
import sys
sys.path.append("../")
from bar_package import module_1  # 跨包援用模块


if __name__ == "__main__":
    print("module_3 exec successfully!")

# 进入到 module_3.py 所在的目录, 输入失常:
$ src/foo_package 
$ python3 -B module_3.py 
module_3 exec successfully!

# 间接在根目录下执行会报错:
$ python3 -B src/foo_package/module_3.py
Traceback (most recent call last):
  File "src/foo_package/module_3.py", line 3, in <module>
    from bar_package import module_1  # 跨包援用模块
ModuleNotFoundError: No module named 'bar_package'

另一种简洁的写法是:

import sys
import oss
sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))

5. 尽量不要应用绝对援用

Python3 不倡议应用绝对援用,最好遵循肯定的开发标准,不要在代码中混用相对援用与绝对援用。

Reference

[1] https://blog.csdn.net/weixin_…

[2] https://zhuanlan.zhihu.com/p/…

[3] https://www.cnblogs.com/schip…

[4] https://www.jianshu.com/p/88b…

退出移动版