![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=1739090353-vXRre9bzrDT9ioE5N7VO5bnnN0dsaOVw-0-f6f8116446dc757aa1ccfaf2fdec328a)
由于print是一个函数,因此我们不用再去实现一遍打印到屏幕的功能,减少了大量的重复代码,同时看到print就可以知道这一行是用来打印的,可读性自然也提高了,另外如果打印出现问题只要去查看print函数的内部就可以了,而不用再去看print以外的代码,这体现了模块化的思想。
但是,内置函数的功能非常有限,我们需要根据实际需求编写自己的函数,这样才能进一步提高程序的简洁性、可读性和可扩展性。
3.4.2 函数的定义和调用
1.定义
和数学中的函数类似,Python中的函数需要先定义才能使用,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/58_03.jpg?sign=1739090353-YanBt8ZB11cGV0YAPJPJZqSNQLdp8Cg9-0-73eb5e4e67f7328162f3b031aeba9591)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_01.jpg?sign=1739090353-Y14H4nsPnZrlT2Sf5H84UkAs5ge9czpI-0-2359e9ecd2275f1fd47f91a304c468ff)
这是一个基本的函数定义,其中第1、4、6行是函数特有的,其他我们都已经学习过了。
先看第1行:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_02.jpg?sign=1739090353-uo5VLsvq79mcxk8x8MSNCk3Qah6gBcet-0-90a4f9cd17ea120e2abc251c96f916fe)
这一行有四个关键点:
● def:函数定义的关键字,写在最前面。
● ask_me_to:函数名,命名要求和变量一致。
● (string):函数的参数,多个参数用逗号隔开。
● 结尾冒号:函数声明的语法要求。
然后看第2到第5行:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_03.jpg?sign=1739090353-OMHWDFxbYiaVVpPD2uWwQdKictrSdYun-0-6ad95db0c26f02491a7b2589c4f1d61f)
它们都缩进了四个空格,意味着它们构成了一个代码块,同时从第2行可以看到函数内是可以接着调用函数的。
接着再看第4行:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_04.jpg?sign=1739090353-TdpcONKiD344ogMzY4Fb7XfiQQOW1Fma-0-3fb364b0a8f9a31bdcef0f5f7ac658e4)
这里引入了一个新关键字:return,它的作用是结束函数并返回到之前调用函数处的下一句。返回的对象是return后面的表达式,如果表达式为空则返回None。第6行跟第4行功能相同,这里不再赘述。
2.调用
在数学中函数需要一个自变量才会得到因变量,Python的函数也是一样,只是定义的话并不会执行,还需要调用,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_05.jpg?sign=1739090353-FMHblyxqVsLjiaJeMcpCgnTpv8litl3R-0-b08b3052050163acace06519179c9d73)
注意这里是两个函数嵌套,首先调用的是我们自定义的函数ask_me_to,接着ask_me_to的返回值传给了print,所以会输出ask_me_to的返回值:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/59_06.jpg?sign=1739090353-KZAphWp19SKhiIoNG8998UTiquIAZuUD-0-6f64adf0e3743b5bdfa3c31bdc56b64d)
定义和调用都很好理解,接下来了解函数的参数怎么设置。
3.4.3 函数的参数
Python的函数参数非常灵活,我们已经学习了最基本的一种,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_01.jpg?sign=1739090353-wITf8gUK8AkLOktGB4Qdq7NpspMmdrJY-0-62e21e02205296ea117d0d3d560beb24)
它拥有一个参数,名字为string。
函数参数的个数可以为0个或多个,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_02.jpg?sign=1739090353-Yl1kwLGHwxY4TcfmPydVMt2jP6dEZ2Gg-0-08ff526e6443f88bb37362218a07a4c3)
我们可以根据需求去选择参数个数,但是要注意的是,即使没有参数,括号也不可省略。
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=1739090353-ZEc0brGnH4Z6DpYNTL1zr6zvj5kDFNwJ-0-c5e8c14c57581e0bddf18f4e992caf3c)
这是一个格式化输出日期的函数,注意其中的月份和天数参数,用一个等号表明赋了默认值。于是可以分别以1,2,3个参数调用这个函数,同时也可以指定某个特定参数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_04.jpg?sign=1739090353-ydrStGZFrXuK6JVvt83xaxaMpMDUwNVX-0-4dcf37da33431d29f6c9bd2293979b03)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/60_05.jpg?sign=1739090353-b5C8780sZdvbJJ4vZG8Ijpvz34Wtgc7G-0-987e249121edb235a7d843875ce9d244)
我们依次看一下这些调用:
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=1739090353-OJLBnwMTvGHgRBOcK6GEJXTxO6imF8fs-0-6714c68f45ac6245cb2e856aa4649b16)
诊断代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_02.jpg?sign=1739090353-KKSd0jRKWpxjHiaAUlmpfwCydiJ1VI9h-0-dd5f6c55797ee15599112f743717a910)
任意位置参数的特点就是它只占一个参数,并且以 ∗ 开头。其中args为一个List,包含了所有传入的参数,顺序为调用时候的传参的顺序。
3.4.6 任意键值参数
除了接受任意数量的参数,如果我们希望给每个参数一个名字,那么可以这么声明参数:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_03.jpg?sign=1739090353-fCUlta4YCme8Tc7By7sQAoqe8HEElP4s-0-3592e4ab265d03a0d13ca547f2c15445)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_04.jpg?sign=1739090353-T3xdNkEP1zAJgAFLT8W6fPXqur31YGQU-0-aa9d427df4f1dd68e109700e90cf69c4)
跟之前讲过的任意位置参数使用非常类似,但是kwargs这里是一个Dict(字典),其中Key和Value为调用时候传入的参数的名称和值,顺序和传参顺序一致。
3.4.7 组合使用
我们现在知道了这四类参数,它们可以同时使用,但是需要满足一定的条件,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/61_05.jpg?sign=1739090353-xRY5TeGSQXbx52UETs5XvsML5BLCVnaA-0-b5ceb124c518fc00d7f8742844bcf1e8)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_01.jpg?sign=1739090353-BvhhUzt0uI4Wslh7cBp2HfwUqpag9xxt-0-04b2fd29d128be0321e6ee890d9b727e)
可以看出,四种参数在定义时应该满足这样的顺序:非默认参数、默认参数、任意位置参数、任意键值参数。
调用的时候,参数分为两类:位置相关参数和无关键词参数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_02.jpg?sign=1739090353-nELXjxN1JR7SFw78rtOWDDIrrasl8gwD-0-67aba4409b71b2c9cb5cd7a85aa9afa2)
这句代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_03.jpg?sign=1739090353-r2Vp5F5xXt7nDLuZAhakunEsi8dMHYk4-0-dea11f375682232fdc901375b43c90ec)
其中前三个就是位置相关参数,最后一个是关键词参数。位置相关参数是顺序传入的,而关键词参数则可以乱序传入,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_04.jpg?sign=1739090353-CxDR6RYTfXwXIAEdBUvBEFsfREqLpNaq-0-080b29ca7cf01d8eb637ca4ad4f1f4a8)
这句代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_05.jpg?sign=1739090353-BUbV5S60Q5loYe2SY2llt4YBIqxcHs1l-0-a93bc4a837efeb4a326493caf8a5a3b3)
总之在调用的时候,参数顺序应该满足的规则是:
● 位置相关参数不能在关键词参数之后。
● 位置相关参数优先。
这么看有些抽象,不如看看两个错误用法,第一个错误用法:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_06.jpg?sign=1739090353-Db11Rpw6f5FXGhX16XRcQ8AxMQhvS7p1-0-fa3cf9addc16d972318e41242cb8c989)
这句代码会报错:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_07.jpg?sign=1739090353-Iw16ZbJ4RJVLmDmZrdlABm9jqkly3Wqx-0-8c8925df3f3c26fdda5dab512fd861bf)
报错的意思是位置相关参数不能在关键词参数之后。也就是说,必须先传入位置相关参数,再传入关键词参数。
再看第二个错误用法:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/62_08.jpg?sign=1739090353-xZgBfV0S9wHLMJCgMjl0WSwkUjsZTTiC-0-f3401873d70d73dff9dd459f98a442c7)
这句代码会报错:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_01.jpg?sign=1739090353-QYA7Th6Sec5bQ36V90ffh0suoLU4j5bT-0-af361e20d10d7c1751baf93313796c97)
报错的意思是函数的参数arg1接收到了多个值。也就是说,位置相关参数会优先传入,如果再指定相应的参数,那么就会发生错误。
3.4.8 修改传入的参数
先补充有关传入参数的两个重要概念:
● 按值传递:复制传入的变量,传入函数的参数是一个和原对象无关的副本。
● 按引用传递:直接传入原变量的一个引用,修改参数就是修改原对象。
在有些编程语言中,可能是两种传参方式同时存在、可供选择,但是Python只有一种传参方式就是按引用传递,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_02.jpg?sign=1739090353-NHr5w5LOa8In1S1FnchV5LEpg9IBvzHM-0-d78eea07cced06ff469aff77050fd8f1)
注意在函数内通过append修改了mylist的元素,由于mylist是list1的一个引用,因此实际上修改的就是list1的元素,所以这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_03.jpg?sign=1739090353-M2j8kpfg6puyY5HiCmNpEbJRssHPnslR-0-e3e91214581459f0ab27922322a8eff3)
这是符合我们的预期的,但是看另一个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_04.jpg?sign=1739090353-60hsgmbrtzqOx90drQqnWpn9QnhD6zXr-0-03d6129c112e7d03964d056d6fa81a0a)
按照之前的理论,number应该是num的一个引用,所以这里应该输出3,但是实际上的输出是:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/63_05.jpg?sign=1739090353-ZziuvOBWdoRSOi95s5CQYFEKW8QcLuld-0-7c8ea42242eea4db5560d271bd648fcf)
为什么会这样呢?在第6章提到:特别地,字符串是一个不可变的对象。实际上,包括字符串在内,数值类型和Tuple也是不可变的,而这里正是因为num是不可变类型,所以函数的行为不符合我们的预期。
为了深入探究其原因,我们引入一个新的内建函数id,它的作用是返回对象的id。对象的id是唯一的,但是可能有多个变量引用同一个对象,比如下面这个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/64_01.jpg?sign=1739090353-sPvvywLYd0ce0S5O76sraYRt0voTUXn1-0-18f4eea4a511a4ee7b9eb897464abf2b)
我们可以得到这样的输出(这里id的输出不一定跟本书一致,但是第1,2,4个id应该是相同的):
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/64_02.jpg?sign=1739090353-dSmyXjsSYrTYjJJab4yJ6W4hblgO2tec-0-fbf7a387a658208d5545e1b5708e7cf1)
其实除了函数参数是引用传递,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=1739090353-2Eu91hXTDz7WOrbff7WfRmWgJe0jLRwd-0-aba6142333373553f05451f7d3272ae4)
这样输出就是我们预期的3了。
特殊地,这里举例用了一个很大的数字是有原因的。由于0~256这些整数使用地比较频繁,为了避免小对象的反复内存分配和释放造成内存碎片,所以Python对0~256这些数字建立了一个对象池。
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_01.jpg?sign=1739090353-jR8Es8NRAbKtHxEI3dIXw6ydwdtTHTn8-0-e005b9a962dc3e0c87e5ae03097f0cca)
我们可以得到输出(这里输出的两个id应该是一致的,但是数字不一定跟本书中的相同)为:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_02.jpg?sign=1739090353-7dom1IXRhYZlQCB1qglhGyYLMDJglneK-0-fdc1bb666d956681cdc90e60db49d525)
可以看出,虽然alice和bob无关,但是它们引用的是同一个对象,所以为了方便说明之前取了一个比较大的数字用于赋值。
3.4.9 函数的返回值
1.返回一个值
函数在执行的时候,会在执行到结束或者return语句的时候返回调用的位置。如果我们的函数需要返回一个值,那需要用return语句,比如最简单地返回一个值:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_03.jpg?sign=1739090353-xUguFfoZdPr4DmMpthhKfKkNgV4BAV3L-0-ef4d7988b79a4c8d76698a8a54cb2a3c)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_04.jpg?sign=1739090353-2woBdXBAfm5UC5NMJQv3nxpr8tymW5hM-0-b013fcc41093ecb403200219db24f6d5)
这个multiply函数将输入的两个参数相乘,然后返回结果。
2.什么都不返回
如果我们不想返回任何内容,可以只写一个return,它会停止执行后面代码的立即返回,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_05.jpg?sign=1739090353-6IPrip68C2lRRJzKahbzUf18Q2fSFPYR-0-50ec78d3db5263eecf132b942e0c32e8)
这里只要函数参数不是' secret '就不会输出任何内容,因为return后面的代码不会被执行。另外return跟return None是等价的,也就是说默认返回的是None。
3.返回多个值
和大部分编程语言不同,Python支持返回多个参数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/65_06.jpg?sign=1739090353-7cWUKy23JmyBxlmBuHiEMAhXSVLJ4Qb5-0-f4db62c7fe863e8a714f5de669d04ecf)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_01.jpg?sign=1739090353-ysbXGLIQAGiK9kbofNjf0kGhuLwaVYLN-0-fe230b2411a5d3e8d1ecb47b70ff1cd6)
这里要注意接收返回值的时候不能再像之前用一个变量,而是要用和返回值数目相同的变量接收,其中返回值赋值的顺序是从左到右的,跟直觉一致。
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_02.jpg?sign=1739090353-HQ6bDaURsr8sUBnONlQnBXTFnNeprCi1-0-452b9254aaa05765bcba430f88adfc72)
所以这个函数的作用就是把输入的三个变量顺序翻转一下。
3.4.10 函数的嵌套
我们可以在函数内定义函数,这对于简化函数内重复逻辑很有用,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_03.jpg?sign=1739090353-PFKVzoVkLfKiFdI24YUxbvUCaekKe1iW-0-864b29a2f4d852d4ebb94707bb2d80ac)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_04.jpg?sign=1739090353-Ben2sYnvaHiLl866RjiCgkr9vs8tOL7N-0-f7da67c33cc060288e8320a914ac2307)
需要注意的一点是,内部的函数只能在它所处的代码块中使用,在上面这个例子中,inner在outer外面是不可见的,这个概念叫作作用域。
1.作用域
作用域是一个很重要的概念,我们看一个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/66_05.jpg?sign=1739090353-R9OADHmU0aVNHFwZozfvJkYj71OYkgJx-0-809f0c33012e4a58da74c3ba7c9316af)
这里函数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=1739090353-D8gqRucoCEAYHH4xayaGspWCfK3OJRgF-0-27db25b5e3e5f3c8d57ad120d9cc5d4b)
其中要注意的是func3没有Enclosing作用域,至于闭包是什么会在后面的章节中介绍到,这里只要理解LEGB原则就可以了。
2.global和nonlocal
根据上述LEGB原则,我们在函数中是可以访问到全局变量的,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/67_02.jpg?sign=1739090353-AkHbnF2dHrbpC6MCdtu9ig71qm27HFSV-0-cd559260f9f5b8f86496de0a42baf681)
但是LEGB规则仿佛出了点问题,因为会报错:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/67_03.jpg?sign=1739090353-kGDZK4VO5ZJZ5cgIXncurwpemzyEUV4K-0-a74a36c748a20dfebd2839f902aa10cf)
这并不是Python的问题,反而是Python的一个特点,也就是说Python会在阻止用户在不知情的情况下修改非局部变量,那么怎么访问非局部变量呢?
为了修改非局部变量,需要使用global和nonlocal关键字,其中nonlocal关键字是Python3中才有的新关键字,看一个例子:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/67_04.jpg?sign=1739090353-amtiAxon9gLA6Km9461W6gGZnGezpnzx-0-9bc4f746ed2aaa45909e4b5c87eaf29a)
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_01.jpg?sign=1739090353-4gecJWoifk98g3Jc5aeGZIdVIMn8z3QM-0-4ddd0c6da7df2a6ead53c3822efa406f)
也就是说global会使得相应的全局变量在当前作用域内可见,而nonlocal可以让闭包中非全局变量可见,所以这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_02.jpg?sign=1739090353-UL9vXf3u5VABPvVe4VwxMHnsGC3UWFUO-0-1e16411407ed23f6f85b51c031d40799)
3.4.11 使用轮子
这里的“使用轮子”可不是现实中那种使用轮子,而是指直接使用别人写好并封装好的易于使用的库,进而极大地减少重复劳动,提高开发效率。
Python自带的标准库就是一堆鲁棒性强,接口易用,涉猎广泛的“轮子”,善于利用这些轮子可以极大地简化代码,这里简单介绍一些常用的库。
1.随机库
Python中的随机库用于生成随机数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_03.jpg?sign=1739090353-ShHtJq7TCVoAJFZRYGbIouGYrcnnXK9Z-0-657e5961fc81055d6867cb92e396e69e)
它会输出一个随机的[1,5)范围内的整数。我们无需关心它的实现,只要知道这样可以生成随机数就可以了。
其中import关键字的作用是导入一个包,有关包和模块的内容后面章节会细讲,这里只讲基本使用方法。
用import导入的基本语法是:import包名,包提供的函数的用法是包名.函数名。当然不仅函数,包里面的常量和类都可以通过类似的方法调用,不过我们这里会用函数就够了。
此外如果不想写包名,也可以这样:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_04.jpg?sign=1739090353-uxSSySfM9nQhoXnXd1UBKSNNnBnOKpIu-0-abc988d248b2069d6d7f9da933c819fc)
然后就可以直接调用randint而不用写前面的random了。
如果有很多函数要导入的话,我们还可以这么写:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_05.jpg?sign=1739090353-RH25zhocWlHNTypvk23PcQoOEXidKH77-0-2298c419c832454501f8d668a86e0160)
这样random包里的一切就都包含进来了,可以不用random直接调用。不过不太推荐这样写,因为不知道包内都有什么,容易造成名字的混乱。
特殊地,import random还有一种特殊写法:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/68_06.jpg?sign=1739090353-hEv0SLMva4kTRmMxT2N6TTPtaDFHbuRu-0-3a7a5a5c1219a3d0cb7304420241bc70)
它和import random没有本质区别,仅仅是给了random一个方便输入的别名rnd。
2.日期库
这个库可以用于计算日期和时间,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_01.jpg?sign=1739090353-wnpKlaN5NrY8ezmgrfEd2ThwRvhKpcX3-0-d8c62070ce63d789b4b22b89c42e9cc9)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_02.jpg?sign=1739090353-Sh8elo3xGYjWHid5MQ9nBJgbGeU7c9MP-0-83df0d37094f4e925126065388e696aa)
3.数学库
这个库有着常用的数学函数,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_03.jpg?sign=1739090353-aahN17oJn8ax0UH9anAkct36j0sOiCnj-0-9af751952dcd5fd9f315093fd501ac98)
这段代码会输出:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_04.jpg?sign=1739090353-ZlAdHGeksjZQxxTRPbhwLchNtVASkJTF-0-9015cb644f814228f7c85fd209ca287f)
其中第二个结果其实就是0,但是限于浮点数的精度问题无法精确表示为0,所以我们在编写代码涉及浮点数比较的时候一定要这么写:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_05.jpg?sign=1739090353-ryi2u4cApGyaaLTknhAebGXehMhD8xF0-0-98bed92932177ebbe66f1449ce6f2b5d)
这里EPS就是指允许的误差范围。也就是说浮点数没有真正的相等,只是在一定误差范围内的相等。
4.操作系统库
这个库包含操作系统的一些操作,例如列出目录:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_06.jpg?sign=1739090353-W79hbgIQynjcorIABMRg10rWepjzu6u3-0-b53ca62326bf3aeb80a676d5f83f28ad)
在之后的文件操作章节还会见到这个库。
5.第三方库
可以用第3章讲过的pip来方便地安装各种第三方库,比如:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_07.jpg?sign=1739090353-UXkxbz5U5goBRyKzuMXv3JjmYkOAWGcG-0-5f7c2611eac25d0f0c96ce406367e1b5)
通过一行指令我们就可以安装numpy这个库了,然后就可以在代码中正常import这个库:
![](https://epubservercos.yuewen.com/82BE21/21511156608178806/epubprivate/OEBPS/Images/69_08.jpg?sign=1739090353-tJ3ccox7uliGowQOB7ZymE6ZAyifd8nv-0-2880ab76d2397eb6e00245d3eb340f29)
这也正是pip作为包管理器强大的地方,方便易用。