关闭

Python学习笔记之七——模块

标签: python
1172人阅读 评论(0) 收藏 举报
分类:

模块

  当程序越来越大的时候需要把程序进行分类,分别放在不同的文件中,这样易于查找与维护,同时对于公共类、函数等可以放在独立的文件中,这样其他多个程序都可以使用而不必把这些公共性的类、函数等在每个程序中复制一份,这样的文件就叫做模块。
  模块中的定义可以导入到其它模块或主模块中。模块是包含Python定义和声明的文件。文件名就是模块名加上.py 后缀,在模块里面,模块的名字(是一个字符串)可以由全局变量__name__的值得到。
例如:创建名为fibo.py的文件,内容如下:

# Fibonacci numbers module 

def fib(n):    # write Fibonacci series up to n 
    a, b = 0, 1 
    while b < n: 
        print(b, end=' ') 
        a, b = b, a+b 
    print() 

def fib2(n): # return Fibonacci series up to n 
    result = [] 
    a, b = 0, 1 
    while b < n: 
        result.append(b) 
        a, b = b, a+b 
    return result 

进入Python 解释器使用以下命令导入这个模块:

>>> import fibo

这不会直接把fibo中定义的函数的名字导入到当前的符号表中(每个模块都会有一个符号表,记录着这个模块的模块名称、函数名称、成员变量名称等),它只会把模块名字fibo 导入其中,可以通过导入的模块名访问它所拥有的函数、成员变量等。

>>> fibo.fib(1000) 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 
>>> fibo.fib2(100) 
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] 
>>> fibo.__name__ 
'fibo' 

如果这个模块中的一个函数需要频繁使用,可以将它赋给一个本地变量:

>>> fib = fibo.fib 
>>> fib(500) 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 

深入模块

模块导入

   模块可以包含函数、成员变量、可执行语句等,这些语句通常用于初始化模块。它们只在第一次导入时执行,导入的方式有多种:
第一种是直接导入模块名称,就像上面的import fibo 这种只是把自定义的模块名称放到了系统模块的临时符号表中了,要想访问这个模块中的成员或函数还需要使用导入的模块名称去访问,就像上面的例子一样;
第二种导入方式是把这个模块的函数、成员变量名称直接导入到当前系统模块的临时符号表中,例如:

>>> from fibo import fib, fib2 
>>> fib(500) 
1 1 2 3 5 8 13 21 34 55 89 144 233 377 

这样可以直接使用其函数而不必通过其模块名称去访问函数;
第三种是使用 *号导入模块中定义的所有名字:

>>> from fibo import * 
>>> fib(500) 
1 1 2 3 5 8 13 21 34 55 89 144 233 377

不过这种导入方式不会导入以下划线(_)开头的名称,大多数情况下Python程序员不要使用这个便利的方法,因为它会引入一系列未知的名称到解释器中,这很可能隐藏你已经定义的一些东西。注意一般情况下不赞成从一个模块或包中导入 * ,因为这通常会导致代码很难读。不过,在交互式会话中这样用是可以的,它可以让你少敲一些代码。

如果程序比较大,需要在一个模块中导入另一个模块(一般只导入模块名称),习惯上将所有的import 语句放在模块(或脚本)的开始,但这不是强制性的,被导入的模块的名字会放在导入模块的全局符号表中。

注意: 出于性能考虑,每个模块在每个解释器会话中只导入一遍。因此,如果你修改了你的模块,你必需重新启动解释器 —— 或者,如果你就是想交互式的测试这么一个模块,可以使用imp.reload(), 例如 import imp; imp.reload(modulename) 即相当于重新导入模块。

执行模块

  当用下面的方式运行一个Python模块

python fibo.py <arguments>

模块中的代码将会被执行,就像导入它一样,不过此时 __name__被设置为__main__,我们把fibo.py 中的代码修改如下:

#Fibonacci numbers module
def fib(n): #write Fibonacci series up to n
    a,b = 0,1
    while b < n:
        print(b,end=' ')
        a,b = b,a+b
    print()

def fib2(n): #return Fibonacci series up to n
    result = []
    a,b = 0,1
    while b<n:
        result.append(b)
        a,b = b,a+b
    return result
print(__name__)

用命令执行如下:

>>>python fibo.py
__main__

如果想让此文件既可以作为可执行脚本,也可以当作导入的模块,那么需要在模块后面加入如下代码:

if __name__ == "__main__": 
    import sys 
    fib(int(sys.argv[1])) 

因为解析命令行的那部分代码只有在模块作为”main”文件执行时才会被调用。

$ python fibo.py 50 
1 1 2 3 5 8 13 21 34 

如果模块是导入的将不会运行这段代码:

>>> import fibo 
>>> 

这种方法通常用来为模块提供一个方便的用户接口,或者用来测试(例如直接运行脚本会执行一组测试用例)。

模块搜索路径

  当导入一个模块名称为spam的模块时,解释器首先会搜索内置模块中是否有这个模块,如果找不到,它会接着到sys.path 变量给出的目录中查找名为spam.py 的文件。sys.path 变量的初始值来自这些位置:
- 脚本所在的目录 (如果没有指明文件,则为当前目录)
- PYTHONPATH (一个包含目录名的列表,与shell 变量PATH 的语法相同)
- 与安装相关的默认值

注:在支持符号链接的文件系统中(就像linux 系统中的软链接),输入的脚本所在的目录是符号链接指向的目录,也就是文件真正存在的目录,包含符号链接的目录不会被加到目录搜索路径中
  当sys.path 初始化后,Python程序可以修改它的值,脚本所在的目录被放置在搜索路径的最开始,也就是在标准库的路径之前。这意味着将会加载当前目录中的脚本,库目录中具有相同名称的模块不会被加载。

编译过的”Python”文件

  为了加快加载模块的速度,Python 会在pycache目录下以 module.version.pyc 名字缓存每个模块编译后的版本,这里的版本编制了编译后文件的格式。它通常会包含Python 的版本 号 。 例 如 , 在 CPython 3.3 版 中 , spam.py 编 译 后 的 版 本 将 缓 存 为pycache/spam.cpython-33.pyc。这种命名约定允许由不同发布和不同版本的 Python 编译的模块同时存在。
  Python 会检查源文件与编译版的修改日期以确定它是否过期并需要重新编译。这是完全自动化的过程。同时,编译后的模块是跨平台的,所以同一个库可以在不同架构的系统之间共享。
注: Python 在两种情况下不会检查缓存。
1. 那些从命令行加载的模块,它不会存储它,所以如果这些模块有变化则需要重新加载,或者用reload() 函数重新加载
2. 如果没有源模块它不会检查缓存。 若要支持没有源文件(只有编译版)的发
布,编译后的模块必须在源目录下,并且源目录下不能有源文件。

标准模块

  Python 带有一个标准模块库,并发布有单独的文档叫Python 库参考手册(以下简称”库参考手册”)。有些模块被直接构建在解释器里,这些操作虽然不是语言核心的部分,但是依然被内建进来,一方面是效率的原因,另一方面是为了提供访问操作系统元语如系统调用的功能。这些模块是可配置的,也取决于底层的平台。例如,winreg 模块只在 Windows 系统上提供。有一个特别的模块需要注意: sys,它内置在每一个 Python 解释器中。变量 sys.ps1 和 sys.ps2 定义了主提示符和辅助提示符使用的字符串:

>>> import sys 
>>> sys.ps1
'>>> ' 
>>> sys.ps2
'... ' 
>>> sys.ps1 = 'C> ' 
C> print('Yuck!') 
Yuck! 
C>

只有在交互式模式中,这两个变量才有定义。

变量 sys.path 是一个字符串列表,它决定了解释器搜索模块的路径。它初始的默认路径来自于环境变量 PYTHONPATH,如果 PYTHONPATH 未设置则来自于内置的默认值。
你可以使用标准的列表操作修改它:

>>> import sys 
>>> sys.path.append('/ufs/guido/lib/python')

dir()函数

内置函数 dir() 用来找出模块中定义了哪些名字。它返回一个排好序的字符串列表:

>>> import fibo,sys
fibo
>>> dir(fibo)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fib', 'fib2']
>>> dir(sys)
['__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_getframe', '_home', '_mercurial', '_xoptions', 'abiflags', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getdlopenflags', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'ps1', 'ps2', 'set_coroutine_wrapper', 'setcheckinterval', 'setdlopenflags', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions']

如果不带参数, dir() 列出当前已定义的名称(当前会话显式定义的和程序隐式定义的):

>>> a = [1, 2, 3, 4, 5] 
>>> import fibo 
>>> fib = fibo.fib 
>>> dir() 
['__builtins__', '__name__', 'a', 'fib', 'fibo', 'sys'] 

注意它列出了所有类型的名称: 变量、 模块、 函数等。
dir()不会列出内置的函数和变量的名称。如果你想列出这些内容,它们定义在标准模块,builtins 中:

>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

   包是一种管理Python 模块命名空间的方式,采用”点分模块名称”.例如,模块名A.B 表示包A中一个名为B的子模块。就像模块的使用让不同模块的作者不用担心相互间的全局变量名称一样了怎么办,点分模块的使用让包含多个模块的包(例如Numpy 和Python Imaging Library)的作者也不用担心相互之间的模块重名。
  假如你想要设计一系列模块(或一个“包”)来统一处理声音文件和声音数据。现存很多种不同的声音文件格式 (通常由它们的扩展名来识别,例如: .wav, .aiff, .au),因此你可能需要创建和维护不断增长的模块集合来支持各种文件格式之间的转换。你可能还想针对音频数据做很多不同的操作(比如混音,添加回声,增加均衡器功能,创建人造立体声效果),所以你还需要编写一组永远写不完的模块来处理这些操作。你的包可能会是这个结构(用分层的文件系统表示):

sound/                          Top-level package 
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py 
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py 
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects 
              __init__.py
              echo.py 
              surround.py 
              reverse.py 
              ... 
      filters/                  Subpackage for filters 
              __init__.py 
              equalizer.py 
              vocoder.py
              karaoke.py 
              ...  

导入这个包的时,Python 搜索sys.path 中的目录以寻找这个包的子目录。
注意: 为了让Python将目录当做包,目录下面必须包含__init__.py文件;这样做是为了防止一个具有常见名字(string) 的目录无意中隐藏目录搜索路径中正确的模块。最简单的情况下,__init__.py可以只是一个空文件,但它也可以为包执行初始化代码或设置__all__ 变量。
用户可以从包中导入单独的模块,例如:

import sound.effects.echo 

这样就加载了子模块sound.effects.echo. 引用的时候必须使用其完整名称.

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4) 

导入子模块的另一方法是:

from sound.effects import echo

这样也加载了子模块echo,但是它可以不用包前缀访问,因此它可以按如下方式使用:

echo.echofilter(input, output, delay=0.7, atten=4) 

还有另一种变化方式是直接导入所需的函数或变量:

from sound.effects.echo import echofilter

这再次加载了子模块echo,但这种方式使函数echofilter() 可以直接访问:

echofilter(input,output,delay=0.7,atten=4)

注意使用 from package import item时,item 可以是包的子模块(或子包),也可以是包中定义的一些其它的名称,比如函数、类或者变量。import 语句首先测试item 在包中是否有定义;如果没有,它假定它是一个模块,并尝试加载它。如果未能找到,则引发ImportError 异常。
相反,使用类似import item.subitem.subsubitem 这样的语法时,除了最后一项其他每项必须是一个包;最后一项可以是一个模块或一个包,但不能是在前一个项目中定义的类、函数或变量。

从包中导入 *

   那么现在如果用户写成 from sound.effects import * 会发生什么?理想情况下,他应该是希望到文件系统中寻找包里面有哪些子模块,并把它们全部导入进来。这可能需要很长时间,而且导入子模块可能会产生想不到的副作用,这些副作用本应该只有当子模块是显式导入时才会发生。
  唯一的解决办法就是包的作者为包提供显式的索引。import 语句使用以下约定:如果包中的init.py 代码定义了一个名为 __all__的列表,那么在遇到from package import * 语句的时候,应该把这个列表中的所有模块名字导入。当包有新版本包发布时,就需要包的作者更新这个列表了。如果包的作者认为不可以用import * 方式导入它们的包,也可以决定不支持它。例如,文件 sound/effects/__init__.py 可以包含下面的代码:

__all__ =["echo","surround","reverse"]

这意味着 from sound.effects import * 将只允许导入sound 包的三个子模块,其他模块是无法导入的。
当然如果 __all__没有定义,from sound.effects import *语句不会从sound.effects 包中导入所有的子模块到当前命名空间;它只保证sound.effects 包已经被导入(可能会运行__init__.py 中的任何初始化代码),然后导入包中定义的任何名称(也就是这种导入方式最终导入的到底是什么最终还要看__init__.py 中的代码)。这包括由__init__.py 定义的任何名称(以及它显式加载的子模块)。还包括这个包中已经由前面的import 语句显式加载的子模块。分析以下代码:

import sound.effects.echo 
import sound.effects.surround 
from sound.effects import *

在这个例子中,执行 from…import 语句时,echo 和 surround 模块被导入到当前命名空间是因为它们在sound.effects中有定义。(定义了 all时也会同样工作。)
某些模块设计成使用 import * 时只导出符合特定模式的名称。即便如此,在产品代码中使用这种写法仍然是不好的做法。
记住,使用 from Package import specific_submodule 一点没错 !事实上,这是推荐的写法,除非导入的模块需要使用其它包中的同名子模块。

包内引用

如果一个包是子包(比如例子中的sound 包),你可以使用绝对导入来引用兄弟包的子模块。例如,如果模块 sound.filters.vocoder 需要使用 sound.effects 包中的 echo 模块,它可以使用from sound.effects import echo.

你还可以用from module import name形式的import语句进行相对导入。这些导入使用前导的点号表示相对导入的是从当前包还是上级的包。以 surround 模块为例,你可以使用:

from . import echo 
from .. import formats 
from ..filters import equalizer 

注意: 相对导入基于当前模块的名称。因为主模块的名字是”main” ,Python 应用程序的主模块必须总是用绝对导入。

包含多个目录的包

包还支持一个特殊的属性,__path__.该变量初始化一个包含__init__.py所在目录的列表。这个变量可以修改;如果修改的话会影响未来包中包含的模块和子包的搜索。虽然通常不需要这个功能,但它可以用于扩展包中的模块的集合。
注: 事实上函数定义同样是‘被执行’的‘语句’(和语句被执行是类似的);模块级函数定义的执行使该函数的名称存入了模块的全局符号表中。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:66219次
    • 积分:951
    • 等级:
    • 排名:千里之外
    • 原创:27篇
    • 转载:3篇
    • 译文:4篇
    • 评论:10条
    最新评论