设为首页 收藏本站
查看: 1121|回复: 0

[经验分享] Python基础:模块

[复制链接]

尚未签到

发表于 2015-4-19 06:23:25 | 显示全部楼层 |阅读模式

  • 一、概述
  • 二、导入语句

    • 1、基本语法
    • 2、推荐风格


  • 三、模块

    • 1、模块名
    • 2、模块属性
    • 3、可导出的公有属性
    • 4、直接执行


  • 四、包

    • 1、包名
    • 2、包属性
    • 3、可导出的公有属性
    • 4、其他


  • 五、导入原理

    • 1、导入依赖
    • 2、导入过程
    • 3、更多细节
    • 4、sys.path


  • 六、重新加载
  • 七、相对导入

    • 1、导入语句
    • 2、导入原理
    • 3、直接执行



一、概述
  模块(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) \n[GCC 4.6.3]'
>>> import sys as s
>>> s.version
'2.7.3 (default, Apr 10 2013, 05:46:21) \n[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后缀”后的文件名,即os.path.splitext(os.path.basename(__file__))[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

  2)以模块方式执行

python -m mod

四、包
  一个 就是一个含有__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


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

$ mkdir spam && touch spam/__init__.py
$ python
...
>>> import spam
>>> spam


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 "", line 1, in
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.module或from 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() 提供了这种 重新加载 机制。
  关键字reload与import不同:


  • 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)

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-58483-1-1.html 上篇帖子: Python中的random模块 下篇帖子: 用python读写excel(xlrd、xlwt)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表