20、模块

模块与包

一、概念

提起python文件,经常会听到3个名词,python脚本,python模块,python包。

如果你从 Python 解释器退出后再重新进入, 那么你之前定义的所有 (函式和变量) 都将丢失。因此如果你想写一个更长的程序, 你最好离线地使用文本编辑器保存成文件, 替代解释器的输入来运行。 这称作创建一个脚本。所以脚本的概念是从python交互式命令行引用过来的,把在命令行内运行的代码段复制到一个文件里再运行,这个文件就可以称为一个脚本;

脚本之间可能存在相同的函数等,当你的程序变得更长, 你可能想把它分割成几个文件以能够更简单地维护. 你也许还想在几个相同的程序里使用写过的程序, 而不用把一坨代码拷来拷去, 那这个脚本我们可以称为模块,为了一个脚本能使用另一个脚本里的函数等,也就是说一个模块里的定义是可以被导入到别的模块或者主模块中使用的;

python包是python管理模块命名空间的一种方式,类似python模块解决了不同模块之间的全局变量的重名,python包解决了多个模块组成的包时模块名的重名

二 、什么是模块

模块在Python可理解为对应于一个文件。在创建了一个脚本文件后,定义了某些函数,变量和类。你在其他需要这些功能的文件中,导入这模块,就可重用这些函数和变量。一般用module_name.fun_name,和module_name.变量名进行使用。这样的语义用法使模块看起来很像类或者名字空间,可将module_name 理解为名字限定符。模块名就是文件名去掉.py后缀

在一个模块中, 模块的名字 (一个字符串) 可以通过全局变量 __name__ 得到

三、如何定义

1、概要

定义模块其实就是创建一个.py文件

python之父建议的的模块命名:

  1. 公共模块: 所有的字母小写, 不同的单词之间用下划线_连接
  2. 内部模块: 用下划线_开头, 其余与公共模块一样.

2、举个栗子

  1. 创建一个文件fibo.py, 相当于创建了一个模块fibo
    def fib(n):
        a, b = 0, 1
        for i in range(n):
            print(b, end=" ")
            a, b = b, a + b
        print()
    
  2. 再创建一个模块demo, 作为我们程序的入口, 这样的模块其实就是main moudle. 在主模块内部使用我们第 1 步创建的模块.
    import fibo  # 导入需要的模块
    print(__name__) # 当前模块直接执行, 所以当前模块就是主模块 输出__main__
    # 模块名就成为了在 fibo 这个模块中定义的全局变量, 函数, 类的命名空间
    print(fibo.__name__)    # 输出模块名
    # 调用模块内定义的函数
    fibo.fib(20)
    

四、模块的导入

1、概要

导入模块非常简单,使用Python关键字来导入Python即可,模块的导入语句一般常规性的会放在一个模块的最开始部分

2、导入方式

2.1、 import

  1. 语法格式
    import module_name1 [as name1], module_name2 [as name2]
    
  2. 说明
    先说明一下Python的默认引用方式,在Python2.4及之前,Python只有相对引用这一种方式,
    在Python2.5中实现了绝对引用,但默认没有打开,需要用户自己指定使用该引用方式。
    在之后的版本和Python3版本,绝对引用已经成为默认的引用方式
  3. 注意事项
    执行应用程序的模块,不能使用相对导入
  4. 举个栗子
    import os
    import sys
    import time
    # 一次性导入多个包
    import os, sys, time
    # 给模块起别名
    # sys模块重命名为system
    import sys as system
    print(system.platform)
    
  5. 执行流程
    • 为源代码文件中定义的对象创建一个名字空间,通过这个名字空间可以访问到模块中定义的函数及变量。
    • 在新创建的名字空间里执行源代码文件。
    • 创建一个名为源代码文件的对象,该对象引用模块的名字空间,这样就可以通过这个对象访问模块中的函数及变量。
  6. 附:
    • 绝对导入的格式为 import A.Bfrom A import B
    • 相对导入格式为 from . import Bfrom ..A import B.代表当前模块,..代表上层模块,...代表上上层模块,依次类
    • 相对导入可以避免硬编码带来的维护问题,例如我们改了某一顶层包的名,那么其子包所有的导入就都不能用了。但是 存在相对导入语句的模块,不能直接运行,否则会有异常:
      ValueError: Attempted relative import in non-package
      

2.2、from … import

  1. 概要
    单独使用import是导入整个模块, 默认的所有定义都会导入, 而且会创建新的命名空间. 并且没有使用的模块的代码也会执行.
    如果我们仅仅是用到模块中某个函数或者类, 这个时候, 我们可以只导入我们想要的某个定义, 而不需要导入整个模块.
  2. 语法格式
    from module_name import item1 [as name1], item2 [as name2]
    from module_name import *
    
  3. 举个栗子
    # a.py
    num = 10
    def add(n1, n2):
        return n1 + n2
    class User(object):
        def __init__(self, name):
            self.name = name
         def show_name(self):
             print(self.name)
    
    # b.py
    from a import add, num, User
    sum1 = add(1, 2)
    print(sum1)
    print(num)
    user = User('小明')
    print(user.show_name())
    
    根据你实际的使用场景,上面的做法可能是更好的。在复杂的代码库中,能够看出某个函数是从哪里导入的这点很有用的。不过,如果你的代码维护的很好,模块化程度高,那么只从某个模块中导入一部分内容也是非常方便和简洁的
    # b.py
    # 我们也可以导入模块下的全部
    from a import *
    sum1 = add(1, 2)
    print(sum1)
    print(num)
    user = User('小明')
    print(user.show_name())
    
    这种做法在少数情况下是挺方便的,但是这样也会打乱你的命名空间。问题在于,你可能定义了一个与导入模块中名称相同的变量或函数,这时如果你试图使用模块中的同名变量或函数,实际使用的将是你自己定义的内容。因此,你最后可能会碰到一个相当让人困惑的逻辑错误。标准库中我唯一推荐全盘导入的模块只有Tkinter

2.3 、其它导入方式

  1. 概要
    • 可选导入
    • 局部导入
  2. 可选导入
    如果你希望优先使用某个模块或包,但是同时也想在没有这个模块或包的情况下有备选,你就可以使用可选导入这种方式,这样做可以导入支持某个软件的多种版本或者实现性能提升
    try:
       from httplib import responses
    except ImportError:  
    
  3. 局部导入(了解)
    当你在局部作用域中导入模块时,你执行的就是局部导入。如果你在Python脚本文件的顶部导入一个模块,那么你就是在将该模块导入至全局作用域,这意味着之后的任何函数或方法都可能访问该模块
    import sys 
    def square_root(a):
        import math
        return math.sqrt(a)
    def my_pow(base_num, power):
        return math.pow(base_num, power)
    

2.4、导入其它类型

不过导入模块不限于此可以被 import 语句导入的模块共有以下四类:

  1. 使用Python写的程序( .py文件)
  2. C或C++扩展(已编译为共享库或DLL文件)
  3. 包(包含多个模块)
  4. 内建模块(使用C编写并已链接到Python解释器内)

3、注意事项

3.1、说明

  • 循环导入(circular imports)
  • 覆盖导入(Shadowed imports,覆盖导入)

3.2、循环导入

  1. 说明
    如果你创建两个模块,二者相互导入对方,那么就会出现循环导入
  2. 举个栗子(moudle_a.py)
    import moudle_b
    def a_test():
        print("in a_test")
        moudle_b.b_test()
    a_test()
    
    import moudle_a
    def b_test():
        print('In test_b"')
        moudle_a.a_test()
    b_test()
    
  3. 问题说明
    如果你运行任意一个模块,都会引发AttributeError。这是因为这两个模块都在试图导入对方。简单来说,模块moudle_a想要导入模块moudle_b,但是因为模块moudle_b也在试图导入模块moudle_a(这时正在执行),模块moudle_a将无法完成模块moudle_b的导入。一般通用的解决方案是,你应该做的是重构代码,避免发生这种情况

3.3、循环导入

  1. 说明
  2. 举个栗子
    import math
    def square_root(number):
        return math.sqrt(number)
    square_root(72)
    
  3. 问题说明
    Python解释器首先在当前运行脚本所处的的文件夹中查找名叫math的模块。在这个例子中,解释器找到了我们正在执行的模块,试图导入它。但是我们的模块中并没有叫sqrt的函数或属性,所以就抛出了AttributeError

五、模块运行方式

1、概述

我们有两种方式去运行模块:

  1. 直接使用python 模块名.py. 这种模块为主模块, 这样运行方式叫做以程序的方式运行.
  2. 使用 import语句的方式来运行模块.

2、判断运行方式

  1. 我们可以通过__name__属性的值来判断这个模块的运行方式.
    if __name__ == "__main__":
        # 是 程序的方式运行
        pass
    else:
        # 否 以导入的方式运行
        pass
    

六、模块搜索路径

1、概叙

当我们去加载一个模块的时候, python 会在指定的路径中搜索这个模块, 一旦搜索到则会立即导入. 如果搜索不到则会抛出异常

2、模块的默认搜索路径

  1. 说明
    python 搜索模块按照一定的顺序来搜索的:
    1. 当前目录
    2. 系统内置模块
    3. 安装的第三方模块
      搜索路径存放在syspath变量中
    4. mac
      ['', '/usr/local/Cellar/python3/3.6.4/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/usr/local/Cellar/python3/3.6.4/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/usr/local/Cellar/python3/3.6.4/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Users/zhangwei/Library/Python/3.6/lib/python/site-packages', '/usr/local/lib/python3.6/site-packages']
    5. Windows
      ['', 'C:\python\Python36\Scripts\ipython.exe', 'c:\python\python36\python36.zip', 'c:\python\python36\DLLs', 'c:\python\python36\lib', 'c:\python\python36', 'c:\python\python36\lib\site-packages', 'c:\python\python36\lib\site-packages\IPython\extensions', 'C:\Users\zhangwei\.ipython']
    6. Linux
      ['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages', '/usr/lib/python3.6/dist-packages']

3、如何更改模块的搜索路径

  1. 动态更改
    #sys.path是一个列表, 只需要在里面添加上你需要的路径即可
    sys.path.append("路径")
    
  2. 持久更改
    设置环境变量PYTHONPATH, 在这个环境变量中设置你需要的路径即可. 只添加自己的路径即可, python 自己本身的搜索路径不受影响

七、包(package)

1、概要

一个.py文件就是一个模块, 但是模块一多, 管理起来也是比较麻烦.

python 提供包是为了更好的对模块进行管理.

包可以看做是一组模块的集合, 把这组模块放在一个包的名称下.包这项技术可以解决不同的应用程序之间模块的命名冲突问题.

2、必要条件

  1. **一个文件夹. **
    这个文件夹的名字其实就是包的名字.包的命名规则和公共文件的命名一致:全部字符小写, 不同的单词之间用空格隔开.
  2. 在前面的文件夹下的一个__init__.py的初始化文件.
    这个文件可以为空, 也可以定义一些导入模块时候的初始化代码.
  3. 注意:
    一个包中可以有自己的子包, 子包也可以再有子包... 每一个包都会有一个自己的__init__.py文件.

3、从包中导入模块

  1. 包结构图


    image
  2. 使用import导入模块
    import a.moudle_1
    import b.moudle_1
    import c.moudle_1
    import a.d.moudle_a
    
  3. 使用from导入模块
    from a import moudle_1
    from a.d import moudle_a
    
  4. 使用from直接导入模块中的定义
    from a import moudle_1
    from a.d.moudle_a import add
    add(1, 2)
    

4、注意:

当我们多个模块中都导入同一个模块的时候, 只有第一次导入的时候才会执行包的__init__()和模块中的代码.可以这样理解: 当导入某个模块的时候, 会先查找某个模块是否已经导入过, 如果已经导入过, 就直接使用不再重新导入.

使用*通配符导入

使用from 包 import *, 可以导入整个包下所有的模块.

但是由于各个操作系统的在对文件名的命名上的差异, 默认情况下这个语句一个包都导入不了.

我们可以在这个包的__init__.py定义一个列表, 这个列表中定义使用*的时候可以导入哪些包

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