《Think Python 2e》学习精粹(六):有返回值的函数
空函数(void) :产生某种效果,像打印一个值或是移动乌龟,但是并不产生一个值;
有返回值的函数 :调用这样的函数会生成一个值, 通常将其赋值给某个变量或是作为表达式的一部分;
1、返回值
返回值 :调用一个函数会生成一个值,这个值称为函数的返回值 ;
调用一个有返回值的函数会生成一个值;
通常将函数返回值赋值给某个变量或是作为表达式的一部分;
空函数,泛泛地来看,它们没有返回值,更准确地说,它们的返回值是 None ;
有返回值的函数中,以包含一个表达式的 return 语句结束:“马上从该函数返回,并使用接下来的表达式作为返回值”;
return 语句中表达式可以是任意复杂的;
条件语句的每一个分支内各有一个返回语句会很有用,一旦一条返回语句执行,函数则终止,不再执行后续的语句;
出现在某条return语句之后的代码,或者在执行流程永远不会到达之处的代码,被称为死代码(dead code);
在一个有返回值的函数中, 最好保证程序执行的每一个流程最终都会碰到一个 return 语句;
函数按照流程执行完毕,未碰到一个 return 语句,返回值将是 None;
def absolute_value ( x) :
if x < 0 :
return - x
if x > 0 :
return x
>> > absolute_value( 0 )
None
def compare ( x, y) :
if x > y:
return 1
elif x < y:
return - 1
elif x == y:
return 0
print ( compare( 10 , 10 ) )
PS C: \Users\Administrator> python D: \WorkSpace\thinkpython2e\new23. py
0
2、增量式开发
增量式开发( incremental development ) :通过每次只增加和测试少量代码,来避免长时间的调试,这种开发方式即为增量式开发( incremental development ) ;
从一个能运行的程序开始,并且每次只增加少量改动,无论你何时遇到错误,都能够清楚定位错误的源头;
用临时变量存储中间值,这样你就能显示并检查它们;
一旦程序正确运行,你要删除一些脚手架代码,或者将多条语句组成复合表达式,但是前提是不会影响程序的可读性;
def hypotenuse1 ( a, b) :
return 0
def hypotenuse2 ( a, b) :
squared = a** 2 + b** 2
print ( "squared = " , squared)
return 0
def hypotenuse3 ( a, b) :
squared = a** 2 + b** 2
c = math. sqrt( squared)
print ( "c = " , c)
return 0
def hypotenuse ( a, b) :
squared = a** 2 + b** 2
c = math. sqrt( squared)
return c
print ( hypotenuse1( 3 , 4 ) )
print ( hypotenuse2( 3 , 4 ) )
import math
print ( hypotenuse3( 3 , 4 ) )
print ( hypotenuse( 3 , 4 ) )
PS C: \Users\Administrator> python D: \WorkSpace\thinkpython2e\new24. py
0
squared = 25
0
c = 5.0
0
5.0
脚手架代码(scaffolding) :上述练习中,print("squared = ",squared)
这样的代码对程序的构建很有用,但不是最终产品的一部分,这样的代码即为脚手架代码(scaffolding) ;
3、组合
def area ( radius) :
return math. pi * radius** 2
def distance ( x1, y1, x2, y2) :
dx = x2 - x1
dy = y2 - y1
dsquared = dx** 2 + dy** 2
result = math. sqrt( dsquared)
return result
def circle_area ( xc, yc, xp, yp) :
return area( distance( xc, yc, xp, yp) )
4、布尔函数
函数可以返回布尔值(booleans);
布尔函数返回值:True 或者 False(如果无返回值,则为 None );
def is_between ( x, y, z) :
if x <= y and y <= z:
return True
else :
return False
print ( is_between( 5 , 6 , 4 ) )
PS C: \Users\Administrator> python D: \WorkSpace\thinkpython2e\new25. py
False
5、再谈递归
一个递归定义的函数的例子(阶乘函数 factorial):
def factorial ( n) :
if n == 0 :
return 1
else :
recurse = factorial( n- 1 )
result = n * recurse
return result
递归定义类似循环定义,因为定义中包含一个对已经被定义的事物的引用;
上面递归定义的函数(阶乘函数)引入实参3时的堆栈图:
6、信仰之跃
“信仰之跃” :阅读代码,当遇到一个函数调用时,不再去跟踪程序执行流程,而是假设这个函数正确运行并返回了正确的结果;
当遇到递归调用时, 不用顺着执行流程,应该假设每次递归调用能够正确工作(返回正确的结果);
当然,在没写完函数的时就假设函数正确工作有一点儿奇怪, 但这也是为什么这被称作信仰之跃了;
7、再举一例
另一个递归定义的函数(斐波那契数列 fibonacci ):
def fibonacci ( n) :
if n == 0 :
return 0
elif n == 1 :
return 1
else :
return fibonacci( n- 1 ) + fibonacci( n- 2 )
如果试图跟踪执行流程,即使是相当小的 n ,也足够头疼的,但遵循信仰之跃这种方法,如果假设这两个递归调用都能正确运行,很明显将他们两个相加就是正确结果;
8、检查类型
重构后的阶乘函数,演示了一个有时被称作监护人(guardian) 的模式:
def factorial ( n) :
if not isinstance ( n, int ) :
print ( 'Factorial is only defined for integers.' )
return None
elif n < 0 :
print ( 'Factorial is not defined for negative integers.' )
return None
elif n == 0 :
return 1
else :
return n * factorial( n- 1 )
前两个条件扮演监护人的角色,避免接下来的代码使用引发错误的值;
监护人使得验证代码的正确性成为可能;
9、调试
将一个大程序分解为较小的函数为调试生成了自然的检查点, 如果一个函数不如预期的运行,有三个可能性需要考虑:
该函数获得的实参有些问题,违反先决条件;
该函数有些问题,违反后置条件;
返回值或者它的使用方法有问题;
为了排除第一种可能,你以在函数的开始增加一条 print 语句来打印形参的值(也可以是它们的类型), 或者你可以写代码来显示地检查先决条件;
如果形参看起来没问题,就在每个 return 语句之前增加一条 print 语句,来打印返回值;
如果可能,手工检查结果;
考虑用一些容易检查的值来调用该函数;
如果该函数看起来正常工作,则检查函数调用,确保返回值被正确的使用(或者的确被使用了!);
在一个函数的开始和结尾处增加打印语句,可以使执行流程更明显;
def factorial ( n) :
space = ' ' * ( 4 * n)
print ( space, 'factorial' , n)
if n == 0 :
print ( space, 'returning 1' )
return 1
else :
recurse = factorial( n- 1 )
result = n * recurse
print ( space, 'returning' , result)
return result
factorial( 4 )
PS C: \Users\Administrator> python D: \WorkSpace\thinkpython2e\new26. py
factorial 4
factorial 3
factorial 2
factorial 1
factorial 0
returning 1
returning 1
returning 2
returning 6
returning 24