python函数之--返回值和作用域(+默认值的作用域)

返回值
  • python函数使用return语句返回“返回值”
  • 所有函数都有返回值,如果没有return语句,隐式调用return None
  • 一个函数可以存在多个return语句,但是只有一条可以被执行。如果没有执行return,隐式调用return None
  • return语句不一定是函数语句块的最后一句,但一定是函数执行的最后一句
  • 如果有必要,可以显式调用return None,简写为return
作用域

一个标识符的可见的范围,就是这个标识符的作用域。一般常说的事变量的作用域

def foo():
	x = 100
print(x) #可以访问到么?

上述代码中的x不可以访问到,会抛出异常(NameError:name ‘x’ is not defind),原因在于函数是一个封装,它会开辟一个作用域,x变量被限制在这个作用域中,所以在函数外部x变量不可见。

Ps:每一个函数都会开辟一个作用域

作用域分类
  • 全局作用域
    • 在整个程序运行环境中都可见
    • 全局作用域中的变量称为全局变量
  • 局部作用域
    • 在函数、类等内部可见
    • 局部作用域中的变量称为局部变量,其使用范围不能超过其所在的局部作用域
#局部变量
def fn1():
	x = 100 #局部作用域,x为局部变量,适用范围在fn1内
def fn2():
	print(x) #无法访问x
print(x) #无法访问x
#全局变量
x = 100 #全局变量,也可在函数外定义
def fn3():
	print(x)#可以访问x
fn3()
  • 一般来说,外部作用域的变量在函数内部可见,可以使用
  • 反之,函数内部的局部变量不能被函数外部使用
一个赋值语句的问题
x = 10
def foo():
	print(x)
foo()#正常执行,函数外部的变量在函数内可见
x = 10
def foo():
	y = x + 1
foo()#正常执行,x取函数外部可见变量
x = 10
def foo():
	x = x + 1
foo()#此处报错!

在这里插入图片描述
why???
原因分析

  • x += 1等价于 x = x + 1
  • 相当于在函数foo内部定义了一个局部变量x(赋值即重新定义!重点敲黑板,记下来!)
  • x = x + 1相当于定义了局部变量x,但是x还没有完成赋值,就被右边拿来做+1操作了,所以会报错

如何解决这一常见问题?

再此引入global语句

global语句
x = 10
def foo():
	global x
	x += 1
foo()#正常执行

def foo():
	global x
	x = 10
	x += 1
foo()
print(x)#能读取x么?
  • 使用global关键字定义的变量,虽然在foo中声明,但这是告诉当前foo函数的作用域,这个x的变量将使用外部全局作用域中的x;
  • 即使是在foo中又写了x=10,也不会在foo这个局部作用域中定义局部变量x了;

一个总结

  • x += 1这种特殊形式产生的错误原因?先引用后赋值,而python是动态语言是复制才算定义(赋值即定义!再次敲黑板),才能被引用。解决办法:在这条语句之前加一条x = 0之类的赋值语句,或使用global关键字告诉foo函数,去全局作用域中找变量定义;
  • 在函数内使用globle关键字会导致全局变量在局部作用域内可见,但是函数的的目的就是为了封装,尽量与外界解决,如果函数需要外界变量,可以通过传参解决,所以:尽量不要在函数中使用global关键字
闭包定义
  1. 自由变量:未在本地作用域中定义的变量,例如在内层函数外的外层函数的作用域中定义的变量;
  2. 闭包:就是一个概念,是现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。
def counter():
	count = 0
	def inc()
		count += 1
		return count
	return inc
foo = counter()
print(foo(),foo()

上例一定报错(原因参照前面一个赋值语句的问题),我们可以使用globle来解决:

def counter():
	global count
	count = 0
	def inc()
		global count
		count += 1
		return count
	return inc
foo = counter()
print(foo(),foo()

使用global后就是全局变量,而不是闭包了,而且我们前面也提到尽量不要使用global,所以python3+中提供了nonlocal

nonlocal语句

定义:将变量标记为 不在本地作用域中定义,二是在上级的某一级局部作用域中定义,但不能是全局作用域中定义。
所以上述例子可以改写为:

def counter():
	count = 0
	def inc()
		nonlocal count
		count += 1
		return count
	return inc
foo = counter()
print(foo(),foo()

如此既可以让内部函数引用外部函数的自由变量(全局变量不可以!),又可以形成闭包。

默认值的作用域

思考下面的foo函数连续调用两次的执行结果分别是什么?

def foo(x = []):
	x.append(1)
	print(x)
foo()
foo()	
>>[1]
>>[1, 1]

为什么第二次执行的结果是[1,1] ?

  1. 因为函数也是一个对象,每个函数定义被执行后,就生成了一个函数对象和函数名这个标识符的关联;
  2. python把函数的默认值放在了函数对象的属性中,这个属性伴随函数对象的整个生命周期;
  3. 查看foo.__defaults__属性,它是一个元祖。
def foo(x = []):
	x.append(1)
	print(x)
print(id(foo),foo.__defaults__)
foo()	
print(id(foo),foo.__defaults__)
foo()	
print(id(foo),foo.__defaults__)

执行结果:

>>2268635878808 ([],)
  [1]
>>2268635878808 ([1],)
  [1, 1]
>>2268635878808 ([1, 1],)

函数内存地址并没有改变,也就是说foo这个对象没有改变过,调用时,它的__defaults__属性中使用元组保存的默认值x的默认值是引用类型(不急,慢慢绕),引用类型的元素变动并不是元组的变化;

非引用类型的默认值不存在此效果~

变量名解析原则LEGB
  1. local,本地作用域、局部作用域的命名空间,调用函数时创建,调用结束时消亡;
  2. enclosing,python2.2后引入了函数嵌套,实现了闭包,这个就是嵌套函数外部函数的命名空间;
  3. global,全局作用域,即一个模块的命名空间,模块被import时创建,解释器退出时消亡;
  4. build-in,内置模块命名空间,解释器启动时创建,解释器退出时消亡。
    所以一个名词的查找顺序就是LEGB,如下图所示:
    在这里插入图片描述
函数的销毁
  1. 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象;
  2. 可以使用del语句删除函数,使其引用计数减1;
  3. 可以使用同名函数覆盖原有定义,本质上也是使其引用计数减1;
  4. python程序结束时,所有对象全部销毁;
  5. 函数也是对象,是否销毁,还是看引用计数是否为0,当引用即时为0时,GC(垃圾回收机制)会在适当的时间回收内存(注意不是立即!

Ps:如果本文解决你的困惑,请给个赞~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值