![Python基础及应用](https://wfqqreader-1252317822.image.myqcloud.com/cover/836/41309836/b_41309836.jpg)
3.4 函数的定义与使用
还记得第2章提到过的一个“内置函数”max吗?对于不同的List和Tuple,这个函数总能给出正确的结果——当然有人说用for循环实现也很快很方便,但是有多少个List或Tuple就要写多少个完全重复的for循环,这是很让人厌烦的,这时候就需要函数出场了。
本章会从数学中的函数引入,详细讲解Python中函数的基本用法。
3.4.1 认识Python的函数
函数的数学定义为:给定一个数集A,对A施加对应法则f,记作f(A),得到另一个数集B,也就是B=f(A),那么这个关系式就叫函数关系式,简称函数。
数学中的函数其实就是A和B之间的一种关系,我们可以理解为从A中取出任意一个输入都能在B中找到特定的输出,在程序中,函数也是完成这样的一种输入到输出的映射,但是程序中的函数有着更大的意义。
它首先可以减少重复代码,因为我们可以把相似的逻辑抽象成一个函数,减少重复代码,其次它有可以使程序模块化并且提高可读性。
以之前多次用到的一个函数print为例:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/58_02.jpg?sign=1739103710-Ja3YUsvkyuU6ryRSE7FpQC2wnLsQJbEj-0-c225a46dbe6804bbe1827df05e881441)
由于print是一个函数,因此我们不用再去实现一遍打印到屏幕的功能,减少了大量的重复代码,同时看到print就可以知道这一行是用来打印的,可读性自然也提高了,另外如果打印出现问题只要去查看print函数的内部就可以了,而不用再去看print以外的代码,这体现了模块化的思想。
但是,内置函数的功能非常有限,我们需要根据实际需求编写自己的函数,这样才能进一步提高程序的简洁性、可读性和可扩展性。
3.4.2 函数的定义和调用
1.定义
和数学中的函数类似,Python中的函数需要先定义才能使用,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/58_03.jpg?sign=1739103710-7pyWnmN7Q3wujboBgtguqGnAqYfBWH9B-0-a77d248bf132be6d28098f71d98ff470)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_01.jpg?sign=1739103710-kxJXixzhSHqNUuwjgyp2hG5NTmUpfugT-0-5633b29b1619eb489cf29b370f76b9b6)
这是一个基本的函数定义,其中第1、4、6行是函数特有的,其他我们都已经学习过了。
先看第1行:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_02.jpg?sign=1739103710-Q8Yd8adfV9z9tnyjyC5jNVZTFCuevGkF-0-bb4d35cf32fc3ec9ffd58bbedbd17adc)
这一行有四个关键点:
● def:函数定义的关键字,写在最前面。
● ask_me_to:函数名,命名要求和变量一致。
● (string):函数的参数,多个参数用逗号隔开。
● 结尾冒号:函数声明的语法要求。
然后看第2到第5行:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_03.jpg?sign=1739103710-ZpAFJeEodRUcE4WrJLE41085BNARCXpG-0-455753da75e61096c5c38620cfa1892d)
它们都缩进了四个空格,意味着它们构成了一个代码块,同时从第2行可以看到函数内是可以接着调用函数的。
接着再看第4行:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_04.jpg?sign=1739103710-lu1XI1zgMNF2b6hUEXnoY3V9zWEdvn1T-0-90e1f4d38c3d6e61f18b61e79eddc3f7)
这里引入了一个新关键字:return,它的作用是结束函数并返回到之前调用函数处的下一句。返回的对象是return后面的表达式,如果表达式为空则返回None。第6行跟第4行功能相同,这里不再赘述。
2.调用
在数学中函数需要一个自变量才会得到因变量,Python的函数也是一样,只是定义的话并不会执行,还需要调用,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_05.jpg?sign=1739103710-VOUdmwnTowVKP7eUaOdHxCMmpQ2NhSV0-0-cd0477fab18c9f7b50049a2966ea1e7a)
注意这里是两个函数嵌套,首先调用的是我们自定义的函数ask_me_to,接着ask_me_to的返回值传给了print,所以会输出ask_me_to的返回值:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_06.jpg?sign=1739103710-8c3jEexuJ3zWacYPg5L9EU4sR1omTKZg-0-65848a3545fd675071b7913b79e15033)
定义和调用都很好理解,接下来了解函数的参数怎么设置。
3.4.3 函数的参数
Python的函数参数非常灵活,我们已经学习了最基本的一种,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_01.jpg?sign=1739103710-JJzleAcCIB8SCZNRaDdhyP717YibO9rG-0-98f266b1c7067790842166bcbfd2ddd6)
它拥有一个参数,名字为string。
函数参数的个数可以为0个或多个,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_02.jpg?sign=1739103710-O9zvgaPWeWFtH1EIQit5ysv6n8yBfnTv-0-99c84bbf05badd76580f94ba6548368b)
我们可以根据需求去选择参数个数,但是要注意的是,即使没有参数,括号也不可省略。
Python的一个灵活之处在于函数参数形式的多样性,有以下几种形式。
● 不带默认参数的:deffunc(a):
● 带默认参数的:deffunc(a,b=1):
● 任意位置参数:deffunc(a,b=1,∗c):
● 任意键值参数:deffunc(a,b=1,∗c,∗∗d):
第一种就是我们刚才讲到的一般形式,下面介绍剩下三种如何使用。
3.4.4 默认参数
有时候某个函数参数大部分时候为某个特定值,于是我们希望这个参数可以有一个默认值,这样就不用频繁指定相同的值给这个参数了。默认参数的用法看一个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_03.jpg?sign=1739103710-E8AlUoj8O8L9mTaiJK3RFBW091AtkOl6-0-3ce1c99c1115819e2dbc04bd0c0380f7)
这是一个格式化输出日期的函数,注意其中的月份和天数参数,用一个等号表明赋了默认值。于是可以分别以1,2,3个参数调用这个函数,同时也可以指定某个特定参数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_04.jpg?sign=1739103710-ZfgYneHvgN6TRF0oJQaVDMQxoYJbdmbK-0-9d4710bdc81d26c6f2c7f87741630ad5)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_05.jpg?sign=1739103710-0kbfkWl9sWLqs61zbqEHy6zyiEKdiFtp-0-40c9df1dad9f26bcf57b3dfd7d31002a)
我们依次看一下这些调用:
1)print_date(2018)这种情况下由于默认参数的存在等价于print_date(2018,1,1)。
2)print_date(2018,2,1)这种情况下所有参数都被传入了,因此和无默认参数的行为是一致的。
3)print_date(2018,5)省略了day,因为参数是按照顺序传入的。
4)print_date(2018,day=3)省略了month,由于和声明顺序不一致,所以必须声明参数名称。
5)print_date(2018,month=2,day=5)全部声明也是可以的。
使用默认参数可以让函数的行为更加灵活。
3.4.5 任意位置参数
如果函数想接收任意数量的参数,那么可以这样声明使用:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_01.jpg?sign=1739103710-u4kMBGLsqRYJ0B0tiMrvYJYMRDiaVpJl-0-2f4e00f2e64b6a21bc593b55985f6ef8)
诊断代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_02.jpg?sign=1739103710-iAb19zlGoDmJa0kuXM45NkWTxkWlgdx4-0-a2e3ac9b6a67ac98d4e54ec910da706c)
任意位置参数的特点就是它只占一个参数,并且以 ∗ 开头。其中args为一个List,包含了所有传入的参数,顺序为调用时候的传参的顺序。
3.4.6 任意键值参数
除了接受任意数量的参数,如果我们希望给每个参数一个名字,那么可以这么声明参数:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_03.jpg?sign=1739103710-RwWUkbYdZTgEASjEOPqTciL2FaCKqixe-0-a461672ea47e5ed8ef4323c74dbbabd5)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_04.jpg?sign=1739103710-U0mwId7w6WuKChygf78vIuN64VwSUyl8-0-fe095bbeb9c2734f7db7ab5b2a5607f6)
跟之前讲过的任意位置参数使用非常类似,但是kwargs这里是一个Dict(字典),其中Key和Value为调用时候传入的参数的名称和值,顺序和传参顺序一致。
3.4.7 组合使用
我们现在知道了这四类参数,它们可以同时使用,但是需要满足一定的条件,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_05.jpg?sign=1739103710-MbrhnSl6HPhbDXglUEeTusSXtY75SbPS-0-4c761ecf62b6247eead7ccd54ac8619d)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_01.jpg?sign=1739103710-23lEYVVYoAYpzt5OU4EWIqU0hmRIdPI1-0-855d48eeaf32a3adaca4f841a088e75d)
可以看出,四种参数在定义时应该满足这样的顺序:非默认参数、默认参数、任意位置参数、任意键值参数。
调用的时候,参数分为两类:位置相关参数和无关键词参数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_02.jpg?sign=1739103710-W8L8XoJJ9o6fN2Xzlof4lT914Fkkz8tH-0-f02d2eb7566f71da73a3f57403cf34fb)
这句代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_03.jpg?sign=1739103710-7pQgrcRFw3pFmcM5TSuA5ISFLFC8qtBc-0-7b97cc22b2bf792a1708ac16966ba171)
其中前三个就是位置相关参数,最后一个是关键词参数。位置相关参数是顺序传入的,而关键词参数则可以乱序传入,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_04.jpg?sign=1739103710-t8F8zXU7rtTss2KXf4EXwUY382415McL-0-f2a8da64963e0039f1bbc02245a0e988)
这句代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_05.jpg?sign=1739103710-hbyNrAtU6rq2Ux6GUvKYgOxehmGnmyEM-0-bcd3ce8ad0f90f09ff28997e4d936c44)
总之在调用的时候,参数顺序应该满足的规则是:
● 位置相关参数不能在关键词参数之后。
● 位置相关参数优先。
这么看有些抽象,不如看看两个错误用法,第一个错误用法:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_06.jpg?sign=1739103710-qBmzdJlecd6eENcPiClp7b6lt7p7ubxc-0-1c4d173275269ba2f3fe64b221430e36)
这句代码会报错:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_07.jpg?sign=1739103710-JHDYLVlZCkzdT6GepIW1Gexfo1XD4yBb-0-9bf2ef30b7d44c28b9bbd6e4edbf6359)
报错的意思是位置相关参数不能在关键词参数之后。也就是说,必须先传入位置相关参数,再传入关键词参数。
再看第二个错误用法:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_08.jpg?sign=1739103710-276aavrvfNmXoetUDHZE04ErcD43JAA5-0-0104c4be706334289a8367f62a80a472)
这句代码会报错:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_01.jpg?sign=1739103710-Tq4zozfKWP55cDfJlcJ8DuUW71o3NEyg-0-652eb8a7073bedb3143f50987ed7a26c)
报错的意思是函数的参数arg1接收到了多个值。也就是说,位置相关参数会优先传入,如果再指定相应的参数,那么就会发生错误。
3.4.8 修改传入的参数
先补充有关传入参数的两个重要概念:
● 按值传递:复制传入的变量,传入函数的参数是一个和原对象无关的副本。
● 按引用传递:直接传入原变量的一个引用,修改参数就是修改原对象。
在有些编程语言中,可能是两种传参方式同时存在、可供选择,但是Python只有一种传参方式就是按引用传递,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_02.jpg?sign=1739103710-ooPi8UAtU4EOUp8bPrlJ1s4oEnvatmwS-0-9cae29ee05cf92257b59c3c40697fb6a)
注意在函数内通过append修改了mylist的元素,由于mylist是list1的一个引用,因此实际上修改的就是list1的元素,所以这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_03.jpg?sign=1739103710-sUmQOoXVuNHmv4eH7OzPhpJDBWo1UOdy-0-060efa6c2760181b1a227565752db7e5)
这是符合我们的预期的,但是看另一个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_04.jpg?sign=1739103710-AOCvmozoITiV3MHWnDVYmLRfewrWaVzB-0-e38c04705de9f670075e656b48289d18)
按照之前的理论,number应该是num的一个引用,所以这里应该输出3,但是实际上的输出是:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_05.jpg?sign=1739103710-iTE86jErjQLAFqYsJwVDkP1f8zXtW3ME-0-f4019f6a9ea3f459e2d97111cbe2d1fc)
为什么会这样呢?在第6章提到:特别地,字符串是一个不可变的对象。实际上,包括字符串在内,数值类型和Tuple也是不可变的,而这里正是因为num是不可变类型,所以函数的行为不符合我们的预期。
为了深入探究其原因,我们引入一个新的内建函数id,它的作用是返回对象的id。对象的id是唯一的,但是可能有多个变量引用同一个对象,比如下面这个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/64_01.jpg?sign=1739103710-RqCbHycV8IyLyqrjor8wrGFeDxXhrV5b-0-ae932362631e50d0000fbc2e22e0bcc4)
我们可以得到这样的输出(这里id的输出不一定跟本书一致,但是第1,2,4个id应该是相同的):
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/64_02.jpg?sign=1739103710-pdCGJkZmhzqFmFmZFz9AU768zvMInABL-0-6b05f4acb36d5b27609a46acb87864f0)
其实除了函数参数是引用传递,Python变量的本质就是引用。这也就意味着在把alice赋值给bob的时候,实际上是把alice的引用给了bob,于是这时候alice和bob实际上引用了同一个对象,因此id相同。
接下来修改了alice的值,可以看到Bob的值并没有改变,这符合我们的直觉。但是从引用上看,实际发生的操作是,bob的引用不变,但是alice获得了一个新对象的引用,这个过程充分体现了数值类型不可变的性质——已经创建的对象不会修改,任何修改都是新建一个对象来实现的。
实际上,对于这些不可变类型,每次修改都会创建一个新的对象,然后修改引用为新的对象。在这里,alice和bob已经引用两个完全不同的对象了,这两个对象占用的空间是完全不同的。
那么回到最开始的问题,为什么这些不可变对象在函数内的修改不能体现在函数外呢?虽然函数参数的确引用了原对象,但是我们在修改的时候实际上是创建了一个新的对象,所以原对象不会被修改,这也就解释了刚才的现象。如果一定要修改的话,可以这么写:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/64_03.jpg?sign=1739103710-ztspwvBM94bxFHj8cU4RiUAqamZdS45x-0-2b28f8e7a89c465689dcc1f871c0a13c)
这样输出就是我们预期的3了。
特殊地,这里举例用了一个很大的数字是有原因的。由于0~256这些整数使用地比较频繁,为了避免小对象的反复内存分配和释放造成内存碎片,所以Python对0~256这些数字建立了一个对象池。
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_01.jpg?sign=1739103710-XZuLsuqvUoCiBWj1tWPBnAA7L1YhFDYv-0-79b33914c1b8234d945118982cbf3317)
我们可以得到输出(这里输出的两个id应该是一致的,但是数字不一定跟本书中的相同)为:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_02.jpg?sign=1739103710-0GZJooYz4q9oFMzpadJimjL7Zq0ySzvG-0-5cc5af661bcbc8c511d316ce35ba948a)
可以看出,虽然alice和bob无关,但是它们引用的是同一个对象,所以为了方便说明之前取了一个比较大的数字用于赋值。
3.4.9 函数的返回值
1.返回一个值
函数在执行的时候,会在执行到结束或者return语句的时候返回调用的位置。如果我们的函数需要返回一个值,那需要用return语句,比如最简单地返回一个值:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_03.jpg?sign=1739103710-EQ6DToFgt7dYTkuX0i2PJasYyClwNgU4-0-dd93173c8cbae8b14be5e1819726483d)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_04.jpg?sign=1739103710-XGUUvUXgXBsGGGlYBqKGwdh3mfkDu7jr-0-f72551c16a43a67242b4df21a58fcd62)
这个multiply函数将输入的两个参数相乘,然后返回结果。
2.什么都不返回
如果我们不想返回任何内容,可以只写一个return,它会停止执行后面代码的立即返回,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_05.jpg?sign=1739103710-z4GIBObG6Flv3u9C9NAnlzlYcx1lGjqk-0-9403156c78647ebc1424e1bbedac8346)
这里只要函数参数不是' secret '就不会输出任何内容,因为return后面的代码不会被执行。另外return跟return None是等价的,也就是说默认返回的是None。
3.返回多个值
和大部分编程语言不同,Python支持返回多个参数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_06.jpg?sign=1739103710-IYNm9dXqjsEHwjsLVdUpx5BNVAcK7W3s-0-56214ea3d1500cf6c47b616590935e76)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_01.jpg?sign=1739103710-RfEMMwbXjyLWhaExUSnJAiAp8YnB8BmP-0-fddea3e118d43593914f85c1bf1e1559)
这里要注意接收返回值的时候不能再像之前用一个变量,而是要用和返回值数目相同的变量接收,其中返回值赋值的顺序是从左到右的,跟直觉一致。
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_02.jpg?sign=1739103710-UjPCe0uVDV7z88GAkNLeiyfk2HtUPDer-0-46304c0d432adfa5e0e825b7915f03fd)
所以这个函数的作用就是把输入的三个变量顺序翻转一下。
3.4.10 函数的嵌套
我们可以在函数内定义函数,这对于简化函数内重复逻辑很有用,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_03.jpg?sign=1739103710-d6No3WdjKFci8zJf35jM7vmvLMsgKw1X-0-cfee42602de8ec823fc52286835416be)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_04.jpg?sign=1739103710-3FKcTbbcct4uBuR0crBKbMDWEGYRhDzW-0-0820162fc2ee4ff5088e1ba6e0eeb047)
需要注意的一点是,内部的函数只能在它所处的代码块中使用,在上面这个例子中,inner在outer外面是不可见的,这个概念叫作作用域。
1.作用域
作用域是一个很重要的概念,我们看一个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_05.jpg?sign=1739103710-47kghltgYlFuHePWb6NX74pbWgh0K8Bk-0-e28762c2855067d173eeb230051518df)
这里函数func2中能正常输出x1的值吗?
答案是不能。为了解决这个问题,需要用到Python的变量名称查找顺序,即LEGB原则:
● L: Local(本地)是函数内的名字空间,包括局部变量和形参。
● E: Enclosing(封闭)外部嵌套函数的名字空间(闭包中常见)。
● G: Global(全局)全局函数定义所在模块的名字空间。
● B: Builtin(内建)内置模块的名字空间。
LEGB原则的含义是,Python会按照LEGB这个顺序去查找变量,一旦找到就拿来使用,否则就到更外面一层的作用域去查找,如果都找不到就报错。
可以通过一个例子来认识LEGB,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/67_01.jpg?sign=1739103710-gESyh2uYGBGF4R01bI416ow4PykHCBRs-0-f0584b447dd3fb12477db05111d5855c)
其中要注意的是func3没有Enclosing作用域,至于闭包是什么会在后面的章节中介绍到,这里只要理解LEGB原则就可以了。
2.global和nonlocal
根据上述LEGB原则,我们在函数中是可以访问到全局变量的,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/67_02.jpg?sign=1739103710-Fhwbg6vmWeBcBcESKhZDo139Zlkh0LOe-0-f3b7843b4448f8841f85a3683bc44e73)
但是LEGB规则仿佛出了点问题,因为会报错:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/67_03.jpg?sign=1739103710-gw82drgwcBIefxm3G2SUBbfaxzKewShc-0-26cb44156cec8c094e6135ffe6877d2c)
这并不是Python的问题,反而是Python的一个特点,也就是说Python会在阻止用户在不知情的情况下修改非局部变量,那么怎么访问非局部变量呢?
为了修改非局部变量,需要使用global和nonlocal关键字,其中nonlocal关键字是Python3中才有的新关键字,看一个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/67_04.jpg?sign=1739103710-hk9qid3h65iA7Wjn6d45hObjrQsNO0mu-0-0e596d8a8d1ea0ab1cb0cb84cd92d73b)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_01.jpg?sign=1739103710-81LIRym09ulWAwvakYwbTmv4SwR55vqN-0-e15657efc87a0103b0811c84b956f246)
也就是说global会使得相应的全局变量在当前作用域内可见,而nonlocal可以让闭包中非全局变量可见,所以这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_02.jpg?sign=1739103710-w2kE99SCwaWaigJrRELR81SEcMxqLkPw-0-d19b60198da34b4cd91715c5531aa839)
3.4.11 使用轮子
这里的“使用轮子”可不是现实中那种使用轮子,而是指直接使用别人写好并封装好的易于使用的库,进而极大地减少重复劳动,提高开发效率。
Python自带的标准库就是一堆鲁棒性强,接口易用,涉猎广泛的“轮子”,善于利用这些轮子可以极大地简化代码,这里简单介绍一些常用的库。
1.随机库
Python中的随机库用于生成随机数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_03.jpg?sign=1739103710-ovHo0wLR0nuX0ncYgzTl8GMOijlRYpbD-0-b56ef09f04cd51e5230c2e766d65378e)
它会输出一个随机的[1,5)范围内的整数。我们无需关心它的实现,只要知道这样可以生成随机数就可以了。
其中import关键字的作用是导入一个包,有关包和模块的内容后面章节会细讲,这里只讲基本使用方法。
用import导入的基本语法是:import包名,包提供的函数的用法是包名.函数名。当然不仅函数,包里面的常量和类都可以通过类似的方法调用,不过我们这里会用函数就够了。
此外如果不想写包名,也可以这样:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_04.jpg?sign=1739103710-MRf23n6FCQgrvGHWAsTrEYjltifGJa1y-0-ae52e0bd56fbbd68154066575dad14d1)
然后就可以直接调用randint而不用写前面的random了。
如果有很多函数要导入的话,我们还可以这么写:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_05.jpg?sign=1739103710-9ZFg3ZTEQygVy7YQFanB3DJkl8AfwAud-0-10f2a45376cd412e26a0b79a444a2c8e)
这样random包里的一切就都包含进来了,可以不用random直接调用。不过不太推荐这样写,因为不知道包内都有什么,容易造成名字的混乱。
特殊地,import random还有一种特殊写法:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_06.jpg?sign=1739103710-FuSXdFw9luVjrqNCsTyxo258WylokQrg-0-f060a3ae28d6a64bde3d951acca3e93d)
它和import random没有本质区别,仅仅是给了random一个方便输入的别名rnd。
2.日期库
这个库可以用于计算日期和时间,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_01.jpg?sign=1739103710-jM4lFsXmY2QTsRdUuB3cYCt01h7KIiud-0-da5fa6d341f680d05450370b42d9d717)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_02.jpg?sign=1739103710-JYpsqz4Ih7iIY7Un3FBdfYBue3jXqVTi-0-2b15477cac93413f6d5c20bdfdf83a19)
3.数学库
这个库有着常用的数学函数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_03.jpg?sign=1739103710-os1x7VEhRK7PZHSrci9lvk3LQGIjDe2Z-0-7ef7d39632b6e75032fadbce0891f5cc)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_04.jpg?sign=1739103710-WnyWm4emdCeO4UhvhFDP0T2Usu8LuDBS-0-599a2a6320d4644684fd90bad650868c)
其中第二个结果其实就是0,但是限于浮点数的精度问题无法精确表示为0,所以我们在编写代码涉及浮点数比较的时候一定要这么写:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_05.jpg?sign=1739103710-UwQFUAkWmPxup79w8uSXvxPS9c9Q0Pzo-0-f608539b7638ba9b0b70583ed16ec4d1)
这里EPS就是指允许的误差范围。也就是说浮点数没有真正的相等,只是在一定误差范围内的相等。
4.操作系统库
这个库包含操作系统的一些操作,例如列出目录:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_06.jpg?sign=1739103710-gjwXaEvi5VofeSgFKIoYgGClvTKfsyKI-0-eca550fc5dc837eb62579f9aab9ad7cc)
在之后的文件操作章节还会见到这个库。
5.第三方库
可以用第3章讲过的pip来方便地安装各种第三方库,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_07.jpg?sign=1739103710-Wg1TVUNPV8L2T8bsk152sHcGe2UyFP1T-0-7d67ff5b88d0b4b9cb19da5db98b3110)
通过一行指令我们就可以安装numpy这个库了,然后就可以在代码中正常import这个库:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_08.jpg?sign=1739103710-b3vfbUK4cvAwbe1HMsfDPMdB5kfuBQz5-0-bfab8eeb1722a2a32dce82651e266d96)
这也正是pip作为包管理器强大的地方,方便易用。