爱人不可虚假,恶要厌恶,善要亲近。爱弟兄,要彼此亲热;恭敬人,要彼此推让。殷勤不可懒惰。要心里火热,常常服侍主。在指望中要喜乐,在患难中要忍耐,祷告要恒切。(ROMANS 12:9-12)
#函数(2)
##返回值
所谓返回值,就是函数向调用函数的地方返回的数据。
编写一个斐波那契数列函数,来说明这个问题。还记得斐波那契数列吗?忘了没关系,看看本教程前面的内容即可。
我这里提供一段参考代码(既然是参考,显然不是唯一正确答案):
#!/usr/bin/env python
# coding=utf-8
def fibs(n):
result = [0,1]
for i in range(n-2):
result.append(result[-2] + result[-1])
return result
if __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()
>>> x
1
>>> y
2
>>> z
3
多么神奇。
也不怎么神奇,这也来源于我们前面已经熟知的赋值语句。其效果相当于:
>>> 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-8
def fibs(n):
"""
This is a Fibonacci sequence.
"""
result = [0,1]
for i in range(n-2):
result.append(result[-2] + result[-1])
return result
if __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.breast
90
还记得我们用来查看对象属性和方法的函数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传递对象3
13
>>> 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
结合前面学习过的列表能够被原地修改知识,加上刚才说的参数特点,你是不是能理解上面的操作呢?
###全局变量和局部变量
虽然是讲参数,但是关于全局变量和局部变量的区别也要先弄清楚,因为关系到函数内外有别的大事。
下面是一段代码,注意这段代码中有一个函数funcx(),这个函数里面有一个x=9,在函数的前面也有一个x=2。
x = 2
def funcx():
x = 9
print "this x is in the funcx:-->", x #Python 3请自动修改为print(),下同,从略
funcx()
print "--------------------------"
print "this x is out of funcx:-->", x
这段代码输出的结果是什么呢?看:
this x is in the funcx:--> 9
--------------------------
this x is out of funcx:--> 2
从输出中可以看出,运行funcx(),输出了funcx()里面的变量x引用的对象9;然后执行代码中的最后一行print "this x is out of funcx:-->",x。
特别要关注的是,前一个x输出的是函数内部的变量x;后一个x输出的是函数外面的变量x。两个变量彼此没有互相影响,虽然都是x。两个x各自在各自的领域内起到作用。
把那个只在函数体内(某个范围内)起作用的变量称之为局部变量。
有局部,就有对应的全部,在汉语中,全部变量,似乎有歧义,幸亏汉语丰富,于是又取了一个名词:全局变量
x = 2
def funcx():
global x #跟上面函数的不同之处
x = 9
print "this x is in the funcx:-->",x
funcx()
print "--------------------------"
print "this x is out of funcx:-->",x
以上两段代码的不同之处在于,后者在函数内多了一个global x,这句话的意思是在声明x是全局变量,也就是说这个x跟函数外面的那个x同一个,接下来通过x=9将x的引用对象变成了9。所以,就出现了下面的结果。
this x is in the funcx:--> 9
--------------------------
this x is out of funcx:--> 9
好似全局变量能力很强悍,能够统统率函数内外。但是,要注意,这个东西要慎重使用,因为往往容易带来变量的混乱。内外有别,在程序中一定要注意的。
###命名空间
这是一个比较不容易理解的概念,特别是对于初学者而言,似乎它很飘渺。我在维基百科中看到对它的定义,不仅定义比较好,连里面的例子都不错。所以,抄录下来,帮助读者理解这个名词。
命名空间(英语:Namespace)表示标识符(identifier)的可见范围。一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。这样,在一个新的命名空间中可定义任何标识符,它们不会与任何已有的标识符发生冲突,因为已有的定义都处于其它命名空间中。
例如,设Bill是X公司的员工,工号为123,而John是Y公司的员工,工号也是123。由于两人在不同的公司工作,可以使用相同的工号来标识而不会造成混乱,这里每个公司就表示一个独立的命名空间。如果两人在同一家公司工作,其工号就不能相同了,否则在支付工资时便会发生混乱。
这一特点是使用命名空间的主要理由。在大型的计算机程序或文档中,往往会出现数百或数千个标识符。命名空间(或类似的方法,见“命名空间的模拟”一节)提供一隱藏區域標識符的機制。通过将逻辑上相关的标识符组织成相应的命名空间,可使整个系统更加模块化。
在编程语言中,命名空间是对作用域的一种特殊的抽象,它包含了处于该作用域内的标识符,且本身也用一个标识符来表示,这样便将一系列在逻辑上相关的标识符用一个标识符组织了起来。许多现代编程语言都支持命名空间。在一些编程语言(例如C++和Python)中,命名空间本身的标识符也属于一个外层的命名空间,也即命名空间可以嵌套,构成一个命名空间树,树根则是无名的全局命名空间。
函数和类的作用域可被視作隱式命名空间,它們和可見性、可訪問性和对象生命周期不可分割的联系在一起。
显然,用“命名空间”或者“作用域”这样的名词,就是因为有了函数(后面还会有类)之后,在函数内外都可能有外形一样的符号(标识符),在python中(乃至于其它高级语言),通常就是变量,为了区分此变量非彼变量(虽然外形一样),需要用这样的东西来框定每个变量所对应的值(发生作用的范围)。
前面已经讲过,变量和对象(就是所变量所对应的值)之间的关系是:变量类似标签,贴在了对象上。也就是,通过赋值语句实现了一个变量标签对应一个数据对象(值),这种对应关系让你想起了什么?映射!python中唯一的映射就是dict,里面有“键值对”。变量和值得关系就有点像“键”和“值”的关系。有一个内建函数vars,可以帮助我们研究一下这种对应关系。
>>> x = 7
>>> scope = vars()
>>> scope['x']
7
>>> scope['x'] += 1
>>> x
8
>>> scope['x']
8
既然如此,诚如前面的全局变量和局部变量,即使是同样一个变量名称。但是它在不同范围(还是用“命名空间”这个词是不是更专业呢?)对应不同的值。
如果你认为有必要打赏我,请通过支付宝:qiwsir@126.com,不胜感激。