第十二天学Python:函数(3)变量作用域、匿名函数与递归函数

经过昨天: 函数(2)变化参数.的铺垫,终于在函数第三节来到了难点,今天主要说的内容是:

变量作用域

全局变量与局部变量

  • 全局变量:全局变量在整个程序文件(.py)中声明,全局范围都可以调用它
  • 局部变量:局部变量在某个函数内部声明,只能在这个函数内部调用
>>> i = 5
>>> def n_n(num):
	num = num*num
	return num
>>> n_n(i)
25
>>> print(num)
NameError: name 'num' is not defined

在这样一小段程序中,i 就是一个全局变量,num则是局部变量,所以在print(num)时会报错。
然鹅虽然看起来 i 变成了 25,但是当我们再查看 i 时,我们发现:

>>> print(i)
5

可见,return回来的数并没有把全局变量 i 改变。如果我们在这里把 num 直接换成 i 呢?

>>> i = 5
>>> def n_n():
	i = i*i
	print(i)
>>> n_n()
UnboundLocalError: local variable 'i' referenced before assignment

这就是在函数里修改全局变量时会发生的报错,如果我们真的想在函数内部修改全局变量,就应该告诉这个函数:它是全局变量!而这就用到了关键字:global

global:关键字

承接上文,当我们想要在函数内部修改全局变量时要用到global关键字来告诉函数,将要调用并修改全局变量。所以刚刚的函数应该改为:

>>> def n_n():
	global i #用global关键字声明
	i = i*i
	print(i)
>>> n_n()
25
>>> print(i)
25

如此便能正常修改了。

nonlocal:关键字

Python3为我们提供了一个Python2中没有的nonlocal关键字,它的使用情况较为特殊,具体用法如下:
还是熟悉的的n_n函数,如果我们要在这里面嵌套定义一个内部函数,同样会产生之前变量作用域的问题,比如:

>>> def n_n(num):
	num = num*num
	print(num)
	def n_and_n():
		num = num + num
		print(num)
	n_and_n()
>>> n_n(5)
25
UnboundLocalError: local variable 'num' referenced before assignment

这个函数的意思是:先平方,再乘二,只不过把乘二放到了一个嵌套函数里,但我们在第二层调用第一层定义的num时,依然会发生报错,所以这时候,我们就要用到nonlocal关键字,来告诉第二层,这个变量是第一层的 : )

>>> def n_n(num):
	num = num*num
	print(num)
	def n_and_n():
		nonlocal num #用nonlocal关键字声明
		num = num + num
		print(num)
	n_and_n()
>>> n_n(5)
25
50

其中,这里的num,既不是声明在全局,又不是嵌套函数里的局部变量,它有个专属的名字:闭包
闭包是介于全局变量和局部变量的一种变量,它被定义在外部函数与内部嵌套函数之间。三者的范围关系如下:
全局变量 > 闭包变量 > 局部变量

—————————————分割线———————————————

匿名函数 lambda

匿名函数是一种很特殊的函数,顾名思义,它没有名字:)
命名方法如下:
lambda 参数1,参数2… : 函数体
比如,之前的n_n平方函数就可以改成:

>>> a = lambda x:x*x
>>> a(10)
100

这就是定义了一个匿名函数并赋值给a的意思。不过匿名函数具体会在什么地方用呢?
请原谅鄙人的无知与浅薄:)我只有在没必要单独命名函数的时候才用它。

递归函数

递归函数是一个比较抽象的难点,(以后还有更抽象的2333333)
递归:我理解的递归,是一个过程,是以一种类似循环的方式解决问题的办法。
而用实现了递归的函数就是递归函数,它的特点就是:自己调用自己

递归函数可以有两种写法:以阶乘函数为例

>>> def Factorial(num):
	if num == 1:
		return num
	return Factorial(num-1)*num

或者是

>>> def Factorial(num):
	if num > 1:
		return num*Factorial(num-1)
	return 1

这两种写法都有一个相同点:都设置了递归的出口
也就是循环结束的条件。因为递归是有上限的,一般都是一千次为上限,在如下程序中,如果把递归出口去掉,程序则会报错:

>>> def factorials(num):
	return num*factorials(num-1)

>>> factorials(2)
RecursionError: maximum recursion depth exceeded

而同样因为递归本身是有深度上限,所以即使不把递归出口删去,当递归次数达到上限时也会报错。

>>> def Factorial(num):
	if num == 1:
		return num
	return Factorial(num-1)*num

虽说上限是一千,但可能是我的电脑的问题 ,我在实验的时候发现我自己的上限是994。不过这都是可以解决的,我们可以手动修改递归深度上限:
只需要写上这两句:

>>> import sys
>>> sys.setrecursionlimit(1003)

(我只加三是为了凑一个神奇的数字:)
在这里插入图片描述
果然上限是增加了三,996可以,但997不行/doge。
但是这种方法并不好:)虽然这个阶乘是为了达到上限而达到上限,但是在一般正常的程序中是不会达到递归深度上限的。如果在平时我们遇到了这种问题一定要先思考是不是程序优化得不够好,加一些异常处理、条件判断等等,这样才能从根本上解决问题。

递归函数的原理

递归函数作为一个稍微有点难度的知识点,我们在会用的基础上还需要稍微了解一下它的运行原理才能更好地理解它:

  • 递归函数每递归一次,就在内存中开辟一个新的地址空间以记录过程状态,直到递归出口。

我们用程序来看一下

>>> def Factorial(num):
	if num == 1:
		return num
	print('地址%d'%(id(num)))
	return Factorial(num-1)*num

输出如图:
在这里插入图片描述
我们可以发现,每次递归后都会分配一个新的地址空间,而这个空间其实就是给每次递归的中间值用的。


终于把函数篇也讲完了/doge,其实到目前为止的内容都没有很大的难度,毕竟还没有到涉及到跟“类”有关的内容。今天可得好好整理一下明天要讲的内容233333,能写到这种程度还是第一次,平时给学弟讲也没有到这种程度,他们连C语言都没有学完,我实在担心他们接受不了这些内容。不过,在这里,我同样会尽我所能把我所知的内容讲述得清晰有条理,还是希望大家多多包涵。

填个以前的小坑:逻辑短路

P.S.填一个坑:是逻辑中断的内容:在第四天学Python:IF语句(2)+运算符(2)中的最后部分,提到过逻辑短路的内容。因为当时还没有说函数的知识,所以就只是简单提了一下。
当时只提到了两种逻辑短路:
一是 and 型
在这里插入图片描述
二是 or 型:
在这里插入图片描述
现在来补充一下,and 和 or 都存在的情况:
ABC是一组 001
DEF是一组 100

def A():
    print('A')
    return 0
def B():
    print('B')
    return 0
def C():
    print('C')
    return 1


def D():
    print('D')
    return 1
def E():
    print('E')
    return 0
def F():
    print('F')
    return 0
if A() and b() and C()  or  D() and E() and F():
    print('ok')
else:
    print('over')

输出:

A
D
E
over
>>> 

由此可见,在第一个and语句,ABC里A是0,第一次就假,所以执行完A后直接结束当前的and,判定为0,去了下一个and;而DEF里E是0,第二次才假,所以执行完了DE才结束了当前and,判定也为0。最后:0 or 0还是0,if判定为假,所以才会输出else里的" over "。

嗯坑也填完了/doge,明天就要正经地进入稍微有点难度有些抽象的“类”了。我是康.,在更新完Python系列后会再开机器学习的系列,对此类文章感兴趣的小伙伴欢迎与我一起学习,共同进步/doge,下篇文章见!

原创文章 23 获赞 103 访问量 2689

猜你喜欢

转载自blog.csdn.net/k_ksy/article/details/105805101
今日推荐