一、python函数
- 由若干语句组成的语句块,函数名称,参数列表构成,它是组织代码的最小单元,通过函数完成一定的功能
1、函数的作用
- 结构化编程对代码的最基本封装,一般按照功能组织一段代码
- 封装的目的是为了功能复用,减少冗余代码
- 使代码更加简洁美观,可读易懂
2、函数的分类
- 内建函数,如:max(),reversed()等
- 库函数,如math.ceil()等
3、函数的定义和调用
定义
- def语句定义函数
- 函数名就是标识符,命名要求一样
- 语句块必须缩进,约定4个空格
- python的函数若没有return语句,隐式会返回一个None值
- 定义中的参数列表成为形式参数,只是一种符号表达,简称形参
调用
- 函数定义只是声明了一个函数,它不会被执行,需要调用
- 调用方式,就是函数名加上小括号,括号内写参数
- 调用时写的参数是实际参数,是实实在在的传入的值,简称实参
二、函数参数
- 参数调用时传入的参数要和定义的个数相匹配(可变参数例外)
- 位置参数:按照参数定义顺序传入实参fn(1,2)
- 关键字参数:使用形参的名字来传入实参的方式,如果使用了形参名字,那么传参顺序就可以随意fn(x=1,y=1)
- 传参:要求位置参数必须在关键字参数之前传入,位置参数是位置对应的;
1、函数参数默认值
- 参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
- 参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用
2、可变参数
- 在形参前使用*表示该形参是可变参数,可以接收多个实参
- 收集多个实参为一个tuple元组
def add(*nums) >>> def add(*nums): sum = 0 print(type(nums)) for x in nums: sum += x print(sum) >>> add(3,6,9) <class 'tuple'> 18
3、可变关键字参数
- 形参前使用**符号,表示可以接收多个关键字参数
- 收集的实参名称和值组成一个字典
>>> def show(**kwargs): for k,v in kwargs.items(): print('{}={}'.format(k,v)) >>> show(a=1,b=2,c=3) a=1 c=3 b=2
4、参数总结
- 有位置可变参数和关键字可变参数
- 位置可变参数在形参前使用一个冒号*
- 关键字可变参数在形参前使用两个冒号**
- 位置可变参数和关键字可变参数都可以收集若干个实参
- 混合使用参数的时候,可变参数要放到参数列表的最后,普通参数放在前面
>>> def fn(x,y,*args,**kwargs): print(x) print(y) print(args) print(kwargs) >>> fn(3,5,6,9,10,a=1,b='python') 3 5 (6, 9, 10) {'a': 1, 'b': 'python'} >>> fn(3,5) 3 5 () {} >>> fn(3,5,a=1,b='python') 3 5 () {'a': 1, 'b': 'python'} >>>
5、keyword-only参数
- 如果在一个冒号参数后,或者一个位置可变参数后,出现的普通参数,实际上已经不是普通的参数了,
- 而是keword-only参数
>>> def fn(*args,x): print(x) print(args) >>> fn(3,5) 调用报错 >>> fn(3,5,x=7) args已经截获了所有的位置参数,x不使用关键字参数就不可能拿到实参 >>> def fn(*kwargs,x): print(x) print(args) 直接报语法错误,因为kwargs会截获所有的关键字参数
6、参数规则
- 参数列表一般顺序是,普通参数,缺省参数,可变位置参数,keword-only参数,可变关键字参数
>>> def fn(x,y,z=3,*args,m=4,n,**kwargs): print(x,y,z,m,n) print(args) print(kwargs) def connect(host='localhost', port='3306', user='admin', password='admin', **kwargs): >>> def connect(host='localhost', port='3306', user='admin', password='admin', **kwargs): print(host,port) print(user,password) print(kwargs) >>> connect(db='cmdb') localhost 3306 admin admin {'db': 'cmdb'} >>> connect(host='192.168.1.1',db='cmdb') 192.168.1.1 3306 admin admin {'db': 'cmdb'} >>>
三、参数解构
- 给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取所有元素作为函数的实参
- 非字典类型使用*解构成位置参数
- 字典类型使用**解构成关键字参数
- 提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配
举例: >>> def add(x,y): return x+y 1、位置参数解构 >>> add(*(4,5)) 9 >>> add(*[4,5]) 9 >>> 2、关键字参数解构 >>> d = {'x':5,'y':6} >>> add(**d) 11 >>> 3、参数解构和可变参数 >>> def add(*args): result = 0 for x in args: result += x return result >>> add(1,2,3) 6 >>> add(*[1,2,4]) 7 >>> add(*range(10)) 45 >>>
四、函数的返回值和作用域
1、返回值
- python函数使用return语句返回值,所有函数都有返回值,如果没有return,会隐式调用return None
- return语句并不一定是函数的语句最后一条语句
- 一个函数可以存在多个return语句,但是只有一条可以被执行,如果没有一条return语句被执行到,就执行隐式retnurn
- 如果有必要,可以显示调用return None,可以简写为return
- 如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其他语句不会被执行
- return的作用是结束函数调用,返回值不能同时返回多个值
2、作用域
- 全局作用域:在整个程序运行环境中都可见
- 局部作用域:在函数,类等内部可见,局部变量使用范围不能超过其所在的局部作用域
举例: x = 5 >>> def foo(): x += 1 print(x) >>> foo() x += 1其实是x = x+1,相当于在foo内部定义一个局部变量x,那么foo内部所有x都是这个局部变量 但是这个x还没有完成赋值,就被右边拿来使用了,导致报错 使用全局变量global关键字变量,将foo内的x声明为使用外部的全局作用域中定义的x 全局作用域中必须要有x的定义 >>> def foo(): global x x = 10 x += 1 #x在内部作用域为一个外部作用域的变量赋值,所以x += 1不会报错 print(x) >>> foo()
3、全局作用域global
- 外部作用域变量对内部作用域可见,但也不要在这个内部的局部作用域中直接使用;
- 因为函数的目的就是为了封装,尽量与外界隔离
- 如果函数需要使用外部全局变量,请使用函数的形参传参解决
- 一句话:不用global
五、递归函数
- 函数直接或者间接调用自身就是递归
- 递归一定要有边界条件,递归前进段,递归返回段
- 当边界条件不满足的时候,递归前进段
- 当边界条件不满足的时候,递归返回
1、递归要求
- 递归一定要有退出条件,递归调用一定要执行到这个退出条件,没有退出条件的递归就是无限调用
- 递归调用的深度不宜过深,pthon对递归调用的深度做了限制,以保护解释器
斐波那契数列Fibonacci number:1,1,2,3,5,8,13,21,34,55..... 如果设F(n)为该数列的第n项,那么这句话可以写成如下形式:F(n)=F(n-1)+F(n-2) F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2) pre = 0 cur = 1 print(pre,cur,end='') n = 4 for i in range(n-1): pre,cur = cur,pre+cur print(cur,end='') 解析: fib(3)+fib(2) fib(3)调用fib(3),fib(2),fib(1)
2、递归的性能
- 斐波那契数列求值性能改进
递归举例: import datetime n = 35 start = datetime.datetime.now() def fib(n): return 1 if n<2 else fib(n-1) + fib(n-2) for i in range(n): print(fib(i),end='') delta = (datetime.datetime.now() - start).total_seconds() print(delta) 改下后: pre = 0 cur = 1 print(pre,cur.end='') def fib(n,pre=0,cur=1): pre,cur = cur, pre+cur print(cur,end='') if n == 2: return fib(n-1,pre,cur) fib(n)
3、递归总结
- 递归是一种很自然的表达,符合逻辑思维
- 递归相对运行效率低,每一次调用函数都要开辟栈帧
- 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出
- 递归可以用循环实现,即使递归代码简洁,能不用递归则不用
六、匿名函数
- 使用lambda关键字来定义匿名函数:lambda 参数列表:表达式: 如lambdax:x**2
- 参数列表不需要小括号,冒号是用来分割参数列表和表达式的
- 不需要使用return,表达式的值就是匿名函数的返回值
- lambda表达式只能写在一行,被称为单行函数
- 主要用途于高阶函数传参时,使用lambda表达式简化代码
举例: >>> print((lambda :0)()) 0 >>> print((lambda x,y=3:x+y)(5)) 8 >>> print((lambda x,*,y=30:x+y)(5)) 35 >>> print((lambda x,*,y=30:x+y)(5,y=10)) 15 >>> print((lambda *args:(x for x in args))(*range(5))) <generator object <lambda>.<locals>.<genexpr> at 0x0000000003074258>