Python 中 import module 和 package 方法简单记录

  Python 中以 .py 结尾的文件实际上为一系列 Python 语句的集合,其被称为 Python 脚本(scripts),当用户通过 python test.py 命令运行该脚本时,实际上是由 Python 解释器从上至下逐行对其中的语句进行执行。对于大多数 Python 脚本而言,在开始位置一般通过 import 语句来导入其所需要的模块。这里记录一下 Python 模块和其导入相关的内容,供学习和讨论。主要内容来自 The Python Tutorial -> 6.Modules.

 

  module

  在 Python 中,可以将一系列的函数或变量定义放在一个 .py 文件中,供其他文件进行使用,这样的 .py 文件称为一个模块(module). 模块的名字即为 Python 文件名,如 test.py 即对应 test 模块,在模块内部,可以通过变量 __name__ 来对模块名进行访问。通过 import 关键字,在 Python 脚本中导入该模块相关名字,后续可通过导入的名字进行调用。两种常见的导入模块中名字的方式如下所示。在一个 Python 解释器的运行过程(session)中,每个模块只会被导入一次。在导入过程中,模块中的内容会按照从上到下的顺序进行执行和解析。

    import test        #导入 test 模块,此时将名字 test 引入当前的符号表中(从而可以引用)
    test.doSomething()    #通过 test 模块名使用其中定义的函数 doSomething

    from test import doSomething   #从 test 中引入 doSomething,注意此时 doSomething 被加入当前符号表(可直接引用),而 test 则不会被加入(无法直接引用)
    doSomething()

    from test import *    #从 test 中导入所有的名字,除了以 '_' 开始的名字

  模块作为一个 .py 文件,也可以作为脚本文件使用,当使用 Python test.py 的方式运行模块时,其模块名 __name__ 会被填充为 "__main__".

  用户可以通过内置的 dir() 函数查看模块中定义的名字,其一般有两种用法:

    dir(test)    #查看 test 模块中定义的所有名字
    dir()        #查看当前已定义的所有名字,注意不包括内置定义的名字

    import builtins
    dir(builtins)    #查看所有内置函数和变量的名字

 

  module 的搜索顺序

  当通过 import 语句导入一个模块的名字时,Python 解释器遵循一定的搜索顺序,其基本顺序为:

  1)首先搜索 Python 自带的模块;

  2)在 sys.path 变量规定的一系列目录中寻找名为 模块名.py 的模块文件。sys.path 一般被初始化为 a) 包含当前运行脚本的当前目录;b)PythonPATH 宏定义的路径;c)一般还包括 site-packages 目录,由 site 模块处理;

  sys.path 的相关内容可以参考 sys.path.

 

  package

  Python 的包(package)可以被理解为一系列模块的集合。每个 Python 包需要包含有一个对应的 __init__.py 文件,__init__.py 文件可以为空,也可以用于执行 Python 包导入时的初始化等操作。

    test/    #一个 test 包的定义
    __init__.py #必须包含有 __init__.py 文件
    t1.py    #包中的模块
    t2.py

  当执行 package 的 import 时,Python 解释器通过 sys.path 指定的路径来搜索包的路径。用户通过正常的 import 操作导入包中的名字。

    import test.t1    #导入名字 test.p1,后续通过该名字进行引用
    from test import t1 #同上,但导入的名字为 p1

  不同的 import 语句对于 import 对象有不同的要求。

    from package1 import test #将名字 test 视为定义在包中的名字(如包中定义的函数和变量等)进行定位,若无法定位,则将其视为一个模块进行加载,若加载失败,则会抛出 ImportError 异常
  import package1.package2.test #语句要求除了最后一个名字 test 外,前面所有的名字均需要为 Python 包,最后一个名字可以为一个模块或一个包,但不能为定义的函数或变量。

  当通过 from test import * 语句导入包中的模块时,若 __init__.py 中定义了 __all__ 变量,则 __all__ 变量中包含有的模块名会被导入,若没有定义 __all__ 变量,则上述导入语句不会导入 test 包的任何子模块,其仅将包名 test 导入当前符号表,同时会导入包中定义的变量和函数等名字(如__init__.py 文件中包含的名字)

  当 Python 包被组织为目录结构时,可以通过决定路径或相对路径来进行 import.相对路径指定从 package 开始到具体模块名的路径,而相对路径以 "." 开始,相对于当前模块位置来进行路径搜索。

    from test import t1    #当 test 可以通过 sys.path 定位时,可通过绝对路径进行导入
    from . import t2    #在模块 t1 中可以通过相对路径对 t2 进行导入

  

  Python 脚本的编译

  为了提升 Python 解释器加载模块的速度,模块的定义通常被编译并缓存,编译好的模块文件位于目录 __pycache__ 目录下,并以 module.version.pyc 的格式进行命名,module 对应模块名,verison 对应编译信息,.pyc 为对应的后缀。Python 通过检查源文件的修改日期和编译的 version 信息来决定是否需要进行重新编译。

 

  问题

  当存在当前一个目录结构的 python 工程时,如何从 main.py 中调用 func.py 中定义的函数?

    src/ #源目录
        test/
            func.py #定义了一些函数
        main.py #执行脚本

  1.对于 Python 3.3+ 的解释器版本,可以直接在 main.py 中使用 import 语句,即 from test import func 语句可直接导入 func 模块,进而使用 func 名字来使用 func.py 中定义的函数等内容。这是由于 Python 3.3+ 中允许将不包含 __init__.py 文件的目录视为一个包,其被称为 namespace packages. 这种定义包的方式一般用于 namespace 的共享,不推荐平常使用。具体可参考:Is __init__.py not required for packages in Python 3.3+

  2.由于 module 的搜索路径中包含有 sys.path 中定义的路径,故而可以将 test 目录加入 sys.path 路径中,从而使得解释器在搜索模块时可以直接定位 func.py. 即通过 sys.path.insert(0, './test/'); import func 即可。但该方法一般比较使用于小项目的构建和内部使用的 trick;

  3.将 test 作为一个包进行处理。在 Python 中,包含有 __init__.py 文件的目录即被视为一个 Python 包,之后即可通过对包中的模块导入方式进行引用。故而可以在 test/ 目录下加入 __init__.py 文件,此时 test 被视为一个 Python 包,可以通过 import test.func as func 或者 from test import func 来使用 func 模块中定义的内容;

 

  参考

  IMPORTERROR: ATTEMPTED RELATIVE IMPORT WITH NO KNOWN PARENT PACKAGE

  Is __init__.py not required for packages in Python 3.3+

  关于 import module 和 from module import 可以参考:Use 'import module' or 'from module import'?

posted on 2022-03-07 14:38  yhjoker  阅读(1349)  评论(0编辑  收藏  举报

导航