模块

您已经看到如何通过定义函数在程序中重用代码。如果你想在其它程序中重用一组函数,怎么办?你可能已经猜到了,答案就是模块。

编写模块有各种各样的方法,但是最简单的方法是创建一个以.py为扩展名,包含函数和变量的文件。

编写模块的另一种方式就像是编写Python解释器一样,可以使用C 编程语言编写模块。当它们被编译后,当使用标准的Python解释器时,你可以在Python代码中使用这些模块。

一个模块可以通过imported(导入)另一个程序而使用其功能。我们可以通过同样的方法使用Python标准库。首先 ,我们看一下如何使用标准库模块。

例子 (保存为module_using_sys.py):

import sys

print('命令行参数是:')
for i in sys.argv:
    print(i)

print('\n\nPYTHONPATH是', sys.path, '\n')

输出:

$ python module_using_sys.py we are arguments
命令行参数是:
module_using_sys.py
we
are
arguments


PYTHONPATH是 ['d:\\KanCloud\\from-python-to-django\\a-byte-of-python3', 'C:\\Users\\boris\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 
# 还有很多,这里不一一列出
'C:\\Users\\boris\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages']

它是如何工作的:

首先,我们使用import语句import(导入)sys(系统)模块。通常情况下,这意味着我们告诉Python,我们想使用这个模块。sys模块包含了与Python解释器和其运行环境(如操作系统)有关的函数。

当Python执行import sys语句时,它会查找sys模块。在这里,它是一个内建模块,因此,Python知道到去哪里找到它。

如果它还没有被编译,也就是Python的源代码,那么,Python解释器将在sys.path目录列表中搜索。如果找到了这个模块,那么就执行模块中的代码,对你的程序来说,这个模块就变成有效的。注意,初始化只有在我们第一次导入一个模块时完成。

我们通过点符号访问sys模块中的argv变量,就像这样sys.argv。它表明,argv这个名字是sys模块的一部分。这种方法的另一个优点是,如果你的程序中使用了名为argv的变量,那么他们不会冲突。

sys.argv变量一个字符串list(列表)(我们会在后面的章节详细介绍)。具体来说,sys.argv包含命令行参数的列表,也就是使用命令行向你的程序传递的参数清单。

如果您正在使用IDE编写并运行这些程序,在菜单中寻找一种方法来指定命令行参数传递给你的程序。

这里,当我们执行python module_using_sys.py we are arguments时,我们使用 python命令和后面的参数运行module_using_sys.py模块。Python把命令行参数存储在sys.argv变量中供我们使用。

记住,运行脚本的名字通常是sys.argv列表中的第一个参数。因此,这里的sys.argv[0]'module_using_sys.py'sys.argv[1]'we'sys.argv[2]'are'sys.argv[3]'arguments'。注意,Python从0而不是1开始计数。

sys.path包含可以被导入的模块所在的目录名列表。我们注意到sys.path的第一个字符串就是程序的当前路径。这意味着你可以直接导入位于当前目录中的模块。否则,你必须把你的模块放在sys.path列表中的一个目录中。

请注意,当前目录是程序启动的目录。运行import os; print(os.getcwd())找到你的程序的当前目录。

字节编译的.pyc文件

导入整个模块是一个代价相对较高的事情,所以Python提供了一些技巧使它可以效率更高。一种方法是创建扩展名为.pyc字节编译文件,这是Python将程序转换成的一种中间形式(我们在简介中介绍过Python是如何工作的。当你下次从一个不同程序导入模块时,这种.pyc文件是有很用的--它将快得多,因为导入模块一部分需要处理的工作已经完成了。同时,这些字节编译的文件是与操作系统平台无关的。

注意:这些.pyc文件通常在与之相应的.py文件的同一个目录中创建。如果Python在那个目录中没有写入权限,那么就无法创建.pyc文件。

from ... import语句

如果你想直接导入argv变量到程序中(为了避免每次为它键入sys.),可以使用from sys import argv语句。

一般来说,应该避免使用这个语句,尽量使用import语句,这样可以在程序中避免名称冲突,使你的程序更具有可读性。

例如:

from math import sqrt
print("16的平方根是", sqrt(16))

模块的__name__

每个模块都有一个名字,在模块内部的语句可以获得它所在的模块的名字。当我们想搞清楚模块是直接运行还是被导入的时候,这种设计很方便。前面提到过,当一个模块被第一次导入时,其所包含的代码被执行。我们可以通过使用这个特性,根据模块是直接运行还是从另一个模块被导入,执行不同的操作。我们可以通过使用模块的__name__属性来实现上述功能。

例子 (保存为 module_using_name.py):

if __name__ == '__main__':
    print('这个程序正在被自己运行')
else:
    print('我从别的模块被导入')

输出:

C:\> python module_using_name.py
这个程序是直接运行的
C:\> python3
>>> import using_name
我从被别的模块导入
>>>

它是如何工作的:

每个Python模块都会定义自己的__name__,如果是'__main__',这意味着模块在被用户直接运行,我们可以执行相应的操作。

制作属于你自己的模块

创建自己的模块是很容易的,就像你一直在这样的那样。因为每个Python程序就是一个模块。你只需要确保它有一个.py扩展名。下面的例子会让你明白这一点。

例子 (保存为mymodule.py):

def say_hi():
    print('嗨,这就是我的模块。')

__version__ = '0.1'

上面的是模块的一个例子。正如您看到的,和我们通常的Python程序相比,没有什么特别的。接下来我们看一下如何在我们其它的程序中使用这个模块。

记住,该模块要么放置在我们导入它的程序同一个目录中,要么放置在sys.path目录列表中的一个目录中。

另一个模块(保存为mymodule_demo.py):

import mymodule

mymodule.say_hi()
print('版本', mymodule.__version__)

输出:

C:\> python mymodule_demo.py
嗨,这就是我的模块。
版本 0.1

它是如何工作的:

注意,我们使用相同的点符号来访问模块的成员。Python充分重用相同的符号产生了独特的'Pythonic'的感觉,这样我们不需要总是学习新的方法来做事情。

这是使用from..import语法的一个版本(保存为mymodule_demo2.py):

from mymodule import say_hi, __version__

say_hi()
print('版本', __version__)

mymodule_demo2.pymymodule_demo.py的输出相同。

注意,如果在导入模块中已经有一个__version__名字的声明,这里会有一个冲突。这也可能是因为它是常见的做法--对于每个模块使用这个名字声明它的版本号。因此,我们推荐使用import语句,虽然它可能让你的程序有点长。

你还可以使用:

from mymodule import *

这将导入所有的公共名称如 sayhi,但不会导入_version,因为它始于双下划线。

注意:尽量不要使用import *这种方式,诸如from mymodule import *

Python之禅

Python的一个指导原则是"显式优于隐式"。运行import this去学习更多相关的信息。

dir函数

您可以使用内置的dir()函数列出一个对象定义的标识符。例如,对于一个模块,包括在模块中已经定义的函数、类和变量。

dir()函数有参数。当你给dir()提供一个模块名字时,它返回在那个模块中定义的名字的列表。当没有为其提供参数时, 它返回当前模块中定义的名字的列表。

例如:

C:\>python

>>> import sys 

# 获得属性列表,在这里是sys模块的属性列表
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_base_executable', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_framework', '_getframe', '_git', '_home', '_xoptions', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', '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_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'set_coroutine_wrapper', 'setcheckinterval', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions', 'winver']

# 获得当前模块的属性列表
>>> dir() 
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'sys']

# 创建了一个新变量 'a'
>>> a = 5 

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

# 删除/移除一个名字
>>> del a 

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

>>>

它是如何工作的:

首先,我们在导入的sys模块上使用dir。我们能看到模块包含的巨大的属性列表。

然后,我们使用没有传递参数的dir函数。默认情况下,它返回当前模块的属性列表。注意,导入的模块仍然是这个列表的一部分。

为了看到dir在起作用,我们定义了一个新的变量a,并为其赋值,然后检查dir,我们发现列表中添加了一个同名变量。我们使用del语句移除当前模块的变量或属性,在dir函数的输出中变化再次得到体现。

关于del的一点注意事项--这个语句用于删除一个变量/属性,语句运行后(这里是del a),你不能再访问变量a--就像它从来根本没有存在过。

注意,dir()函数对任何对象都起作用。例如,运行dir('str')来学习str(string)类型的更多知识。

还有一个vars()函数可以列出所有的属性及其值,但并不是在所有情况下都起作用。

包(Packages)

现在,你必须开始观察组织你的程序的层次结构。变量通常在函数内部。函数和全局变量通常在模块内部。如果你想组织模块?这就涉及到打包了。

包只是模块的文件夹,使用一个特殊的__init__.py文件,告诉Python这个文件夹是特殊的,因为它包含Python模块。

假设你想创建一个叫做'世界'的程序包,分装'亚洲'、'非洲'等等,分包按序包含'印度'、'马达加斯加'等等。

这是你的文件结构:

- <在sys.path中现有的一些文件夹>/
    - world/
        - __init__.py
        - asia/
            - __init__.py
            - india/
                - __init__.py
                - foo.py
        - africa/
            - __init__.py
            - madagascar/
                - __init__.py
                - bar.py

包只是为了方便按照层次组织模块。在Python标准库中,你会看到包的许多实例。

小结

就像函数是可重用的一部分程序一样,模块也是可重用的程序。包是组织模块的更高一级的层次结构。Python自带的标准库是一系列包和模块的例子。

我们已经看到了如何使用这些模块和创建我们自己的模块。

接下来,我们将学习一些有趣的称为数据结构的概念。

results matching ""

    No results matching ""