函数 #作用:封装和复用
数学定义:y=f(x) ,y是x的函数,x是自变量。y=f(x0, x1, ..., xn)
Python函数
由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
完成一定的功能
函数的作用
*结构化编程对代码的最基本的封装*,一般按照功能组织一段代码
封装的目的为了复用*,减少冗余代码
代码更加简洁美观、可读易懂
函数的分类
内建函数,如max()、reversed()等
库函数,如math.ceil()等
函数定义、调用
def语句定义函数
def 函数名(参数列表):
函数体(代码块)
[return 返回值]
函数名就是标识符*,命名要求一样
语句块必须缩进,约定4个空格
Python的函数没有return语句,隐式会返回一个None值
*定义中的参数列表成为*形式参数*,只是一种符号表达,简称形参
调用
函数定义,只是声明了一个函数,它不会被执行,需要调用
调用的方式,就是函数名加上小括号,括号内写上参数
*调用时写的参数是*实际参数*,是实实在在传入的值,简称实参
定义时是形参,调用时是实参,调用加小括号
python是动态的,强类型语言
因为是动态的,参数没有定义类型,会带来很大问题(自由工业化开发会带来问题)
无法限制参数类型,除非进行判断
能不能控制这个?是解决团队的问题
函数参数
参数调用时传入的参数要和定义的个数相匹配(可变参数例外)
位置参数
def f(x, y, z) 调用使用 f(1, 3, 5)#顺序对应,个数也要相同#传参,传入实参
按照参数定义顺序传入实参
关键字参数 #谁等于谁就是关键字传参(keyword)
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) #位置参数必须在前,keyword在后是可以的
要求位置参数必须在关键字参数之前传入,位置参数是按位置对应的
函数参数默认值 #记得时候可以记带等号不可以放前面
参数默认值(缺省值)
定义时,在形参后跟上一个值
def add(x=4, y=5):
return x+y
测试调用 add(6, 10) 、add(6, y=7) 、add(x=5) 、add()、add(y=7)、 add(x=5, 6) #这个不可以、add(y=8,
4)、add(x=5, y=6)、add(y=5, x=6)
测试定义后面这样的函数 def add(x=4,y)#非缺省跟在非缺省后不可以(记得时候可以记带等号不可以放前面)
作用
参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用
举例
定义一个函数login,参数名称为host、port、username、password
函数参数默认值
在形参前使用*表示该形参是可变参数,可以接收多个实参
****收集多个实参为一个tuple***
可变类型和解构类型,但不全相同(keyword-only)
def fn(*args,x,y,**kwargs)
(x,y)属于keyword-only:在后面 必须定义关键字传参
可以用缺省值,必须使用关键字传参#经常给keyword-only定义缺省值
def add(*,x,y): #逗号标志后面的是keyword-only
***总结定义形参和传参的几种类型(定义时是形参,调用时是实参)
定义形参:,位置参数,可变位置参数(封装成元组tuple),可变关键字传参(封装成dict字典)
定义传参:位置传参,关键字传参,混着用的时候:位置传参在前,关键字传参在后
普通位置传参:位置传参和关键字传参都支持但是位置传参在前
可变参数:
可变位置传参:不允许关键字传参,只可以写在一起让可变参数对应#add(1,2,3,4)
可变关键字传参只收集关键字传参,不可以位置传参
位置参数往放前(可变的也要放前),关键字参数放后(记带星的放后星星放最后)
传参时对应形参必须只有一个,全是关键字传参可以不考虑顺序
函数参数
参数规则
***参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺
省值)、可变关键字参数
模版:def fn(x, y, z=3, *arg, m=4, n, **kwargs):
print(x,y,z,m,n)
print(args)
print(kwargs)
参数解构
***只能在参数传参中运用
传参的时候才解构,解构出元素作为实参
非字典类型使用*解构成位置参数
****字典类型使用**解构成关键字参数
字典解构:一个*解key,**解字典
add(*{'a': 5, 'b': 6})#解构后为x = 'a',y = 'b'
#add(**{'a': 5, 'b': 6})#解构后为'a' = 5,'b' = 6
函数默认return none,如果想要函数有返回值修改return
------------------------------------------------------------------------------------------------------------------------------------
函数返回值
return 语句
一个函数只有一条return语句
多分支结构可以有多条return,但只能执行一条return
#多分支结构可以设置个变量,最后return变量,可以省着每次分支都写return
if..ret=..else..ret=..最后return ret
函数可以返回值可以不同类型
一个函数只要碰到return就立即返回终止
函数默认return none,往往函数需要写return
return返回必须是一个值,不是一个会被它封装成一个元组
返回多个值包在容器中 得到它们使用解构一一对应
---------------------------------------------------------------------------------------------------------------------------
作用域****
嵌套函数
函数内部的函数不可以在外部单独执行#因为在外部没有定义,报NameError
在外部的函数可以调用嵌套在内的函数
****###函数是有可见范围的,这就是作用域
函数内部的标识符(变量),外部是不可见的
函数定义直接过,执行时看前面定义有没有用到变量
函数内部
x = 5
def show():
x += 1#报错
print(x)
show()
x = x +1 #在函数内部要先定义,要不然会说内部没有定义
#右边x+1,赋值即重新定义,赋值要先算右边,
#右边是内部本地变量(本地作用域),x要重新定义
嵌套函数:赋值即重新定义,但是是定义自己的变量
全局作用域 #在整个运行环境中都可见
局部作用域(local) #在函数和类等内部中可见
---------------------------------------------------------------------------------------------------------------------------
全局作用域(了解,写函数基本不用,改动影响太大)
global x #使用全局作用域变量
#尽可能放在前面第一行
global 用在局部作用域中定义一个全局作用域中操作变量
但是global只影响当前作用域
函数应该用定义形参,传参这种形式用,尽量不要用global
---------------------------------------------------------------------------------------------------------------------------
闭包***#一般在有嵌套函数时候用
自由变量:未在本地作用域中定义的变量。例如定义在内存函数外的外层函数的作用域中的变量
本来变量在外层函数 内层函数用到了这个局部变量,闭包就已经产生
nonlocal 关键字
#声明变量不在本地作用域中,但是在外层作用域中找,但是不会再全局作用域中找
#形成闭包
#一层层向外找但不在全局中找
#不可在全局下一层中用(因为全局中的下一层可以直接用全局变量了)
形参可以当局部变量来看
---------------------------------------------------------------------------------------------------------------------------
默认值的作用域
函数也是对象,如果对象中包含引用类型时,会更改默认值,简单类型不会更改默认值
函数名.__defaults__ #查看默认值属性,使用元组保存所有位置参数默认值#使用元组位置就不会变
函数名.__kwdefaults__ #查看使用字典保存所有keyword-only参数的默认值,dict是关键字传参
缺省值函数默认的特殊属性,生命周期跟函数同样周期共消亡
缺省值只有自己的作用域的,要看缺省值指的是什么,如果指的是缺省值送给形参,形参的作用域就在函数内部,它是局部作用域中的局部变量
#default这个东西它又属于函数对象本身的特殊属性,它是放在函数对象上的
#函数对象是和函数定义相关的,函数在定义时它的标识符(形参)关联到它内存中创建的那一个函数对象上去了
默认值作用域为形参用,形参可以当函数的局部变量来看
如果形参可变的类型(如列表),(无缺省值)没有固定的缺省值,有缺省值也会变的
#有时候这个特性是好的,有的时候这种特性是不好的,有副作用
def foo(xyz=[], u='abc', z=123):
xyz.append(1)
return xyz
print(foo(), id(foo))
print(foo.__defaults__)
print(foo(), id(foo))
print(foo.__defaults__)#引用类型默认值改变
def foo(xyz=[], u='abc', z=123):
#xyz = foo.__defaults__
xyz = xyz[:] # 影子拷贝,简单类型空列表内存中新生成一份,内存地址不同
xyz.append(1)
print(xyz)
foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)#不改变默认值
def foo(xyz=None, u='abc', z=123):#使用不可变类型默认值
if xyz is None:#使用默认值时创建列表
xyz = []
xyz.append(1)# 如果传入一个列表,就修改这个列表
print(xyz)
foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)
第一种方法
使用影子拷贝创建一个新的对象,永远不能改变传入的参数
第二种方法
通过值的判断就可以灵活的选择创建或者修改传入对象
这种方式灵活,应用广泛
很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法
---------------------------------------------------------------------------------------------------------------------------
变量名解析原则LEGB #一个名词的查找顺序就是LEGB,一层一层向外解析
LEGB规定了查找一个名称的顺序为:local-->enclosing function locals-->global-->builtin
Local,本地作用域、局部作用域的local命名空间。函数调用时创
建,调用结束消亡
Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就
是嵌套函数的外部函数的命名空间
Global,全局作用域,即一个模块的命名空间。模块被import时
创建,解释器退出时消亡
Build-in,内置模块的命名空间,生命周期从python解释器启动
时创建到解释器退出时消亡。例如 print(open),print和open都是内置的变量
---------------------------------------------------------------------------------------------------------------------------
函数的销毁
#函数的定义一般只做一次,除非你想把它覆盖掉再定义一次
#定义函数的目的就是复用,要明确为什么要销毁,不要轻易销毁
全局函数的销毁
重新定义同名函数
del 语句删除函数对象
程序结束时
局部函数销毁
重新在上级作用域定义同名函数
del 语句删除函数名称,函数对象的引用计数减1
上级作用域销毁时
--------------------------------------------------------------------------------------------------------------------------
递归函数
#栈和线程相关,栈是每个线程自己的空间
函数执行要压栈,一个落一个,函数局部变量也要压栈,用完后弹出
#局部变量,函数调用时创建,调用结束消亡
递归 Recursion
函数直接或者间接调用自身就是递归
递归一定要有退出条件 return 出一个结果
每次计算后也要return
import sys
print(sys.getrecursionlimit) #查看递归层数
---------------------------------------------------------------------------------------------------------------------------
匿名函数
格式
lambda 参数列表 : 表达式
map是生成一个惰性求值的
(lambda x : 0)(3)
冒号后面不可以用等号
---------------------------------------------------------------------------------------------------------------------------
生成器***
生成器generator
next() #
生成器函数必须包含yield语句,生成器函数的函数体不会立即执行
协程 #非抢占式,轮询
yield from #新语法从可迭代对象中一个个拿元素
for x in range(10) => yield from range(10)
yield x