Python 3 笔记 - 第9章 模块

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” 是一样的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 〇、前言 本文共108张图,流量党请慎重! 历时1个半月,我把自己学习Python基础知识的框架详细梳理了一遍。 ...
    Raxxie阅读 18,948评论 17 410
  • Python 面向对象Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对...
    顺毛阅读 4,213评论 4 16
  • 一、模块 1、模块和导入 当程序代码量变得相当大、逻辑结构变得非常复杂的时候,我们最好把代码按照逻辑和功能划分成一...
    常大鹏阅读 2,982评论 0 9
  • 这算不算我们一起看的第一场电影,只是我在广州你在福州不过时间同步
    我喜欢你久了阅读 180评论 0 0
  • 2017/02/20认识了一篇文章!这篇文章于我而言的思考真的是起到了莫大的作用。 我是左手面粉,右手水,然并卵,...
    平行夏阅读 531评论 3 0