自带电池)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自带电池)相关的知识,希望对你有一定的参考价值。

本文内容全部出自《Python基础教程》第二版,在此分享自己的学习之路。

______欢迎转载:http://www.cnblogs.com/Marlowes/p/5459376.html______

 

                                Created on Marlowes

 

现在已经介绍了Python语言的大部分基础知识。Python语言的核心非常强大,同时还提供了更多值得一试的工具。Python的标准安装中还包括一组模块,称为标准库(standard library)。之前已经介绍了一些模块(例如math和cmath,其中包括了用于计算实数和复数的数学函数),但是标准库还包含其他模块。本章将向读者展示这些模块的工作方式,讨论如何分析它们,学习它们所提供的功能。本章后面的内容会对标准库进行概括,并且着重介绍一部分有用的模块。

 

10.1 模块

现在你已经知道如何创建和执行自己的程序(或脚本)了,也学会了怎么用import从外部模块获取函数并且为自己的程序所用:

>>> import math
>>> math.sin(0)
0.0

让我们来看看怎样编写自己的模块。

 

10.1.1 模块是程序

任何Python程序都可以作为模块导入。假设你写了一个代码清单10-1所示的程序,并且将它保存为hello.py文件(名字很重要)。

代码清单10-1  一个简单的模块

# hello.py
print "Hello, world!"

程序保存的位置也很重要。下一节中你会了解更多这方面的知识,现在假设将它保存在C:\\python(Windows)或者~/python(UNIX/Mac OS X)目录中,接着就可以执行下面的代码,告诉解释器在哪里寻找模块了(以Windows目录为例):

>>> import sys
>>> sys.path.append("c:/python")

注:在UNIX系统中,不能只是简单地将字符串"~/python"添加到sys.path中,必须使用完整的路径(例如/home/yourusername/python)。如果你希望将这个操作自动完成,可以使用sys.path.expanduser("~/python")。

我这里所做的知识告诉解释器:除了从默认的目录中寻找之外,还需要从目录c:\\python中寻找模块。完成这个步骤之后,就能导入自己的模块了(存储在c:\\python\\hello.py文件中):

>>> import hello
Hello, world!

注:在导入模块的时候,你可能会看到有新文件出现——在本例中是c:\\python\\hello.pyc。这个以.pyc为扩展名的文件是(平台无关的)经过处理(编译)的,已经转换成Python能够更加有效地处理的文件。如果稍后导入同一个模块,Python会导入.pyc文件而不是.py文件,除非.py文件已改变,在这种情况下,会生成新的.pyc文件。删除.pyc文件不会损害程序(只要等效的.py文件存在即可)——必要的时候系统还会创建新的.pyc文件。

如你所见,在导入模块的时候,其中的代码被执行了。不过,如果再次导入该模块,就什么都不会发生了:

>>> import hello
>>>

为什么这次没用了?因为导入模块并不意味着在导入时执行某些操作(比如打印文本)。它们主要用于定义,比如变量、函数和类等。此外,因为只需要定义这些东西一次,导入模块多次和导入一次的效果是一样的。

为什么只是一次

 这种“只导入一次”(import-only-once)的行为在大多数情况下是一种实质性优化,对于一下情况尤其重要:两个模块互相导入。

在大多数情况下,你可能会编写两个互相访问函数和类的模块以便实现正确的功能。举例来说,假设创建了两个模块——clientdb和billing——分别包含了用于客户端数据库和计费系统的代码。客户端数据库可能需要调用计费系统的功能(比如每月自动将账单发送给客户),而计费系统可能也需要访问客户端数据库的功能,以保证计费准确。

如果每个模块都可以导入数次,那么就出问题了。模块clientdb会导入billing,而billing又导入clientdb,然后clientdb又······你应该能想象到这种情况。这个时候导入就成了无限循环。(无限递归,记得吗?)但是,因为在第二次导入模块的时候什么都不会发生,所以循环会终止。

如果坚持重新载入模块,那么可以使用内建的reload函数。它带有一个参数(需要重新载入的模块),并且返回重新载入的模块。如果你在程序运行的时候更改了模块并且希望将这些更改反应出来,那么这个功能会比较有用。要重新载入hello模块(只包含一个print语句),可以像下面这样做:

>>> hello = reload(hello)
Hello, world!

这里假设hello已经被导入过(一次)。那么,通过将reload函数的返回值赋给hello,我们使用重新载入的版本替换了原先的版本。如你所见,问候语已经打印出来了,在此我完成了模块的导入。

如果你已经通过实例化bar模块中的Foo类创建了一个对象x,然后重新载入bar模块,那么不管通过什么方式都无法重新创建引用bar的对象x,x仍然是旧版本Foo类的实例(源自旧版本的bar)。如果需要x基于重新载入的模块bar中的新Foo类进行创建,那么你就得重新创建它了。

注意,Python3.0已经去掉了reload函数。尽管使用exec能够实现同样的功能,但是应该尽可能避免重新载入模块。

 

10.1.2 模块用于定义

综上所述,模块在第一次导入到程序中时被执行。这看起来有点用——但并不算很有用。真正的用处在于它们(像类一样)可以保持自己的作用域。这就意味着定义的所有类和函数以及赋值后的变量都成为了模块的特性。这看起来挺复杂的,用起来却很简单。

1.在模块中定义函数

假设我们编写了一个类似代码清单10-2的模块,并且将它存储为hello2.py文件。同时,假设我们将它放置到Python解释器能够找到的地方——可以使用前一节中的sys.path方法,也可以用10.1.3节中的常规方法。

注:如果希望模块能够像程序一样被执行(这里的程序是用于执行的,而不是真正作为模块使用的),可以对Python解释器使用-m切换开关来执行程序。如果progname.py(注意后缀)文件和其他模块都已被安装(也就是导入了progname),那么运行python -m progname args命令就会运行带命令行参数args的progname程序。

代码清单10-2  包含函数的简单模块

# hello2.py
def hello():
    print "Hello, world!"

可以像下面这样导入:

>>> import hello2

模块就会被执行,这意味着hello函数在模块的作用域被定义了。因此可以通过以下方式来访问函数:

>>> hello2.hello()
Hello, world!

我们可以通过同样的方法来使用如何在模块的全局作用域中定义的名称。

我们为什么要这样做呢?为什么不在主程序中定义好一切呢?主要原因是代码重用(code reuse)。如果把代码放在模块中,就可以在多个程序中使用这些代码了。这意味着如果编写了一个非常棒的客户端数据库,并且将它放在叫做clientdb的模块中,那么你就可以在计费的时候、发送垃圾邮件的时候(当然我可不希望你这么做)以及任何需要访问客户数据的程序中使用这个模块了。如果没有将这段代码放在单独的模块中,那么就需要在每个程序中重写这些代码了。因此请记住:为了让代码可重用,请将它模块化!(是的,这当然也关乎抽象)

2.在模块中增加测试代码

模块被用来定义函数、类和其他一些内容,但是有些时候(事实上是经常),在模块中添加一些检查模块本身是否能正常工作的测试代码是很有用的。举例来说,假如想要确保hello函数正常工作,你可能会将hello2模块重写为新的模块——代码清单10-3中定义的hello3。

# hello3.py
def hello():
    print "Hello, world!"

# A test
hello()

这看起来是合理的,如果将它作为普通程序运行,会发现它能够正常工作。但如果将它作为模块导入,然后在其他程序中使用hello函数,测试代码就会被执行,就像本章实验开头的第一个hello模块一样:

>>> import hello3
Hello, world!
>>> hello3.hello.()
Hello, world!

这个可不是你想要的。避免这种情况关键在于:“告知”模块本身是作为程序运行还是导入到其他程序。为了实现这一点,需要使用__name__变量:

>>> __name__
__main__
>>> hello3.__name__
hello3

如你所见,在“主程序”(包括解释器的交互式提示符在内)中,变量__name__的值是‘__main__‘。而在导入的模块中,这个值就被设定为模块的名字。因此,为了让模块的测试代码更加好用,可以将其放置在if语句中,如代码清单10-4所示。

代码清单10-4  使用条件测试代码的模块
# hello4.py

def hello():
    print "Hello, world!"

def test():
    hello()

if __name__ = __main__:
    test()

如果将它作为程序运行,hello函数会被执行。而作为模块导入时,它的行为就会像普通模块一样:

>>> import hello4
>>> hello4.hello()
Hello, world!

如你所见,我将测试代码放在了test函数中,也可以直接将它们放入if语句。但是,将测试代码放入独立的test函数会更灵活,这样做即使在把模块导入其他程序之后,仍然可以对其进行测试:

>>> hello4.test()
Hello, world!

注:如果需要编写更完整的测试代码,将其放置在单独的程序中会更好。关于编写测试代码的更多内容,参见第16章。

 

10.1.3 让你的模块可用

前面的例子中,我改变了sys.path,其中包含了(字符串组成的)一个目录列表,解释器在该列表中查找模块。然而一般来说,你可能不想这么做。在理想情况下,一开始sys.path本身就应该包含正确的目录(包括模块的目录)。有两种方法可以做到这一点:一是将模块放置在合适的位置,另外则是告诉解释器去哪里查找需要的模块。下面几节将探讨这两种方法。

1.将模块放置在正确位置

将模块放置在正确位置(或者说某个正确位置,因为会有多种可能性)是很容易的。只需要找出Python解释器从哪里查找模块,然后将自己的文件放置在那里即可。

注:如果机器上面的Python解释器是由管理员安装的,而你又没有管理员权限,可能无法将模块存储在Python使用的目录中。这种情况下,你需要使用另外一个解决方案:告诉解释器去那里查找。

你可能记得,那些(成为搜索路径的)目录的列表可以在sys模块中的path变量中找到:

>>> import sys, pprint
>>> pprint.pprint(sys.path)
[‘‘,
 /usr/lib/python2.7,
 /usr/lib/python2.7/plat-x86_64-linux-gnu,
 /usr/lib/python2.7/lib-tk,
 /usr/lib/python2.7/lib-old,
 /usr/lib/python2.7/lib-dynload,
 /usr/local/lib/python2.7/dist-packages,
 /usr/lib/python2.7/dist-packages,
 /usr/lib/python2.7/dist-packages/PILcompat,
 /usr/lib/python2.7/dist-packages/gtk-2.0,
 /usr/lib/python2.7/dist-packages/ubuntu-sso-client]

注:如果你的数据结构过大,不能在一行打印完,可以使用pprint模块中的pprint函数替代普通的print语句。pprint是个相当好的打印函数,能够提供更加智能的打印输出。

这是安装在elementary OS上的Python2.7的标准路径,不同的系统会有不同的结果。关键在于每个字符串都提供了一个放置模块的目录,解释器可以从这些目录中找到所需的模块。尽管这些目录都可以使用,但site-packages目录是最佳的选择,因为它就是用来做这些事情的。查看你自己的sys.path,找到site-packages目录,将代码清单10-4的模块存储在其中,要记得改名,比如改成another_hello.py,然后测试:

>>> import another_hello
>>> another_hello.hello()
Hello, world!

只要将模块放入类似site-packages这样的目录中,所有程序就都能将其导入了。

2.告诉编译器去那里找

“将模块放置在正确的位置”这个解决方案对于以下几种情况可能并不适用:

? 不希望将自己的模块填满Python解释器的目录;

? 没有在Python解释器目录中存储文件的权限;

? 想将模块放在其他地方。

最后一点是“想将模块放在其他地方”,那么就要告诉解释器去哪里找。你之前已经看到了一种方法,就是编辑sys.path,但这不是通用的方法。标准的实现方法是在PYTHONPATH环境变量中包含模块所在的目录。

PYTHONPATH环境变量的内容会因为使用的操作系统不同而有所差异(参见下面的“环境变量”),但基本上来说,它与sys.path很类似——一个目录列表。

环境变量

环境变量并不是Python解释器的一部分——它们是操作系统的一部分。基本上,它相当于Python变量,不过是在Python解释器外设置的。有关设置的方法,你应该参考操作系统文档,这里只给出一些相关提示。

在UNIX和Mac OS X中,你可以在一些每次登陆都要执行的shell文件内设置环境变量。如果你使用类似bash的shell文件,那么要设置的就是.bashrc,你可以在主目录中找到它。将下面的命令添加到这个文件中,从而将~/python加入到PYTHONPATH:

export PYTHON=$PYTHONPATH:~/python

注意,多个路径以冒号分隔。其他的shell可能会有不同的语法,所以你应该参考相关的文档。

对于Windows系统,你可以使用控制面板编辑变量(适用于高级版本的Windows,比如Windows XP、2000、NT和Vista,旧版本的,比如Windows 98就不适用了,而需要修改autoexec.bat文件,下段会讲到)。依次点击开始菜单→设置→控制面板。进入控制面板后,双击“系统”图标。在打开的对话框中选择“高级”选项卡,点击“环境变量”按钮。这时会弹出一个分为上下两栏的对话框:其中一栏是用户变量,另外一栏就是系统变量,需要修改的是用户变量。如果你看到其中已经有PYTHONPATH项,那么选中它,单击“编辑”按钮进行编辑。如果没有,单击“新建”按钮,然后使用PYTHONPATH作为“变量名”,输入目录作为“变量值”。注意,多个目录以分号分分隔。

如果上面的方法不行,你可以编辑autoexec.bat文件,该文件可以在C盘的根目录下找到(假设是以标准模式安装的Windows)。用记事本(或者IDLE编辑器)打开它,增加一行设置PYTHONPATH的内容。如果想要增加目录C:\\pyrhon。可以像下面这样做:

set PYTHONPATH=%PYTHONPATH%;C:\\python

注意,你所使用的IDE可能会有自身的机制,用于设置环境变量和Python路径。

注:你不需要使用PYTHONPATH来更改sys.path。路径配置文件提供了一个有用的捷径,可以让Python替你完成这些工作。路径配置文件是以.pth为扩展名的文件,包括应该添加到sys.path中的目录信息。空行和以#开头的行都会被忽略。以import开头的文件会被执行。为了执行路径配置文件,需要将其放置在可以找到的地方。对于Windows来说,使用sys.prefix定义的目录名(可能类似于C:\\Python22);在UNIX和Mac OS X中则使用site-packages目录(更多信息可以参见Python库参考中site模块的内容,这个模板在Python解释器初始化时会自动导入)。

3.命名模块

你可能注意到了,包含模块代码的文件的名字要和模块名一样,再加上.py扩展名。在Windows系统中,你也可以使用.pyw扩展名。有关文件扩展名含义的更多信息请参见第12章。

 

10.1.4 包

为了组织好模块,你可以将它们分组为(package)。包基本上就是另外一个类模块,有趣的地方就是它们能包含其他模块。当模块存储在文件中时(扩展名.py),包就是模块所在的目录。为了让Python将其作为包对待,它必须包含一个命名为__init__.py的文件(模块)。如果将它作为普通模块导入的话,文件的内容就是包的内容。比如有个名为constants的包,文件constants/__init__.py包括语句PI=3.14,那么你可以像下面这么做:

import constants
print constants.PI

为了将模块放置在包内,直接把模块放在包目录内即可。

比如,如果要建立一个叫做drawing的包,其中包括名为shapes和colors的模块,你就需要创建表10-1中所示的文件和目录(UNIX路径名)。

表10-1 简单的包布局

~/python/                      PYTHONPATH中的目录

~/python/drawing/                   包目录(drawing包)

~/python/drawing/__init__.py            包代码(drawing模块)

~/python/drawing/colors.py             colors模块

~/python/drawing/shapes.py              shapes模块

对于表10-1中的内容,假定你已经将目录~/python放置在PYTHONPATH。在Windows系统中,只要用C:\\python替换~/python,并且将正斜线为反斜线即可。

依照这个设置,下面的语句都是合法的:

import drawing                    # (1) Imports the drawing package
import drawing.colors             # (2) Imports the colors module
from drawing import shapes        # (3) Imports the shapes module

在第1条语句drawing中__init__模块的内容是可用的,但drawing和colors模块则不可用。在执行第2条语句之后,colors模块可用了,可以通过短名(也就是仅使用shapes)来使用。注意,这些语句只是例子,执行顺序并不是必需的。例如,不用像我一样,在导入包的模块前导入包本身,第2条语句可以独立使用,第3条语句也一样。我们还可以在包之间进行嵌套。

 

10.2 探究模块

在讲述标准库模块前,先教你如何独立地探究模块。这种技能极有价值,因为作为Python程序员,在职业生涯中可能会遇到很多有用的模块,我又不能在这里一一介绍。目前的标准库已经大到可以出本书了(事实上已经有这类书了),而且它还在增长。每次新的模块发布后,都会添加到标准库,一些模块经常发生一些细微的变化和改进。同时,你还能在网上找到些有用的模块并且可以很快理解(grok)它们,从而让编程轻而易举地称为一种享受。

 

10.2.1 模块中有什么

探究模块最直接的方式就是在Python解释器中研究它们。当然,要做的第一件事就是导入它。假设你听说有个叫做copy的标准模块:

>>> import copy

没有引发异常,所以它是存在的。但是它能做什么?它又有什么?

1.使用dir

查看模块包含的内容可以使用dir函数,它会将对象的所有特性(以及模块的所有函数、类、变量等)列出。如果想要打印出dir(copy)的内容,你会看到一长串的名字(试试看)。一些名字以下划线开始,暗示(约定俗成)它们并不是为在模块外部使用而准备的。所以让我们用列表推导式(如果不记得如何使用了,请参见5.6节)过滤掉它们:

>>> [n for n in dir(copy) if not n.startswith("_")]
[Error, PyStringMap, copy, deepcopy, dispatch_table, error, name, t, weakref]

这个列表推导式是个包含dir(copy)中所有不以下划线开头的名字的列表。它比完整列表要清楚些。(如果喜欢用tab实现,那么应该看看库参考中的readline和rlcompleter模块。它们在探究模块时很有用)

2.__all__变量

在上一节中,通过列表推导式所做的事情是推测我可能会在copy模块章看到什么。但是我们可以直接从列表本身获得正确答案。在完整的dir(copy)列表中,你可能注意到了__all__这个名字。这个变量包含一个列表,该列表与我之前通过列表推导式创建的列表很类似——除了这个列表在模块本身中已被默认设置。我们来看看它都包含哪些内容:

>>> copy.__all__
[Error, copy, deepcopy]

我的猜测还不算太离谱吧。列表推导式得到的列表只是多出了几个我用不到的名字。但是__all__列表从哪来,它为什么会在那儿?第一个问题很容易回答。它是在copy模块内部被设置的,像下面这样(从copy.py直接复制而来的代码):

__all__ = ["Error", "copy", "deepcopy"]

那么它为什么在那呢?它定义了模块的公有接口(public interface)。更准确地说,它告诉解释器:从模块导入所有名字代表什么含义。因此,如果你使用如下代码:

from copy import *

那么,你就能使用__all__变量中的4个函数。要导入PyStringMap的话,你就得显示地实现,或者导入copy然后使用copy.PyStringMap,或者使用from copy import PyStringMap。

在编写模块的时候,像设置__all__这样的技术是相当有用的。因为模块中可能会有一大堆其他程序不需要或不想要的变量、函数和类,__all__会“客气地”将它们过滤了出去。如果没有设定__all__,用import *语句默认将会导入模块中所有不以下划线开头的全局名称。

 

10.2.2 用help获取帮助

目前为止,你已经通过自己的创造力和Python的多个函数和特殊特性的知识探究了copy模块。对于这样的探究工作,交互式解释器是个非常强大的工具,而对该语言的精通程度决定了对模块探究的深度。不过,还有个标准函数能够为你提供日常所需的信息,这个函数叫做help。让我们先用copy函数试试:

>>> help(copy.copy)
Help on function copy in module copy:

copy(x)
    Shallow copy operation on arbitrary Python objects.

    See the modules __doc__ string for more info.

这些内容告诉你:copy带有一个参数x,并且是“浅复制操作”。但是它还提到了模块的__doc__字符串。这是什么呢?你可能记得第六章提到的文档字符串,它就是写在函数开头并且简述函数功能的字符串,这个字符串可以通过函数的__doc__特性引用。就像从上面的帮助文本中所理解到的一样,模块也可以有文档字符串(写在模块开头),类也一样(写在类开头)。

事实上,前面的帮助文本是从copy函数的文档字符串中取出的。

>>> print copy.copy.__doc__
Shallow copy operation on arbitrary Python objects.

    See the modules __doc__ string for more info.

使用help与直接检查文档字符串相比,它的好处在于会获得更多信息,比如函数签名(也就是所带的参数)。试着调用help(copy)(对模块本身)看看得到什么。它会打印出很多信息,包括copy和deepcopy之间区别的透彻的讨论(从本质来说,deepcopy(x)会将存储在x中的值作为属性进行复制,而copy(x)只是复制x,将x中的值绑定到副本的属性上)。

 

10.2.3 文档

模块信息的自然来源当然是文件。我把对文档的讨论推后在这里,是因为自己先检查模块总是快一些。举例来说,你可能会问“range的参数是什么”。不用在Python数据或者标准Python文档中寻找有关range的描述,而是可以直接查看:

>>> print range.__doc__
range(stop) -> list of integers
range(start, stop[, step]) -> list of integers

Return a list containing an arithmetic progression of integers.
range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
When step is given, it specifies the increment (or decrement).
For example, range(4) returns [0, 1, 2, 3].  The end point is omitted!
These are exactly the valid indices for a list of 4 elements.

这样就获得了关于range函数的精确描述,因为Python解释器可能已经运行了(在编程的时候,经常会像这样怀疑函数的功能),访问这些信息花不了几秒钟。

但是,并非每个模块和函数都有不错的文档字符串(尽管都应该有),有些时候可能需要十分透彻地描述这些模块和函数是如何工作的。大多数从网上下载的模块都有相关的文档。在我看来,学习Python编程最有用的文档莫过于Python库参考,它对所有标准库中的模块都有描述。如果想要查看Python的知识。十有八九我都会去查阅它。库参考可以在线浏览(http://python.org/doc/lib),并且提供下载,其他一些标准文档(比如Python指南或者Python语言参考)也是如此。所有这些文档都可以在Python网站上(http://python.org/doc)找到。

 

10.2.4 使用源代码

到目前为止,所讨论的探究技术在大多数情况下都已经够用了。但是,对于希望真正理解Python语言的人来说,要了解模块,是不能脱离源代码的。阅读源代码,事实上是学习Python最好的方式,除了自己编写代码外。

真正的阅读不是问题,但是问题在于源代码在哪里。假设我们希望阅读标准模块copy的源代码,去哪里找呢?一种方案是检查sys.path,然后自己找,就像解释器做的一样。另外一种快捷的方法是检查模块的__file__属性:

>>> print copy.__file__
C:\\Python27\\lib\\copy.pyc

注:如果文件名以.pyc结尾,只要查看对应的以/py结尾的文件即可。

就在那!你可以使用代码编辑器打开copy.py(比如IDLE),然后查看它是如何工作的。

注:在文本编辑器中打开标准库文件的时候,你也承担着意外修改它的风险。这样做可能会破坏它,所以在关闭文件的时候,你必须确保没有保存任何可能做出的修改。

注意,一些模块并不包含任何可以阅读的Python源代码。它们可能已经融入到解释器内了(比如sys模块),或者可能是使用C程序语言写成的(如果模块是使用C语言编写的,你也可以查看它的C源代码)。(请查看第17章以获得更多使用C语言扩展Python的信息)

 

10.3 标准库:一些最爱

有的读者会觉得本章的标题不知所云。“充电时刻”(batteries included)这个短语最开始由Frank Stajano创造,用于描述Python丰富的标准库。安装Python后,你就“免费”获得了很多有用的模块(充电电池)。因为获得这些模块的更多信息的方式很多(在本章的第一部分已经解释过了),我不会在这里列出完整的参考资料(因为要占去很大篇幅),但是我会对一些我最喜欢的标准模块进行说明,从而激发你对模块进行探究的兴趣。你会在“项目章节”(第20章~第29章)碰到更多的标准模块。模块的描述并不完全,但是会强调每个模块比较有趣的特征。

 

10.3.1 sys

sys这个模块让你能够访问与Python解释器联系紧密的变量和函数,其中一些在表10-2中列出。

表10-2 sys模块中一些重要的函数和变量

argv                命令行参数,包括脚本名称

exit([arg])             退出当前的程序,可选参数为给定的返回值或者错误信息

modules              映射模块名字到载入模块的字典

path                查找模块所在目录的目录名列表

platform              类似sunos5或者win32的平台标识符

stdin                 标准输入流——一个类文件(file-like)对象

stdout               标准输出流——一个类文件对象

stderr                  标准错误流——一个类文件对象

变量sys.argv包含传递到Python解释器的参数,包括脚本名称。

函数sys.exit可以退出当前程序(如果在try/finally块中调用,finally子句的内容仍然会被执行,第八章对此进行了探讨)。你可以提供一个整数作为参数,用来标识程序是否成功运行,这是UNIX的一个惯例。大多数情况下使用该整数的默认值就可以了(也就是0,表示成功)。或者你也可以提供字符串参数,用作错误信息,这对于用户找出程序停止运行的原因会很有用。这样,程序就会在退出的时候提供错误信息和标识程序运行失败的代码。

映射sys.modules将模块名映射到实际存在的模块上,它只应用于目前导入的模块。

sys.path模块变量在本章前面讨论过,它是一个字符串列表,其中的每个字符串都是一个目录名,在import语句执行时,解释器就会从这些目录中查找模块。

sys.platform模块变量(它是个字符串)是解释器正在其上运行的“平台”名称。它可能是标识操作系统的名字(比如sunos5或win32),也可能标识其他种类的平台,如果运行Jython的话,就是Java的虚拟机(比如java1.4.0)。

sys.stdin、sys.stdout和sys.stderr模块变量是类文件流对象。它们表示标准UNIX概念中的标准输入、标准输出和标准错误。简单来说,Python利用sys.stdin获得输入(比如用于函数input和raw_input中的输入),利用sys.stdout输出。第十一章会介绍更多有关于文件(以及这三个流)的知识。

举例来说,我们思考一下反序打印参数的问题。当你通过命令行调用Python脚本时,可能会在后面加上一些参数——这就是命令行参数(command-line argument)。这些参数会放置在sys.argv列表中,脚本的名字为sys.argv[0]。反序打印这些参数很简单,如代码清单10-5所示。

# 代码清单10-5 反序打印命令行参数

import sys

args = sys.argv[1:]
args.reverse()
print " ".join(args)

正如你看到的,我对sys.argv进行了复制。你可以修改原始的列表,但是这样做通常是不安全的,因为程序的其他部分可能也需要包含原始参数的sys.argv。注意,我跳过了sys.argv的第一个元素,这是脚本的名字。我使用args.reverse()方法对列表进行反向排序,但是不能打印出这个操作结果的,这是个返回None的原地修改操作。下面是另外一种做法:

print " ".join(reversed(sys.argv[1:]))

最后,为了保证输出得更好,我使用了字符串方法join。让我们试试看结果如何(我使用的是MS-DOS,在UNIX Shell下它也会工作的同样好):

D:\\Workspace\\Basic tutorial>python Code10-5.py this is a test
test a is this

 

10.3.2 os

os模块提供了访问多个操作系统服务的功能。os模块包括的内容很多,表10-3中只是其中一些最有用的函数和变量。另外,os和它的子模块os.path还包括一些用于检查、构造、删除目录和文件的函数,以及一些处理路径的函数(例如,os.path.split和os.path.join让你在大部分情况下都可以忽略os.pathsep)。关于它的更多信息,请参见标准库文档。

表10-3 os模块中一些重要函数和变量

environ                    对环境变量进行映射

system(command)               在子shell中执行操作系统命令

sep                      路径中的分隔符

pathsep                     分隔路径的分隔符

linesep                     行分隔符("\\n", "\\r", or "\\r\\n")

urandom(n)                  返回n字节的加密强随机数据

os.environ映射包含本章前面讲述过的环境变量。比如要访问系统变量PYTHONPATH,可以使用表达式os.environ["PYTHONPATH"]。这个映射也可以用来更改系统环境变量,不过并非所有系统都支持。

os.system函数用于运行外部程序。也有一些函数可以执行外部程序。还有open,它可以创建与程序连接的类文件。

关于这些函数的更多信息,请参见标准库文档。

注:当前版本的Python中,包括subprocess模块,它包括了os.system、execv和open函数的功能。

os.sep模块变量是用于路径名字中的分隔符。UNIX(以及Mac OS X中命令行版本的Python)中的标准分隔符是"/",Windows中的是"\\\\"(即Python针对单个反斜线的语法),而Mac OS中的是":"(有些平台上,os.altsep包含可选的路径分隔符,比如Windows中的"/")。

你可以在组织路径的时候使用os.pathsep,就像在PYTHONPATH中一样。pathsep用于分割路径名:UNIX(以及Mac OS X中的命令行版本的Python)使用":",Windows使用";",Mac OS使用"::"。

模块变量os.linesep用于文本文件的字符串分隔符。UNIX中(以及Mac OS X中命令行版本的Python)为一个换行符(\\n),Mac OS中为单个回车符(\\r),而在Windows中则是两者的组合(\\r\\n)。

urandom函数使用一个依赖于系统的"真"(至少是足够强度加密的)随机数的源。如果正在使用的平台不支持它,你会得到NotImplementedError异常。

例如,有关启动网络浏览器的问题。system这个命令可以用来执行外部程序,这在可以通过命令行执行程序(或命令)的环境中很有用。例如在UNIX系统中,你可以用它来列出某个目录的内容以及发送Email,等等。同时,它对在图形用户界面中启动程序也很有用,比如网络浏览器。在UNIX中,你可以使用下面的代码(假设/usr/bin/firefox路径下有一个浏览器):

os.system("/usr/bin/firefox")

以下是Windows版本的调用代码(也同样假设使用浏览器的安装路径):

os.system(r"C:\\‘Program Files‘\\‘Mozilla Firefox‘\\firefox.exe")

注意,我很仔细地将Program Files和Mozilla Firefox放入引号中,不然DOS(它负责处理这个命令)就会在空格处停不下来(对于在PYTHONPATH中设定的目录来说,这点也同样重要)。同时,注意必须使用反斜线,因为DOS会被正斜线弄糊涂。如果运行程序,你会注意到浏览器会试图打开叫做Files‘\\Mozilla...的网站——也就是在空格后面的命令部分。另一方面,如果试图在IDLE中运行该代码,你会看到DOS窗口出现了,但是没有启动浏览器并没有出现,除非关闭DOS窗口。总之,使用以上代码并不是完美的解决方法。

另外一个可以更好地解决问题的函数是Windows特有的函数——os.startfile:

os.startfile(r"C:\\Program Files\\Mozilla Firefox\\firefox.exe")

可以看到,os.startfile接受一般路径,就算包含空格也没问题(也就是不用像在os.system例子中那样将Program Files放在引号中)。

注意,在Windows中,由os.system(或者os.startfile)启动了外部程序之后,Python程序仍然会继续运行,而在UNIX中,程序则会中止,等待os.system命令完成。

更好的解决方案:WEBBROWSER

在大多数情况下,os.system函数很有用,但是对于启动浏览器这样特定的任务来说,还有更好的解决方案:webbrowser模块。它包括open函数,它可以自动启动Web浏览器访问给定的URL。例如,如果希望程序使用Web浏览器打开Python的网站(启动新浏览器或者使用已经运行的浏览器),那么可以使用以下代码:

import webbrowser
webbrowser.open("http://www.python.org")

 

10.3.3 fileinput

第十一章将会介绍很多读写文件的知识,现在先做个简短的介绍。fileinput模块让你能够轻松地遍历文本文件的所有行。如果通过以下方式调用脚本(假设在UNIX命令行下):

$ python some_script.py file1.txt file2.txt file3.txt

这样就可以以此对file1.txt到file3.txt文件中的所有行进行遍历了。你还能对提供给标准输入(sys.stdin,记得吗)的文本进行遍历。比如在UNIX的管道中,使用标准的UNIX命令cat:

$ cat file.txt | python some_script.py

如果使用fileinput模块,在UNIX管道中使用cat来调用脚本的效果和将文件名作为命令行参数提供给脚本是一样的。fileinput模块最重要的函数如表10-4所示。

fileinput.input是其中最重要的函数。它会返回能够于for循环遍历的对象。如果不想使用默认行为(fileinput查找需要循环遍历的文件),那么可以给函数提供(序列形式的)一个或多个文件名。你还能将inplace参数设为其真值(inplace=True)以进行原地处理。对于要访问的每一行,需要打印出替代的内容,以返回到当前的输入文件中。在进行原地处理的时候,可选的backup参数将文件名扩展备份到通过原始文件创建的备份文件中。

表10-4 fileinput模块中重要的函数

input(files[, inplace[, backup]])                    便于遍历多个输入流中的行

filename()                                返回当前文件的名称

lineno()                                 返回当前(累计)的行数

filelineno()                               返回当前文件的行数

isfirstline()                               检查当前行是否是文件的第一行

isstdin()                                检查最后一行是否来自sys.stdin

nextfile()                                 关闭当前文件,移动到下一个文件

close()                                 关闭序列

fileinput.filename函数返回当前正在处理的文件名(也就是包含了当前正在处理的文本行的文件)。

fileinput.lineno返回当前行的行数。这个数值是累计的,所以在完成一个文件的处理并且开始处理下一个文件的时候,行数并不会重置。而是将上一个文件的最后行数加1作为计数的起始。

fileinput.filelineno函数返回当前处理文件的当前行数。每次处理完一个文件并且开始处理下一个文件时,行数都会重置为1,然后重新开始计数。

fileinput.isfirstline函数在当前行是当前文件的第一行时返回真值,反之返回假值。

fileinput.isstdin函数在当前文件为sys.stdin时返回真值,否则返回假值。

fileinput.nextfile函数会关闭当前文件,跳到下一个文件,跳过的行并不计。在你知道当前文件已经处理完的情况下,这个函数就比较有用了——比如每个文件都包含经过排序的单词,而你需要查找某个词。如果已经在排序中找到了这个词的位置,那么你就能放心地跳到下一个文件了。

fileinput.close函数关闭整个文件链,结束迭代。

为了演示fileinput的使用,我们假设已经编写了一个Python脚本,现在想要为其代码进行编号。为了让程序在完成代码行编号之后仍然能够正常运行,我们必须通过在每一行的右侧加上作为注释的行号来完成编号工作。我们可以使用字符串格式化来将代码行和注释排成一行。假设每个程序行最多有45个字符,然后把行号注释加在后面。代码清单10-6展示了使用fileinput以及inplace参数来完成这项工作的简单方法:

# 代码清单10-6 为Python脚本添加行号

#!/usr/bin/env python
# coding=utf-8

# numberlines.py

import fileinput

for line in fileinput.input(inplace=True):
    line = line.rstrip()
    num = fileinput.lineno()

    print "%-40s # %2i" % (line, num)

 

如果你像下面这样在程序本身上运行这个程序:

$ python numberline.py numberline.py

程序会变成类似于代码清单10-7那样。注意,程序本身已经被更改了,如果这样运行多次,最终会在每一行中添加多个行号。我们可以回忆一下之前的内容:rstrip是可以返回字符串副本的字符串方法,右侧的空格都被删除(请参见3.4节,以及附录B中的表B-6)。

# 代码清单10-7 为已编号的行进行编号

#!/usr/bin/env python                         #  1
# coding=utf-8                                #  2
                                              #  3
# numberline.py                               #  4
                                              #  5
import fileinput                              #  6
                                              #  7
for line in fileinput.input(inplace=True):    #  8
    line = line.rstrip()                      #  9
    num = fileinput.lineno()                  # 10
                                              # 11
    print "%-45s # %2i" % (line, num)         # 12

注:要小心使用inplace参数,它很容易破坏文件。你应该在不使用inplace设置的情况下仔细测试自己的程序(这样只会打印出错误),在确保程序工作正常后再修改文件。

另外一个使用fileinput的例子,请参见本章后面的random模块部分。

 

10.3.4 集合、堆和双端队列

在程序设计中,我们会遇到很多有用的数据结构,而Python支持其中一些相对通用的类型,例如字典(或者说散列表)、列表(或者说动态数组)是语言必不可少的一部分。其他一些数据结构尽管不是那么重要,但有些时候也能派上用场。

1.集合

集合(set)在Python2.3才引入。Set类位于sets模块中。尽管可以在现在的代码中创建Set实例。但是除非想要兼容以前的程序,否则没有什么必要这样做。在Python2.3中,集合通过set类型的实例成为了语言的一部分,这意味着不需要导入sets模块——直接创建集合即可:

>>> set(range(10))
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

集合是由序列(或者其他可迭代的对象)构建的。它们主要用于检查成员资格,因此副本是被忽略的:

>>> set(["fee", "fie", "foe"])
set([foe, fee, fie])

除了检查成员资格外,还可以使用标准的集合操作(可能你是通过数学了解到的),比如求并集和交集,可以使用方法,也可以使用对整数进行位操作时使用的操作(参见附录B)。比如想要找出两个集合的并集,可以使用其中一个集合的union方法或者使用按位与(OR)运算符"|":

>>> a = set([1, 2, 3])
>>> b = set([2, 3, 4])
>>> a.union(b)
set([1, 2, 3, 4])
>>> a | b
set([1, 2, 3, 4])

以下列出了一些其他方法和对应的运算符,方法的名称已经清楚地表明了其用途:

>>> c = a & b
>>> c.issubset(a)
True
>>> c <= a
True
>>> c.issuperset(a)
False
>>> c >= a
False
>>> a.intersection(b)
set([2, 3])
>>> a & b
set([2, 3])
>>> a.difference(b)
set([1])
>>> a - b
set([1])
>>> a.symmetric_difference(b)
set([1, 4])
>>> a ^ b
set([1, 4])
>>> a.copy()
set([1, 2, 3])
>>> a.copy() is a
False

还有一些原地运算符和对应的方法,以及基本方法add和remove。关于这方面更多的信息,请参看Python库参考的3.7节(http://python.org/doc/lib/types-set.html)。

注:如果需要一个函数,用于查找并且打印两个集合的并集,可以使用来自set类型的union方法的未绑定版本。这种做法很有用,比如结合reduce来使用:

>>> mySets = []
>>> for i in range(10):
...     mySets.append(set(range(i, i + 5)))
... 
>>> reduce(set.union, mySets)
set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])

集合是可变的,所以不能用做字典中的键。另外一个问题就是集合本身只能包含不可变(可散列的)值,所以也就不能包含其他集合。在实际当中,集合的集合是很常用的,所以这个就是个问题了。幸好还有个frozenset类型,用于代表不可变(可散列)的集合:

>>> a = set()
>>> b = set()
>>> a.add(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: set
>>> a.add(frozenset(b))
>>> a
set([frozenset([])])

frozenset构造函数创建给定集合的副本,不管是将集合作为其他集合成员还是字典的键,frozenset都很有用。

2.堆

另外一个众所周知的数据结构是(heap),它是优先队列的一种。使用优先队列能够以任意顺序增加对象,并且能在任何时间(可能增加对象的同时)找到(也可能是移除)最小的元素,也就是说它比用于列表的min方法要有效率得多。

事实上,Python中并没有独立的堆类型,只有一个包含一些堆操作函数的模块,这个模块叫做heapq(q是queue的缩写,即队列),包括6个函数(参见表10-5),其中前4个直接和堆操作相关。你必须将列表作为堆对象本身。

表10-5 heapq模块中重要

以上是关于自带电池)的主要内容,如果未能解决你的问题,请参考以下文章

Python中自带电池是什么意思?

自带电池)

三菱q系列plc特殊模块错误怎么办

模块化电池能量单元

Python--33 像一个极客去思考

Python强大的自有模块——标准库