python基础学习-函数
函数
- 数学定义:y=f(x),y是x的函数,x是自变量。y=f(x0,x1,…,xn)
- Python函数
- 由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
- 完成一定的功能
- 函数的作用
- 函数是为了完成某几种功能
- 函数的作用就是简单封装,不会做非常复杂的封装
- 结构化编程对代码的最基本的封装,一般按照功能组织一段代码
- 封装的目的是为了复用,减少冗余代码
- 函数本身也是方法,叫函数或方法都可以。
- 代码更加简洁美观、可读易懂
- 函数的分类
- 内建函数,如max()、reversed()等
- 库函数,如math.ceil()等
函数定义、调用
- def语句定义函数
1 | def 函数名(参数列表): |
调用
- 函数定义,只是声明了一个函数,它不会被执行,需要调用
- 调用的方式,就是函数名加上小括号,括号内写上参数
- 调用时写的参数是实际参数,是实实在在传入的值,简称实参
函数举例
1 | def add(x,y): |
函数参数
- 参数调用时传入的参数要和定义的个数相匹配(可变参数例外)
- 位置参数
def f(x,y,z)
调用使用f(1,3,5)
- 按照参数定义顺序传入实参
- 关键字参数
def f(x,y,z)
调用使用f(x=1,y=3,z=5)
- 使用形参的名字来输入实参的方式,如果使用了形参名字,那么传参顺序就可和定义顺序不同
- 传参
f(z=None,y=10,x=[1])
f((1,),z=6,y=4.1)
f(y=5,z=6,2)
# 这会报错- 要求位置参数在关键字参数之前传入,位置参数是按位置对应的
- 位置参数与关键字参数只是传参的方法不一样而已,定义函数时的方法是一样的
函数参数默认值
- 参数默认值(缺省值)
- 定义时,在后跟上一个值
1 | def add(x=4,y=5): |
- 作用
- 参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
- 参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用
- 举例
1 | # 定义一个函数login,参数名称为host、port、username、password |
可变参数
- 问题
1 | # 有多个数,需要累加求和 |
可变参数
- 一个形参可以匹配任意个参数
位置参数的可变参数
1 | # 有多个数,需要累加求和 |
- 关键字参数的可变参数
1 | # 配置信息打印 |
- 可变参数混合使用
1 | # 配置信息打印 |
总结
- 有位置可变参数和关键字可变参数
- 位置可变参数在形参前使用一个星号*
- 关键字可变参数在形参前使用两个星号**
- 位置可变参数和关键字可变参数都可以收集若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集形成一个dict
- 混合使用参数的时候,可变参数要放到参数列表的最后,普通参数需要放到参数列表前面,位置可变参数需要在关键字可变参数之前
举例
1 | def fn(x,y,*args,**kwargs): |
keyword-only参数
- keyword-only参数(Python3加入)
1 | # 如果在一个星号参数后,或者一个位置可变参数后,出现的普通参数,实际上已经不是普通的参数了,而是keyword-only参数 |
可变参数和参数默认值
1 | def fn(*args,x=5): |
函数参数规则
- 参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数
1 | def fn(x,y,z=3,*arg,m=4,n,**kwargs): |
注意
- 代码应该易读易懂,而不是为难别人
- 请按照书写习惯定义函数参数
参数规则举例
1 | def connect(host='localhost', port='3306', user='admin', password='admin', **kwargs): |
参数解构
- 举例
1 | # 加法函数 |
- 参数解构
- 给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参
- 非字典类型使用*解构成位置参数
- 字典类型使用**解构成关键字参数
- 提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配
- 参数解构,有时候有,有时候没有。如果有,就把参数一个个解构出来,如果没有,就解构成一个整体,如列表或元组
1 | def add(x, y): |
- 参数解构和可变参数
1 | # 给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参 |
函数的返回值
1 | def showplus(x): |
- 多条return语句
1 | def guess(x): |
- 举例
1 | def fn(x): |
总结
- Python函数使用return语句返回“返回值”
- 所有函数都有返回值,如果没有使用return语句,隐式调用return None
- return语句并不一定是函数的语句块的最后一条语句
- 一个函数可以存在多个return语句,但是只有一条可以被执行。如果没有一条return语句被执行到,隐式调用return None
- 如果有必要,可以显示调用return None,可以简写为return
- 如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其它语句就不会被执行了
- 作用:结束函数调用、返回值
返回多个值
1 | def showlist(): |
函数嵌套
- 在一个函数中定义了另外一个函数
1 | def outer(): |
作用域***
- 一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域
1 | - 举例,对比下面两个函数,x到底是否可见? |
- 全局作用域
- 在整个程序运行环境中都可见
- 局部作用域
- 在函数、类等内部可见
- 局部变量使用范围不能超过其所在的局部作用域
1 | def fn1(): |
- 全局变量global
1 | def foo(): |
- global总结
- x+=1这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义,才能被引用。解决办法是在这条语句前增加x=0之类的赋值语句,或者使用global告诉内部作用域,去全局作用域查找变量定义
- 内部作用域使用x=5之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么x=5相当于在为全局作用域的变量x赋值
- global使用原则
- 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请使用函数的形参传参解决
- 一句话:不用global。学习它就是为了深入理解变量作用域
闭包*
- 自由变量:未在本地作用域中定义的变量。例如定义在内存函数外的外层函数的作用域中的变量
- 闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。这实际是一个函数嵌套,内层的函数引用到了上一层函数的问题。如果引用的是全局作用域中的函数就不是闭包。
1 | - c[0] += 1这行会报错吗? |
nonlocal关键字
- 使用了nonlocal关键字,将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中定义
1 | def counter(): |
默认值作用域
1 | def foo(xyz=2): |
- 使用可变类型作为默认值,就可能修改这个默认值
- 有时候这个特性是好的,有的时候这种特性是不好的,有副作用
1 | # 如何做到按需改变?如下面两例: |
- 方法一:
- 使用影子拷贝创建一个新的对象,永远不能改变传入的参数
- 方法二:
- 通过值的判断就可以灵活的选择创建或者修改传入对象
- 这种方式灵活,应用广泛
- 很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用方法
变量名解析原则LEGB
- Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
- Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
- Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
- Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如print(open),print和open都是内置的变量
- 所以一个名词的查找顺序就是LEGB
函数的销毁
- 全局函数
1 | def foo(xyz=[],u='abc',z=123): |
全局函数销毁
- 重新定义同名函数
- del语句删除函数对象
- 程序结束时
局部函数
1 | def foo(xyz=[],u='abc',z=123): |
- 局部函数销毁
- 重新在上级作用域定义同名函数
- del语句删除函数名称,函数对象的引用计数减1
- 上级作用域销毁时