一、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>