| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> 【Python学习教程】Python函数和lambda表达式 -> 正文阅读 |
|
[Python知识库]【Python学习教程】Python函数和lambda表达式 |
文章目录
转载于http://c.biancheng.net/python/ 函数就是一段封装好的,可以重复使用的代码,它使得我们的程序更加模块化,不需要编写大量重复的代码。 函数可以提前保存起来,并给它起一个独一无二的名字,只要知道它的名字就能使用这段代码。函数还可以接收数据,并根据数据的不同做出不同的操作,最后再把处理结果反馈给我们。 本章不仅会介绍 Python 定义和使用函数的基本语法,还有很多高级的函数用法(例如 lambda 匿名函数),都会为你一一详解。 Python函数(函数定义、函数调用)用法详解Python 中函数的应用非常广泛,前面章节中我们已经接触过多个函数,比如 input() 、print()、range()、len() 函数等等,这些都是 Python 的内置函数,可以直接使用。 除了可以直接使用的内置函数外,Python 还支持自定义函数,即将一段有规律的、可重复使用的代码定义成函数,从而达到一次编写、多次调用的目的。 举个例子,前面学习了 len() 函数,通过它我们可以直接获得一个字符串的长度。我们不妨设想一下,如果没有 len() 函数,要想获取一个字符串的长度,该如何实现呢?请看下面的代码:
程序执行结果为: 30 要知道,获取一个字符串长度是常用的功能,一个程序中就可能用到很多次,如果每次都写这样一段重复的代码,不但费时费力、容易出错,而且交给别人时也很麻烦。 所以 Python 提供了一个功能,即允许我们将常用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就可以重复使用它,这个模块就叫做函数(Function)。 比如,在程序中定义了一段代码,这段代码用于实现一个特定的功能。问题来了,如果下次需要实现同样的功能,难道要把前面定义的代码复制一次?如果这样做实在太傻了,这意味着每次当程序需要实现该功能时,都要将前面定义的代码复制一次。正确的做法是,将实现特定功能的代码定义成一个函数,每次当程序需要实现该功能时,只要执行(调用)该函数即可。 其实,函数的本质就是一段有特定功能、可以重复使用的代码,这段代码已经被提前编写好了,并且为其起一个“好听”的名字。在后续编写程序过程中,如果需要同样的功能,直接通过起好的名字就可以调用这段代码。 下面演示了如何将我们自己实现的 len() 函数封装成一个函数:
程序执行结果为: 30 如果读者接触过其他编程语言中的函数,以上对于函数的描述,肯定不会陌生。但需要注意的一点是,和其他编程语言中函数相同的是,Python 函数支持接收多个( ≥0 )参数,不同之处在于,Python 函数还支持返回多个( ≥0 )值。 比如,上面程序中,我们自己封装的 my_len(str) 函数,在定义此函数时,我们为其设置了 1 个 str 参数,同时该函数经过内部处理,会返回给我们 1 个 length 值。 通过分析 my_len() 函数这个实例不难看出,函数的使用大致分为 2 步,分别是定义函数和调用函数。接下来一一为读者进行详细的讲解。 Python函数的定义定义函数,也就是创建一个函数,可以理解为创建一个具有某些用途的工具。定义函数需要用 def 关键字实现,具体的语法格式如下: def 函数名(参数列表): 其中,用 [] 括起来的为可选择部分,即可以使用,也可以省略。 此格式中,各部分参数的含义如下:
注意,在创建函数时,即使函数不需要参数,也必须保留一对空的“()”,否则 Python 解释器将提示“invaild syntax”错误。另外,如果想定义一个没有任何功能的空函数,可以使用 pass 语句作为占位符。 例如,下面定义了 2 个函数:
另外值得一提的是,函数中的 return 语句可以直接返回一个表达式的值,例如修改上面的 str_max() 函数:
该函数的功能,和上面的 str_max() 函数是完全一样的,只是省略了创建 str 变量,因此函数代码更加简洁。 Python函数的调用调用函数也就是执行函数。如果把创建的函数理解为一个具有某种用途的工具,那么调用函数就相当于使用该工具。 函数调用的基本语法格式如下所示: [返回值] = 函数名([形参值]) 其中,函数名即指的是要调用的函数的名称;形参值指的是当初创建函数时要求传入的各个形参的值。如果该函数有返回值,我们可以通过一个变量来接收该值,当然也可以不接受。
例如,我们可以调用上面创建的 pass_dis() 和 str_max() 函数:
首先,对于调用空函数来说,由于函数本身并不包含任何有价值的执行代码,也没有返回值,应该调用空函数不会有任何效果。 其次,对于上面程序中调用 str_max() 函数,由于当初定义该函数为其设置了 2 个参数,因此这里在调用该参数,就必须传入 2 个参数。同时,由于该函数内部还使用了 return 语句,因此我们可以使用 strmax 变量来接收该函数的返回值。 因此,程序执行结果为: http://c.biancheng.net/shell 为函数提供说明文档前面章节讲过,通过调用 Python 的 help() 内置函数或者 doc 属性,我们可以查看某个函数的使用说明文档。事实上,无论是 Python 提供给我们的函数,还是自定义的函数,其说明文档都需要设计该函数的程序员自己编写。 其实,函数的说明文档,本质就是一段字符串,只不过作为说明文档,字符串的放置位置是有讲究的,函数的说明文档通常位于函数内部、所有代码的最前面。 以上面程序中的 str_max() 函数为例,下面演示了如何为其设置说明文档:
程序执行结果为: Help on function str_max in module main: str_max(str1, str2) 上面程序中,还可以使用 doc 属性来获取 str_max() 函数的说明文档,即使用最后一行的输出语句,其输出结果为: 比较 2 个字符串的大小 Python函数值传递和引用传递(包括形式参数和实际参数的区别)通常情况下,定义函数时都会选择有参数的函数形式,函数参数的作用是传递数据给函数,令其对接收的数据做具体的操作处理。 在使用函数时,经常会用到形式参数(简称“形参”)和实际参数(简称“实参”),二者都叫参数,之间的区别是:
实参和形参的区别,就如同剧本选主角,剧本中的角色相当于形参,而演角色的演员就相当于实参。 明白了什么是形参和实参后,再来想一个问题,那就是实参是如何传递给形参的呢? Python 中,根据实际参数的类型不同,函数参数的传递方式可分为 2 种,分别为值传递和引用(地址)传递:
值传递和引用传递的区别是,函数参数进行值传递后,若形参的值发生改变,不会影响实参的值;而函数参数继续引用传递后,改变形参的值,实参的值也会一同改变。 例如,定义一个名为 demo 的函数,分别为传入一个字符串类型的变量(代表值传递)和列表类型的变量(代表引用传递):
运行结果为: -------值传递----- 分析运行结果不难看出,在执行值传递时,改变形式参数的值,实际参数并不会发生改变;而在进行引用传递时,改变形式参数的值,实际参数也会发生同样的改变。 对于初学者来说,本节只需要了解形参和实参,值传递和引用传递的区别即可。对于函数参数的传递方法,如果读者想深入探究其原因,可阅读《Python函数参数传递机制》一节。 Python函数参数传递机制(超级详细)通过学习《Python函数值传递和引用传递》一节我们知道,根据实际参数的类型不同,函数参数的传递方式分为值传递和引用传递(又称为地址传递),Python 底层是如何实现它们的呢?Python 中函数参数由实参传递给形参的过程,是由参数传递机制来控制的。 本节将围绕值传递和引用传递,深度剖析它们的底层实现。 Python函数参数的值传递机制Python 函数参数的值传递,其本质就是将实际参数值复制一份,将其副本传给形参。这意味着,采用值传递方式的函数中,无论其内部对参数值进行如何修改,都不会影响函数外部的实参。 值传递的方式,类似于《西游记》里的孙悟空,它复制一个假孙悟空,假孙悟空具有的能力和真孙悟空相同,可除妖或被砍头。但不管这个假孙悟空遇到什么事,真孙悟空都不会受到任何影响。与此类似,传入函数的是实际参数值的复制品,不管在函数中对这个复制品如何操作,实际参数值本身不会受到任何影响。 下面程序演示了函数参数进行值传递的效果:
运行上面程序,将看到如下运行结果: swap函数里,a = 9 b = 6 从上面的运行结果来看,在 swap() 函数里,经过交换形参 a 和 b 的值,它们的值分别变成了 9 和 6,但函数外部变量 a 和 b 的值依然是 6 和 9。这也证实了,swap() 函数的参数传递机制,采用的是值传递,函数内部使用的形参 a 和 b,和实参 a、b 没有任何关系。
如果读者依旧不是很理解,下面通过示意图来说明上面程序的执行过程。 上面程序开始定义了 a、b 两个局部变量,这两个变量在内存中的存储示意图如图 1 所示。
当程序执行 swap() 函数时,系统进入 swap() 函数,并将主程序中的 a、b 变量作为参数值传入 swap() 函数,但传入 swap() 函数的只是 a、b 的副本,而不是 a、b 本身。进入 swap() 函数后,系统中产生了 4 个变量,这 4 个变量在内存中的存储示意图如图 2 所示。
当在主程序中调用 swap() 函数时,系统分别为主程序和 swap() 函数分配两块栈区,用于保存它们的局部变量。将主程序中的 a、b 变量作为参数值传入 swap() 函数,实际上是在 swap() 函数栈区中重新产生了两个变量 a、b,并将主程序栈区中 a、b 变量的值分别赋值给 swap() 函数栈区中的 a、b 参数(就是对 swap() 函数的 a、b 两个变量进行初始化)。此时,系统存在两个 a 变量、两个 b 变量,只是存在于不同的栈区中而己。 程序在 swap() 函数中交换 a、b 两个变量的值,实际上是对图 2 中灰色区域的 a、b 变量进行交换。交换结束后,输出 swap() 函数中 a、b 变量的值,可以看到 a 的值为 9,b 的值为 6,此时在内存中的存储示意图如图 3 所示。
对比图 3 与图 1,可以看到两个示意图中主程序栈区中 a、b 的值并未有任何改变,程序改变的只是 swap() 函数栈区中 a、b 的值。这就是值传递的实质:当系统开始执行函数时,系统对形参执行初始化,就是把实参变量的值赋给函数的形参变量,在函数中操作的并不是实际的实参变量。 Python函数参数的引用传递如果实际参数的数据类型是可变对象(列表、字典),则函数参数的传递方式将采用引用传递方式。 下面程序示范了引用传递参数的效果:
运行上面程序,将看到如下运行结果: swap 函数里,a = 9 b = 6 从上面的运行结果来看,在 swap() 函数里,dw 字典的 a、b 两个元素的值被交换成功。不仅如此,当 swap() 函数执行结束后,主程序中 dw 字典的 a、b 两个元素的值也被交换了。 注意,这里这很容易造成一种错觉,读者可能认为,在此 swap() 函数中,使用 dw 字典,就是外界的 dw 字典本身,而不是他的复制品。这只是一种错觉,实际上,引用传递的底层实现,依旧使用的是值传递的方式。下面还是结合示意图来说明程序的执行过程。 程序开始创建了一个字典对象,并定义了一个 dw 引用变量(其实就是一个指针)指向字典对象,这意味着此时内存中有两个东西:对象本身和指向该对象的引用变量。此时在系统内存中的存储示意图如图 4 所示:
接下来主程序开始调用 swap() 函数,在调用 swap() 函数时,dw 变量作为参数传入 swap() 函数,这里依然采用值传递方式:把主程序中 dw 变量的值赋给 swap() 函数的 dw 形参,从而完成 swap() 函数的 dw 参数的初始化。值得指出的是,主程序中的 dw 是一个引用变量(也就是一个指针),它保存了字典对象的地址值,当把 dw 的值赋给 swap() 函数的 dw 参数后,就是让 swap() 函数的 dw 参数也保存这个地址值,即也会引用到同一个字典对象。图 5 显示了 dw 字典传入 swap() 函数后的存储示意图。
从图 5 来看,这种参数传递方式是不折不扣的值传递方式,系统一样复制了dw 的副本传入 swap() 函数。但由于 dw 只是一个引用变量,因此系统复制的是 dw 变量,并未复制字典本身。 当程序在 swap() 函数中操作 dw 参数时,由于 dw 只是一个引用变量,故实际操作的还是字典对象。此时,不管是操作主程序中的 dw 变量,还是操作 swap() 函数里的 dw 参数,其实操作的都是它们共同引用的字典对象,它们引用的是同一个字典对象。因此,当在 swap() 函数中交换 dw 参数所引用字典对象的 a、b 两个元素的值后,可以看到在主程序中 dw 变量所引用字典对象的 a、b 两个元素的值也被交换了。 为了更好地证明主程序中的 dw 和 swap() 函数中的 dw 是两个变量,在 swap() 函数的最后一行增加如下代码: #把dw 直接赋值为None,让它不再指向任何对象 运行上面代码,结果是 swap() 函数中的 dw 变量不再指向任何对象,程序其他地方没有任何改变。主程序调用 swap() 函数后,再次访问 dw 变量的 a、b 两个元素,依然可以输出 9、6。可见,主程序中的 dw 变量没有受到任何影响。实际上,当在 swap() 函数中增加“dw =None”代码后,在内存中的存储示意图如图 6 所示。
从图 6 来看,把 swap() 函数中的 dw 赋值为 None 后,在 swap() 函数中失去了对字典对象的引用,不可再访问该字典对象。但主程序中的 dw 变量不受任何影响,依然可以引用该字典对象,所以依然可以输出字典对象的 a、b 元素的值。 通过上面介绍可以得出如下两个结论:
什么是位置参数,Python位置参数位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。 实参和形参数量必须一致在调用函数,指定的实际参数的数量,必须和形式参数的数量一致(传多传少都不行),否则 Python 解释器会抛出 TypeError 异常,并提示缺少必要的位置参数。 例如:
运行结果为: Traceback (most recent call last): 可以看到,抛出的异常类型为 TypeError,具体是指 girth() 函数缺少一个必要的 height 参数。 同样,多传参数也会抛出异常:
运行结果为: Traceback (most recent call last): 通过 TypeErroe 异常信息可以知道,girth() 函数本只需要 2 个参数,但是却传入了 3 个参数。 实参和形参位置必须一致在调用函数时,传入实际参数的位置必须和形式参数位置一一对应,否则会产生以下 2 种结果:
Python函数关键字参数及用法目前为止,我们使用函数时所用的参数都是位置参数,即传入函数的实际参数必须与形式参数的数量和位置对应。而本节将介绍的关键字参数,则可以避免牢记参数位置的麻烦,令函数的调用和参数传递更加灵活方便。 关键字参数是指使用形式参数的名字来确定输入的参数值。通过此方式指定函数实参时,不再需要与形参的位置完全一致,只要将参数名写正确即可。 因此,Python 函数的参数名应该具有更好的语义,这样程序可以立刻明确传入函数的每个参数的含义。 例如,在下面的程序中就使用到了关键字参数的形式给函数传参:
程序执行结果为: str1: http://c.biancheng.net/python/ 可以看到,在调用有参函数时,既可以根据位置参数来调用,也可以使用关键字参数(程序中第 8 行)来调用。在使用关键字参数调用时,可以任意调换参数传参的位置。 当然,还可以像第 7 行代码这样,使用位置参数和关键字参数混合传参的方式。但需要注意,混合传参时关键字参数必须位于所有的位置参数之后。也就是说,如下代码是错误的:
Python 解释器会报如下错误: SyntaxError: positional argument follows keyword argument Python函数默认参数设置(超级详细)我们知道,在调用函数时如果不指定某个参数,Python 解释器会抛出异常。为了解决这个问题,Python 允许为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值。这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该参数可以直接使用定义函数时设置的默认值。 Python 定义带有默认值参数的函数,其语法格式如下: def 函数名(…,形参名,形参名=默认值): 注意,在使用此格式定义函数时,指定有默认值的形式参数必须在所有没默认值参数的最后,否则会产生语法错误。 下面程序演示了如何定义和调用有默认参数的函数:
运行结果为: str1: http://c.biancheng.net/shell/ 上面程序中,dis_str() 函数有 2 个参数,其中第 2 个设有默认参数。这意味着,在调用 dis_str() 函数时,我们可以仅传入 1 个参数,此时该参数会传给 str1 参数,而 str2 会使用默认的参数,如程序中第 6 行代码所示。 当然在调用 dis_str() 函数时,也可以给所有的参数传值(如第 7 行代码所示),这时即便 str2 有默认值,它也会优先使用传递给它的新值。 同时,结合关键字参数,以下 3 种调用 dis_str() 函数的方式也是可以的:
再次强调,当定义一个有默认值参数的函数时,有默认值的参数必须位于所有没默认值参数的后面。因此,下面例子中定义的函数是不正确的:
显然,str1 设有默认值,而 str2 和 str3 没有默认值,因此 str1 必须位于 str2 和 str3 之后。 有读者可能会问,对于自己自定义的函数,可以轻易知道哪个参数有默认值,但如果使用 Python 提供的内置函数,又或者其它第三方提供的函数,怎么知道哪些参数有默认值呢? Pyhton 中,可以使用“函数名.defaults”查看函数的默认值参数的当前值,其返回值是一个元组。以本节中的 dis_str() 函数为例,在其基础上,执行如下代码:
程序执行结果为: (‘http://c.biancheng.net/python/’,) Python函数可变参数(*args,**kwargs)详解Python 在定义函数时也可以使用可变参数,即允许定义参数个数可变的函数。这样当调用该函数时,可以向其传入任意多个参数。 可变参数,又称不定长参数,即传入函数中的实际参数可以是任意多个。Python 定义可变参数,主要有以下 2 种形式。 1) 可变参数:形参前添加一个 ‘*’此种形式的语法格式如下所示: *args args 表示创建一个名为 args 的空元组,该元组可接受任意多个外界传入的非关键字实参。 下面程序演示了如何定义一个参数可变的函数:
程序执行结果为: http://c.biancheng.net 上面程序中,dis_str() 函数的最后一个参数就是 str 元组,这样在调用该函数时,除了前面位置参数接收对应位置的实参外,其它非关键字参数都会由 str 元组接收。 当然,可变参数并不一定必须为最后一个函数参数,例如修改 dis_str() 函数为:
可以看到,str 可变参数作为 dis_str() 函数的第一个参数。但需要注意的是,在调用该函数时,必须以关键字参数的形式给普通参数传值,否则 Python 解释器会把所有参数都优先传给可变参数,如果普通参数没有默认值,就会报错。 也就是说,下面代码调用上面的 dia_str() 函数,是不对的:
Python 解释器会提供如下报错信息: TypeError: dis_str() missing 1 required keyword-only argument: ‘home’ 翻译过来就是我们没有给 home 参数传值。当然,如果 home 参数有默认参数,则此调用方式是可行的。 2) 可变参数:形参前添加两个’*’这种形式的语法格式如下: **kwargs **kwargs 表示创建一个名为 kwargs 的空字典,该字典可以接收任意多个以关键字参数赋值的实际参数。 例如如下代码:
程序输出结果为: C语言中文网 上面程序在调用 dis_str() 函数时,第 1 个参数传递给 home 参数,第 2、3 个非关键字参数传递给 str 元组,最后 2 个关键字参数将由 course 字典接收。 注意,*args 可变参数的值默认是空元组,**kwargs 可变参数的值默认是空字典。因此,在调用具有可变参数的函数时,不一定非要给它们传值。以调用 dis_str(home, *str, **course) 为例,下面的调用方式也是正确的:
程序执行结果为: http://c.biancheng.net/shell/ Python逆向参数收集详解(进阶必读)前面章节中介绍了,Python 支持定义具有可变参数的函数,即该函数可以接收任意多个参数,其中非关键字参数会集中存储到元组参数(*args)中,而关键字参数则集中存储到字典参数(**kwargs)中,这个过程可称为参数收集。 不仅如此,Python 还支持逆向参数收集,即直接将列表、元组、字典作为函数参数,Python 会将其进行拆分,把其中存储的元素按照次序分给函数中的各个形参。 在以逆向参数收集的方式向函数参数传值时,Pyhon 语法规定,当传入列表或元组时,其名称前要带一个 * 号,当传入字典时,其名称前要带有 2 个 * 号。 举个例子:
程序执行结果为: name: Python教程 再举个例子:
程序执行结果为: name: Python教程 此外,以逆向参数收集的方式,还可以给拥有可变参数的函数传参,例如:
程序执行结果为: name: Python教程 上面程序中,也同样可以用逆向参数收集的方式给 name 参数传值,只需要将 “python教程” 放到 data 列表中第一个位置即可。也就是说,上面程序中,以下面代码调用 dis_str() 函数的方式也是可行的:
执行此程序,会发现其输出结果和上面一致。 再次强调,如果使用逆向参数收集的方式,必须注意 * 号的添加。以逆向收集列表为例,如果传参时其列表名前不带 * 号,则 Python 解释器会将整个列表作为参数传递给一个参数。例如:
程序执行结果为: name: [‘Python教程’, ‘http://c.biancheng.net/python/’, ‘http://c.biancheng.net/shell/’, ‘http://c.biancheng.net/golang/’] Python None(空值)及用法在 Python 中,有一个特殊的常量 None(N 必须大写)。和 False 不同,它不表示 0,也不表示空字符串,而表示没有值,也就是空值。 这里的空值并不代表空对象,即 None 和 []、“” 不同: >>> None is [] None 有自己的数据类型,我们可以在 IDLE 中使用 type() 函数查看它的类型,执行代码如下: >>> type(None) 可以看到,它属于 NoneType 类型。 需要注意的是,None 是 NoneType 数据类型的唯一值(其他编程语言可能称这个值为 null、nil 或 undefined),也就是说,我们不能再创建其它 NoneType 类型的变量,但是可以将 None 赋值给任何变量。如果希望变量中存储的东西不与任何其它值混淆,就可以使用 None。 除此之外,None 常用于 assert、判断以及函数无返回值的情况。举个例子,在前面章节中我们一直使用 print() 函数输出数据,其实该函数的返回值就是 None。因为它的功能是在屏幕上显示文本,根本不需要返回任何值,所以 print() 就返回 None。 >>> spam = print(‘Hello!’) 另外,对于所有没有 return 语句的函数定义,Python 都会在末尾加上 return None,使用不带值的 return 语句(也就是只有 return 关键字本身),那么就返回 None。 Python return函数返回值详解到目前为止,我们创建的函数都只是对传入的数据进行了处理,处理完了就结束。但实际上,在某些场景中,我们还需函数将处理的结果反馈回来,就好像主管向下级员工下达命令,让其去打印文件,员工打印好文件后并没有完成任务,还需要将文件交给主管。 Python中,用 def 语句创建函数时,可以用 return 语句指定应该返回的值,该返回值可以是任意类型。需要注意的是,return 语句在同一函数中可以出现多次,但只要有一个得到执行,就会直接结束函数的执行。 函数中,使用 return 语句的语法格式如下: return [返回值] 其中,返回值参数可以指定,也可以省略不写(将返回空值 None)。 【例 1】
运行结果为: 7 本例中,add() 函数既可以用来计算两个数的和,也可以连接两个字符串,它会返回计算的结果。 通过 return 语句指定返回值后,我们在调用函数时,既可以将该函数赋值给一个变量,用变量保存函数的返回值,也可以将函数再作为某个函数的实际参数。 【例 2】
运行结果为: True 可以看到,函数中可以同时包含多个 return 语句,但需要注意的是,最终真正执行的做多只有 1 个,且一旦执行,函数运行会立即结束。 以上实例中,我们通过 return 语句,都仅返回了一个值,但其实通过 return 语句,可以返回多个值,读者可以阅读《Python函数返回多个值》一节做详细了解。 Python函数返回多个值的方法(入门必读)通常情况下,一个函数只有一个返回值,实际上 Python 也是如此,只不过 Python 函数能以返回列表或者元组的方式,将要返回的多个值保存到序列中,从而间接实现返回多个值的目的。 因此,实现 Python 函数返回多个值,有以下 2 种方式:
下面程序演示了以上 2 种实现方法:
程序执行结果为: retu_list = [‘http://c.biancheng.net/python/’, ‘http://c.biancheng.net/shell/’, ‘http://c.biancheng.net/golang/’] 在此基础上,我们可以利用 Python 提供的序列解包功能,之间使用对应数量的变量,直接接收函数返回列表或元组中的多个值。这里以 retu_list() 为例:
程序执行结果为: pythonadd= http://c.biancheng.net/python/ Python函数递归(带实例演示)一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。
下面我们通过一个实例,看看递归函数到底是如何运作的。 有这样一个数学题。己知有一个数列:f(0) = 1,f(1) = 4,f(n + 2) = 2*f(n+ 1) +f(n),其中 n 是大于 0 的整数,求 f(10) 的值。这道题可以使用递归来求得。下面程序将定义一个 fn() 函数,用于计算 f(10) 的值。
在上面的 fn() 函数体中再次调用了 fn() 函数,这就是函数递归。注意在 fn() 函数体中调用 fn 的形式: return 2 * fn(n - 1) + fn(n - 2) 对于 fn(10),即等于 2fn(9)+fn(8),其中 fn(9) 又等于 2fn(8)+fn(7)……依此类推,最终会计算到 fn(2) 等于 2*fn(1)+fn(0),即 fn(2) 是可计算的,这样递归带来的隐式循环就有结束的时候,然后一路反算回去,最后就可以得到 fn(10) 的值。 仔细看上面递归的过程,当一个函数不断地调用它自身时,必须在某个时刻函数的返回值是确定的,即不再调用它自身:否则,这种递归就变成了无穷递归,类似于死循环。因此,在定义递归函数时有一条最重要的规定: 递归一定要向已知方向进行。 例如,如果把上面数学题改为如此。己知有一个数列:f(20)=1,f(21)=4,f(n + 2)=2*f(n+1)+f(n),其中 n 是大于 0 的整数,求 f(10) 的值。那么 f(10) 的函数体应该改为如下形式:
从上面的 fn() 函数来看,当程序要计算 fn(10) 的值时,fn(10) 等于 fn(12)-2fn(11),而 fn(11) 等于 fn(13)-2fn(12)……依此类推,直到 fn(19) 等于 fn(21)-2fn(20),此时就可以得到 fn(19) 的值,然后依次反算到 fn(10) 的值。这就是递归的重要规则:对于求 fn(10) 而言,如果 fn(0) 和 fn(1) 是已知的,则应该采用 fn(n)=2fn(n-1)+fn(n-2) 的形式递归,因为小的一端已知;如果 fn(20) 和 fn(21) 是已知的,则应该采用 fn(n)=fn(n+2)-2*fn(n+1) 的形式递归,因为大的一端已知。 递归是非常有用的,例如程序希望遍历某个路径下的所有文件,但这个路径下的文件夹的深度是未知的,那么就可以使用递归来实现这个需求。系统可定义一个函数,该函数接收一个文件路径作为参数,该函数可遍历出当前路径下的所有文件和文件路径,即在该函数的函数体中再次调用函数自身来处理该路径下的所有文件路径。 总之,只要在一个函数的函数体中调用了函数自身,就是函数递归。递归一定要向已知方向进行。 Python变量作用域(全局变量和局部变量)所谓作用域(Scope),就是变量的有效范围,就是变量可以在哪个范围以内使用。有些变量可以在整段代码的任意位置使用,有些变量只能在函数内部使用,有些变量只能在 for 循环内部使用。 变量的作用域由变量的定义位置决定,在不同位置定义的变量,它的作用域是不一样的。本节我们只讲解两种变量,局部变量和全局变量。 Python局部变量在函数内部定义的变量,它的作用域也仅限于函数内部,出了函数就不能使用了,我们将这样的变量称为局部变量(Local Variable)。 要知道,当函数被执行时,Python 会为其分配一块临时的存储空间,所有在函数内部定义的变量,都会存储在这块空间中。而在函数执行完毕后,这块临时存储空间随即会被释放并回收,该空间中存储的变量自然也就无法再被使用。 举个例子:
程序执行结果为: 函数内部 add = http://c.biancheng.net/python/ 可以看到,如果试图在函数外部访问其内部定义的变量,Python 解释器会报 NameError 错误,并提示我们没有定义要访问的变量,这也证实了当函数执行完毕后,其内部定义的变量会被销毁并回收。 值得一提的是,函数的参数也属于局部变量,只能在函数内部使用。例如:
程序执行结果为: 函数内部 name = Python教程
Python全局变量除了在函数内部定义变量,Python 还允许在所有函数的外部定义变量,这样的变量称为全局变量(Global Variable)。 和局部变量不同,全局变量的默认作用域是整个程序,即全局变量既可以在各个函数的外部使用,也可以在各函数内部使用。 定义全局变量的方式有以下 2 种:
获取指定作用域范围中的变量在一些特定场景中,我们可能需要获取某个作用域内(全局范围内或者局部范围内)所有的变量,Python 提供了以下 3 种方式: 1) globals()函数globals() 函数为 Python 的内置函数,它可以返回一个包含全局范围内所有变量的字典,该字典中的每个键值对,键为变量名,值为该变量的值。 举个例子:
程序执行结果为: { … , ‘Pyname’: ‘Python教程’, ‘Pyadd’: ‘http://c.biancheng.net/python/’, …}
可以看到,通过调用 globals() 函数,我们可以得到一个包含所有全局变量的字典。并且,通过该字典,我们还可以访问指定变量,甚至如果需要,还可以修改它的值。例如,在上面程序的基础上,添加如下语句:
程序执行结果为: Python教程 2) locals()函数locals() 函数也是 Python 内置函数之一,通过调用该函数,我们可以得到一个包含当前作用域内所有变量的字典。这里所谓的“当前作用域”指的是,在函数内部调用 locals() 函数,会获得包含所有局部变量的字典;而在全局范文内调用 locals() 函数,其功能和 globals() 函数相同。 举个例子:
程序执行结果为: 函数内部的 locals:
注意,当使用 locals() 函数获得所有局部变量组成的字典时,可以向 globals() 函数那样,通过指定键访问对应的变量值,但无法对变量值做修改。例如:
程序执行结果为: shell教程 显然,locals() 返回的局部变量组成的字典,可以用来访问变量,但无法修改变量的值。 3) vars(object)vars() 函数也是 Python 内置函数,其功能是返回一个指定 object 对象范围内所有变量组成的字典。如果不传入object 参数,vars() 和 locals() 的作用完全相同。
举个例子:
程序执行结果为: 有 object: Python如何在函数中使用同名的全局变量?《Python局部变量和全局变量》一节中提到,全局变量可以在程序中任何位置被访问甚至修改,但是,当函数中定义了和全局变量同名的局部变量时,那么在当前函数中,无论是访问还是修改该同名变量,操作的都是局部变量,而不再是全局变量。
有读者可能并不能完全理解上面这段话,没关系,这里举个实例:
程序执行结果为: Python教程 上面程序中,第 4 行直接访问 name 变量,这是允许的。在上面程序的基础上,在函数内部添加一行代码,如下所示:
执行此程序,Python 解释器报如下错误: UnboundLocalError: local variable ‘name’ referenced before assignment 该错误直译过来的意思是:所访问的 name 变量还未定义。这是什么原因呢?就是我们添加第 5 行代码导致的。 Python 语法规定,在函数内部对不存在的变量赋值时,默认就是重新定义新的局部变量。上面程序中,第 5 行就定义了一个新的 name 局部变量,由于该局部变量名和全局变量名 name 同名,局部 name 变量就会“遮蔽”全局 name 变量,再加上局部变量 name 在 print(name) 后才被初始化,违反了“先定义后使用”的原则,因此程序会报错。 那么,如果就是想在函数中访问甚至修改被“遮蔽”的变量,怎么办呢?可以采取以下 2 中方法:
Python局部函数及用法(包含nonlocal关键字)通过前面的学习我们知道,Python 函数内部可以定义变量,这样就产生了局部变量,有读者可能会问,Python 函数内部能定义函数吗?答案是肯定的。Python 支持在函数内部定义函数,此类函数又称为局部函数。 那么,局部函数有哪些特征,在使用时需要注意什么呢?接下来就给读者详细介绍 Python 局部函数的用法。 首先,和局部变量一样,默认情况下局部函数只能在其所在函数的作用域内使用。举个例子:
程序执行结果为: http://c.biancheng.net/python/ 就如同全局函数返回其局部变量,就可以扩大该变量的作用域一样,通过将局部函数作为所在函数的返回值,也可以扩大局部函数的使用范围。例如,修改上面程序为:
程序执行结果为: 调用局部函数 因此,对于局部函数的作用域,可以总结为:如果所在函数没有返回局部函数,则局部函数的可用范围仅限于所在函数内部;反之,如果所在函数将局部函数作为返回值,则局部函数的作用域就会扩大,既可以在所在函数内部使用,也可以在所在函数的作用域中使用。 以上面程序中的 outdef() 和 indef() 为例,如果 outdef() 不将 indef 作为返回值,则 indef() 只能在 outdef() 函数内部使用;反之,则 indef() 函数既可以在 outdef() 函数内部使用,也可以在 outdef() 函数的作用域,也就是全局范围内使用。
另外值得一提的是,如果局部函数中定义有和所在函数中变量同名的变量,也会发生“遮蔽”的问题。例如:
执行此程序,Python 解释器会报如下错误: UnboundLocalError: local variable ‘name’ referenced before assignment 此错误直译过来的意思是“局部变量 name 还没定义就使用”。导致该错误的原因就在于,局部函数 indef() 中定义的 name 变量遮蔽了所在函数 outdef() 中定义的 name 变量。再加上,indef() 函数中 name 变量的定义位于 print() 输出语句之后,导致 print(name) 语句在执行时找不到定义的 name 变量,因此程序报错。 由于这里的 name 变量也是局部变量,因此前面章节讲解的 globals() 函数或者 globals 关键字,并不适用于解决此问题。这里可以使用 Python 提供的 nonlocal 关键字。 例如,修改上面程序为:
程序执行结果为: 所在函数中定义的 name 变量 Python lambda表达式(匿名函数)及用法对于定义一个简单的函数,Python 还提供了另外一种方法,即使用本节介绍的 lambda 表达式。 lambda 表达式,又称匿名函数,常用来表示内部仅包含 1 行表达式的函数。如果一个函数的函数体仅有 1 行表达式,则该函数就可以用 lambda 表达式来代替。 lambda 表达式的语法格式如下: name = lambda [list] : 表达式 其中,定义 lambda 表达式,必须使用 lambda 关键字;[list] 作为可选参数,等同于定义函数是指定的参数列表;value 为该表达式的名称。 该语法格式转换成普通函数的形式,如下所示:
举个例子,如果设计一个求 2 个数之和的函数,使用普通函数的方式,定义如下:
程序执行结果为: 7 由于上面程序中,add() 函数内部仅有 1 行表达式,因此该函数可以直接用 lambda 表达式表示:
程序输出结果为: 7 可以这样理解 lambda 表达式,其就是简单函数(函数体仅是单行的表达式)的简写版本。相比函数,lamba 表达式具有以下 2 个优势:
Python函数式编程(map()、filter()和reduce())详解所谓函数式编程,是指代码中每一块都是不可变的,都由纯函数的形式组成。这里的纯函数,是指函数本身相互独立、互不影响,对于相同的输入,总会有相同的输出。 除此之外,函数式编程还具有一个特点,即允许把函数本身作为参数传入另一个函数,还允许返回一个函数。 例如,想让列表中的元素值都变为原来的两倍,可以使用如下函数实现:
需要注意的是,这段代码不是一个纯函数的形式,因为列表中元素的值被改变了,如果多次调用 multiply_2() 函数,那么每次得到的结果都不一样。 而要想让 multiply_2() 成为一个纯函数的形式,就得重新创建一个新的列表并返回,也就是写成下面这种形式:
注意,纯粹的函数式编程语言(比如 Scala),其编写的函数中是没有变量的,因此可以保证,只要输入是确定的,输出就是确定的;而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出。 Python 允许使用变量,所以它并不是一门纯函数式编程语言。Python 仅对函数式编程提供了部分支持,主要包括 map()、filter() 和 reduce() 这 3 个函数,它们通常都结合 lambda 匿名函数一起使用。接下来就对这 3 个函数的用法做逐一介绍。 Python map()函数map() 函数的基本语法格式如下: map(function, iterable) 其中,function 参数表示要传入一个函数,其可以是内置函数、自定义函数或者 lambda 匿名函数;iterable 表示一个或多个可迭代对象,可以是列表、字符串等。 map() 函数的功能是对可迭代对象中的每个元素,都调用指定的函数,并返回一个 map 对象。
【例 1】还是对列表中的每个元素乘以 2。
运行结果为: [2, 4, 6, 8, 10] 【例 2】map() 函数可传入多个可迭代对象作为参数。
运行结果为: [4, 6, 8, 10, 12] 注意,由于 map() 函数是直接由用 C 语言写的,运行时不需要通过 Python 解释器间接调用,并且内部做了诸多优化,所以相比其他方法,此方法的运行效率最高。 Python filter()函数filter()函数的基本语法格式如下: filter(function, iterable) 此格式中,funcition 参数表示要传入一个函数,iterable 表示一个可迭代对象。 filter() 函数的功能是对 iterable 中的每个元素,都使用 function 函数判断,并返回 True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合。 【例 3】返回一个列表中的所有偶数。
运行结果为: [2, 4] 【例 4】filter() 函数可以接受多个可迭代对象。
运行结果为: [True, False, False] Python reduce()函数reduce() 函数通常用来对一个集合做一些累积操作,其基本语法格式为: reduce(function, iterable) 其中,function 规定必须是一个包含 2 个参数的函数;iterable 表示可迭代对象。 注意,由于 reduce() 函数在 Python 3.x 中已经被移除,放入了 functools 模块,因此在使用该函数之前,需先导入 functools 模块。 【例 5】计算某个列表元素的乘积。
运行结果为: 120 总结通常来说,当对集合中的元素进行一些操作时,如果操作非常简单,比如相加、累积这种,那么应该优先考虑使用 map()、filter()、reduce() 实现。另外,在数据量非常多的情况下(比如机器学习的应用),一般更倾向于函数式编程的表示,因为效率更高。 当然,在数据量不多的情况下,使用 for 循环等方式也可以。不过,如果要对集合中的元素做一些比较复杂的操作,考虑到代码的可读性,通常会使用 for 循环。 Python 3函数注解:为函数提供类型提示信息函数注解是 Python 3 最独特的功能之一,关于它的介绍,官方文档是这么说的,“函数注解是关于用户自定义函数使用类型的完全可选的元信息”。也就是说,官方将函数注解的用途归结为:为函数中的形参和返回值提供类型提示信息。 下面是对 Python 官方文档中的示例稍作修改后的程序,可以很好的展示如何定义并获取函数注解:
输出结果为: {‘ham’: <class ‘str’>, ‘egg’: <class ‘str’>, ‘return’: <class ‘str’>} 如上所示,给函数中的参数做注解的方法是在形参后添加冒号“:”,后接需添加的注解(可以是类(如 str、int 等),也可以是字符串或者表示式);给返回值做注解的方法是将注解添加到 def 语句结尾的冒号和 -> 之间。
给函数定义好注解之后,可以通过函数对象的 annotations 属性获取,它是一个字典,在应用运行期间可以获取。这里再举一个例子:
运行结果为: 100 事实上,函数注解并不局限于类型提示,而且在 Python 及其标准库中也没有单个功能可以利用这种注解,这也是这个功能独特的原因。 注意,函数注解没有任何语法上的意义,只是为函数参数和返回值做注解,并在运行获取这些注解,仅此而已。换句话说,为函数做的注解,Python不做检查,不做强制,不做验证,什么操作都不做,函数注解对Python解释器没任何意义。 函数注解可能的用法PEP 3107 作为提议函数注解的官方文档,其中列出了以下可能的使用场景:
总之,虽然函数注解存在的时间和 Python 3 一样长,但目前仍未找到任一常见且积极维护的包,将函数注解用作类型检查之外的功能。Python 3 最初发布时包含函数注解的最初目的也仅是用于试验和玩耍。 提高代码可读性和颜值的几点建议(初学者必读)学习过程中,我们经常会阅读他人写的代码,如果注意观察就会发现,好的代码本身就是一份文档,解决同样的问题,不同的人编写的代码,其可读性千差万别。 有些人的设计风格和代码风格犹如热刀切黄油,从顶层到底层的代码看下来酣畅淋漓,注释详尽又精简;深入到细节代码,无需注释也能理解清清楚楚。而有些人,代码勉勉强强能跑起来,遇到稍微复杂的情况就会出崩溃,且代码中处处都是堆积在一起的变量、函数和类,很难理清代码的实现思路。 Python 创始人 Guido van Rossum(吉多·范罗苏姆)说过,代码的阅读频率远高于编写代码的频率。毕竟是在编写代码的时候,我们自己也需要对代码进行反复阅读和调试,来确认代码能够按照期望运行。 本节,在读者学会如何使用 Puython 函数的基础上,教大家怎么才能合理分解代码,提高代码的可读性。 首先,大家在编程过程中,一定要围绕一个中心思想:不写重复性的代码。因为,重复代码往往是可以通过使用条件、循环、构造函数和类(后续章节会做详细介绍)来解决的。 例如,仔细观察下面的代码:
这段代码中,同样的 send 语句出现了两次,其实它完全可以进行合并,把代码改造成下面这样:
与此同时,还要学会刻意地减少代码的迭代层数,尽可能让 Python 代码扁平化。例如:
上面这段代码层层缩进,如果我们没有比较强的逻辑分析能力,理清这段代码是比较困难。其实,这段代码完全可以改成如下这样:
可以看到,所有的判断语句都位于同一层级,同之前的代码格式相比,代码层次清晰了很多。 另外,在使用函数时,函数的粒度应该尽可能细,不要让一个函数做太多的事情。往往一个复杂的函数,我们要尽可能地把它拆分成几个功能简单的函数,然后合并起来。 如何拆分函数呢?这里,举一个二分搜索的例子。给定一个非递减整数数组,和一个 target 值,要求你找到数组中最小的一个数 x,满足 x*x > target,如果不存在,则返回 -1。 大家不妨先独立完成,写完后再对照着来看下面的代码,找出自己的问题:
对于上面这样的写法,应付算法比赛和面试已经绰绰有余。但如果从工程的角度考虑,还需要进行深度优化:
在这段代码中,我们把不同功能的代码单独提取出来作为独立的函数。其中,comp() 函数作为核心判断,提取出来之后,可以让整个程序更清晰;同时,还把二分搜索的主程序提取了出来,只负责二分搜索;最后的 solve() 函数拿到结果,决定返回不存在,还是返回值。这样一来,每个函数各司其职,阅读性也能得到一定提高。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/16 1:21:27- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |