函数
1. 函数的定义
由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元。
完成一定的功能。
函数也是对象,python把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期。
2. 函数的作用
- 结构化编程是对代码的最基本的<font color = red >封装</font>,一般按照功能组织一段代码。
- 封装的目的是为了<font color = red >复用</font>,减少冗余代码。
- 代码更加简洁美观,可读易懂。
3. 函数的分类:
内建函数;库函数;自建函数
4. 函数的定义、调用
定义
def语句定义函数
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
定义中的参数列表成为形式参数,只是一种符号表达,简称形参。
定义需要在调用前,否则会抛出NameError异常。
调用
函数定义,只是声明了一个函数,它不会被指执行,需要调用。
调用的方式,就是函数名加上小括号,括号内写上参数。
调用时写的参数时实际参数,是实实在在传入的值,简称实参
传参时位置参数要放在关键字参数前面。
参数传递:不可变类型,传递副本给函数,函数内操作不影响原始值
可变类型,传递的是地址引用,函数内操作可能影响原始值
定义形参和传递实参时候的注意事项
- 参数调用时传入的参数要和定义的个数相匹配,可变参数例外
- 定义时,缺省参数要放在非缺省参数前。
- 定义时加* :可变位置参数:可以收集位置参数传入的所有参数,收集多个实参为一个tuple。可变位置参数不能用关键字传参。
- 形参加**:可变关键字参数,只能用关键字传参。可变关键字参数,收集的实参名称和值组成一个字典,所以可修改。
- 函数名也是标识符,返回值也是值,函数是可调用的对象,callable(函数名) -> True。
- 混合使用参数的时候,可变参数要放到参数列表的最后,普通参数要放到参数列表的最前面,可变位置参数发要放在可变关键字参数的前面。
- keyword-only参数:如果在一个可变位置参数后面,出现了普通参数,此时这个普通参数已经变成了一个keyword-only参数
- 参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数。
- 参数解构:
- 给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参。
- 非字典类型使用*解构成位置参数
- 字典类型使用**解构成关键字参数
- 提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配。
5.函数的返回值
python函数使用return语句返回“返回值”。
所有函数都有返回值。如果没有return语句,隐式调用return None。
return语句并不一定是函数的语句块的最后一条语句
return语句只能执行一次,执行完,函数结束,当前return后面的语句就不会再运行了。所以函数一次只能返回一个值,不能返回多个值,但是可以返回容器,容器里面包含多个值。(return [1,3,5]是指明返回一个列表,是一个列表对象;return 1,3,5看似返回多个值,隐式的被python封装成一个元组)
作用:结束函数调用、返回值。
函数的嵌套
函数有可见范围。这就是作用域的概念
外层变量作用域在内层作用域可见
内部函数不能在外部直接使用,会抛NameError异常,因为它不可见。
6. 作用域
一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域。
全局作用域:在整个函数运行环境中都可见。
局部作用域:在函数、类内部可见;局部变量的使用范围不能超过其所在的局部作用域。
例子:
a = 5
def foo():
a += 1
foo()
---------------------------------------------------------------------------
UnboundLocalError Traceback (most recent call last)
UnboundLocalError: local variable 'a' referenced before assignment
报错原因:
a += 1其实就是a = a + 1,a = 5是全局的变量,虽然能在内部函数foo中可见,但是在foo函数内部出现了 a = ,出现等号就是即赋值即重新定义,那么=的右边作为赋值的内容 :a+1,但在函数中,此时的a已经算是重新定义了一个局部变量,而不是用外面的全局变量,但是此时a还没有完成赋值就被拿来进行加1操作,所以才会报错。
解决办法:
在这条语句前增加x=0之类的赋值语句,或者使用global 告诉内部作
用域,去全局作用域查找变量定义
默认值的作用域
函数名.__defaults__属性:使用元组来保存所有位置参数默认值,它不会因为在函数体中使用了它而发生了变化。
函数名.__kwdefaults__属性:使用字典保存所有keyword-only参数的默认值。
使用可变类型(引用参数)作为默认值,就有可能修改这个默认值。
使用按需修改,例子。
def foo(xyz=[], u='abc', z=123):
xyz = xyz[:] # 影子拷贝
xyz.append(1)
print(xyz)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
# 函数体内,不改变默认值
# 使用影子拷贝创建一个新的对象,永远不能改变传入的参数
# xyz都是传入参数或者默认参数的副本,如果就想修改原参数,无能为力
def foo(xyz=None, u='abc', z=123):
if xyz is None:
xyz = []
xyz.append(1)
print(xyz)
# 使用不可变类型默认值
# 如果使用缺省值None就创建一个列表
# 如果传入一个列表,就修改这个列表
全局变量global
使用global关键字的变量,将函数内的定义的局部变量声明成全局变量。
如果函数需要使用外部全局变量,请使用函数的形参传参解决。
尽量不使用
nonlocal关键字
nonlocal将变量标记为不再本地作用域定义,而在<font color =red>上一级的某一级</font>局部作用域中定义,但不能是全局作用域中定义。
7.<font color = blue>闭包</font>
自由变量:未在本地作用域中定义的变量,例如定义在内层函数外的外层函数的作用域中的变量
<font color = red >闭包</font>:是一概念,是嵌套函数中,指的是在内层函数中引用到外层函数的自由变量,就形成了闭包。
8.变量名解析原则LEGB
Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡。
Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间。
Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡。
Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如 print(open),print和open都是内置的变量。
函数变量作用域:级别:Built_in(内建) > Global(全局) > Enclosing(封装)> local(本地)
9.函数的销毁
全局函数销毁
- 重新定义同名函数
- del 语句删除函数对象名称,函数对象的引用计数减1
- 程序结束时
def foo(xyz=[], u='abc', z=123):
xyz.append(1)
return xyz
print(foo(), id(foo), foo.__defaults__)
def foo(xyz=[], u='abc', z=123):
xyz.append(1)
return xyz
print(foo(), id(foo), foo.__defaults__)
del foo
print(foo(), id(foo), foo.__defaults__)
局部函数销毁
- 重新在上级作用域定义同名函数
- del 语句删除函数名称,函数对象的引用计数减1
- 上级作用域销毁时
def foo(xyz=[], u='abc', z=123):
xyz.append(1)
def inner(a=10):
pass
print(inner)
def inner(a=100):
print(xyz)
print(inner)
return inner
bar = foo()
print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
del bar
print(id(foo),id(bar), foo.__defaults__, bar.__defaults__)
10.递归函数
函数是需要压栈的,栈和线程相关。
11.匿名函数
没有名字的函数,python借助lamdba表达式构建匿名函数。
参数列表不需要小括号。
冒号是用来区分参数列表和表达式的。
不需要return,表达式的值,就是匿名函数返回值。
lambda表达式(匿名函数)只能写在一行上,被成为单行函数。
用途:在高阶函数传参时,使用lambda表达式,往往能简化代码
格式:lambda 参数列表:表达式
lambda x : x**2
(lambda x : x**2) () #调用
12.高阶函数
参时是一个函数,或者输出一个函数
13.装饰器
装饰器本质上是一个 Python 函数或类。
它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
多装饰器的函数执行顺序,由底向上
14.参数注解
文档注解:函数内的最前面,使用三个双引号
函数注解:
- python3.5引入
- 对函数的参数进行类型注解
- 对函数的返回值进行类型注解
- 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
- 提供给第三方工具,做代码分析,发现隐藏的bug
- 函数注解的信息,保存在__annotations__属性中
变量注解:python3.6引入
函数参数类型检查
思路:
- 函数参数的检查,一定是在函数外
- 函数应该作为参数,传入到检查函数中
- 检查函数拿到函数传入的实际参数,与形参声明对比
- __annotations__属性是一个字典,其中包括返回值类型的声明,加入要位置参数的判断,无法和字典中的声明对应,使用inspect模块
inspect模块:提取获取对象信息的函数,可以检查函数和类、类型检查
- inspect.isfunction(add) , 是否是函数
- inspect.ismethod(add) , 是否是类的方法
- inspect.isgenerator(add) , 是否是生成器对象
- inspect.isgeneratorfunction(add) , 是否是生成器函数
- inspect.isclass(add) , 是否是类
- inspect.ismodule(inspect) , 是否是模块
- inspect.isbuiltin(print) , 是否是内建对象
signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名,它的参数类型,它的所在的类和名称空间及其他信息)
Parameter对象
保存在元组中
输入属性:inspect.signature.parameters.annotation/name/kind/default
返回属性:inspect.signature.return_annotation
当不知道该方法下面有多少属性的时候,可以先用type查看该它的类型,然后通过导入模块,使用参数注解的方式来查看。