zoukankan      html  css  js  c++  java
  • Python基础:模块

    Python基础:模块

    一、概述

    模块(module)和 包(package)是Python用于组织大型程序的利器。

    模块 是一个由 变量函数 等基本元素组成的功能单元,设计良好的模块通常是高内聚、低耦合、可复用、易维护的。 是管理模块的容器,它具有 可嵌套性:一个包可以包含模块和其他包。从文件系统的视角来看,包就是目录,模块就是文件。

    从本质上讲,一个模块就是一个独立的名字空间(namespace),单纯的多个模块只能构成扁平结构的名字空间集;而包的可嵌套性,使得多个模块可以呈现出多层次结构的名字空间树。

    二、导入语句

    如果要在一个模块A中使用另一个模块B(即访问模块B的属性),则必须首先 导入 模块B。此时模块A称为导入模块(即importer),而模块B称为被导入模块(即importee)。

    导入语句(import statement)有两种风格:import <>from <> import。对模块的导入同时支持这两种风格。

    1、基本语法

    1)导入模块module(重命名为name)

    import module [as name]

    >>> import sys
    >>> sys.version
    '2.7.3 (default, Apr 10 2013, 05:46:21) 
    [GCC 4.6.3]'
    >>> import sys as s
    >>> s.version
    '2.7.3 (default, Apr 10 2013, 05:46:21) 
    [GCC 4.6.3]'
    

    2)导入模块module1(重命名为name1),模块module2(重命名为name2),等等

    import module1 [as name1], module2 [as name2], ...

    >>> import sys, os
    >>> sys.platform, os.name
    ('linux2', 'posix')
    >>> import sys as s, os as o
    >>> (s.platform, o.name)
    ('linux2', 'posix')
    

    3)从模块module中导入属性attribute(重命名为name)

    from module import attribute [as name]

    >>> from sys import executable
    >>> executable
    '/usr/bin/python'
    >>> from sys import executable as exe
    >>> exe
    '/usr/bin/python'
    

    4)从模块module中导入属性attribute1(重命名为name1),属性attribute2(重命名为name2),等等

    from module import attribute1 [as name1], attribute2 [as name2], ...

    >>> from sys import platform, executable
    >>> platform, executable
    ('linux2', '/usr/bin/python')
    >>> from sys import platform as plf, executable as exe
    >>> plf, exe
    ('linux2', '/usr/bin/python')
    

    5)从模块module中导入属性attribute1(重命名为name1),属性attribute2(重命名为name2),等等

    from module import (attribute1 [as name1], attribute2 [as name2], ...)

    >>> from sys import (platform, executable)
    >>> platform, executable
    ('linux2', '/usr/bin/python')
    >>> from sys import (platform as plf, executable as exe)
    >>> plf, exe
    ('linux2', '/usr/bin/python')
    

    6)从模块module中导入所有属性

    from module import *

    >>> from sys import *
    >>> platform, executable
    ('linux2', '/usr/bin/python')
    

    2、推荐风格

    以下是在Python程序中推荐使用的导入语句:

    • import module [as name](导入单个模块)
    • from module import attribute [as name](导入单个属性)
    • from module import attribute1 [as name1], attribute2 [as name2], ...(导入较少属性时,单行书写)
    • from module import (attribute1 [as name1], attribute2 [as name2], ...)(导入较多属性时,分行书写)

    应当尽量避免使用的导入语句是:

    • import module1 [as name1], module2 [as name2], ...

      它会降低代码的可读性,应该用多个import module [as name]语句代替。

    • from module import *

      它会让importer的名字空间变得不可控(很可能一团糟)。

    三、模块

    1、模块名

    一个 模块 就是一个Python源码文件。如果文件名为mod.py,那么模块名就是mod。

    模块的导入和使用都是借助模块名来完成的,模块名的命名规则与变量名相同。

    2、模块属性

    模块属性 是指在模块文件的全局作用域内,或者在模块外部(被其他模块导入后)可以访问的所有对象名字的集合。这些对象名字构成了模块的名字空间,这个名字空间其实就是全局名字空间(参考 名字空间与作用域)。

    模块的属性由两部分组成:固有属性 和 新增属性。可以通过 M.__dict__ 或 dir(M) 来查看模块M的属性。

    1)固有属性

    固有属性 是Python为模块默认配置的属性。

    例如,新建一个空文件mod.py:

    $ touch mod.py
    $ python
    ...
    >>> import mod # 导入模块mod
    >>> mod.__dict__ # 模块mod的属性全貌
    {'__builtins__': {...}, '__name__': 'mod', '__file__': 'mod.pyc', '__doc__': None, '__package__': None} 
    >>> dir(mod) # 只查看属性名
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__']
    

    上述示例中,空模块mod的所有属性都是固有属性,包括:

    • __builtins__ 内建名字空间(参考 名字空间
    • __file__ 文件名(对于被导入的模块,文件名为绝对路径格式;对于直接执行的模块,文件名为相对路径格式)
    • __name__ 模块名(对于被导入的模块,模块名为去掉“路径前缀”和“.pyc后缀”后的文件名,即__file__.rpartition('/')[2].split('.')[0];对于直接执行的模块,模块名为__main__
    • __doc__ 文档字符串(即模块中在所有语句之前第一个未赋值的字符串)
    • __package__ 包名(主要用于相对导入,请参考 PEP 366

    2)新增属性

    新增属性 是指在模块文件的顶层(top-level),由赋值语句(如import、=、def和class)创建的属性。

    例如,修改文件mod.py为:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    '''this is a test module'''
    
    import sys
    
    debug = True
    
    _static = ''
    
    class test_class(object): 
        def say(self): pass
    
    def test_func(): 
        var = 0
    

    再次查看模块mod的属性:

    >>> import mod
    >>> dir(mod)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '_static', 'debug', 'sys', 'test_class', 'test_func']
    

    对比上一小节可知,除开固有属性外的其他属性都是新增属性,包括:

    • sys(由“import”创建)
    • debug和_static(均由“=”创建)
    • test_class(由“class”创建)
    • test_func(由“def”创建)

    这些属性的共同点是:它们都在模块文件的顶层创建。相比之下,类方法say(在类test_class内部创建)和局部变量var(在函数test_func内部创建)都不在顶层,因此不在新增属性之列。(作为一个例子,'''this is a test module'''就是模块mod的文档字符串,即mod.__doc__的值)

    3、可导出的公有属性

    在 『导入语句』 中描述的基本语法可以归纳为三类:

    • import module 在导入模块(importer)中,可以通过module.*的形式访问模块module中的所有属性
    • from module import attribute 只能通过名字attribute访问模块module中的指定属性module.attribute
    • from module import * 可以直接通过属性名访问模块module中的所有 公有属性

    换句话说,模块的 公有属性 就是那些可以通过from module import *被导出给其他模块直接使用的属性。

    模块的公有属性有以下特点:

    • 可以在模块中定义一个特殊列表__all__,其中包含所有可导出的公有属性的字符串名称,从而实现对公有属性的定制
    • 如果没有定义__all__,那么默认所有不以下划线“_”开头的属性都是可导出的公有属性

    以 『新增属性』 中的mod.py为例,没有定义__all__的公有属性:

    >>> dir() # 导入模块mod前的名字空间
    ['__builtins__', '__doc__', '__name__', '__package__']
    >>> from mod import * # 导入模块mod中的所有公有属性
    >>> dir() # 导入模块mod后的名字空间
    ['__builtins__', '__doc__', '__name__', '__package__', 'debug', 'sys', 'test_class', 'test_func']
    

    对比导入模块mod前后的情况可知,模块mod中的sys、debug、test_class和test_func都属于公有属性,因为它们的名字不以“_”开头;而其他以“_”开头的属性(包括所有“固有属性”,以及“新增属性”中的_static)都不在公有属性之列。

    如果在模块文件mod.py中顶层的任何位置,增加定义一个特殊列表__all__ = ['sys', '_static', 'test_func'](此时__all__也是模块属性),那么此时的公有属性:

    >>> dir() # 导入模块mod前的名字空间
    ['__builtins__', '__doc__', '__name__', '__package__']
    >>> from mod import * # 导入模块mod中的所有公有属性
    >>> dir() # 导入模块mod后的名字空间
    ['__builtins__', '__doc__', '__name__', '__package__', '_static', 'sys', 'test_func']
    

    可以看出,只有在__all__中指定的属性才是公有属性。

    4、直接执行

    模块可以在命令行下被直接执行,以模块mod(对应文件mod.py)为例:

    1)以脚本方式执行

    python mod.py <arguments>
    

    2)以模块方式执行

    python -m mod <arguments>
    

    四、包

    一个  就是一个含有__init__.py文件的目录。

    包与模块之间的包含关系是:一个包可以包含子包或子模块,但一个模块却不能包含子包和子模块。

    1、包名

    与模块名类似,包名的命名规则也与变量名相同。

    此外,需要特别注意的是:如果在同一个目录下,存在两个同名的包和模块,那么导入时只会识别包,而忽略模块。(参考specification for packages 中的 『What If I Have a Module and a Package With The Same Name?』)

    例如,在目录dir下新建一个文件spam.py(即模块spam),此时import spam会导入模块spam:

    $ cd dir/
    $ touch spam.py
    $ python
    ...
    >>> import spam
    >>> spam
    <module 'spam' from 'spam.py'>
    

    如果在目录dir下再新建一个含有__init__.py文件的目录spam(即包spam),此时import spam则会导入包spam(而不再是模块spam):

    $ mkdir spam && touch spam/__init__.py
    $ python
    ...
    >>> import spam
    >>> spam
    <module 'spam' from 'spam/__init__.py'>
    

    2、包属性

    包属性与模块属性非常相似,也分为 固有属性 和 新增属性

    1)固有属性

    与模块相比,包的 固有属性 仅多了一个__path__属性,其他属性完全一致(含义也类似)。

    __path__属性即包的路径(列表),用于在导入该包的子包或子模块时作为搜索路径;修改一个包的__path__属性可以扩展该包所能包含的子包或子模块。(参考 Packages in Multiple Directories

    例如,在dir目录下新建一个包pkg(包含一个模块mod),显然在包pkg中只能导入一个子模块mod:

    $ mkdir pkg && touch pkg/__init__.py
    $ touch pkg/mod.py
    $ python
    ...
    >>> import pkg.mod # 可以导入子模块mod
    >>> import pkg.mod_1 # 不能导入子模块mod_1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ImportError: No module named mod_1
    

    如果在dir目录下再新建一个包pkg_1(包含一个模块mod_1):

    $ mkdir pkg_1 && touch pkg_1/__init__.py
    $ touch pkg_1/mod_1.py
    

    并且在pkg/__init__.py中修改包pkg的__path__属性:

    print('before:', __path__)
    __path__.append(__path__[0].replace('pkg', 'pkg_1')) # 将“包pkg_1所在路径”添加到包pkg的__path__属性中
    print('after:', __path__)
    

    此时,在包pkg中就可以导入子模块mod_1(仿佛子模块mod_1真的在包pkg中):

    $ python
    ...
    >>> import pkg.mod # 可以导入子模块mod
    ('before:', ['pkg'])
    ('after:', ['pkg', 'pkg_1'])
    >>> import pkg.mod_1 # 也可以导入子模块mod_1
    

    2)新增属性

    包的 新增属性 包括两部分:静态的新增属性和动态的新增属性。

    静态的新增属性是指:在__init__.py的顶层(top-level),由赋值语句(如import、=、def和class)创建的属性。这部分与模块的新增属性一致。

    动态的新增属性是指:在执行导入语句后动态添加的新增属性。具体而言,如果有一个导入语句导入了某个包pkg中的子模块submod(或子包subpkg),那么被导入的子模块submod(或子包subpkg)将作为一个属性,被动态添加到包pkg的新增属性当中。

    以包含模块mod的包pkg为例:

    >>> import pkg
    >>> dir(pkg)
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__']
    >>> import pkg.mod # 该语句导入了包pkg中的模块mod
    >>> dir(pkg) # mod成为了包pkg的“动态的新增属性”
    ['__builtins__', '__doc__', '__file__', '__name__', '__package__', '__path__', 'mod']
    

    3、可导出的公有属性

    在公有属性方面,包与模块的行为完全一致,当然别忘了包还有 动态的新增属性

    4、其他

    1)导入语句

    加入包的概念以后,导入语句的风格(与仅有模块时相比)不变,但是语法上有一些细微差异:用“.”来表示包与模块之间的包含关系;可操作的对象扩充为 模块 和 属性

    下面是涉及包时,一些典型的导入语句:

    • import package
    • import package.module
    • import package.subpackage
    • import package.subpackage.module
    • from packagae import module
    • from packagae import subpackagae
    • from packagae import subpackagae.modulefrom packagae.subpackagae import module
    • from packagae.module import attribute
    • from packagae.subpackagae.module import attribute

    2)__init__.py

    关于包的__init__.py,有以下几点总结:

    • 一般为空即可
    • 有时也可以放置一些初始化代码,用于在包加载时执行
    • 少数情况下,还可以用于定制包的一些属性(如__all____path__等)

    五、导入原理

    模块与包的导入原理几乎完全一致,因此下面以模块为主进行讨论,仅在有显著差异的地方对包作单独说明。

    1、导入依赖

    对于模块M而言,根据导入语句的不同(指明了模块M是否在一个包中),可能存在导入依赖的问题:

    • import M

      模块M不在一个包中,因此无导入依赖:直接以“M”为 完整名(fully qualified name)导入模块M

    • import A.B.M或者from A.B import M

      模块M在一个子包B中,而子包B又在一个包A中,因此存在导入依赖:会首先以“A”为 完整名 导入包A,接着以“A.B”为 完整名 导入子包B,最后以“A.B.M”为 完整名 导入模块M。

    2、导入过程

    一个模块的导入过程主要分三步:搜索加载 和 名字绑定。(具体参考 The import statement

    1)搜索

    搜索 是整个导入过程的核心,也是最为复杂的一步。对于被导入模块M,按照先后顺序,搜索的处理步骤为:

    • 在缓存 sys.modules 中查找模块M,若找到则直接返回模块M
    • 否则,顺序搜索 sys.meta_path,逐个借助其中的 finder 来查找模块M,若找到则加载后返回模块M
    • 否则,如果模块M在一个包P中(如import P.M),则以P.__path__为搜索路径进行查找;如果模块M不在一个包中(如import M),则以 sys.path 为搜索路径进行查找

    2)加载

    正如 『搜索』 步骤中所述,对于找到的模块M:如果M在缓存 sys.modules 中,则直接返回;否则,会加载M。

    加载 是对模块的初始化处理,包括以下步骤:

    • 设置属性:包括__name____file____package____loader__(对于包,则还有__path__
    • 编译源码:将模块文件(对于包,则是其对应的__init__.py文件)编译为字节码(*.pyc),如果字节码文件已存在且仍然是最新的,则不会重编
    • 执行字节码:执行编译生成的字节码(即模块文件或__init__.py文件中的语句)

    有一点值得注意的是,加载不只是发生在导入时,还可以发生在 reload() 时。

    3)名字绑定

    加载完importee模块后,作为最后一步,import语句会为 导入的对象 绑定名字,并把这些名字加入到importer模块的名字空间中。其中,导入的对象 根据导入语句的不同有所差异:

    • 如果导入语句为import obj,则对象obj可以是包或者模块
    • 如果导入语句为from package import obj,则对象obj可以是package的子包、package的属性或者package的子模块
    • 如果导入语句为from module import obj,则对象obj只能是module的属性

    3、更多细节

    根据 The import statement 中的描述,以下是导入原理对应的Python伪码:

    import sys
    import os.path
    
    def do_import(name):
        '''导入'''
    
        parent_pkg_name = name.rpartition('.')[0]
        if parent_pkg_name:
            parent_pkg = do_import(parent_pkg_name)
        else:
            parent_pkg = None
        return do_find(name, parent_pkg)
    
    def do_find(name, parent_pkg):
        '''搜索'''
    
        if not name:
            return None
    
        # step 1
        if name in sys.modules:
            return sys.modules[name]
        else:
            # step 2
            for finder in sys.meta_path:
                module = do_load(finder, name, parent_pkg)
                if module:
                    return module
            # step 3
            src_paths = parent_pkg.__path__ if parent_pkg else sys.path
            for path in src_paths:
                if path in sys.path_importer_cache:
                    finder = sys.path_importer_cache[path]
                    if finder:
                        module = do_load(finder, name, parent_pkg)
                        if module:
                            return module
                    else:
                        # handled by an implicit, file-based finder
                else:
                    finder = None
                    for callable in sys.path_hooks:
                        try:
                            finder = callable(path)
                            break
                        except ImportError:
                            continue
                    if finder:
                        sys.path_importer_cache[path] = finder
                    elif os.path.exists(path):
                        sys.path_importer_cache[path] = None
                    else:
                        sys.path_importer_cache[path] = # a finder which always returns None
                    if finder:
                        module = do_load(finder, name, parent_pkg)
                        if module:
                            return module
            raise ImportError
    
    def do_load(finder, name, parent_pkg):
        '''加载'''
    
        path = parent_pkg.__path__ if parent_pkg else None
        loader = finder.find_module(name, path)
        if loader:
            return loader.load_module(name)
        else:
            return None
    

    4、sys.path

    正如 『导入过程』 中所述,sys.path是 不在包中的模块(如import M)的“搜索路径”。在这种情况下,控制sys.path就能控制模块的导入过程。

    sys.path 是一个路径名的列表,按照先后顺序,其中的路径主要分为以下四块:

    • 程序主目录(默认定义):如果是以脚本方式启动的程序,则为 启动脚本所在目录;如果在交互式命令行中,则为 当前目录
    • PYTHONPATH目录(可选扩展):以 os.pathsep 分隔的多个目录名,即环境变量os.environ['PYTHONPATH'](类似shell环境变量PATH)
    • 标准库目录(默认定义):Python标准库所在目录(与安装目录有关)
    • .pth文件目录(可选扩展):以“.pth”为后缀的文件,其中列有一些目录名(每行一个目录名),用法参考 site

    为了控制sys.path,可以有三种选择:

    • 直接修改sys.path列表
    • 使用PYTHONPATH扩展
    • 使用.pth文件扩展

    六、重新加载

    关于导入,还有一点非常关键:加载只在第一次导入时发生。这是Python特意设计的,因为加载是个代价高昂的操作。

    通常情况下,如果模块没有被修改,这正是我们想要的行为;但如果我们修改了某个模块,重复导入不会重新加载该模块,从而无法起到更新模块的作用。有时候我们希望在 运行时(即不终止程序运行的同时),达到即时更新模块的目的,内建函数reload() 提供了这种 重新加载 机制。

    关键字reloadimport不同:

    • import是语句,而reload是内建函数
    • import使用 模块名,而reload使用 模块对象(即已被import语句成功导入的模块)

    重新加载(reload(module))有以下几个特点:

    • 会重新编译和执行模块文件中的顶层语句
    • 会更新模块的名字空间(字典 M.__dict__):覆盖相同的名字(旧的有,新的也有),保留缺失的名字(旧的有,新的没有),添加新增的名字(旧的没有,新的有)
    • 对于由import M语句导入的模块M:调用reload(M)后,M.x为 新模块 的属性x(因为更新M后,会影响M.x的求值结果)
    • 对于由from M import x语句导入的属性x:调用reload(M)后,x仍然是 旧模块 的属性x(因为更新M后,不会影响x的求值结果)
    • 如果在调用reload(M)后,重新执行import M(或者from M import x)语句,那么M.x(或者x)为 新模块 的属性x

    七、相对导入

    严格来说,模块(或包)的导入方式分为两种:绝对导入 和 相对导入。以上讨论的导入方式都称为 绝对导入,这也是Python2.7的默认导入方式。相对导入是从Python2.5开始引入的,主要用于解决“用户自定义模块可能会屏蔽标准库模块”的问题(参考 Rationale for Absolute Imports)。

    相对导入 使用前导的“.”来指示importee(即被导入模块或包)与importer(当前导入模块)之间的相对位置关系。相对导入只能使用from <> import风格的导入语句,import <>风格的导入语句只能用于 绝对导入。(相对导入的更多细节,请参考PEP 328

    1、导入语句

    例如有一个包的布局如下:

    pkg/
        __init__.py
        subpkg1/
            __init__.py
            modX.py
            modY.py
        subpkg2/
            __init__.py
            modZ.py
        modA.py
    

    假设当前在文件modX.py或subpkg1/__init__.py中(即当前包为subpkg1),那么下面的导入语句都是相对导入:

    from . import modY            # 从当前包(subpkg1)中导入模块modY
    from .modY import y           # 从当前包的模块modY中导入属性y
    from ..subpkg2 import modZ    # 从当前包的父包(pkg)的包subpkg2中导入模块modZ
    from ..subpkg2.modZ import z  # 从当前包的父包的包subpkg2的模块modZ中导入属性z
    from .. import modA           # 从当前包的父包中导入模块modA
    from ..modA import a          # 从当前包的父包的模块modA中导入属性a
    

    2、导入原理

    与绝对导入不同,相对导入的导入原理比较简单:根据 模块的__name__属性 和 由“.”指示的相对位置关系 来搜索并加载模块(参考 Relative Imports and __name__)。

    3、直接执行

    由于相对导入会用到模块的__name__属性,而在直接执行的主模块中,__name__值为__main__(没有包与模块的信息),所以在主模块中:尽量全部使用绝对导入。

    如果非要使用相对导入,也可以在顶层包(top-level package)的外部目录下,以模块方式执行主模块:python -m pkg.mod(假设顶层包为pkg,mod为主模块,其中使用了相对导入)。(具体参考 PEP 366

     
     
     
    标签: PythonPython基础
  • 相关阅读:
    新手建站必看
    88.com域名邮箱免费注册了
    屏蔽博客园的广告
    跳过烦人的hCaptcha验证
    pap.er 专为 Mac 设计的壁纸应用
    TrafficMonitor
    利用CloudFlare自动DDNS
    P.SDA1.DEV
    谷歌浏览器又隐藏的HTTPS和WWW前缀
    谷歌浏览器扩展 crx 下载
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3329424.html
Copyright © 2011-2022 走看看