11.1.2 返回值与函数类型
过程可理解为函数,返回none
下面代码可简单理解函数返回值的概念:
>>> defhello():
print "hello world!"
>>> res =hello()
hello world!
>>> printres
None
>>>type(res)
<type'NoneType'>
>>> res =hello
>>> res()
hello world!
另外,与其他大多数的语言一样,python 里的函数可以返回一个值或者对象。只是在返回一个容器对象的时候有点不同,看起来像是能返回多个对象。好比说,你不能拿着大量零散的商品离开百货店,但是你可以将它们放在一个购物袋里,然后带着这个袋子从商店走出去,合理合法。
>>> def foo():
return["xyz",1000000,-98.6]
>>> def bar():
return"abc",[42,"python"], "guido"
foo()函数返回一个列表,bar()函数返回一个元组。由于元组语法上不需要一定带上圆括号,所以让人真的以为可以返回多个对象。如果我们要恰当地给这个元组加上括号, bar()的定义看起来会是这样:
>>> def bar():
return("abc",[42,"python"], "guido")
当然,加上括号总是好的。我们可以这样接收返回值:
>>> aTuple = bar()
>>> x, y, z = bar()
>>> (a, b, c) = bar()
>>> aTuple
('abc', [42, 'python'], 'guido')
>>> x
'abc'
>>> x, y, z
('abc', [42, 'python'], 'guido')
>>> (a, b, c)
('abc', [42, 'python'], 'guido')
11.2 调用函数
11.2.2 关键字参数
关键字参数的概念仅仅针对函数的调用。这种理念是让调用者通过函数调用中的参数名字来区分参数。这样规范允许参数缺失或者不按顺序,因为解释器能通过给出的关键字来匹配参数的值。
例子如下:
>>> def foo(name = "", age = 0):
print "name:%s andage:%d" % (name, age)
>>> foo(name="fzyz.abc",age=23)
name:fzyz.abc and age:23
>>> foo(age=23, name="fzyz.abc")
name:fzyz.abc and age:23
>>> foo(age=23)
name: and age:23
11.2.4 参数组
当然,你可以向函数中传递没有显式定义的参数,用元祖或字典即可。标准格式如下:
func(positional_args,keyword_args,*tuple_grp_nonkw_args, **dict_grp_kw_args)
我们来简单看个例子:
from operator import add, sub
from random import randint, choice
ops = {"+":add, "-":sub}
MAXTRIES = 2
def doprob():
op = choice("+-")
nums = [randint(1,10) for i inrange(2)]
nums.sort(reverse = True)
ans = ops[op](*nums)
pr = "%d %s %d =" %(nums[0], op, nums[1])
oops = 0
while True:
try:
if int(raw_input(pr)) ==ans:
print"correct"
break
if oops == MAXTRIES:
print"answer\n%s%d" % (pr, ans)
else:
print"incorrect... try again"
oops += 1
except (KeyboardInterrupt,EOFError, ValueError):
print "invalid input...try again"
def main():
while True:
doprob()
try:
opt =raw_input("again?[y]").lower()
if opt and opt[0] == "n":
break
except (KeyboardInterrupt,EOFError):
break
if __name__ == "__main__":
main()
程序输入输出:
>>>
7 - 1 =6
correct
again?[y]y
9 - 5 =4
correct
again?[y]n
这个程序挺好玩的。
11.3 创建函数
函数是用def 语句来创建的,语法如下:
def function_name(arguments):
"function_documentation_string"
function_body_suite
标题行由def 关键字,函数的名字,以及参数的集合(如果有的话)组成。def 子句的剩余部分包括了一个虽然可选但是强烈推荐的文档字串,和必需的函数体。
Python中并没有函数声明和定义的区分,因为通常将它们视为一体。
11.3.5 内部/内嵌函数
在函数体内创建另外一个函数(对象)是完全合法的。这种函数叫做内部/内嵌函数。
>>> def foo():
def bar():
print "bar()called"
print "foo() called"
bar()
>>> foo()
foo() called
bar() called
当然,如果你想单独访问bar函数怎么办?对于函数来说,貌似访问不了,你可以声明一个类,然后把函数往类中堆叠即可。用类来访问:
>>> class foo(object):
def bar(self):
print "bar()called"
>>> foo().bar()
bar() called
11.3.6 *函数(与方法)装饰器
装饰器背后的主要动机源自python 面向对象编程。装饰器是在函数调用之上的修饰。这些修饰仅是当声明一个函数或者方法的时候,才会应用的额外调用。
装饰器的语法以@开头,接着是装饰器函数的名字和可选的参数。紧跟着装饰器声明的是被修饰的函数,和装饰函数的可选参数。装饰器看起来会是这样:
@decorator(dec_opt_args)
def func2Bdecorated(func_opt_args):
:
为什么会产生装饰器呢?我们来看看静态方法的实现:
class MyClass(object):
def staticFoo():
:
staticFoo = staticmethod(staticFoo)
在这个类的声明中,我们定义了叫staticFoo()的方法。现在因为打算让它成为静态方法,我们省去它的self 参数,self 参数在标准的类方法中是必需的。接着用staticmethod()内建函数来将这个函数“转化“为静态方法,但是在defstaticFoo()后跟着staticFoo = staticmethod (sta- ticFoo)显得有多么的臃肿。使用装饰器,你现在可以用如下代码替换掉上面的:
class MyClass(object):
@staticmethod
def staticFoo():
此外,装饰器可以如函数调用一样“堆叠“起来,这里有一个更加普遍的例子,使用了多个装饰器:
@deco2
@deco1
def func(arg1, arg2, ...): pass
这和创建一个组合函数是等价的。
def func(arg1, arg2, ...): pass
func = deco2(deco1(func))
我们来举个简单的装饰器例子:
class MyClass(object):
@staticmethod
def the_static_method(x):
print x
MyClass.the_static_method(2)
我原本想写个复杂的装饰器程序的,结果写了半天没写出来,我去,我想把装饰器弄到类里面来个函数嵌套函数的调用,
有参数和无参数的装饰器
没有参数的情况:
def foo(): pass
....非常的直接
foo = deco(foo)
跟着是无参函数(如上面所见)组成。然而,带参数的装饰器decomaker()
@decomaker(deco_args)
def foo(): pass
. . .
需要自己返回以函数作为参数的装饰器。换句话说,decomaker()用deco_args 做了些事并返回
函数对象,而该函数对象正是以foo 作为其参数的装饰器。简单的说来:
foo = decomaker(deco_args)(foo)
自己写不出例子,那么我们来看看书本上的例子吧(真的很想通过装饰器来完成函数调用函数的举例,不过总是出错,我去。。。)
这个装饰器(以及闭包)示范表明装饰器仅仅是用来“装饰“(或者修饰)函数的包装,返回一个修改后的函数对象,将其重新赋值原来的标识符,并永久失去对原始函数对象的访问。
from time import ctime, sleep
def tsfunc(func):
def wrappedFunc():
print "[%s] %s()called" % (ctime(), func.__name__)
return func
return wrappedFunc
@tsfunc
def foo():
pass
foo()
for i in range(2):
sleep(1)
foo()
程序输出:
>>>
[Fri Jun 14 20:16:00 2013] foo() called
[Fri Jun 14 20:16:01 2013] foo() called
[Fri Jun 14 20:16:02 2013] foo() called
这段程序很简单,当时我一直在想,如何复杂了foo,并且在装饰器函数中对foo进行复杂的操作。
11.4 传递函数
一个将函数作为参数传递,并在函数体内调用这些函数,更加实际的例子。这个脚本用传入的转换函数简单将一个序列的数转化为相同的类型。特别地,test()函数传入一个内建函数int(),long(), 或者float()来执行转换。
def convert(func, seq):
return [func(eachNum) foreachNum in seq]
myseq = (123, 45.67, -6.2e8, 9999999999L)
print convert(int, myseq)
print convert(long, myseq)
print convert(float, myseq)
程序输出:
>>>
[123, 45, -620000000, 9999999999L]
[123L, 45L, -620000000L, 9999999999L]
[123.0, 45.67, -620000000.0, 9999999999.0]
我们来弄个默认参数的例子:
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:
process(retval)
if __name__ == "__main__":
download()
程序就是把百度的首页用HTML的格式输出。
看得有点头晕脑胀的,不过继续看啦。公司居然断网了,瞬间无语。。。。
原本休息的时候还可以写邮件的,结果一切白谈。。。。
11.6 可变长度的参数
11.6.1 非关键字可变长参数(元祖)
当函数被调用的时候,所有的形参(必须的和默认的)都将值赋给了在函数声明中相对应的局部变量。剩下的非关键字参数按顺序插入到一个元组中便于访问。
def fun(value1, value2 = "hello", *args):
print value1,
print value2,
for item in args:
print item,
fun("123","world","1","2","3")
程序输出:
>>>
123 world 1 2 3
但是默认参数貌似没使用成功,先继续往下看,看默认参数如何和元祖结合使用的。
往下看的时候才知道,如果要用到默认参数的话,必须传递的是字典而不是元祖。请看下面的例子:
def fun(value1, value2 = "hello", **args):
print value1
print value2
for item in args.keys():
print "key is: %s andvalue is:%s" % (item, str(args[item]))
fun("123",value2 = "world", a = 1,b = 2, c = 3)
程序输出:
>>>
123
world
key is: a and value is:1
key is: c and value is:3
key is: b and value is:2
这里有点要注意到是,不能写成这样:
fun("123",value2 = "world", "a" =1,"b" = 2, "c" = 3)
想想就知道了,”a” = 1本身就是一个错误的表达式。
关键字和非关键字可变长参数都有可能用在同一个函数中,只要关键字字典是最后一个参数并且非关键字元组先于它之前出现。
我去,这好复杂,复杂的表达式能少用就尽量少用。
11.6.3 调用带有可变长参数对象函数
函数式编程举例
def testit(func, *nkwargs, **kwargs):
try:
retval = func(*nkwargs,**kwargs)
result = (True, retval)
except Exception, diag:
result = (False, str(diag))
return result
def test():
funcs = (int, long, float)
vals = (1234, 12.34,"1234", "12.34")
for eachFunc in funcs:
print "-" * 20
for eachVal in vals:
retval =testit(eachFunc, eachVal)
if retval[0]:
print"%s(%s)=" % (eachFunc.__name__,eachVal), retval[1]
else:
print "%s(%s) =failed:" % (eachFunc.__name__, eachVal), retval[1]
if __name__ == "__main__":
test()
程序输出:
>>>
--------------------
int(1234)= 1234
int(12.34)= 12
int(1234)= 1234
int(12.34) = failed: invalid literal for int() with base 10: '12.34'
--------------------
long(1234)= 1234
long(12.34)= 12
long(1234)= 1234
long(12.34) = failed: invalid literal for long() with base 10: '12.34'
--------------------
float(1234)= 1234.0
float(12.34)= 12.34
float(1234)= 1234.0
float(12.34)= 12.34
这例子特别好,现在开始对单元测试有点了解了。
11.7 函数式编程
来进入传说中的函数式编程,函数式编程是LISP的核心知识,其实在python中并不太重要,因为python可以用一些技巧来达到函数式编程的效果,比如列表推导式。
11.7.1 匿名函数与lambda
python 允许用lambda 关键字创造匿名函数。匿名是因为不需要以标准的方式来声明,比如说,使用def 语句。(除非赋值给一个局部变量,这样的对象也不会在任何的名字空间内创建名字.)然而,作为函数,它们也能有参数。一个完整的lambda“语句”代表了一个表达式,这个表达式的定义体必须和声明放在同一行。我们现在来演示下匿名函数的语法:
lambda [arg1[, arg2,... argN]]: expression
参数是可选的,如果使用的参数话,参数通常也是表达式的一部分。
>>> true = lambda : True
>>> true
<function <lambda> at 0x020C41F0>
>>> true()
True
你只要把lambda整个表达式看成一个函数调用即可。
>>> (lambda x, y = 2: x + y)(2,3)
5
当然,不要在lambda中编写复杂的表达式,如果这样,你倒不如直接写个函数得了。
11.7.2 内建函数apply(), filter(), map(), reduce()
apply(func[, nkw][,kw])
用可选的参数来调用func,nkw 为非关键字参数,kw 关键字参数;返回值是函数调用的返回值。
filter(func, seq)
调用一个布尔函数func来迭代遍历每个seq 中的元素; 返回一个使func 返回值为ture 的元素的序列。
map(func,seq1[,seq2...])
将函数func 作用于给定序列(s)的每个元素,并用一个列表来提供返回值;如果func 为None, func 表现为一个身份函数,返回一个含有每个序列中元素集合的n 个元组的列表。
reduce(func, seq[,init])
将二元函数作用于seq 序列的元素,每次携带一对(先前的结果以及下一个序列元素),连续的将现有的结果和下次给值作用在获得的随后的结果上,最后减少我们的序列为一个单一的返回值;如果初始值init 给定,第一个比较会是init 和第一个序列元素而不是序列的头两个元素。
Filter的使用:
def func(num):
return num > 5
seq = [1,2,3,4,5,6,7,8,9,10]
print filter(func, seq)
程序输出:
>>>
[6, 7, 8, 9, 10]
其实可以直接用列表推导式来代替filter:
[item for item in seq if item> 5]
Map的使用:
>>> map(lambda x,y:x + y, [1,3,5],[2,4,6])
[3, 7, 11]
>>> map(lambda x,y:(x+y, x-y), [1,3,5],[2,4,6])
[(3, -1), (7, -1), (11, -1)]
>>> map(None,[1,3,5],[2,4,6])
[(1, 2), (3, 4), (5, 6)]
这貌似用列表推导式无法办到,在多余两个参数迭代的情况下。
Reduce的使用:
reduce(func, [1, 2,3]) = func(func(1, 2), 3)
所以,可以用reduce轻而易举的进行列表的累加和计算:
>>> reduce(lambda x,y:x + y, range(5))
10
11.7.3 偏函数应用
刚才看了一下,刚开始觉得神奇,但是突然想起,这不是LISP里面lambda最简单的应用吗?我同样可以用lambda来完成书上的例子:
>>> fun1 = lambda x:x + 1
>>> fun1(4)
5
>>> fun1 = lambda x:int(x, base=2)
>>>fun1("10010")
18
但是,我们还是得简单的看看这个偏函数的一个使用方法吧。
偏函数应用GUI
from functools import partial
import Tkinter
root = Tkinter.Tk()
MyButton =partial(Tkinter.Button, root, fg="white",bg="blue")
b1 =MyButton(text="button 1")
b2 =MyButton(text="button 2")
qb = MyButton(text="quit",bg="red",command=root.quit)
b1.pack()
b2.pack()
qb.pack(fill=Tkinter.X,expand=True)
root.title("PFAs!")
root.mainloop()
程序效果:
其实这里偏函数的作用就是将一个函数封装起来,当作参数传递罢了。
11.8 变量作用域
11.8.1 全局变量与局部变量
“声明适用的程序的范围被称为了声明的作用域。在一个过程中,如果名字在过程的声明之内,它的出现即为过程的局部变量;否则的话,出现即为非局部的“
全局变量的一个特征是除非被删除掉,否则它们的存活到脚本运行结束,且对于所有的函数,他们的值都是可以被访问的,然而局部变量,就像它们存放的栈,暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动。当一个函数调用出现时,其局部变量就进入声明它们的作用域。在那一刻,一个新的局部变量名为那个对象创建了,一旦函数完成,框架被释放,变量将会离开作用域。
当我们需要在函数内用到全局变量的时候,可以用关键字global。
11.8.4 闭包
根本就不知道闭包是什么东东,我去,看例子最重要,从例子中进行理解。
def counter(start_at = 0):
count = [start_at]
def incr():
count[0] += 1
return count[0]
return incr
程序输出:
>>> count =counter(5)
>>> count()
6
>>> count()
7
>>> count2 =counter(100)
>>> count2()
101
>>> count()
8
突然明白,闭包也不过是局部函数调用全局函数的一种方法而已。其实用类的思想完全可以代替的。
我们来做做练习吧:
11-3 函数。在这个练习中,我们将实现max()和min()内建函数。
(a) 写分别带两个元素返回一个较大和较小元素,简单的max2()核min2()函数。他们应该可以用任意的python 对象运作。举例来说,max2(4,8)和min2(4,8)会各自每次返回8 和4。
(b) 创建使用了在a 部分中的解来重构max()和min()的新函数my_max()和my_min().这些函数分别返回非空队列中一个最大和最小值。它们也能带一个参数集合作为输入。用数字和字符串来测试你的解。
a)
def max2(num1, num2):
return num1 if num1 > num2 else num2
def min2(num1, num2):
return num1 if num1 < num2 else num2
print max2(4,8)
print min2(4,8)
程序输出:
>>>
8
4
b)
def max2(num1, num2):
return num1 if num1 > num2 else num2
def min2(num1, num2):
return num1 if num1 < num2 else num2
def my_max(*args):
return reduce(max2, args)
def my_min(*args):
return reduce(min2, args)
print my_max(1,2,3,4,5,6,7,8)
print my_min(1,2,3,4,5,6,7,8)
程序输出:
>>>
8
1
11–4. 返回值。给你在5-13 的解创建一个补充函数。创建一个带以分为单位的总时间以及返回一个以小时和分为单位的等价的总时间。
"""将小时:分钟形式直接表示为分钟格式"""
def TranHourToMinute(strTime):
num = []
num = strTime.split(":")
return int(num[0]) * 60 + int(num[1])
def TranMinuteToHour(strTime):
hour,minute = divmod(strTime, 60)
return str(hour)+":"+str(minute)
if __name__ == "__main__":
while True:
strTime = raw_input("please enter the time,like 12:12(q to quit):")
if strTime.lower() == "q":
break
print "the time is:%d minutes" % (TranHourToMinute(strTime))
print "the same time is: %s" % (TranMinuteToHour(TranHourToMinute(strTime)))
程序输入输出:
>>>
please enter the time,like 12:12(q to quit):12:12
the time is:732 minutes
the same time is: 12:12
please enter the time,like 12:12(q to quit):q
11–6. 变长参数。下一个称为printf()的函数。有一个值参数,格式字符串。剩下的就是根据格式化字符串上的值,要显示在标准输出上的可变参数,格式化字符串中的值允许特别的字符串格式操作指示符,如%d, %f, etc。提示:解是很琐碎的----无需实现字符串操作符功能性,但你需要显示用字符串格式化操作(%)
这道题做出来很难,用python实现printf。
先放着
11–7. 用map() 进行函数式编程。给定一对同一大小的列表, 如[1 , 2 , 3] 和['abc','def','ghi',....],将两个标归并为一个由每个列表元素组成的元组的单一的表,以使我们的结果看起来像这样:{[(1, 'abc'), (2, 'def'), (3, 'ghi'), ...}.(虽然这问题在本质上和第六章的一个问题相似,那时两个解没有直接的联系)然后创建用zip 内建函数创建另一个解。
>>> map(lambda x, y:(x, y),[1,2,3],["abc","def","ghi"])
[(1, 'abc'), (2, 'def'), (3, 'ghi')]
>>> zip([1,2,3],["abc","def","ghi"])
[(1, 'abc'), (2, 'def'), (3, 'ghi')]
11–9. 用reduce()进行函数式编程。复习11.7.2 部分,阐述如何用reduce()数字集合的累加的代码。修改它,创建一个叫average()的函数来计算每个数字集合的简单的平均值。
def average(*args):
return reduce(lambda x, y: x + y, args) / len(args)
print average(1,2,3,4,5)
程序输出:
>>>
3
11–10.用filter()进行函数式编程。在unix 文件系统中,在每个文件夹或者目录中都有两个特别的文件:'.'表示现在的目录,'..'表示父目录。给出上面的知识,看下os.listdir()函数的文档并描述这段代码做了什么:files = filter(lambda x: x and x[0] != '.', os. listdir(folder))
将文件过滤出来。
11–11.用map()进行函数式编程。写一个使用文件名以及通过除去每行中所有排头和最尾的空白来“清洁“文件。在原始文件中读取然后写入一个新的文件,创建一个新的或者覆盖掉已存在的。给你的用户一个选择来决定执行哪一个。将你的解转换成使用列表解析。
这里直接用strip方法最简单,如果非要用map来进行过滤的话,需要自己编写类似strip的一个函数。
def strip(strTemp):
return strTemp.strip()
while True:
fileName = raw_input("please enter the fileName(q to quit):")
if fileName.lower() == "q":
break
choice = raw_input("n to new file, or c to cover file:")
if choice.lower() == "n":
newFileName = raw_input("please enter the new file name:")
with open(newFileName,"w") as fobj:
with open(fileName) as fobjold:
lines = fobjold.readlines()
for line in map(strip, lines):
fobj.write(repr(line))
fobj.write("\n")
else:
with open(fileName) as fobjold:
lines = fobjold.readlines()
with open(fileName,"w") as fobjold:
for line in map(strip, lines):
fobjold.write(repr(line))
fobjold.write("\n")
看了一下文件,结果正确。
11–12. 传递函数。给在这章中描述的testit()函数写一个姊妹函数。timeit()会带一个函数对象(和参数一起)以及计算出用了多少时间来执行这个函数,而不是测试执行时的错误。返回下面的状态:函数返回值,消耗的时间。你可以用time.clock()或者time.time(),无论哪一个给你提供了较高的精度。(一般的共识是在POSIX 上用time.time(),在win32 系统上用time.clock())注意:timeit()函数与timeit 模块不相关(在python2.3 中引入)
import time
def timeit(fun, *args):
time1 = time.clock()
for i in range(10000000):
fun(*args)
time2 = time.clock()
return time2 - time1
def fun(*args):
return reduce(lambda x, y:x + y, args)
if __name__ == "__main__":
usingTime = timeit(fun, 1,2,3,4,5,6,7,8,9,10)
print "using time is: %d" % usingTime
程序输出:
>>>
using time is: 19
11–13.使用reduce()进行函数式编程以及递归。在第8 张中,我们看到N 的阶乘或者N!作为从1 到N 所有数字的乘积。
(a) 用一分钟写一个带x,y 并返回他们乘积的名为mult(x,y)的简单小巧的函数。
(b)用你在a 中创建mult()函数以及reduce 来计算阶乘。
(c)彻底抛弃掉mult()的使用,用lamda 表达式替代。
(d)在这章中,我们描绘了一个递归解决方案来找到N!用你在上面问题中完成的timeit()函数,并给三个版本阶乘函数计时(迭代的,reduce()以及递归)
import time
def mult(x, y):
return x * y
def fac1(n):
return reduce(mult, range(1,n))
def fac2(n):
return reduce(lambda x, y: x * y, range(1,n))
def timeit(fun, *args):
time1 = time.clock()
for i in range(1000000):
fun(*args)
time2 = time.clock()
return time2 - time1
print timeit(fac1, 10)
print timeit(fac2, 10)
程序输出:
>>>
2.41728863642
2.46803054498
11–14. 递归。我们也来看下在第八章中的Fibonacci 数字。重写你先前计算Fibonacci 数字的解(练习8-9)以便你可以使用递归。
def fib(num):
a,b = 1,1
while num - 1:
a,b = b,a + b
num -= 1
return a
def new_fib(num, a = 1, b = 1):
if num - 1:
a,b = b,a+b
return new_fib(num - 1, a, b)
else:
return a
print new_fib(8)
print fib(8)
不过这里递归用的好丑,但是也不知道该怎么弄好就是了。
11–15.递归。从写练习6-5 的解,用递归向后打印一个字符串。用递归向前以及向后打印一个字符串。
def printLeft(strTemp):
if strTemp:
print strTemp[0],
return printLeft(strTemp[1:])
def printRight(strTemp):
if strTemp:
print strTemp[-1],
return printRight(strTemp[:-1])
printLeft("hello world!")
print
printRight("hello world!")
程序输出:
>>>
h e l l o w o r l d !
! d l r o w o l l e h
11–17.定义
(a) 描述偏函数应用和currying 之间的区别。
(b) 偏函数应用和闭包之间有什么区别?
(c) 最后,迭代器和生成器是怎么区别开的?
个人感觉,偏函数就是lambda罢了。
然后迭代器和生成器的区别就是一个运行时候很顺利,一个运行时候会一卡一卡的。
PS:异常那章先放在,看得烦躁。
来源:oschina
链接:https://my.oschina.net/u/1017135/blog/138203