函数及特性
函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。
函数的特性:
减少重复代码,提高应用的模块性和代码的重复利用率,用来实现单一,或相关联功能。
可扩展性:方便修改,更容易扩展代码的功能。
保持代码的一致性,修改函数功能之后所有调用函数的地方都会随之修改。
函数的定义和调用
在 Python 中定义函数使用 def
关键字,一般格式如下:
1 | def 函数名(参数列表): |
函数名的命名规则,与变量名类似:
函数名区分大小写。
函数名只能是 字母、数字 或 下划线 的任意组合,不能使用任何的标点符号。
函数名不能以数字开头。
函数名不能与 Python 的关键字同名。
函数的参数
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
形参和实参
形参指的是形式参数,不是实际存在,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接收实参(实参个数,类型应与实参一一对应)。
实参是指实际参数,调用函数时传给函数的参数,可以是常量,变量,表达式,函数,传给形参。
形参和实参的区别是,形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元,实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。
1 | import time |
传入函数的参数
位置参数
位置参数也叫必备参数,是必须要传给函数的参数,并且在传参时必须以正确的顺序传入函数,调用时的数量必须和函数声明时的一样。
1 | def f(name, age): |
关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
1 | def f(name, age): |
定义函数时的参数
默认参数
默认参数也叫缺省参数,同时也是关键字参数,调用函数时缺省参数的值如果没有传入,则被认为是默认值。默认参数常用于多数情况下对象的值是固定的情况,例如一些女子学校中,学生的性别是固定的。
1 | def echo_info(name, age, sex='male'): |
默认参数的陷阱: 在定义函数时,如果默认参数的值是可变数据类型,那么每一次调用函数的时候如果不传入值,就会公用这个数据类型的资源,即每次都操作的是同一个对象。例如:
1 | def test(names=[]): |
输出结果:
1 | ['Tom'] |
再比如:
1 | def atest(k, data={}): |
输出结果:
1 | {1: 'v'} |
不定长参数
有时候可能需要一个函数能处理比当初声明时更多的参数,这些参数就叫不定长参数,也叫动态参数。动态参数和位置参数、关键字参数不同,声明时不会命名。例如下面的加法器,能且仅能接收三个参数,并求出和:
1 | def add(x, y, z): |
如果在变量名前面加了星号 *
,则变量会以元组的形式存放所有 未命名 的变量参数,例如:
1 | def add(*args): |
下面的打印基本信息的函数,最多只能接收三个参数,如果再想添加其他参数就需要重新定义函数的参数:
1 | def echo_info(name, age, sex='male'): |
而如果在变量名前面加了 **
, 则变量会以字典的形式存放 有命名 的变量参数,例如:
1 | def echo_info(*args, **kwargs): |
参数的位置关系
定义函数时应该 *args
放左边,**kwargs
放右边,下面是错误的写法:
1 | def print_info(name, **kwargs, *args): # 报错 |
按照规范,定义函数时默认参数应该放在位置参数之后。
1 | def test(name, age, *args, sex='fmale', **kwargs): |
调用函数进行传参时,关键字参数应该放在位置(必备)参数之后。
1 | echo_info('Tom', 18, hobby='girl', nationality='Chinese', ability='Python') |
其他传参方式
函数在接收动态参数时,会把接收的参数聚合成一个元组,例如:
1 | def func(*args): |
而在函数中使用 *args
的方式调用参数时,会把聚合的元组打散,例如:
1 | def func(*args): |
这种方式其实又还原成了最原始传入参数的方式,这种情况最常用的情形是在装饰器内:
1 | def wrapper(funcname): |
如果传给函数的参数是个列表或元组,还可以使用 *
将其打散之后传入:
1 | def func(*args): |
如果函数接收的是 **kwargs
,那么单纯地传入字典会被识别为位置参数,必须打散才能被接收。例如:
1 | def func(**kargs): |
函数的返回值
要想获取函数的执行结果,就可以用 return
语句把结果返回。
注意:
函数在执行过程中只要遇到
return
语句,就会停止执行并返回结果,也可以理解为return
语句代表着函数的结束。如果未在函数中指定
return
,那这个函数的返回值为None
。return
可以接收多个对象,此时 Python 会将多个对象封装成一个元组,并将这个元组对象返回。
1 | #!/usr/bin/env python3 |
函数的注释
定义了函数后,应该对整个函数作注释而不是逐行注释。以下是建议的注释方式:
1 | def func(name,sex): |
命名空间
从 python 解释器开始执行代码之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来。
但是当遇到函数定义的时候解释器只是象征性的将函数名读入内存,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心。
等执行到函数调用的时候,Python 解释器会再开辟一块内存来存储这个函数里的内容,这个时候才关注函数里面有哪些变量,而函数中的变量会存储在新开辟出来的内存中。函数中的变量只能在函数的内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
命名空间是指存放名字与值的绑定关系的空间,分为三种:
代码在运行一开始创建的存储“变量名与值的关系”的空间叫做全局命名空间。
在函数的运行中开辟的临时的空间叫做局部命名空间。
内置命名空间中存放了python解释器为我们提供的名字:input,print,str,list,tuple…。
这三种命名空间之间的加载顺序为:
内置命名空间(程序运行前加载) => 全局命名空间(程序运行中:从上到下加载) => 局部命名空间(程序运行中:调用时才加载)
取值顺序为:
在局部调用:局部命名空间 => 全局命名空间 => 内置命名空间
在全局调用:全局命名空间 => 内置命名空间
作用域
作用域就是作用范围,按照生效范围可以分为两类:
全局作用域:包含内置命名空间、全局命名空间,在整个文件的任意位置都能被引用、全局有效 。
局部作用域:局部命名空间,只能在局部范围内生效。
使用 globals()
和 locals()
方法则会以字典类型返回当前位置的全局变量和局部变量:
1 | # 全局调用 |
作用域介绍
python中的作用域分4种情况:
L:local,局部作用域,即函数中定义的变量;
E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
G:global,全局变量,就是模块级别定义的变量;
B:built-in,系统固定模块里面的变量,比如
int
,bytearray
等。 搜索变量的优先级顺序依次是:局部作用域 => 外层作用域 => 当前模块中的全局 => Python 内置作用域,也就是 LEGB 。
1 | x = int(2.9) # int built-in |
并且,local
和 enclosing
是相对的,enclosing
变量相对上层来说也是 local
。
作用域产生
在 Python 中只有模块(module
),类(class
)以及函数(def
、lambda
)才会引入新的作用域,其它的代码块(如 if
、try
、for
等)是不会引入新的作用域的,如下代码中 if
并没有引入一个新的作用域,x
仍处在当前作用域中,后面代码可以使用:
1 | if 2>1: |
但是函数 def
、class
、lambda
会引入新的作用域:
1 | def test(): |
变量的修改
1 | x = 6 |
上述代码出现错误的原因在于 print(x)
时,解释器会先在局部作用区域找到变量 x
。这是因为在进行函数调用( f2()
)之前,其前面的代码包括函数都会先加载到内存,因此执行到 print(x)
会先在局部区域找到 x = 5
,但 x
的使用出现在 x
的声明前了,所以报错。
1 | # x=6 |
上述代码出现错误的原因在于 print(x)
时,解释器会在局部作用域找,找不到 变量 x
的声明,然后去全局作用域找也找不到,最后在 Python 的内置区域找也找不到,因此会报变量未声明的错误。
1 | x = 6 |
x += 1
即 x = x + 1
是先加再赋值,解释器会在局部作用域找,会找到 x += 1
(函数已经加载到内存),但 x
的加法运算出现在赋值前了,所以报错。
关键字 global
当内部作用域想修改外部作用域的变量时,就要用到 global
和 nonlocal
关键字了,当修改的变量是在全局作用域(global 作用域)上的,就要使用 global 先声明一下。例如:
1 | count = 10 |
关键字 nonlocal
关键字 global
声明的变量必须在全局作用域上,不能应用在嵌套作用域上,当要修改嵌套作用域( enclosing 作用域,外层非全局作用域)中的变量怎么办呢,这时就需要 nonlocal
关键字了:
1 | def outer(): |
总结
- 对于一个变量,内部作用域先声明就会覆盖外部变量,不在内部声明而直接使用,就会使用外部作用域的变量。
- 内部作用域要修改外部作用域变量的值时,全局变量要使用 global 关键字,嵌套作用域变量要使用 nonlocal 关键字。nonlocal 是 Python3 新增的关键字,有了这个关键字就能完美的实现闭包了。