函数(2)
爱人不可虚假,恶要厌恶,善要亲近。爱弟兄,要彼此亲热;恭敬人,要彼此推让。殷勤不可懒惰。要心里火热,常常服侍主。在指望中要喜乐,在患难中要忍耐,祷告要恒切。(ROMANS 12:9-12)
函数(2)
返回值
所谓返回值,就是函数向调用函数的地方返回的数据。
编写一个斐波那契数列函数,来说明这个问题。还记得斐波那契数列吗?忘了没关系,看看本教程前面的内容即可。
我这里提供一段参考代码(既然是参考,显然不是唯一正确答案):
#!/usr/bin/env python# coding=utf-8def fibs(n): result = [0,1] for i in range(n-2): result.append(result[-2] + result[-1]) return resultif __name__ == "__main__": lst = fibs(10) print lst
把含有这些代码的文件保存为名为20202.py的文件。
在这个文件中,首先定义了一个函数,名字叫做fibs
,其参数是输入一个整数(但是,你并没有看到我在哪里做了对这个要输入的值的约束,就意味着,你输入非整数,甚至字符串,也使可以的,只是结果会不同,不妨试试吧),然后通过lst = fibs(10)
调用这个函数。这里参数给的是10,就意味着要得到n=10
的斐波那契数列。
运行后打印数列:
$ python 20202.py[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
当然,如果要换n的值,只需要在调用函数的时候,修改一下参数即可。这才体现出函数的优势呢。
观察fibs()
函数,最后有一个语句return result
,意思是将变量result的值返回。返回给谁呢?这要看我们当前在什么位置调用该函数了。
在上面的程序中,以lst = fibs(10)
语句的方式,调用了函数,那么函数就将值返回到当前状态,并记录在内存中,然后把它赋值给变量lst
。
注意:上面的函数只返回了一个返回值(是一个列表),有时候需要返回多个,是以元组形式返回。
>>> def my_fun():... return 1, 2, 3... >>> a = my_fun()>>> a(1, 2, 3)
对这个函数,我们还可以用这样的方式来接收函数的返回值。
>>> x, y, z = my_fun()>>> x1>>> y2>>> z3
多么神奇。
也不怎么神奇,这也来源于我们前面已经熟知的赋值语句。其效果相当于:
>>> x, y, z = a>>> x, y, z(1, 2, 3)
不是所有的函数都有return
的,比如有的函数,就是执行某个语句或者什么也不做,不需要返回值。事实上,不是没有返回值,也有,只不过是None。比如这样一个函数:
>>> def foo():... pass...
我在交互模式下构造一个很简单的函数,注意,我这是构造了一个简单函数,如果是复杂的,千万不要在交互模式下做。如果你非要做,是能尝到苦头的。
这个函数的作用就是pass——什么也不做,当然是没有return了。
>>> a = foo()
我们再看看那个变量a,到底是什么
>>> print a #Python 3: print(a)None
这就是没有return
的函数,事实上返回的是一个None
。而None
,你有可以理解成没有返回任何东西。
这种模样的函数,通常不用上述方式调用,而采用下面的方式,因为他们返回的是None,似乎这个返回值利用价值不高,于是就不用找一个变量来接受返回值了。
>>> foo()
特别注意那个return
,它还有一个作用,请先观察下面的函数和执行结果,并试图找出其作用。
>>> def my_fun():... print "I am coding." #Python 3的用户请修改为print()... return... print "I finished."... >>> my_fun()I am coding.
看出玄机了吗?
在函数中,本来有两个print
,但是中间插入了一个return
,仅仅是一个return
。当执行函数的时候,只执行了第一个print
,第二个并没有执行。这是因为第一个之后,遇到了return,它告诉函数要返回,即中断函数体内的流程,离开这个函数。结果第二个print
就没有被执行。所以,return
在这里就有了一个作用,结束正在执行的函数,并离开函数体返回到调用位置,有点类似循环中的break
的作用。
函数中的文档
“程序在大多数情况下是给人看的,只是偶尔被机器执行。”
所以,写程序必须要写注释。前面已经有过说明,如果用#
开始,Python就不执行那句(Python看不到它,但是人能看到),它就作为注释存在。
除了这样的一句之外,一般在每个函数名字的下面,还有比较多的说明,这个被称为“文档”,在文档中主要是说明这个函数的用途。
#!/usr/bin/env python# coding=utf-8def fibs(n): """ This is a Fibonacci sequence. """ result = [0,1] for i in range(n-2): result.append(result[-2] + result[-1]) return resultif __name__ == "__main__": lst = fibs(10) print lst
在这个函数的名称下面,用三个引号的方式,包裹着对这个函数的说明,那个就是函数文档。
还记得在《自省》那节中,提到的__doc__
吗?对于函数,它的内容就来自这里。
>>> def my_fun():... """... This is my function.... """... print "I am a craft."... >>> my_fun.__doc__'\n This is my function.\n '
如果在交互模式中用help(my_fun)
得到的也是三个引号所包裹的文档信息。
Help on function my_fun in module __main__:my_fun() This is my function.
函数的属性
任何对象都具有属性,比如“孔乙己的茴香豆”,这里“孔乙己”是一个对象,“茴香豆”是一个属性,世界上“茴香豆”很多,但是这里所说的“茴香豆”是比较特殊的,它归属于“孔乙己”。如果用符号的方式来表示“孔乙己的茴香豆”,一般习惯用句点(英文的)代替中间的“的”子,也就是句点表示了属性的归属,表示为:孔乙己.茴香豆
。
前面已经说过,函数是对象。那么它也有属性。
>>> def cang(): """This is a function of canglaoshi""" pass
对于这个函数,最熟悉的一个属性就应该是前面提到的函数文档,它可以用句点的方式表示为cang.__doc__
。
>>> cang.__doc__ 'This is a function of canglaoshi'
这就体现出这种方式表示属性的优势了,只要对象不同,不管属性的名字是否相同,用句点就可以说明该属性所对应的对象。
还可以为对象增加属性。
>>> cang.breast = 90
这样就为对象cang
增加了一个属性breast
,并且设置该属性的值是90。接下来就可以调用该属性。
>>> cang.breast90
还记得我们用来查看对象属性和方法的函数dir()
吗?现在又可以请它出来,一览众属性。
>>> dir(cang)['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'breast']
这里列出了所有cang
这个对象的属性和方法,仔细观察,我们刚才用过的cang.__doc__
和刚刚设置的cang.breast
都历历在目。至于这里有很多属性的名字都是用双下划线开始和结束,这类属性可以称之为特殊属性(因为名字样式特殊吗?)。
>>> cang.__name__'cang'>>> cang.__module__'__main__'
所有这些属性,都可以用句点的方式调用。
参数和变量
函数的参数,还是很有话题的。比如在别的程序员嘴里,你或许听说过“形参”、“实参”、“参数”等名词,到底指什么呢?
在定义函数的时候(def来定义函数,称为def语句),函数名后面的括号里如果有变量,它们通常被称为“形参”。调用函数的时候,给函数提供的值叫做“实参”,或者“参数”。
其实,如果你区别不开,也不会耽误你写代码,这只不过类似孔乙己先生知道茴香豆的茴字有多少种写法罢了。但是,我居然碰到过某公司的面试官问这种问题。
我们就简化一下,笼统地把函数括号里面的变量叫做参数吧,当然你叫变量也无妨,只要大家知道值得是什么东西就好了。虽然这样会引起某些认真的人来喷口水,但也不用担心,反正本书已经声明是很“水”的了。
但如果有人较真,非要让你区分,为了显示你的水平,你可以引用微软网站上的说明。我认为这段说明高度抽象,而且意义涵盖深远的说明。摘抄过来,请读一读,是否理解。
参数和变量之间的差异 (Visual Basic)
多数情况下,过程必须包含有关调用环境的一些信息。执行重复或共享任务的过程对每次调用使用不同的信息。此信息包含每次调用过程时传递给它的变量、常量和表达式。
若要将此信息传递给过程,过程先要定义一个形参,然后调用代码将一个实参传递给所定义的形参。 您可以将形参当作一个停车位,而将实参当作一辆汽车。 就像一个停车位可以在不同时间停放不同的汽车一样,调用代码在每次调用过程时可以将不同的实参传递给同一个形参。
形参表示一个值,过程希望您在调用它时传递该值。
当您定义 Function 或 Sub 过程时,需要在紧跟过程名称的括号内指定形参列表。对于每个形参,您可以指定名称、数据类型和传入机制(ByVal (Visual Basic) 或 ByRef (Visual Basic))。您还可以指示某个形参是可选的。这意味着调用代码不必传递它的值。
每个形参的名称均可作为过程内的局部变量。形参名称的使用方法与其他任何变量的使用方法相同。
实参表示在您调用过程时传递给过程形参的值。调用代码在调用过程时提供参数。
调用 Function 或 Sub 过程时,需要在紧跟过程名称的括号内包括实参列表。每个实参均与此列表中位于相同位置的那个形参相对应。
与形参定义不同,实参没有名称。每个实参就是一个表达式,它包含零或多个变量、常数和文本。求值的表达式的数据类型通常应与为相应形参定义的数据类型相匹配,并且在任何情况下,该表达式值都必须可转换为此形参类型。
如果硬着头皮看完这段引文,发现里面有几个关键词:参数、变量、形参、实参。本来想弄清楚参数和变量,结果又冒出另外两个词,更混乱了。请稍安勿躁,在编程业界,类似的东西有很多名词。下次听到有人说这些,不用害怕啦,反正自己听过了。
在Python中,没有这么复杂。
看完上面让人晕头转向的引文之后,再看下面的代码,就会豁然开朗了。
>>> def add(x): #x是参数,准确说是形参... a = 10 #a是变量... return a+x #x就是那个形参作为变量,其本质是要传递赋给这个函数的值... >>> x = 3 #x是变量,只不过在函数之外>>> add(x) #这里的x是参数,但是它由前面的变量x传递对象313>>> add(3) #把上面的过程合并了13
至此,是否清楚了一点点。当然,我所表述不正确之处或者理解错误之处,请不吝赐教,小可作揖感谢。
其实没有那么复杂。关键要理解函数名括号后面的东东(管它什么参呢)的作用是“传对象引用”——这又是一种貌似高深的说法。
>>> def foo(lst):... lst.append(99)... return lst... >>> x = [1, 3, 5]>>> y = foo(x)>>> y[1, 3, 5, 99]>>> x[1, 3, 5, 99]>>> id(x)3075464588L>>> id(y)3075464588L
结合前面学习过的列表能够被原地修改知识,加上刚才说的参数特点,你是不是能理解上面的操作呢?