Python函数、文件与模块“探索Python”这一系列的前几篇文章已为Python编程新手介绍了几方面的主题,包括变量、容器对象和复合语句。本文以这些概念为基础,构造一个完整的Python程序。引入了Python函数和模块,并展示了构建一个Python程序、将其存储在文件中以及通过命令行运行该程序的方法。返璞归真许多流行的玩具都以这样一个概念为基础:简单的积木。这些简单的积木可通过多种方式组合在一起构造出全新的作品——有时甚至完全令人出乎意料。这一概念同样适用于现实生活中的建筑领域,将基本原材料组合在一起,形成有用的建筑物。平凡无奇的材料、技术和工具简化了新建筑物的建造过程,同样也简化了对新踏入此领域的人员的培训。相同的基本概念也适用于计算机程序开发技术,包括以Python编程语言编写的程序。本文介绍了使用Python创建基本构件(buildingblock)的方法,可用于解决更为复杂的问题。这些基本构件可能小而简单,也可能庞大而复杂。无论采用哪种形式,我们这场游戏的目的就是定义基本构件,然后使用它们来创建专属于您的杰作。函数:封装逻辑在本系列的前几篇文章中,您通常不得不重复输入所有代码,即便它与上一行代码完全相同。此要求的惟一特例就是变量的使用:一旦初始化了变量的内容,之后就可以随时重用。显而易见,这一用法的普及对我们大有好处。描述杰出程序员的最流行的箴言之一就是他们很懒惰。这并不表示杰出的程序员不努力工作——而是说他们喜欢灵活的工作方法,除非绝对必要,否则从不反复做任何相同的事情。这也就意味着在您需要编写代码之前,首先考虑如何实现重用。Python中有多种可实现重用的途径,但最简单的技术莫过于使用函数,也称为方法或子例程。与绝大多数现代编程语言类似,Python支持使用方法将一组语句封装在一起,从而可在必要时重复使用。清单1给出了一段简单的伪代码,为您展示如何在Python中编写方法。清单1.定义函数的伪代码defmyFunction(optionalinputdata):initializeanylocaldataactualstatementsthatdotheworkoptionallyreturnanyresults如您所见,在Python中,函数的基本组成部分是包装器代码,指明将被重用的一系列Python语句。函数可接受输入参数,输入参数在紧接着函数名(在本例中为myFunction)之后的圆括号内提供。函数还可返回值(更为正式的说法是:对象),包括像tuple这样的Python容器。在真正着手构建函数之前,让我们先来看看关于伪代码的一些简单却重要的要点:请注意函数名中所用的字符大小写:大多数字符都是小写的,但在使用多个单词连接成一个函数名时,后接的各单词首字母应大写(例如,myFunction中的F)。这就是所谓的驼峰式大小写风格(camelcasing),是Python和其他编程语言中广泛采用的一种技术,可使函数的名称更易阅读。函数定义中的程序语句采用了缩进式排版,函数体由Python语句块构成,它们也必须像循环或条件语句那样缩进。函数定义的第一行也称为方法签名(methodsignature),以def开头(def是define这个单词的缩写)。方法签名以冒号结尾,表示下面的代码行是函数体。至此,您或许已认可了使用方法的好处。那么让我们投入进去,开始编写函数吧。“DiscoverPython,Part6:ProgramminginPython,Forthefunofit”中使用了一个for循环来创建乘法表。清单2展示了同样的概念,但本例中创建的是一个函数,用于封装乘法表计算背后的逻辑。清单2.第一个函数>>>deftimesTable():...forrowinrange(1,6):............forcolinrange(1,6):print\"%3d\"%(row*col),print>>>timesTable()123452468103691218121620510152025>>>t=timesTable>>>type(t)
>>>t>>>t()123452468103691218121620510152025timesTable函数定义起来非常简单,它不接受任何输入参数,也不返回任何结果。函数体几乎与“DiscoverPython,Part6”中的语句完全相同(但该文章中的乘法表为从1到10)。为了调用方法,并使其发挥作用,只需输入函数名后接圆括号即可。本例中还输出了乘法表。在Python中,函数是一类对象,与整型变量和容器对象相同。因而,您可以将函数指派给一个变量(切记,在Python中变量是动态类型化的)。在清单2中,我们将timesTable函数指派给变量t。接下来的两行代码表示变量t确实指向函数。最后,我们使用变量t调用timesTable函数。函数:动态更改逻辑清单2中的timesTable函数不复杂,但也不是特别有用。更有用的示例允许您指定用于生成乘法表的行数和列数——换言之,允许您在调用函数时动态地更改函数的操作方式。在函数定义中使用两个输入参数即可实现这一功能,如清单3所示。清单3.更好的乘法表函数>>>deftimesTable2(nrows=5,ncols=5):...............>>>timesTable2(4,6)1234123451234524682468102468103691236912153691218121812162051015205101520256121824forrowinrange(1,nrows+1):forcolsinrange(1,ncols+1):print\"%3d\"%(row*cols),print>>>timesTable2()>>>timesTable2(ncols=3)两个乘法表函数的定义非常相近,但清单3中的函数有用得多(通过清单3中的3次调用即可看出这一点)。为函数添加此附加功能的方法非常简单:提供名为nrows和ncols的两个输入参数,允许在调用函数时更改乘法表的大小。这两个参数随后会被提供给生成乘法表的两个for循环。关于timesTable2函数的另一要点就是两个输入参数有默认值。在函数签名中为参数提供默认值,方法是在参数名后添加等号和值,例如nrows=5。默认参数使程序获得了更高的灵活性,因为在您调用函数时,可以包含两个输入参数,也可以仅包含一个输入参数,甚至可以一个参数都不包含。但这种方法可能会导致某些问题。如果您在函数调用期间未指定全部参数,则必须显式地写出您所指定的参数的名称,以使Python解释器能够正确地调用函数。最后一个函数调用正体现了这一点,它显式地调用了带有ncols=3的timesTable2函数,函数创建了一个5行(默认值)3列(所提供的值)的乘法表。函数:返回数据使用方法时,人们最希望获得的结果并非总是乘法表。您可能希望完成一次计算,并将计算结果值返回给调用代码。有时要实现这两个目的,需要分别调用不返回任何数据的调用方法(子例程)和返回值的方法(函数)。但在Python中,您无需担心这些语义问题,因为通过使用return语句,几乎可以相同的方式实现这两个目的(参见清单4)。清单4.在函数中返回一个值>>>defstats(data):...............>>>stats([1,2,3,4,5])3.0>>>stats((1,2,3,4,5))3.0>>>stats()Traceback(mostrecentcalllast):File\"\line1,in?TypeError:stats()takesexactly1argument(0given)>>>stats(\"12345\")Traceback(mostrecentcalllast):File\"\line1,in?File\"\line4,instatsTypeError:unsupportedoperandtype(s)for+=:'float'and'str'#Findthemeanvaluefromatuple#Findthemeanvaluefromalistsum=0.0forvalueindata:sum+=valuereturn(sum/len(data))这个简单的函数遍历data(假设data为一个容纳有数字数据的Python容器),计算一组数据的平均值,然后返回值。函数定义接受一个输入参数。平均值通过return语句传回。当您调用带有包含数字1到5的list或tuple的函数时,返回值会显示在屏幕上。如果调用不带任何参数的函数、带非容器数据类型的函数或带内含非数字数据的容器的函数,就会导致出错。(在此类情况下抛出错误是很有意义的。更高级的处理方法应包含恰当的错误检查和处理,以应对这些情况,但这不在本文讨论范围内。)此示例已经非常有用,但还可使它更强大,如清单5所示。在Python中,函数可返回任何有效的对象类型,包括容器类型在内。因此,您可以计算多个数量,并轻松地将多个结果返回给调用语句。清单5.返回复合值>>>defstats(data):..............................>>>stats([1,2,3,4,5])(3.0,2.5)>>>(m,v)=stats([1,2,3,4,5,6,7,8,9])>>>printm,v5.07.5sum=0.0forvalueindata:sum+=valuemean=sum/len(data)sum=0.0forvalueindata:sum+=(value-mean)**2variance=sum/(len(data)-1)return(mean,variance)为了从一个函数中返回多个值,要将其括在一个括号中并以逗号分隔——换句话说,创建并返回一个tuple。新stats函数的函数体要略加修改,以计算数字序列的样本方差。最后,正如stats函数的两次调用所示,tuple值可作为一个tuple存取,也可将其解包为各自的分量。模块:简化代码重用至此,您或许已相信了代码重用的价值。但即便是使用函数,您依然需要在打算使用函数时重新输入函数体。例如,当您打开一个新的Python解释器时,必须键入之前所创建的所有函数。幸运的是,您可以使用模块将相关函数(和其他Python对象)封装在一起,将其保存在一个文件中,然后将这些已定义好的函数导入到新Python代码内,包含于Python解释器之中。为介绍在Python中使用模块的方法,我们将重用清单5中的stats方法。有两个选择:您可以从与本文相关的压缩文件中提取名为test.py的文件,也可以在编辑器中键入函数,然后将文件保存为test.py。完成上一步后,在您保存test.py的目录中启动一个新的Python解释器,然后输入如清单6所示的语句。清单6.使用模块>>>importtest>>>test.stats([1,2,3,4,5,6,7,8,9])(5.0,7.5)>>>fromtestimportstats>>>(m,v)=stats([1,2,3,4,5,6,7,8,9])>>>printm,v5.07.5第一行importtest打开文件test.py并处理文件中的各条语句。这里仅定义了stats函数,但若需要,您还可定义更多的函数。调用stats函数时,应以模块名test作为函数前缀。之所以使用这种复杂的名称,是出于作用域方面的考虑,作用域表示一个程序内名称的有效范围。为告知Python您要调用的是哪个stats方法,就必须提供完整的名称。这一点非常重要,因为您可能拥有多个名称相同的对象。作用域规则可帮助Python判断您想使用的对象。第三行fromtestimportstats也打开了文件test.py,但它隐式地将stats方法置入当前文件的作用域内,以使您能够直接调用stats函数(无需使用模块名)。明智地使用from...import...语法可使您的程序更简洁,但过度的使用也会导致混淆,甚至出现更糟糕的作用域冲突错误。不要滥用您的新武器!模块库使用Python编程语言的一个主要好处就是大型的内置式标准库,可作为Python模块访问。常用模块示例如下:math包含有用的数学函数。sys包含用于与Python解释器交互的数据和方法。array包含数组数据类型和相关函数。datetime包含有用的日期和时间处理函数。由于这些都是内置模块,因此您可以通过帮助解释器来了解更多相关内容,如清单7所示。清单7.获得关于math模块的帮助信息>>>help(math)Traceback(mostrecentcalllast):File\"\line1,in?NameError:name'math'isnotdefined>>>importmath>>>help(math)Helponmodulemath:NAMEmathFILE/System/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/lib-dynload/math.soDESCRIPTIONThismoduleisalwaysavailable.ItprovidesaccesstothemathematicalfunctionsdefinedbytheCstandard.FUNCTIONSacos(...)acos(x)#NeedtoimportmathmoduleinordertouseitReturnthearccosine(measuredinradians)ofx.asin(...)asin(x)Returnthearcsine(measuredinradians)ofx.math模块的帮助输出展示了所支持的大量数学函数,包括sqrt函数在内。您可以利用此函数将您的样本方差计算转换为样本标准差计算,如清单8所示。清单8.使用多个模块>>>frommathimportsqrt>>>fromtestimportstats>>>(m,v)=stats([1,2,3,4,5,6,7,8,9])>>>printm,sqrt(v)5.02.73861278753如您所见,您可以将多个模块导入到一个Python程序中。在大型、内置的模块库与更大量的公用库(其中许多都是开放源码的)的共同协助下,您很快也会成为一名懒惰——也就是杰出——的程序员。可执行文件导入一个模块时,Python解释器会处理模块文件内的各行。实际上,您可以调用Python解释器使其仅处理包含于一个文件中的一个Python程序。在基于UNIX®的操作系统中,您可以轻松创建可执行的文件,如清单9所示。清单9.一个完整的Python程序#!/usr/bin/envpythondefstats(data):sum=0.0forvalueindata:sum+=valuemean=sum/len(data)sum=0forvalueindata:sum+=(value-mean)**2variance=sum/(len(data)-1)return(mean,variance)(m,v)=stats([1,2,3,4,5,6,7,8,9])print\"Themeanandvarianceofthevalues\"\"from1to9inclusiveare\v观察上例,您应该会产生几分好感,将Python程序置于文件内,并使其运行是如此简单。本例与test.py文件中的代码之间惟一的差异就是包含了第一行。在基于UNIX的操作系统中,本行会使Python解释器自动启动,并在终止前处理文件中的语句。本示例中的其他行定义了stats函数、调用了函数,并输出了结果。要运行本文件中的语句,您需要启动一个Python解释器,并让它去读取和处理文件的内容。为实现这一目的,您必须首先将清单9中的示例输入到一个名为mystats.py的文件中,也可从与本文相关的压缩文件中提取文件。进入包含此文件的目录,然后按清单10中所示命令执行。注意对于Microsoft®Windows®操作系统而言,仅应使用第一条命令;其他命令是供UNIX系统(如Linux®或MacOSX)使用的。清单10.执行Python程序rb%pythonmystats.pyThemeanandvarianceofthevaluesfrom1to9inclusiveare5.07.5rb%chmod+xmystats.pyrb%./mystats.pyThemeanandvarianceofthevaluesfrom1to9inclusiveare5.07.5清单10中的命令展示了运行一个包含于文件之中的Python程序的方法。第一条命令以文件名调用Python解释器,无论使用哪种系统安装Python、Python解释器位于哪个目录下,这种方法都有效。第二条命令chmod使包含Python程序的文件成为可执行文件。第三条命令告诉操作系统运行程序。这是通过使用env程序实现的,这是一种于操作系统的技术,用于定位和运行程序——本例中是Python解释器。重用与缩减本文介绍了如何在Python中编写可重用代码。讨论了如何在Python程序中使用方法或可重用块。方法可接受输入参数,也可返回数据,包括容器数据类型在内。这种功能使方法成为一种可处理大量问题的强大途径。本文还介绍了模块,模块可使您将相关方法及数据合并入一个有组织的层次结构中,而此结构可方便地在其他Python程序中重用。最后还介绍了如何将所有这些内容组合在一起以创建一个功能完整、的Python程序。您已经看到,代码的重用也就意味着您的工作量缩减。对于程序员而言,懒惰是一种优势而非陋习。