谁能使我们与基督的爱隔绝呢?难道是患难么,是困苦么,是逼迫么,是饥饿么,是赤身露体么,是危险么,是刀剑么。 然而靠着爱我们的主,在这一切的事上,已经得胜有余了。因为我深信无论是死、是生、是天使、是掌权的,是有能的,是现在的事,是将来的事,是高处的,是低处的,是别的受造之物,都不能叫我们与神的爱隔绝。这爱是在我们的主基督耶稣里的。(ROMANS 8:35,37-39)
函数(1)
函数,对于人类来讲,能够发展到这个数学思维层次,是一个飞跃。可以说,它的提出,直接加快了现代科技和社会的发展,不论是现代的任何科技门类,乃至于经济学、政治学、社会学等,都已经普遍使用函数。
下面一段来自维基百科(在本教程中,大量的定义来自维基百科,因为它真的很百科):函数词条
函数这个数学名词是莱布尼兹在1694年开始使用的,以描述曲线的一个相关量,如曲线的斜率或者曲线上的某一点。莱布尼兹所指的函数现在被称作可导函数,数学家之外的普通人一般接触到的函数即属此类。对于可导函数可以讨论它的极限和导数。此两者描述了函数输出值的变化同输入值变化的关系,是微积分学的基础。
中文的“函数”一词由清朝数学家李善兰译出。其《代数学》书中解释:“凡此變數中函(包含)彼變數者,則此為彼之函數”。
函数,从简单到复杂,各式各样。前面提供的维基百科中的函数词条,里面可以做一个概览。但不管什么样子的函数,都可以用下图概括:
理解函数
在中学数学中,可以用这样的方式定义函数:y=4x+3,这就是一个一次函数,当然,也可以写成:f(x)=4x+3。其中x是变量,它可以代表任何数。
当x=2时,代入到上面的函数表达式:
f(2) = 4*2+3 = 11
所以:f(2) = 11
但是,这并不是函数的全部,在函数中,其实变量并没有规定只能是一个数,它可以是馒头、还可是苹果,不知道读者是否对函数有这个层次的理解。请继续阅读即更深刻
变量不仅仅是数
变量x
只能是任意数吗?
其实,一个函数,就是一个对应关系。
读者尝试着将上面表达式的x
理解为馅饼,4x+3
就是4个馅饼在加上3(一般来讲,单位是统一的,但你非让它不统一,也无妨),这个结果对应着另外一个东西,那个东西比如说是iphone。或者说可以理解为4个馅饼加3就对应一个iphone。这就是所谓映射关系。
所以,x,不仅仅是数,可以是你认为的任何东西。
变量本质——占位符。
函数中为什么变量用x?这是一个有趣的问题,自己google一下,看能不能找到答案。很巧,在“知乎”上还真有人询问这个问题,可以阅读。
我也不清楚原因。不过,我清楚地知道,变量可以用x,也可以用别的符号,比如y,z,k,i,j...,甚至用alpha,beta这样的字母组合也可以。
变量在本质上就是一个占位符。这是一针见血的理解。
什么是占位符?就是先把那个位置用变量占上,表示这里有一个东西,至于这个位置放什么东西,以后再说,反正先用一个符号占着这个位置(占位符)。
其实在高级语言编程中,变量比我们在初中数学中学习的要复杂。但是,先不管那些,复杂东西放在以后再说了。现在,就按照初中数学的水平来研究Python中的变量。
通常使小写字母来命名Python中的变量,也可以是用下划线连接的多个单词。比如:alpha,x,j,p_beta,这些都可以做为Python的变量。
下面按照纯粹数学的方式,在Python中建立函数。
>>> a = 2
>>> y = 3 * a + 2
>>> y
8
这种方式建立的函数,跟在初中数学中学习的没有什么区别。在纯粹数学中,也常这么用。这种方式在Python中还有效吗?
既然在上面已经建立了一个函数,那么我就改变变量a的值,看看得到什么结果。
>>> a = 3
>>> y
8
是不是很奇怪?为什么后面已经让a等于3了,结果y还是8。
还记得前面已经学习过的关于“变量赋值”的原理吗?a=2
的含义是将2这个对象贴上了变量a标签,经过计算,得到了8,之后变量y引用了对象8。当变量a引用的对象修改为3的时候,但是y引用的对象还没有变,所以,还是8。再计算一次,y的连接对象就变了:
>>> a = 3
>>> y
8
>>> y = 3 * a + 2
>>> y
11
特别注意,如果没有先 a = 2
,就直接下函数表达式了,像这样,就会报错。
>>> y = 3 * a + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
注意看错误提示,a
是一个变量,提示中告诉我们这个变量没有定义。显然,如果函数中要使用某个变量,不得不提前定义出来。定义方法就是给这个变量赋值——这跟纯粹数学有所区别了。
用纯粹数学的方式建立函数,对Python不适用,如果非要找个根由,我想可能是“=”造成的,这个符号在数学中是等号,但是在Python中,包括所有的高级编程语言中,是“赋值”。这是我的肤浅理解。更深层的缘由,还在于计算机处理数据的原理与人不同。所以,要有一种新的定义函数的方式
定义函数
在Python中,规定了一种定义函数的格式,下面的举例就是一个函数,以这个函数为例来说明定义函数的格式和调用函数的方法。
#!/usr/bin/env python
#coding:utf-8
def add_function(a, b):
c = a + b
return c
if __name__ == "__main__":
result = add_function(2, 3)
print result #python3: print(result)
然后将文件保存,我把她命名为20101.py,你根据自己的喜好取个名字。
然后我就进入到那个文件夹,运行这个文件,出现下面的结果,如图:
你运行的结果是什么?如果没有得到上面的结果,你就非常认真地检查代码,是否跟我写的完全一样,注意,包括冒号和空格,都得一样。冒号和空格很重要。
下面开始庖丁解牛:
def
: 这里是函数的开始。在声明要建立一个函数的时候,一定要使用def(def 就是英文define的前三个字母),意思就是告知计算机,这里要声明一个函数。def
做在那一行,包括后面的add_function(a, b)
,被称为函数头。add_function
:这是函数名称。取名字是有讲究的,就好比你的名字一样。在Python中取名字的讲究就是要有一定意义,能够从名字中看出这个函数是用来干什么的。从add_function这个名字中,是不是看出她是用来计算加法的呢(严格地说,是把两个对象“相加”,这里相加的含义是比较宽泛的,包括对字符串等相加)?(a,b)
:这是参数列表。要写在括号里面。这是一个变量(参数)列表,其中的变量(参数)指向函数的输入。在这个例子中,函数有两项输入,分别是a
和b
。在通常的函数中,输入项没有限定,可以是任意数量,当然也可以没有输入,这时候的参数列表就是一对空着的圆括号(),但是,必须得有这个圆括号。:
:这个冒号非常非常重要,如果少了,就报错了。这和前面的语句是类似的,冒号表示函数头结束,下面要开始函数体的内容了。c = a + b
:这一行开始,就是函数体。函数体使一个缩进了四个空格的代码块,完成你需要完成的工作。在这个代码块中,可以使用函数头中的变量,当然,不使用也可以。缩进四个空格。这是Python的规定,要牢记,不可丢掉,丢了就报错。这句话就是将函数头的变量相加,结果赋值与另外一个变量c。return c
:还是提醒看官注意,缩进四个空格。return
是函数的关键字,意思是要返回一个值。return
语句执行时,Python跳出当前的函数并返回到调用这个函数的地方。在下面,有调用这个函数的地方result = add_function(2, 3)
。但是,函数中的return
语句也不是必须要写的,如果不写,Python将认为使以return None
来作为结束的。也就是说,如果你的函数中没有return
,事实上,在调用的时候,Python也会返回一个结果,这个结果就是None。if __name__ == "__main__"
: 这句话先照抄,不解释,因为在《自省》有说明,不知道你是不是认真阅读了。注意就是不缩进了。result = add_function(2, 3)
:这是调用前面建立的函数,并且传入两个值a=2
和b=3
。仔细观察传入参数的方法,就是相当于把2放在a那个位置,3放在b那个位置(所以说,变量就是占位符)。当函数运行,遇到了return
语句,就将函数中的结果返回到这里,赋值给result。还要啰嗦一句,是“相当于”把2和3分别放在a和b的位置,这个“相当于”的是有含义的,暂且存疑,后续会讲解。
解牛完毕,做个总结:
定义函数的格式为:
def 函数名(参数1,参数2,...,参数n):
函数体(语句块)
是不是样式很简单呢?
几点说明:
- 函数名的命名规则要符合Python中的命名要求。一般用小写字母和单下划线、数字等组合,有人习惯用aaBb的样式,但我不推荐
- def是定义函数的关键词,这个简写来自英文单词define
- 函数名后面是圆括号,括号里面,可以有参数列表,也可以没有参数
- 千万不要忘记了括号后面的冒号
- 函数体(语句块),相对于def缩进,按照python习惯,缩进四个空格
看简单例子,深入理解上面的要点:
>>> def name(): #定义一个无参数的函数,只是通过这个函数打印
... print "qiwsir" #缩进4个空格
...
>>> name() #调用函数,打印结果
qiwsir
>>> def add(x,y): #定义一个非常简单的函数
... return x+y #缩进4个空格
...
>>> add(2,3) #通过函数,计算2+3
5
注意上面的add(x,y)
函数,在这个函数中,没有特别规定参数x
、y
的类型。其实,这句话本身就是错的,还记得在前面已经多次提到,在Python中,变量无类型,只有对象才有类型,这句话应该说成:x
、y
并没有严格规定其所引用的对象类型。这是Python跟某些语言比如java很大的区别,在有些语言中,需要在定义函数的时候告诉函数参数的数据类型。Python不用那样做。
为什么?列位不要忘记了,这里的所谓参数,跟前面说的变量,本质上是一回事。只有当用到该变量的时候,才建立变量与对象的引用关系,否则,关系不建立。而对象才有类型。那么,在add(x,y)
函数中,x
,y
在引用对象之前,是完全飘忽的,没有被贴在任何一个对象上,换句话说它们有可能引用任何对象,只要后面的运算许可,如果后面的运算不许可,则会报错。
>>> add("qiw","sir") #这里,x="qiw",y="sir",让函数计算x+y,也就是"qiw"+"sir"
'qiwsir'
>>> add("qiwsir",4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in add
TypeError: cannot concatenate 'str' and 'int' objects #仔细阅读报错信息,就明白错误之处了
从实验结果中发现:x+y
的意义完全取决于对象的类型。在Python中,将这种依赖关系,称之为多态。对于Python中的多态问题,以后还会遇到,这里仅仅以此例子显示一番。请看官要留心注意的:Python中为对象编写接口,而不是为数据类型。读者先留心一下这句话,或者记住它,随着学习的深入,会领悟到其真谛的。
此外,也可以将函数通过赋值语句,与某个变量建立引用关系:
>>> result = add(3, 4)
>>> result
7
在这里,其实解释了函数的一个秘密。add(x, y)
在被运行之前,计算机内是不存在的,直到代码运行到这里的时候,在计算机中,就建立起来了一个对象,这就如同前面所学习过的字符串、列表等类型的对象一样,运行add(x,y)
之后,也建立了一个add(x,y)
的对象,这个对象与变量result
可以建立引用关系,并且add(x,y)
将运算结果返回。于是,通过result
就可以查看运算结果。
>>> add
<function add at 0x00000000007BAC80>
如果使用add(x, y)
的样式,是调用那个函数。但是如果只写函数的名字,不写参数列表,就如同上面那样,我们得到的是该函数在内存汇总的存储信息。你还可以这样做:
>>> type(add)
<class 'function'> #Python 2下的反馈信息略有差异
这说明add
是一个对象,因为只有对象才有类型,并且它是一个function
类。按照我们的经验,对象都可以与一个变量建立引用关系,从而通过那个变量访问对象。
>>> r = add
>>> r
<function add at 0x00000000007BAC80>
>>> r(3, 4)
7
>>> add(3, 4)
7
>>> type(r)
<class 'function'>
通过赋值语句,变量r
和函数对象建立了引用关系之后,就可以做所有add(x, y)
能做的事情,因为r
就是那个函数的代表。
刚开始接触函数,可能有点吃力。先放松一下,看看“名不正言不顺”的Python版。
关于命名
到现在为止,我们已经接触过变量的命名、函数的命名问题。似乎已经到了将命名问题进行总结的时候了。
在某国,向来重视“名”,所谓“名不正言不顺”,取名字或者给什么东西命名,常常是天大的事情,在很多时候就是为了那个“名”进行争斗。
江湖上还有的大师,会通过某个人的名字来预测他/她的吉凶祸福等。看来名字这玩意太重要了。“名不正,言不顺”,歪解:名字不正规化,就不顺。这是歪解,希望不要影响读者正确理解。不知道大师们是不是能够通过外国人名字预测外国人的吉凶祸福呢?比如Aoi sola,这个人怎么样?不管怎样,某国人是很在意名字的,旁边有个国家似乎就不在乎,比如山本五十六,在名字中间出现数字,就好像我们的张三李四王二麻子那样随便,不过,有一种说法,“山本五十六”的意思是这个人出生时,他父亲56岁,看来跟张三还不一样的。
Python也很在乎名字问题,其实,所有高级语言对名字都有要求。为什么呢?因为如果命名乱了,计算机就有点不知所措了。看Python对命名的一般要求。
文件名:全小写,可使用下划线
函数名:小写,可以用下划线风格单词以增加可读性。如:myfunction,my_example_function。注意:混合大小写仅被允许用于这种风格已经占据优势的时候,以便保持向后兼容。有的人,喜欢用这样的命名风格:myFunction,除了第一个单词首字母外,后面的单词首字母大写。这也是可以的,因为在某些语言中就习惯如此。但我不提倡,这是我非常鲜明的观点。
函数的参数:命名方式同变量(本质上就是变量)。如果一个参数名称和Python保留的关键字冲突,通常使用一个后缀下划线会好于使用缩写或奇怪的拼写。
变量:变量名全部小写,由下划线连接各个单词。如color = WHITE,this_is_a_variable = 1。
其实,关于命名的问题,还有不少争论呢?最典型的是所谓匈牙利命名法、驼峰命名等。如果你喜欢,可以google一下。以下内容供参考:
- 匈牙利命名法
- 驼峰式大小写
- 帕斯卡命名法
- python命名的官方要求,如果看官的英文可以,一定要阅读。如果英文稍逊,可以来阅读中文,不用梯子能行吗?看你命了。
调用函数
前面的例子中已经有了一些关于调用的问题,为了深入理解,把这个问题单独拿出来看看。
为什么要写函数?从理论上说,不用函数,也能够编程,我们在前面已经写了程序,就没有写函数,当然,用Python的内建函数姑且不算了。现在之所以使用函数,主要是:
- 降低编程的难度,通常将一个复杂的大问题分解成一系列更简单的小问题,然后将小问题继续划分成更小的问题,当问题细化为足够简单时,就可以分而治之。为了实现这种分而治之的设想,就要通过编写函数,将各个小问题逐个击破,再集合起来,解决大的问题。(请注意,分而治之的思想是编程的一个重要思想,所谓“分治”方法也。)
- 代码重(chong,二声音)用。在编程的过程中,比较忌讳同样一段代码不断的重复,所以,可以定义一个函数,在程序的多个位置使用,也可以用于多个程序。当然,后面我们还会讲到“模块”(此前也涉及到了,就是
import
导入的那个东西),还可以把函数放到一个模块中供其他程序员使用。也可以使用其他程序员定义的函数(比如import ...
,前面已经用到了,就是应用了别人——创造python的人——写好的函数)。这就避免了重复劳动,提供了工作效率。
这样看来,函数还是很必要的了。
废话少说,那就看函数怎么调用吧。以add(x,y)
为例,前面已经演示了基本调用方式,此外,还可以这样:
Python2:
>>> def add(x,y): #为了能够更明了显示参数赋值特点,重写此函数
... print "x=",x #分别打印参数赋值结果
... print "y=",y
... return x+y
...
Python 3:
>>> def add(x, y):
print("x={}".format(x))
print("y={}".format(y))
return x+y
>>> add(10, 3) #x=10,y=3
x= 10
y= 3
13
>>> add(3, 10) #x=3,y=10
x= 3
y= 10
13
所谓调用,最关键是要弄清楚如何给函数的参数赋值。这里就是按照参数次序赋值,根据参数的位置,值与之对应。
>>> add(x=10, y=3)
x= 10
y= 3
13
还可以直接把赋值语句写到里面,就明确了参数和对象的关系。当然,这时候顺序就不重要了,也可以这样
>>> add(y=10, x=3)
x= 3
y= 10
13
在定义函数的时候,参数可以像前面那样,等待被赋值,也可以定义的时候就赋给一个默认值。例如:
>>> def times(x, y=2): #y的默认值为2
... print "x=",x #Python 3: print("x={}".format(x)),以下类似,从略。
... print "y=",y
... return x*y
...
>>> times(3) #x=3,y=2
x= 3
y= 2
6
>>> times(x=3) #同上
x= 3
y= 2
6
如果不给那个有默认值的参数传递值(赋值的另外一种说法),那么它就是用默认的值。如果给它传一个,它就采用新赋给它的值。如下:
>>> times(3, 4) #x=3,y=4,y的值不再是2
x= 3
y= 4
12
>>> times("qiwsir") #再次体现了多态特点
x= qiwsir
y= 2
'qiwsirqiwsir'
请读者在闲暇之余用Python完成:写两个数的加、减、乘、除的函数,然后用这些函数,完成简单的计算。
在程序中调用函数,还需要注意一个貌似废话的事项,那就是“先定义,后使用”。说是废话,是因为在理解上似乎当然这样,但是,在实践中,常会遇到此类错误。
>>> def foo():
print('Hello, Teacher Cang!') #Python 2的使用者请自动调整为print语句
bar()
这里定义了一个函数foo()
,在这个函数里面还调用了一个函数bar()
,但是这个bar()
函数,此前并没有在什么地方定义。所以,如果调用foo()
函数,就会这样:
>>> foo()
Hello, Teacher Cang!
Traceback (most recent call last):
File "<pyshell#44>", line 1, in <module>
foo()
File "<pyshell#43>", line 3, in foo
bar()
NameError: name 'bar' is not defined
NameError:
是一种错误信息。错误不可怕,可怕的是不认真看提示信息,只要耐心地认真地阅读提示信息,就能晓得错误原因。提示信息中分明告诉我们,那个bar
没有定义。
所以要必须先定义,后使用。
>>> def bar(): pass
这就定义了bar()
,虽然非常简短,函数体内的代码就一个pass
,意思是里面什么也不做,统统地pass。然后调用foo()
>>> foo()
Hello, Teacher Cang!
不再报错了。
虽然将bar()
定义在了foo()
的后面,只要定义了,无论先后,就可以使用。
注意事项
下面的若干条,是常见编写代码的注意事项:
- 别忘了冒号。一定要记住复合语句首行末尾输入“:”(if,while,for等的第一行)
- 从第一行开始。要确定顶层(无嵌套)程序代码从第一行开始。
- 空白行在交互模式提示符下很重要。模块文件中符合语句内的空白行常被忽视。但是,当你在交互模式提示符下输入代码时,空白行则是会结束语句。
- 缩进要一致。避免在块缩进中混合制表符和空格。
- 使用简洁的for循环,而不是while or range.相比,for循环更易写,运行起来也更快
- 要注意赋值语句中的可变对象。
- 不要期待在原处修改的函数会返回结果,比如list.append(),这在可修改的对象中特别注意
- 调用函数是,函数名后面一定要跟随着括号,有时候括号里面就是空空的,有时候里面放参数。
- 不要在导入和重载中使用扩展名或路径。
以上各点如果有不理解的,也不要紧,在以后编程中,时不时地回来复习一下,能不断领悟其内涵。
如果你认为有必要打赏我,请通过支付宝:[email protected],微信号:qiwsir,不胜感激。