X Tutup
>谁能使我们与基督的爱隔绝呢?难道是患难么,是困苦么,是逼迫么,是饥饿么,是赤身露体么,是危险么,是刀剑么。 >然而靠着爱我们的主,在这一切的事上,已经得胜有余了。因为我深信无论是死、是生、是天使、是掌权的,是有能的,是现在的事,是将来的事,是高处的,是低处的,是别的受造之物,都不能叫我们与神的爱隔绝。这爱是在我们的主基督耶稣里的。(ROMANS 8:35,37-39) #函数(1) 函数,对于人类来讲,能够发展到这个数学思维层次,是一个飞跃。可以说,它的提出,直接加快了现代科技和社会的发展,不论是现代的任何科技门类,乃至于经济学、政治学、社会学等,都已经普遍使用函数。 下面一段来自维基百科(在本教程中,大量的定义来自维基百科,因为它真的很百科):[函数词条](http://zh.wikipedia.org/zh/%E5%87%BD%E6%95%B0) >函数这个数学名词是莱布尼兹在1694年开始使用的,以描述曲线的一个相关量,如曲线的斜率或者曲线上的某一点。莱布尼兹所指的函数现在被称作可导函数,数学家之外的普通人一般接触到的函数即属此类。对于可导函数可以讨论它的极限和导数。此两者描述了函数输出值的变化同输入值变化的关系,是微积分学的基础。 >中文的“函数”一词由清朝数学家李善兰译出。其《代数学》书中解释:“凡此變數中函(包含)彼變數者,則此為彼之函數”。 函数,从简单到复杂,各式各样。前面提供的维基百科中的函数词条,里面可以做一个概览。但不管什么样子的函数,都可以用下图概括: ![](./2images/20101.png) ##理解函数 在中学数学中,可以用这样的方式定义函数: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一下,看能不能找到答案。很巧,在“知乎”上还真有人询问[这个问题](http://www.zhihu.com/question/20112835),可以阅读。 我也不清楚原因。不过,我清楚地知道,变量可以用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 "", line 1, in 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,你根据自己的喜好取个名字。 然后我就进入到那个文件夹,运行这个文件,出现下面的结果,如图: ![](./2images/20102.png) 你运行的结果是什么?如果没有得到上面的结果,你就非常认真地检查代码,是否跟我写的完全一样,注意,包括冒号和空格,都得一样。**冒号和空格很重要。** 下面开始庖丁解牛: - `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__"`: 这句话先照抄,不解释,因为在[《自省》](./130.md)有说明,不知道你是不是认真阅读了。注意就是不缩进了。 - `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 "", line 1, in File "", 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 如果使用`add(x, y)`的样式,是调用那个函数。但是如果只写函数的名字,不写参数列表,就如同上面那样,我们得到的是该函数在内存汇总的存储信息。你还可以这样做: >>> type(add) #Python 2下的反馈信息略有差异 这说明`add`是一个对象,因为只有对象才有类型,并且它是一个`function`类。按照我们的经验,对象都可以与一个变量建立引用关系,从而通过那个变量访问对象。 >>> r = add >>> r >>> r(3, 4) 7 >>> add(3, 4) 7 >>> type(r) 通过赋值语句,变量`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一下。以下内容供参考: - [匈牙利命名法](http://zh.wikipedia.org/zh/%E5%8C%88%E7%89%99%E5%88%A9%E5%91%BD%E5%90%8D%E6%B3%95) - [驼峰式大小写](http://zh.wikipedia.org/wiki/%E9%A7%9D%E5%B3%B0%E5%BC%8F%E5%A4%A7%E5%B0%8F%E5%AF%AB) - [帕斯卡命名法](http://zh.wikipedia.org/w/index.php?title=%E5%B8%95%E6%96%AF%E5%8D%A1%E5%91%BD%E5%90%8D%E6%B3%95&variant=zh-cn) - [python命名的官方要求](http://legacy.python.org/dev/peps/pep-0008/#prescriptive-naming-conventions),如果看官的英文可以,一定要阅读。如果英文稍逊,可以来阅读[中文](http://wiki.jiayun.org/PEP_8_--_Style_Guide_for_Python_Code#.E5.91.BD.E5.90.8D.E6.85.A3.E4.BE.8B),不用梯子能行吗?看你命了。 ##调用函数 前面的例子中已经有了一些关于调用的问题,为了深入理解,把这个问题单独拿出来看看。 为什么要写函数?从理论上说,不用函数,也能够编程,我们在前面已经写了程序,就没有写函数,当然,用Python的内建函数姑且不算了。现在之所以使用函数,主要是: 1. 降低编程的难度,通常将一个复杂的大问题分解成一系列更简单的小问题,然后将小问题继续划分成更小的问题,当问题细化为足够简单时,就可以分而治之。为了实现这种分而治之的设想,就要通过编写函数,将各个小问题逐个击破,再集合起来,解决大的问题。(请注意,分而治之的思想是编程的一个重要思想,所谓“分治”方法也。) 2. 代码重(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 "", line 1, in foo() File "", 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()`的后面,只要定义了,无论先后,就可以使用。 ##注意事项 下面的若干条,是常见编写代码的注意事项: 1. 别忘了冒号。一定要记住复合语句首行末尾输入“:”(if,while,for等的第一行) 2. 从第一行开始。要确定顶层(无嵌套)程序代码从第一行开始。 3. 空白行在交互模式提示符下很重要。模块文件中符合语句内的空白行常被忽视。但是,当你在交互模式提示符下输入代码时,空白行则是会结束语句。 4. 缩进要一致。避免在块缩进中混合制表符和空格。 5. 使用简洁的for循环,而不是while or range.相比,for循环更易写,运行起来也更快 6. 要注意赋值语句中的可变对象。 7. 不要期待在原处修改的函数会返回结果,比如list.append(),这在可修改的对象中特别注意 8. 调用函数是,函数名后面一定要跟随着括号,有时候括号里面就是空空的,有时候里面放参数。 9. 不要在导入和重载中使用扩展名或路径。 以上各点如果有不理解的,也不要紧,在以后编程中,时不时地回来复习一下,能不断领悟其内涵。 ------ [总目录](./index.md)   |   [上节:自省](./130.md)   |   [下节:函数(2)](./202.md) 如果你认为有必要打赏我,请通过支付宝:**qiwsir@126.com**,微信号:**qiwsir**,不胜感激。
X Tutup