软微9133 软微9133
首页
  • 后端

    • Docker
    • MySQL
    • Linux
    • Git
  • Vue

    • vue-todos 学习
  • 算法

    • 贪心算法
    • 双指针与滑动窗口
    • 二分查找
    • 排序算法
    • 搜索算法
    • 动态规划
    • 系列问题
  • 数据结构

    • 链表
    • 树
    • 栈和队列
    • 并查集
  • 计算机网络

    • 《图解HTTP》笔记
  • Python

    • Python
  • docker
  • git
  • Linux
  • SQL
  • Vim
  • Tmux
环境配置
关于
  • PHP
  • Hexo
  • 转载文章
  • 分类
  • 标签
  • 归档
GitHub

PPsteven

屁屁斯蒂芬
首页
  • 后端

    • Docker
    • MySQL
    • Linux
    • Git
  • Vue

    • vue-todos 学习
  • 算法

    • 贪心算法
    • 双指针与滑动窗口
    • 二分查找
    • 排序算法
    • 搜索算法
    • 动态规划
    • 系列问题
  • 数据结构

    • 链表
    • 树
    • 栈和队列
    • 并查集
  • 计算机网络

    • 《图解HTTP》笔记
  • Python

    • Python
  • docker
  • git
  • Linux
  • SQL
  • Vim
  • Tmux
环境配置
关于
  • PHP
  • Hexo
  • 转载文章
  • 分类
  • 标签
  • 归档
GitHub
  • Python装饰器:将装饰器定义为类
  • 如何在python项目中使用import导入自编模块
    • 导入 Python 模块的各种姿势
    • 常规导入
    • 从模块导入
    • 相对导入
      • 一些例子
      • 只导入兄弟模块
      • 使用索引父节点
      • 把代码组织成很多分层模块的包
    • 可选导入
      • github2
      • lxml
    • 本地导入
    • 导入的注意事项
      • 循环导入
    • 覆盖导入(Shadowed imports)
    • 参考资料
  • Python并发编程——多进程编程 multiprocessing 模块
  • python并发编程——多线程编程 threading 协程
  • Python编码规范
  • Python 内存泄漏分析--常用命令
  • 如何打包发布Python包至私有仓库
  • python
ppsteven
2020-01-26

如何在python项目中使用import导入自编模块

背景是在python项目中导入模块时碰到的问题,当需要导入的模块是位于项目的不同层级的时候,导入文件就变成了一个非常麻烦的事情。

下面举一个例子

. # 当前工作目录
├── DB 
│   ├── MySQLClient.py
│   └── RedisClient.py
└── Uti
│    └── LogHandler.py
├── Email
│      └── SendEmail.py  # 本文件
│      └── Email_setting.py  # 待导入文件
1
2
3
4
5
6
7
8
9

DB 和 Util 是我们经常使用到的模块,若是我们的工作目录位于项目文件的第一层的话,当我们导入 Email_setting.py 的时候,我们应该是

from DB.MySQLClient import MySQLClass
from Util.LogHandler import LogHandler
from Email_setting import *  # 这是错误的例子
from Email.Email_setting import *
1
2
3
4

下面我们讨论一下如何在python中导入模块

{% blockquote %}

参考自: Python 101: All about imports

{% endblockquote %}

# 导入 Python 模块的各种姿势

  • Regular imports
  • Using from
  • Relative imports
  • Optional imports
  • Local imports
  • import Pitfalls

# 常规导入

我们最常见的导入方式是 import module ,我们一般用来导入 官方库 和 第三方库 。

import math # 官方库
from bs4 import Beautfiul # 第三方库
import pandas as pd 
1
2
3

以上两个来源的库用起来比较省心,因为它们的目录已经加入了环境变量中了

# 查看环境变量的方式
>>> import sys
>>> sys.path
['', '/Users/ppsteven/anaconda3/lib/python37.zip', '/Users/ppsteven/anaconda3/lib/python3.7', '/Users/ppsteven/anaconda3/lib/python3.7/lib-dynload', '/Users/ppsteven/anaconda3/lib/python3.7/site-packages', '/Users/ppsteven/anaconda3/lib/python3.7/site-packages/aeosa', '/Users/ppsteven/anaconda3/lib/python3.7/site-packages/xgboost-1.0.0_SNAPSHOT-py3.7.egg']
1
2
3
4

# 从模块导入

from functools import lru_cache
lru_cache(*args)
1
2

同样,你也可以导入该模块中的所有函数和变量,只是这种导入方式是不被推荐的

from os import *
1

官方建议我们,需要对 import 的函数,要显式的写出,但是当函数过多的时候,我们可能写成多行的形式

from os import path, walk, unlink
from os import uname, remove
1
2

为了能用一个 import 实现,我们可以利用 括号 帮助,或者 \

from os import (path, walk, unlink, uname, 
                remove, rename)
from os import path, walk, unlink, uname, \
                remove, rename
1
2
3
4

# 相对导入

当使用的是绝对路径时,容易出现的问题是,在大型的项目中,当你改变包结构的时候,你需要对你的代码进行大幅度的修改。

另外,如果没有相对路径,那么包内的模块无法轻松导入自身。

# 一些例子

from .foo import bar
from ...foo import bar
1
2

这两种形式有两种不同的建议语义。一种语义是使每个点代表一个级别。但是数需要多少个点也是一件麻烦事。

另一种选择是只允许一个相对导入级别,这样的话又会制约模块的功能。

最后的选择是定义一种算法,用于查找相关的模块和软件包。 这里的反对意见是“明确胜于隐含”。 (建议的算法是“从当前程序包目录中搜索,直到最终的父程序包被命中为止。”)

# 只导入兄弟模块

一种建议是,只导入 兄弟 模块。换言之,对于更高层级的模块,使用绝对路径

from .spam import eggs
import .spam.eggs
1
2

# 使用索引父节点

from -2.spam import eggs # 高层级
from .spam import eggs # 本地
1
2

# 把代码组织成很多分层模块的包

使用一个 leading dot 作为相对路径,两个或以上代表父目录。

这里我们把整个项目作为一个包来看待,需要对每一层写一个 __init__.py 文件 这里,我们整个项目可以看成 package 包,和 subpackage1 和 subpackage2 两个子包。

实现的方法很简单,就是确保在每一层目录上添加一个 __init__.py 文件

下面是我们的目录结构

package/
    __init__.py
    subpackage1/
        __init__.py
        moduleX.py  # 当前文件
        moduleY.py
    subpackage2/
        __init__.py # 当前文件
        moduleZ.py 
    moduleA.py
1
2
3
4
5
6
7
8
9
10

假设 moduleX.py 和 __init__.py 是我们的当前文件,那么正确的导入的做法是

from .moduleY import spam
from .moduleY import spam as ham
from . import moduleY
from ..subpackage1 import moduleY
from ..subpackage2.moduleZ import eggs
from ..moduleA import foo
from ...package import bar
from ...sys import path
1
2
3
4
5
6
7
8

{% blockquote %}

相对路径必须使用 from <> import

绝对路径使用 import <>

{% endblockquote %}

# my_package/__init__.py
from . import subpackage1
from . import subpackage2

# my_package/subpackage1/__init__.py
from . import module_x
from . import module_y

# my_package/subpackage1/module_x.py
from .module_y import spam as ham
 
def main():
    ham()
# my_package/subpackage1/module_y.py
def spam():
    print('spam ' * 3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们切换到 my_package 上一层的目录,运行下面代码是正常的。

In [1]: import my_package
 
In [2]: my_package.subpackage1.module_x
Out[2]: <module 'my_package.subpackage1.module_x' from 'my_package/subpackage1/module_x.py'>
 
In [3]: my_package.subpackage1.module_x.main()
spam spam spam
1
2
3
4
5
6
7

# 可选导入

可选导入用的情况比较少,一般是用在需要导入一个模块,但是这个模块并不一定存在的情况。比如我们使用的python 版本不一致的时候,需要导入的模块也会有所不同,这样的写法能加强模块的健壮性=

# github2

下面是一段来自 github2 的例子

try:
    # For Python 3
    from http.client import responses
except ImportError:  # For Python 2.5-2.7
    try:
        from httplib import responses  # NOQA
    except ImportError:  # For Python 2.4
        from BaseHTTPServer import BaseHTTPRequestHandler as _BHRH
        responses = dict([(k, v[0]) for k, v in _BHRH.responses.items()])
1
2
3
4
5
6
7
8
9

# lxml

下面是一段来自 lxml package 的例子

try:
    from urlparse import urljoin
    from urllib2 import urlopen
except ImportError:
    # Python 3
    from urllib.parse import urljoin
    from urllib.request import urlopen
1
2
3
4
5
6
7

# 本地导入

导入的模块分为 local scope 和 global scope 空间。当你在 python script 的头部导入的时候,作用在全局域。当在函数中导入的时候是本地域。

import sys  # global scope
 
def square_root(a):
    # This import is into the square_root functions local scope
    import math
    return math.sqrt(a)
 
def my_pow(base_num, power):
    # 这里直接使用 math 会报错的
    return math.pow(base_num, power)
 
if __name__ == '__main__':
    print(square_root(49))
    print(my_pow(2, 3))
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 导入的注意事项

容易犯错误的主要有两点

  • 循环导入
  • Shadowed imports

# 循环导入

简言之,就是模块相互导入

# a.py
import b
 
def a_test():
    print("in a_test")
    b.b_test()
 
a_test()
1
2
3
4
5
6
7
8

我们在相同的文件夹下,创建一个文件 b.py

import a
 
def b_test():
    print('In test_b"')
    a.a_test()
 
b_test()
1
2
3
4
5
6
7

如果是你运行这些模块的话,你将会获得 AttributeError 报错。虽然有一些旁门左道的变通方法可以解决,但是还是建议重构代码。

# 覆盖导入(Shadowed imports)

覆盖导入是指导入一个和官方库起名一样的模块,会报错。

主要的原因是,python 会首先搜索本地文件夹下的模块,其次是搜索其他路径。

import math
 
def square_root(number):
    return math.sqrt(number)
 
square_root(72)
1
2
3
4
5
6

# 参考资料

Python 101: All about imports

编辑
#Python#Cookbook
上次更新: 2020/11/04, 12:11:00
Python装饰器:将装饰器定义为类
Python并发编程——多进程编程 multiprocessing 模块

← Python装饰器:将装饰器定义为类 Python并发编程——多进程编程 multiprocessing 模块→

最近更新
01
如何打包发布Python包至私有仓库
10-29
02
Go Retry ——Go 重试方法实现
08-12
03
Go标准库学习02--ioutil
07-24
更多文章>
Theme by Vdoing | Copyright © 2019-2021 majutsushi.world All rights reserved | 沪ICP备2020031729号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式