1. 模块的基础知识
模块其实也是一个后缀为 .py 的文件,包含定义的函数和变量。通过封装模块,公共的函数或数据得到共享。Python 标准库就是通过模块的方式被应用程序使用。
1) 通过 import 语句引入模块
语法格式为:
import module1[, module2[, ... moduleN]
当 Python 解释器遇到 import 语句后,会在搜索路径中进行搜索(关于搜索路径,在本章最后部分说明)
注意这种引入方式下对函数的调用,需要采用 “module.func()” 这种完全限定的格式,否则会出现 “NameError: name 'func_xxxx' is not defined” 运行错误。要注意这种方式虽然简化了导入的方式,但增加了使用模块内部函数或变量的代码,
这种方式本质上相当于将整个源文件引入当前文件的命名空间中。
# File : MyFunction.py
def func1() :
print("This is func1")
def func2() :
print("This is func2")
# File : Test.py
import MyFunction # 导入 MyFuction 模块
MyFunction.func1() # 调用 MyFunciton 模块的函数
MyFunction.func2() # 调用 MyFunciton 模块的函数
执行结果为:
This is func1
This is func2
2) 通过 form ... import ... 语句引入模块
语法格式为:
from module import func1[, func2[, ... funcN]]
这种方法代表从模块中明确导入部分函数。
# File : Test.py
from MyFunction import func1, func2 # 导入 MyFuction 模块中 func1 和 func2 两个函数
func1() # 调用 func1 函数
func2() # 调用 func2 函数
执行结果为:
This is func1
This is func2
由于指定了明确的函数名,因此不再需要完全限定格式(反而会出现错误)
# File : Test.py
from MyFunction import func1, func2 # 导入 MyFuction 模块中 func1 和 func2 函数
MyFunction.func1() # 调用 func1 函数
MyFunction.func2() # 调用 func2 函数
执行结果为:
MyFunction.func1()
NameError: name 'MyFunction' is not defined
3) 通过 from ... import * 语句引入所有模块
语法格式为:
from module import *
这种方法代表从模块中明确导入全部函数,写法简单,但不建议这样用,因为会导致当前文件命名空间变大。
# File : Test.py
from MyFunction import * # 导入 MyFuction 模块中所有函数
func1() # 调用 func1 函数
func2() # 调用 func2 函数
执行结果为:
This is func1
This is func2
4) 除了函数,变量数据也可以导入
模块中不仅仅有函数定义,变量数据也是可以被导入的东西。
# File : MyFunction.py
def func1() :
print("This is MyFunction func1")
DESP = 'This is MyFunction common description'
# File : Test.py
import MyFunction # 导入 MyFunction 模块
MyFunction.func1() # 调用 MyFunction 模块的 func1 函数
print(MyFunction.DESP) # 访问 MyFunction 模块的 DESP 变量数据
执行结果为:
This is MyFunction func1
This is MyFunction common description
2. 关于符号表
前面给出的例子比较简单,也是比较常见的用法。但我们仍然需要深入一点理解模块的本质,也就是从符号表的角度,看看被导入模块的函数与变量,与当前文件(或者也叫做模块)的关系。
首先,每个模块有各自独立的符号表。因此模块开发者在模块内部可以放心的使用各个变量,不用担心搞坏使用者的代码。
其次,对于模块使用者,import 语句会将模块中的符号导入到当前文件的符号表。
这句话说起来有点绕,我们还是以下面这段代码为例:当导入的模块与当前文件中定义了相同名字的函数,以谁为准呢?
第一段代码:
# File : MyFunction.py
def func1() :
print("This is MyFunction func1")
# File : Test.py
from MyFunction import func1
def func1() :
print('This is Test func1')
func1() # 调用哪个 func1 函数呢?
执行结果为:
This is Test func1
第二段代码:
# File : MyFunction.py
def func1() :
print("This is MyFunction func1")
# File : Test.py
def func1() :
print('This is Test func1')
from MyFunction import func1
func1() # 调用哪个 func1 函数呢?
执行结果为:
This is MyFunction func1
嗯,执行结果出现了差异,仅仅是因为 import 的位置不同。
import 语句会将模块中的符号表导入到当前文件中(Test),当然符号表中不允许出现重复的符号,而后出现的符号会覆盖先出现的符号,因此出现了上述执行结果的差异。
也正是因为符号表覆盖的问题,from module import * 这种方法不建议使用,因为打击面太广,在不知情的情况下,覆盖调用者文件的命名空间符号表,导致难以预料的结果。
3. 关于name属性
有的时候,被导入的模块不仅仅包含函数和变量数据,可能还有自己的主程序代码(也就是没有包在函数定义中的代码)。这些代码不一定是导入者希望执行的,怎么避免这些代码的运行呢?这就要用到 “name” 属性了。
在 MyFunction 中增加一点主程序代码:
# File : MyFunction.py
# 定义 func1 函数
def func1() :
print("This is MyFunction func1")
# 以下是主程序代码
print("Welcome to MyFunction")
# File : Test.py
from MyFunction import func1
func1()
执行结果为:
Welcome to MyFunction
This is MyFunction func1
在上述代码中,MyFunction 模块中的主程序代码在导入的时候就被自动运行了
我们对 MyFunction 做一点修改:
# File : MyFunction.py
# 定义 func1 函数
def func1() :
print("This is MyFunction func1")
# 使用 __name__ 属性保证作为是否作为模块使用时代码的隔断
if __name__ == '__main__' :
# 这段代码在 MyFunction 不作为模块时被执行
print("Welcome to MyFunction")
else :
pass
# File : Test.py
from MyFunction import func1
func1()
执行结果为:
This is MyFunction func1
在上述代码中,MyFunction 模块中的主程序在导入的时候,不会运行 “name == 'main'” 分支中的代码
注意,每个模块都有一个 “name” 属性。可以通过 “dir([module_name])” 系统函数查看指定模块定义的所有的名称,也就是符号表。如果没有指定 “module_name”,则查看的是当前模块符号表。
# File : MyFunction.py
# 定义 func1 函数
def func1() :
print("This is MyFunction func1")
# 定义变量
DESP = 'This is MyFunction description'
# 打印 MyFunction 符号表
print(dir())
执行结果为:
['DESP', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'func1']
通过执行结果能够看出,变量(或者说属性)打印在前,函数打印在后。
Python 本身带着一些标准的模块库.有些模块直接被构建在解析器里,这些虽然不是一些语言内置的功能,但是他却能很高效的使用,甚至是系统级调用也没问题。这些组件会根据不同的操作系统进行不同形式的配置,比如 winreg 这个模块就只会提供给 Windows 系统。应该注意到这有一个特别的模块 sys ,它内置在每一个 Python 解析器中。
-- from Runoob Python3 教程
4. 包
和 Java 语言类似,Python 也利用“包”的概念来对模块进行管理。不同包中的模块,可以拥有相同的名字。
Python 包的表示方法和 Java 一样,也采用 “.” 来进行分隔。但需要注意的是,对于包的最后一个部分,比如 “A.B.C” 中的 “C”,可能仍然是一个目录,但也可能是一个具体的模块(Python 文件)。
Python 解释器的规则是:先按照包来寻找,也就是文件夹;然后再按照模块,也就是文件。如果都没有找到则抛出异常。
注意:在 Python 中,目录中必须包含一个叫做 init.py 的文件才会被认作是一个包,否则仅仅是一个目录。
写一个小例子:
1) 包结构如下:platform - util - math,math 中有几个小的数学算法代码
# 包结构示例
main.py # 主程序文件
platform / # 一级包目录
util / # 二级包目录
math / # 三级包目录
__init__.py
fibo.py # 模块文件
parity.py # 模块文件
prime.py # 模块文件
readme.py # 模块文件
__init__.py
__init__.py
2) main 主程序文件
# 指定引入具体的模块
from platform.util.math import parity
from platform.util.math import prime
from platform.util.math import fibo
####################################################################
num = int(input('\nEven or Odd Judgement: Input a number : '))
parity.parity(num) # 模块名.函数名
####################################################################
num = int(input('\nPrime Number or not Judgement: Input a number : '))
if (prime.prime(num)): # 模块名.函数名
print(num, 'is a prime number')
else:
print(num, 'is not a prime number')
print('All prime numbers before', num, 'as follows :')
prime.prime_list(num) # 模块名.函数名
####################################################################
num = int(input('\nFibonacci Series Output: Input a number : '))
fibo.fibo_list(num) # 模块名.函数名
3) parity 模块文件
# 函数名称:parity
# 功能描述:输出指定数字的奇偶性
def parity(num) :
if num % 2 == 0 :
print("Number " + str(num) + " is even")
else :
print("Number " + str(num) + " is odd")
# 函数名称:parity_test
# 功能描述:测试 parity 函数
def parity_test(num) :
parity(num)
# 主流程
if __name__ == '__main__' :
print("\nEnter 'q' or 'quit' to end this program.")
while True:
num = input("\nPlease input a number : ")
if (num == "q") or (num == "quit"):
print("\nGoodbye")
break
parity_test(int(num))
4) prime 模块文件
# 功能描述:输出指定数字之前的所有质数
def prime_list(num) :
for x in range(2, num) :
if (prime(x)) :
print(x, end=', ')
# 函数名称:prime_test
# 功能描述:测试 prime 函数
def prime_test(num) :
if (prime(num)) :
print(num, 'is a prime number')
else :
print(num, 'is not a prime number')
# 函数名称:prime_list_test
# 功能描述:测试 prime 函数
def prime_list_test(num):
print('All prime numbers before', num, 'as follows :')
prime_list(num)
# 主流程
if __name__ == '__main__' :
print("\nEnter 'q' or 'quit' to end this program.")
while True:
num = input("\nPlease input a number : ")
if (num == "q") or (num == "quit"):
print("\nGoodbye")
break
prime_test(int(num))
prime_list_test(int(num))
5) fibo 模块文件
# 函数名称:fibo_list
# 功能描述:输出指定数字以内的所有质数
# 备注说明:这是最简单的实现代码,只用两个变量就完成算法。
# 注意复合赋值的语句规则:
# 1)右边表达式会在变量赋值之前被执行
# 2)右边表达式的执行顺序是从左往右的
# 关键字end可以用于将结果输出到同一行,或者在输出的末尾添加不同的字符
def fibo_list(num) :
a, b = 0, 1
while b < num :
print(b, end = ', ')
a, b = b, a + b
# 函数名称:fibo_list_test
# 功能描述:测试 fibo_list 函数
def fibo_list_test(num) :
fibo_list(num)
# 主流程
if __name__ == '__main__' :
print("\nEnter 'q' or 'quit' to end this program.")
while True:
num = input("\nPlease input a number : ")
if (num == "q") or (num == "quit"):
print("\nGoodbye")
break
fibo_list_test(int(num))
6) 执行结果
Even or Odd Judgement: Input a number : 121
Number 121 is odd
Prime Number or not Judgement: Input a number : 99
99 is not a prime number
All prime numbers before 99 as follows :
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
Fibonacci Series Output: Input a number : 99
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,
在 main 主程序文件中,也可以把这种引入方式
# 指定引入具体的模块
from platform.util.math import parity
from platform.util.math import prime
from platform.util.math import fibo
修改为
# 指定引入具体的函数
from platform.util.math.parity import parity
from platform.util.math.prime import prime, prime_list
from platform.util.math.fibo import fibo_list
由于显示导入了具体的函数名,这样在后面的调用处,就不需要再加上包名了。
包的引入本质上和模块的引入相同,但在这篇笔记中,不详细写那些很细枝末节的语法规则,上述也是比较常见的用法。详细的“学术研究”,可以参考其他专业文档。
通常我们并不主张使用*这种方法来导入模块,因为这种方法经常会导致代码的可读性降低。只要记住使用from Package import specific_submodule这种方法永远不会有错。事实上,这也是推荐的方法。
-- from Runoob Python3 教程
5. 搜索路径
当 Python 解释器读到 from 和 import 语句后,知道要寻找包和模块了,从哪里寻找呢?如果对 Java 类定位的机制有一定了解,理解这个问题会更简单。搜索路径是在Python 编译或安装的时候确定的,搜索路径被存储在 sys 模块中的 path 变量中。
>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']
上述是在我的 MacOS 上的执行结果,Linux 和 Windows 可能是不一样的。其中第一项是个空字符串,代表当前目录,也就是我们执行 Python 解释器的目录(对于脚本的话就是运行的脚本所在的目录)。
如果希望修改搜索路径,本质上就是修改 sys.path 的值,通常有几种方法:
1) 在脚本中动态添加,本次运行时生效
import sys
sys.path.append('$PATH$')
2) 增加 .pth 文件,全局生效
首先,我们在上面提到的 sys.path 中找到 “site-packages” 所在路径,注意在 MacOS、Linux 和 Windows 上,这个路径都不同。我的 MacOS 上的路径为:
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
然后,进入到 “site-packages” 这个目录中,新建一个后缀为 “.pth” 的文件,在里面写上自己定义的搜索路径。我创建的文件名为 “mypath.pth”:
Andy-Macbook:site-packages andy$ pwd
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
Andy-Macbook:site-packages andy$ ls -l
total 24
-rw-rw-r-- 1 root admin 119 3 28 18:05 README.txt
drwxrwxr-x 4 root admin 128 5 24 23:40 __pycache__
-rw-rw-r-- 1 root admin 126 5 24 23:40 easy_install.py
-rw-r--r--@ 1 andy admin 11 6 22 09:35 mypath.pth
drwxrwxr-x 23 root admin 736 5 24 23:40 pip
drwxrwxr-x 10 root admin 320 5 24 23:40 pip-9.0.3.dist-info
drwxrwxr-x 7 root admin 224 5 24 23:40 pkg_resources
drwxrwxr-x 42 root admin 1344 5 24 23:40 setuptools
drwxrwxr-x 13 root admin 416 5 24 23:40 setuptools-39.0.1.dist-info
=================== 以下是 mypath.pth 的内容 ===================
/Users/andy
最后,再次查看 “sys.path” 属性值,在最后已经能够看到 “/User/andy” 被加入到搜索路径中了。
>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages', '/Users/andy']
写一个小程序测试一下。我把上文中的 “parity.py” 移动到 “/User/andy” 目录中,并在另外一个目录 “/User/andy/Research” 中创建一个 “test.py”:
# parity.py 定义在 /User/andy 目录中
# 函数名称:parity
# 功能描述:输出指定数字的奇偶性
def parity(num) :
if num % 2 == 0 :
print("Number " + str(num) + " is even")
else :
print("Number " + str(num) + " is odd")
#########################################
# test.py 定义在 /User/andy/Research 目录中
import parity
parity.parity(100)
执行结果:
Number 100 is even
另外一种测试方法,直接在 Terminal 中调用 Python 解释器:
>>> import parity
>>> parity.parity(123)
Number 123 is odd
看,由于将 parity.py 放到了搜索路径中,因此对 parity 模块进行导入是可以直接被定位到的,就和使用系统模块 “sys” 是一样的。