Python中的模块是包含Python定义和语句的文件(A module is a file containing Python definitions and statements),其文件名是模块名加后缀名.py。在模块内部,通过全局变量__name__可以获取模块名。
模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在import语句第一次遇到模块名时执行。
模块有自己的私有符号表,用作模块中所有函数的全局符号表。因此,在模块内使用全局变量时,不用担心与用户定义的全局变量发生冲突。另一方面,可以用与访问模块函数一样的标记法,访问模块的全局变量:modname.itemname
可以把其它模块导入模块。按惯例,所有import语句都放在模块(或脚本)开头,但这不是必须的。导入的模块名存在导入模块的全局符号表里。
import语句有一个变体,可以直接把模块里的名称导入到另一个模块的符号表,如下:这段代码不会把模块名foo导入到局部符号表里
from foo import foo_func, save
还有一种变体可以导入模块内定义的所有名称,如下:这种方式会导入所有不以下划线(_)开头的名称。大多数情况下,不要用这种功能,这种方式向解释器导入了一批未知的名称,可能会覆盖已经定义的名称
from foo import *
注意,一般情况下,不建议从模块或包导入*,因为,这项操作经常让代码变得难以理解。不过,为了在交互式编译器中少打几个字,这么用也没问题。
模块名后使用as时,直接把as后的名称与导入模块绑定,如下:与import foo一样,这种方式也可以有效地导入模块,唯一的区别是,导入的名称是ex_foo
import foo as ex_foo
from中也可以使用这种方式,效果类似,如下:
from foo import foo_func as foos
为了保证运行效率,每次解释器会话只导入一次模块。如果更改了模块内容,必须重启解释器。
以脚本方式执行模块:这项操作将执行模块里的代码,和导入模块一样,但会把__name__赋值为”__main__”: python foo.py <arguments>
模块搜素路径:导入spam模块时,解释器首先查找名为spam的内置模块。如果没找到,解释器再从sys.path变量中的目录列表里查找spam.py文件。
为了快速加载模块,Python把模块的编译版缓存在__pycache__目录中,文件名为module.version.pyc,version对编译文件格式进行编码,一般是Python的版本号。例如,CPython的3.8发行版中,foo.py的编译版本(compiled version)缓存为__pycache__/foo.cpython-38.pyc。使用这种命名惯例,可以让不同Python发行版及不同版本的已编译模块共存。Python对比编译版本与源码的修改日期,查看它是否已过期,是否要重新编译,此过程完全自动化。此外,编译模块与平台无关,因此,可在不同架构系统之间共享相同的支持库。
Python在两种情况下不检查缓存:(1).从命令行直接载入模块,只重新编译,不存储编译结果。(2).没有源模块,就不会检查缓存。
Python中的包(package)是一种通过使用”点式模块名(dotted module names)”来构建Python模块命名空间的方法。例如,模块名A.B表示包A中名为B的子模块。导入包时,Python搜索sys.path里的目录,查找包的子目录。
Python只把含__init__.py文件的目录当成包(Python3.2之后的版本不需要再额外的去专门创建一个__init__.py文件)。最简情况下,__init__.py只是一个空文件,但该文件也可以执行包的初始化代码,或设置__all__变量。当import指定的包时,此包内的__init__.py会被隐性执行,且只执行一次。
注意:使用from package import item时,item可以是包的子模块(或子包),也可以是包中定义的函数、类或变量等其它名称。import语句首先测试包中是否定义了item;如果未在包中定义,则假定item是模块,并尝试加载。如果找不到item,则触发ImportError异常。相反,使用import item.subitem.subsubitem句法时,除最后一项外,每个item都必须是包;最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。
import语句使用如下惯例:
(1).如果包的__init__.py代码定义了列表__all__,运行from package import *时,它就是用于导入的模块名列表。发布包的新版本时,包的作者应更新此列表。即如果显式声明了__all__,import *就只会导入__all__列出的成员,如果__all__定义有误,会抛出异常。
(2).如果没有定义__all__,from package.subpackage import *语句不会把包package.subpackage中所有子模块都导入到当前命名空间;该语句只确保导入包package.subpackage(可能还会运行__init__.py中的初始化代码),然后再导入包中定义的名称。这些名称包括__init__.py中定义的任何名称(以及显式加载的子模块),还包括之前import语句显式加载的包里的子模块。
包中含有多个子包时,可以使用绝对导入引用兄弟包中的子模块。还可以用import语句的from module import name形式执行相对导入。这些导入语句使用前导句点表示相对导入中的当前包(.)和父包(..),如下:
from . import echo
from .. import formats
Python包有多种结构。每个.py文件可作为单个模块。__init__.py文件定义方式如下,也可以使用不同方式的组合:
第一种方式:from .module import *
优点:
(1).用户不需要知道模块名称,例如,哪个功能在哪个模块中,他们只需要包名和函数名。
(2).导入顶级包(import top-level package)后,用户可以访问任何功能。
(3).制表符(Tab-completion)会补全为你提供的所有内容。
(4).当模块添加新特性时,不需要更新任何导入语句,它们将自动被包含。
缺点:
(1).要求所有函数和类必须命名唯一,即在模块中不能含有相同名字的函数。在模块中若含有相同名字的函数,虽然可能不会报错,但是调用的函数可能未必是你希望的那个。
(2).如果包很大,它会向命名空间添加很多内容,并且会减慢速度。
(3).对于有些函数防止用户调用的话,还需要作单独处理,如使用下划线来防止函数导入。
建议:
(1).在难以预测用户的工作流程时使用。
(2).当用户可能经常在不同模块之间来回切换时使用。
(3).当用户仅与几个模块(a few modules)一起使用。如果有很多模块,新用户可能更难在文档中找到他们想要的功能。
(4).在可能频繁添加或删除对象(object)时使用。
第二种方式:from .module import func 或 from .module import func1, func2
优点:
(1).拥有第一种方式的所有优点,并更容易控制哪些对象可供用户使用。
缺点:
(1).如果有许多模块,并且每个模块中有许多函数,那么__init__.py中内容会变得非常混乱。
(2).当向模块中添加新类或函数时,它们也必须显式添加到__init__.py文件中。
建议:
(1).当你的模块由单个类组成时特别有用。
(2).当你有少量对象要导入时使用。
(3).当你的对象具有明确名称时使用。
(4).当你确切地知道你的用户需要哪些对象以及他们不需要哪些对象时使用。
(5).当你不希望频繁添加大量需要导入的新模块和对象时使用。
第三种方式:import package.module
优点:
(1).简化了__init__.py文件,仅在添加新的模块时才需要更新。
(2).它是灵活的,它可用于仅导入用户需要的内容或导入所有内容。
(3).使用别名可以清理长的package.module规范(例如, import matplotlib.pyplot as plt)。
(4).可以有多个同名对象(例如,foo和bar模块中都调用了save()的函数)。
缺点:
(1).一些导入方法会使代码阅读起来更复杂,例如,foo.foo_func()不指示foo来自哪个包。
(2).最易读的方法(import test_package,没有别名)可能会导致长代码块(例如, test_package.foo.foo_func())使事情变得混乱。
(3).用户很难找到所有可能的功能。
建议:
(1).当你有一系列复杂的模块时使用,其中大多数用户永远不需要。
(2).import test_package导入大量对象时使用,可能会很慢。
(3).当你可以为不同类型的用户定义非常清晰的工作流程时使用。
(4).当你期望用户能够很好地浏览你的文档时使用。
第四种方式:使用__all__
注:以上内容及以下测试代码主要来自于:
1. https://towardsdatascience.com/whats-init-for-me-d70a312da583
2. https://docs.python.org/zh-cn/3/tutorial/modules.html
测试代码组织结构如下:顶层目录有一个test_package_main.py文件和一个test_package目录,test_package目录下有4个.py文件,各个文件内容如下:
test_package_main.py:
import sys
var = 4
if var == 1: # 第一种方式,导入模块中所有内容
import test_package
test_package.foo_func()
test_package.bar_func()
test_package.baz_func()
test_package.save() # 不能确定调用的是bar.py中的是还是foo.py中的. 由__init__.py决定,会调用后import的
#test_package._calc() # AttributeError: module 'test_package' has no attribute '_calc'
elif var == 2: # 第二种方式,指定模块中要导入的内容,同一模块中若导入多个函数,中间用逗号分割: from .module import func1, func2
import test_package
test_package.foo_func()
test_package.bar_func()
test_package.baz_func()
elif var == 3: # 第三种方式
# 用户可以采用三种不同的使用方法
method = 3
if method == 1:
import test_package
test_package.foo.foo_func()
test_package.bar.bar_func()
test_package.baz.baz_func()
elif method == 2:
from test_package import foo, bar, baz # 从包中导入子模块
foo.foo_func()
bar.bar_func()
baz.baz_func()
elif method == 3:
import test_package.foo as ex_foo # 从包中导入单个模块
import test_package.bar as ex_bar
import test_package.baz as ex_baz
ex_foo.foo_func()
ex_bar.bar_func()
ex_baz.baz_func()
ex_foo.save()
ex_bar.save()
print("csdn addr:", ex_foo.csdn_addr)
print("sys path:", sys.path)
print("dir:", dir(ex_foo)) # dir为内置函数,获取指定模块的方法列表,包括变量、函数等等
elif var == 4: # 使用__all__
from test_package import * # import * 就只会导入__all__列出的成员
foo.foo_func()
bar.bar_func()
#baz.baz_func() # NameError: name 'baz' is not defined
test_package/foo.py:
def foo_func():
print("this is a foo function")
def save():
print("this is a foo save function")
def _calc():
print("this is a foo _calc function")
github_addr = "https://github.com/fengbingchun"
csdn_addr = ""
test_package/bar.py:
def bar_func():
print("this is a bar function")
def save():
print("this is a bar save function")
def _calc():
print("this is a bar _calc function")
test_package/baz.py:
def baz_func():
print("this is a baz function")
test_package/__init__.py:
# reference: https://towardsdatascience.com/whats-init-for-me-d70a312da583
var = 4
print("import package: test_package")
if var == 1: # 第一种方式,导入模块中所有内容
# from .module import *
from .foo import *
from .bar import *
from .baz import *
elif var == 2: # 第二种方式,指定模块中要导入的内容,同一模块中若导入多个函数,中间用逗号分割: from .module import func1, func2
# from .module import func
from .foo import foo_func
from .bar import bar_func
from .baz import baz_func
elif var == 3: # 第三种方式
import test_package.foo
import test_package.bar
import test_package.baz
elif var == 4: # 使用__all__
__all__ = ["foo", "bar"]