zoukankan      html  css  js  c++  java
  • Python 简明教程 --- 17,Python 模块与包

    微信公众号:码农充电站pro
    个人主页:https://codeshellme.github.io

    正确的判断来源于经验,然而经验来源于错误的判断。
    —— Fred Brooks

    目录

    在这里插入图片描述

    我们已经知道函数是一种重复利用代码的机制。

    本节我们来介绍模块,Python 中的模块,也是一种重复利用代码的机制。我们可以将有特定功能的代码(比如函数等)写在模块中,供他人使用,便于重复利用,便于维护。

    在前面的章节中,我们也接触过模块。Python 功能强大的一个重要的原因,就是它有各种方便使用的模块。

    Python 中的模块可以分为两种:

    • 内建模块:Python 自带的模块,安装好Python 解释器后,内建模块就已经安装好了,直接import 就可使用
    • 第三方模块:由个人,公司或组织开发的模块,使用之前需要先安装,再import

    1,定义模块

    我们写的Python 代码都是以.py 为后缀,Python 模块也是以.py 为后缀,其实每个Python 代码文件,都可以看做是一个Python 模块。

    我们编写如下代码:

    #! /usr/bin/env python3
    
    # 定义一个字符串
    PY = 'Hi Python.'
    
    # 定义一个函数
    def hello_py():
        print('Hello Python.') 
    

    将该代码写在一个名为hello.py 的文件中,该文件就是一个hello 模块,将该文件放在~/hi 目录中。

    注意:
    在定义模块时,要避免与已有的模块起相同的名字,会引起冲突

    2,使用模块

    我们进入~/hi 目录,打开python3 交互式环境。

    dir() 函数

    在引入模块之前,我们先使用dir() 函数,来查看当前环境可用的函数/变量

    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__']
    

    可以看到,除了几个魔法方法外,没有任何其他可用的函数/变量

    我们定义一个变量s

    >>> s = 'abc'
    

    再用dir() 函数查看,可以看到多了一个s 变量:

    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 's']
    

    import 模块

    在使用模块时,需要先引入模块。引入模块,需要使用import 关键字。

    我们输入如下代码将hello 模块引入:

    >>> import hello
    

    再用dir() 函数,来查看当前环境可用的函数/变量

    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 'hello', 's']
    

    可以看到,多了一个hello。这是因为引入的模块会使用模块名来作为可用的标识符。

    我们可以用dir(hello) 来查看hello 模块支持的函数/变量

    >>> dir(hello)
    ['__builtins__', '__cached__', '__doc__', 
    '__file__', '__loader__', '__name__', 
    '__package__', '__spec__', 'PY', 'hello_py']
    

    可以看到,有两个标识符PYhello_py 可以使用,但并不能看出PYhello_py 是一个函数,还是一个变量。

    可以通过type(hello.标识符) 来查看标识符的类型:

    >>> type(hello.PY)               # 字符串
    <class 'str'>
    >>> type(hello.hello_py)         # 函数
    <class 'function'>
    

    可见PY 是一个字符串, hello_py 是一个函数。

    注意:

    引用一个模块中的标识符,使用的是点. 操作符

    比如这里的hello.PYhello.hello_py

    我们可以这样来调用一个模块中的函数/变量

    >>> hello.PY
    'Hi Python.'
    >>> hello.hello_py()
    hello python.
    

    3,模块文档注释

    我们知道编写函数时可以有文档注释,同样模块中也可以有文档注释,任何模块的第一个字符串将作为该模块的文档注释,如下:

    #! /usr/bin/env python3
    
    '''这是hello 模块的说明文档'''
    
    # 定义一个字符串
    PY = 'Hi Python.'
    
    # 定义一个函数
    def hello_py():
        print('Hello Python.') 
    

    可以用魔法方法__doc__ 来访问文档字符串:

    >>> hello.__doc__
    '这是hello 模块的说明文档'
    

    4,私有变量和私有函数

    在Python 模块中,规定以单下划线_ 和双下划线__ 开头的函数/变量,被称为私有函数/变量(private),不应该被外部模块使用。

    不以___ 开头的函数/变量,被称为共有函数/变量 (public),可被外部使用。

    如下:

    #! /usr/bin/env python3
    
    '''这是hello 模块的说明文档'''
    
    PY = 'Hi Python.'
    
    def _hello(s):
        print('hello %s.' % s)
    
    def hello_py():
        _hello('python')
    
    def hello_java():
        _hello('java')
    

    在该模块中,hello_pyhello_javaPY 是提供给外部使用的,而_hello 只建议在模块内部使用。

    其实在Python 中的这种访问权限的控制,实际上是“假的”,因为从技术上来说,这种方式,并不能完全的禁止外部调用,只是相当于一种编程规范

    如果我们非要使用这种以下划线开头的函数,从技术上来说也是行得通的,如下:

    >>> import hello
    >>> hello._hello('python')
    hello python.
    

    但一般我们并不建议这样做。

    5,__name__ 属性

    每个模块中都自带一个__name__ 属性,运行当前模块时,该属性的值为__main__,这经常与if 语句一起,被用来测试代码使用,这类似其它编程语言中的main() 函数。

    如下,文件名为hello.py

    #! /usr/bin/env python3
    
    '''这是hello 模块的说明文档'''
    
    PY = 'Hi Python.'
    
    def hello_py():
        print('hello python.')
    
    if __name__ == '__main__':
        hello_py()
    

    当我们在命令行执行python3 hello.py 时,if __name__ == '__main__': 内的代码块都会被执行。

    但当在其它模块引入hello 模块时,if __name__ == '__main__': 内的代码块并不会被执行。

    6,Python 包

    Python 包是比模块更高层的概念,一个Python 包中可以包含一个或多个模块。

    一个包含多个模块的Python 包结构如下:

    my_abc/
    ├── __init__.py
    ├── a.py
    ├── b.py
    └── c/
        ├── __init__.py
        └── d.py
    

    my_abc 是顶层包名,my_abc 目录中有一个__init__.py 文件,拥有这个__init__.py 文件的目录才会被认为是一个Python 包,否则,只是一个普通的目录。

    __init__.py 文件可以为空,也可以有Python 代码,my_abc/c/ 目录中的__init__.py 文件也是同样的道理。

    my_abc/__init__.py 文件中写入如下内容:

    #! /usr/bin/env python3
    
    print('这是my_abc 模块中的 __init__ 文件')
    

    my_abc/c/__init__.py 文件中写入如下内容:

    #! /usr/bin/env python3
    
    print('这是my_abc.c 模块中的 __init__ 文件')
    

    我们在my_abc 同级目录下运行python3 解释器,当我们引入my_abc 包时,my_abc/__init__.py 文件中的代码就会被执行:

    >>> import my_abc  # 第一次引入
    这是my_abc 模块中的 __init__ 文件
    >>> import my_abc  # 第二次引入
    

    可以看到当输入import my_abc 代码时,字符串这是my_abc 模块中的 __init__ 文件 被输出,说明my_abc/__init__.py 文件中的内容被执行了。

    紧接着再次输入import my_abc 代码时,并没有任何输出,说明某个模块中的__init__.py 文件中的内容,只有在第一次导入该模块时,才会执行。

    此时,当前环境中多了一个可用的my_abc 标识符:

    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 'my_abc']
    

    同样,当我们引入my_abc.c 包时,my_abc/c/__init__.py 文件中的代码就会被执行:

    >>> import my_abc.c
    这是my_abc.c 模块中的 __init__ 文件
    

    from ... import ... 语法

    我们可以使用from... import... 语法来引入一个包中的模块,比如我们引入my_abc 包中的a 模块,如下:

    >>> from my_abc import a
    这是my_abc 模块中的 __init__ 文件
    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 'a']
    

    可以看到,使用该语法引入a 模块时,my_abc/__init__.py 文件中的内容也执行了,此时,当前环境中多了一个可用的a 标识符。

    from ... import ... as... 语法

    可以使用as 关键字为模块重命名,例如将a 模块重命名为a_test

    >>> from my_abc import a as a_test
    这是my_abc 模块中的 __init__ 文件
    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 'a_test']
    

    可以看到本次导入时,没有a 标识符,取而代之的a_test

    from ... import * 语法

    from ... import * 语法会导入某个包中__init__.py 文件内的__all__ 列表的所有内容,当__all__ 列表不存在时,该语法将导入__init__.py 文件中的所有可用标识符。

    例如,my_abc/__init__.py 文件中内容如下,没有__all__ 列表:

    #! /usr/bin/env python3
    
    print('这是my_abc 模块中的 __init__ 文件')
    
    i = 1
    j = 2
    
    def hello():
        print('hello')
    

    使用from ... import * 语法,将导入__init__.py 文件中所有可用的标识符:

    >>> from my_abc import *
    这是my_abc 模块中的 __init__ 文件
    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 
    'hello', 'i', 'j']
    

    可见__init__.py 文件中的所有可用标识符,都被导入到了当前环境中。

    如果在__init__.py 文件中声明一个__all__ 列表,如下:

    #! /usr/bin/env python3
    
    print('这是my_abc 模块中的 __init__ 文件')
    
    __all__ = ['i', 'hello']
    
    i = 1
    j = 2
    
    def hello():
        print('hello') 
    

    from ... import * 语法将只会导入__all__ 列表中的内容,如下

    >>> from my_abc import *
    这是my_abc 模块中的 __init__ 文件
    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 
    'hello', 'i']
    

    因为__all__ 列表中只有i, hello 两个元素,所以只导入了这两个标识符。

    7,模块的导入路径

    从内建模块sys 中的path 标识符可查看导入模块的路径:

    >>> import sys
    >>> sys.path
    ['', 
    '/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']
    

    其中,第一个'' 表示从当前目录中导入,其它的目录是系统中的标准包路径

    Python 会按照该列表,从前往后依次从这些路径中导入你想import 的模块,先从当前目录中尝试导入你想导入的模块,如果没有找到,会依次从后边的路径去查找,如果都没有找到,最后会抛出异常。

    比如,现在有一个test 目录,该目录下有一个a.py 文件:

    test/
    └── a.py
    

    如果在test 同级目录下导入a.py,将出现异常,因为无法从sys.path 中找到a.py

    >>> import a
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ModuleNotFoundError: No module named 'a'
    

    此时,可以将./test 目录appendsys.path 中,然后再import

    >>> import sys
    >>> sys.path.append('./test')
    >>> import a  # 没有出现异常,说明导入成功
    >>> dir()
    ['__annotations__', '__builtins__', 
    '__doc__', '__loader__', '__name__', 
    '__package__', '__spec__', 
    'a', 'sys']    # a 模块被成功导入
    

    (完。)


    推荐阅读:

    Python 简明教程 --- 12,Python 字典

    Python 简明教程 --- 13,Python 集合

    Python 简明教程 --- 14,Python 数据结构进阶

    Python 简明教程 --- 15,Python 函数

    Python 简明教程 --- 16,Python 高阶函数


    欢迎关注作者公众号,获取更多技术干货。

    码农充电站pro

  • 相关阅读:
    zookeeper
    消息中间件介绍
    Java实现动态代理的两种方式
    深入理解Java内存模型
    消息中间件(一)MQ详解及四大MQ比较
    synchronized关键字详解及分析锁升级过程
    JDK1.6的Synchronized底层优化(偏向锁、轻量级锁)
    分布式通信-tcp/ip socket
    技术点
    大众点评 cat
  • 原文地址:https://www.cnblogs.com/codeshell/p/13158924.html
Copyright © 2011-2022 走看看