SECTION 15 函数和函数式编程(二)

15.1传递函数

函数是可以被引用的(访问或者以其他变量作为其别名),也作为参数传入函数,以及作为列表和字典等等容器对象的元素
函数有一个独一无二的特征使它同其他对象区分开来,那就是函数是可调用的。

举例来说,可以通过函数操作来调用他们。(在 python 中有其他的可调用对象)在以上的描述中,我们注意到可以用其他的变量来做作为函数的别名,因为所有的对象都是通过引用来传递的,函数也不例外。当对一个变量赋值时,实际是将相同对象的引用赋值给这个变量。如果对象是函数的话,这个对象所有的别名都是可调用的。

>>> def foo():
... print 'in foo()'
...
>>> bar = foo
>>> bar()
in foo()

当我们把 foo 赋值给 bar 时,bar 和 foo 引用了同一个函数对象,所以能以和调用 foo()相同的方式来调用 bar()。确定你明白**“foo”(函数对象的引用)和"foo()"(函数对象的调用)**的区别。稍微深入下我们引用的例子,我们甚至可以把函数作为参数传入其他函数来进行调用。

>>> def bar(argfunc):
... 	argfunc()
...
>>> bar(foo)
in foo()

注意到函数对象 foo 被传入到 bar()中。bar()调用了 foo()(用局部变量 argfunc 来作为其别名,就如同在前面的例子中我们把 foo 赋给 bar 一样)

 #!/usr/bin/env python

 def convert(func, seq):
 	'conv. sequence of numbers to same type'
 	return [func(eachNum) for eachNum in seq]

 myseq = (123, 45.67, -6.2e8, 999999999L)
 print convert(int, myseq)
 print convert(long, myseq)
 print convert(float, myseq)

一个将函数作为参数传递,并在函数体内调用这些函数,更加实际的例子。这个脚本用传入的转换函数简单将一个序列的数转化为相同的类型。特别地,convert()函数传入一个内建函数 int(),long()或者 float()来执行转换。
如果我们运行这个程序,我们将会得到如下输出:

$ numconv.py
[123, 45, -620000000, 999999999]
[123L, 45L, -620000000L, 999999999L]
[123.0, 45.67, -620000000.0, 999999999.0]

15.2 形式参数

python 函数的形参集合由在调用时要传入函数的所有参数组成,这参数与函数声明中的参数列表精确的配对。这些参数包括了所有必要参数(以正确的定位顺序来传入函数的),关键字参数(以顺序或者不按顺序传入,但是带有参数列表中曾定义过的关键字),以及所有含有默认值,函数调用时不必要指定的参数。(声明函数时创建的)局部命名空间为各个参数值,创建了一个名字。一旦函数开始执行,即能访问这个名字。

15.2.1 位置参数

这些我们都是熟悉的标准化参数。位置参数必须以在被调用函数中定义的准确顺序来传递。另外,没有任何默认参数(见下一个部分)的话,传入函数(调用)的参数的精确的数目必须和声明的数字一致。

>>> def foo(who): # defined for only 1 argument
... print 'Hello', who
...
>>> foo() # 0 arguments... BAD Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: not enough arguments; expected 1, got 0
>>>
>>> foo('World!') # 1 argument... WORKS Hello World!
>>>
>>> foo('Mr.', 'World!')# 2 arguments... BAD Traceback (innermost last):
File "<stdin>", line 1, in ?
TypeError: too many arguments; expected 1, got 2
15.2.2 默认参数

对于默认参数如果在函数调用时没有为参数提供值则使用预先定义的的默认值。这些定义在函数声明的标题行中给出。c++也支持默认参数,和 python 有同样的语法:参数名等号默认值。这个从句法上来表明如果没有值传递给那个参数,那么这个参数将取默认值。
python 中用默认值声明变量的语法是所有的位置参数必须出现在任何一个默认参数之前

def func(posargs, defarg1=dval1, defarg2=dval2,...):
	"function_documentation_string"
	function_body_suite

每个默认参数都紧跟着一个用默认值的赋值语句。如果在函数调用时没有给出值,那么这个赋值就会实现。

15.2.3 为什么用默认参数?
  1. 默认参数让程序的健壮性上升到极高的级别,因为它们补充了标准位置参数没有提供的一些灵活性。
  2. 使用默认参数的概念与在你的电脑上安装软件的过程类似。一个人会有多少次选择默认安装而不是自定义安装?我可以说可能几乎都是默认安装。这既方便,易于操作,又能节省时间。如果是那些总是选择自定义安装的顽固分子,请记着你只是少数人之一
  3. 另外一个让开发者受益的地方在于,使开发者更好地控制为顾客开发的软件。当提供了默认值的时候,他们可以精心选择“最佳“的默认值,所以用户不需要马上面对繁琐的选项。随着时间流逝,当用户对系统或者 api 越来越熟悉的时候,他们最终能自行给出参数值,便不再需要使用“学
    步车“了
15.2.4 抓取网页(示例)

这段脚本下载了一个 web 页面(默认为本地的 www 服务器)并显示了 html 文件的第一个以及最后一个非空格行。由于 download()函数的双默认参数允许用不同的 urls 或者指定不同的处理函数来进行覆盖,灵活性得倒了提高。

#!/usr/bin/env python

from urllib import urlretrieve

def firstNonBlank(lines):
	for eachLine in lines:
		if not eachLine.strip():
			continue
		else:
			return eachLine

def firstLast(webpage):
	f = open(webpage)
	lines = f.readlines()
	f.close()
	print firstNonBlank(lines),
	lines.reverse()
	print firstNonBlank(lines),

def download(url='http://www.baidu.com',process=firstLast):
	try:
		retval = urlretrieve(url)[0]
	except IOError:
		retval = None
	if retval: # do some processing
		process(retval)

if __name__ == '__main__':
	download()
$ grabWeb.py
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
</BODY></HTML>

15.3 可变长度的参数

可能会有需要用函数处理可变数量参数的情况,这时可使用可变长度的参数列表。

变长的参数在函数声明中不是显式命名的,因为参数的数目在运行时之前是未知的(甚至在运行的期间,每次函数调用的参数的数目也可能是不同的),这和常规参数(位置和默认)明显不同,常规参数都是在函数声明中命名的。由于函数调用提供了关键字以及非关键字两种参数类型,python 用两种方法来支持变长参数,
在前几节中,我们了解了在函数调用中使用***符号来指定元组和字典的元素作为非关键字以及关键字参数的方法。在这个部分中,我们将再次使用相同的符号,但是这次在函数的声明中,表示在函数调用时接收这样的参数。这语法允许函数接收在函数声明中定义的形参之外的参数。

15.3.1 非关键字可变长参数(元组/Tuple)

当函数被调用的时候,所有的形参(必须的和默认的)都将值赋给了在函数声明中相对应的局部变量。剩下的非关键字参数按顺序插入到一个元组中便于访问。在函数调用时,接受一个不定(非固定)数目的参数。可变长的参数元组必须在位置和默认参数之后,带元组(或者非关键字可变长参数)的函数普遍的语法如下:

def function_name([formal_args,] *vargs_tuple):
	"function_documentation_string"
	function_body_suite

星号操作符之后的形参将作为元组传递给函数,元组保存了所有传递给函数的"额外"的参数(匹配了所有位置和具名参数后剩余的)。如果没有给出额外的参数,元组为空。

正如我们先前看见的,只要在函数调用时给出不正确的函数参数数目,就会产生一个 TypeError异常。通过末尾增加一个可变的参数列表变量,我们就能处理当超出数目的参数被传入函数的情形,因为所有的额外(非关键字)参数会被添加到变量参数元组。(额外的关键字参数需要关键字变量参数.)正如预料的那样,由于和位置参数必须放在关键字参数之前一样的原因,所有的形式参数必须先于非正式的参数之前出现。

#!/usr/bin/python2.7

def tupleVarArgs(arg1, arg2='defaultB', *theRest):
	'display regular args and non-keyword variable args'
	print 'formal arg 1:',arg1 
	print 'formal arg 2:',arg2 
	for eachXtrArg in theRest:
		print 'another arg:',eachXtrArg

tupleVarArgs('abc',123,'xyz',[1,2,3])
[root@localhost day4]# ./tu.py 
formal arg 1: abc
formal arg 2: 123
another arg: xyz
another arg: [1, 2, 3]
15.3.2 关键字变量参数(字典/Dictionary)

在我们有不定数目的或者额外集合的关键字的情况中, 参数被放入一个字典中,字典中键为参数名,值为相应的参数值。为什么一定要是字典呢?因为为每个参数-参数的名字和参数值–都是成对给出—用字典来保存这些参数自然就最适合不过了。

这给出使用了变量参数字典来应对额外关键字参数的函数定义的语法:

def function_name([formal_args,][*vargst,] **vargsd):
	function_documentation_string 
	function_body_suite

为了区分关键字参数和非关键字非正式参数,使用了双星号(**)。 **是被重载了的以便不与幂运算发生混淆。关键字变量参数应该为函数定义的最后一个参数,带**。我们现在展示一个如何使用字典的例子:

def dictVarArgs(arg1, arg2='defaultB', **theRest):
	'display 2 regular args and keyword variable args'
	print 'formal arg1:', arg1
	print 'formal arg2:', arg2
	for eachXtrArg in theRest.keys():
		print 'Xtra arg %s: %s' % (eachXtrArg, str(theRest[eachXtrArg]))

在解释器中执行这个代码,我们得到以下输出。

>>> dictVarArgs(1220, 740.0, c='grail')
formal arg1: 1220
formal arg2: 740.0
Xtra arg c: grail
>>>
>>> dictVarArgs(arg2='tales', c=123, d='poe', arg1='mystery')
formal arg1: mystery
formal arg2: tales
Xtra arg c: 123
Xtra arg d: poe
>>>
>>> dictVarArgs('one', d=10, e='zoo', men=('freud', 'gaudi'))
formal arg1: one
formal arg2: defaultB
Xtra arg men: ('freud', 'gaudi')
Xtra arg d: 10
Xtra arg e: zoo

变长参数都有可能用在同一个函数中,只要关键字字典是最后一个参数并且非关键字元组先于它之前出现,正如在如下例子中的一样:

def newfoo(arg1, arg2, *nkw, **kw):
	display regular args and all variable args'
	print 'arg1 is:', arg1 
	print 'arg2 is:', arg2 
	for eachNKW in nkw:
		print 'additional non-keyword arg:', eachNKW
	for eachKW in kw.keys():
		print "additional keyword arg '%s': %s" % (eachKW, kw[eachKW])

在解释器中调用我们的函数,我们得到如下的输出:

>>> newfoo('wolf', 3, 'projects', freud=90, gamble=96)
arg1 is:wolf arg2 is:3
additional non-keyword arg: projects
additional keyword arg 'freud': 90
additional keyword arg 'gamble': 96

调用带有可变长参数对象函数

>>> newfoo(10, 20, 30, 40, foo=50, bar=60)
arg1 is: 10
arg2 is: 20
additional non-keyword arg: 30
additional non-keyword arg: 40
additional keyword arg 'foo': 50
additional keyword arg 'bar': 60

我们现在进行相似的调用;然而,我们将非关键字参数放在元组中将关键字参数放在字典中,而不是逐个列出变量参数:

>>> newfoo(2, 4, *(6, 8), **{'foo': 10, 'bar': 12})
arg1 is: 2
arg2 is: 4
additional non-keyword arg: 6
additional non-keyword arg: 8
additional keyword arg 'foo': 10
additional keyword arg 'bar': 12

最终,我们将再另外进行一次调用,但是是在函数调用之外来创建我们的元组和字典。

>>> aTuple = (6, 7, 8)
>>> aDict = {'z': 9}
>>> newfoo(1, 2, 3, x=4, y=5, *aTuple, **aDict)
arg1 is: 1
arg2 is: 2
additional non-keyword arg: 3
additional non-keyword arg: 6
additional non-keyword arg: 7
additional non-keyword arg: 8
additional keyword arg 'z': 9
additional keyword arg 'x': 4
additional keyword arg 'y': 5

注意我们的元组和字典参数仅仅是被调函数中最终接收的元组和字典的子集。额外的非关键字值‘3’以及‘x’和‘y’关键字对也被包含在最终的参数列表中,而它们不是***的可变参数中的元素。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值