1、函数简介
1.1语法
# 语法
# def 函数名([形参1,形参2,形参3....]):
# 代码块
#函数名()调用函数
1.2介绍
函数是个对象(function)
函数用来保存一些可执行的代码,在你需要的时候可以对这些语句进行多次调用,函数中保存的代码不会立即执行,需要调用函数的时候代码才执行
fn是函数对象 fn()调用函数
print是函数对象 print()调用函数
2、函数的参数
2.1形式参数
在定义函数的时候,可以在函数后面的括号里面定义数量不等的形参
多个形参要以 ,隔开,形参也叫形式参数,定义形参就相当于在函数内部声明了变量,但是并没有赋值
def s(a,b):
a = None#内部声明,但不赋值
b = None#内部声明,但不赋值
print(a + b)
s()
2.2实际参数
如果函数在定义的时候,指定了形参,那么在调用函数的时也必须传递形参实参将会赋值给对应的形参,有几个形参就得传几个实参
实参可以传递任意类型的对象:数字、字符串、列表、元组、字典、集合
2.3函数的传递方式
定义形参时,可以为形参指定默认值。指定了默认值以后,如果用户传递了参数则默认值不会生效。如果用户没有传递,则默认值就会生效,如果为函数中的形参进行赋值,则不会影响其他的变量
def function(a,b=60):#a= 20 b= 30
# b = 60 #a= 20 b= 60
print('a=',a,'b=',b)
function(20,30)
位置参数:位置参数就是将对应位置的实参赋值给对应位置的形参
关键字参数 : 关键字参数可以不按照形参定义的顺序去传递,而根据参数名进行传递
混合使用位置参数和关键字参数的时候必须将位置参数写到关键字参数前面去
3、不定长参数
①在定义函数的时候,可以在形参的前面加上一个* 这样这个形参将会获取到所有的实参它会将这些实参保存到一个元祖当中
②带星号的参数是可以和其他参数配合使用的,但是注意带星号的参数只能放在最后一个
③形参可以接收其他关键字参数,它会将这些参数统一保存到一个字典当中
字典的key就是参数的名字,字典的value就是参数的值
④形参也只能有一个,并且必须写在所有参数的后面
(*args:位置参数,定义函数时不知传多少参数就用它,它可以接受0或多个参数 **kwargs:关键字参数定义函数时不知传多少关键字参数就用它,它可以接受0或多个关键字参数)
4、参数的解包
传递实参时,也可以在序列类型的参数前添加星号,这样它会自动的将序列中元素依次作为参数传递
li = (1,2,3) a,b,c = li print(b)
要求序列中的元素的个数必须和形参的个数一致
5、函数的返回值
返回值就是函数执行以后返回的结果
通过return来指定函数的返回值
return后面可以跟任意对象,返回值甚至可以是一个函数
fn是函数对象 打印fn就是在打印函数对象
fn()调用函数 打印fn()实际上我就是在打印fn()的返回值
print是函数对象 print()调用函数
在函数中,return后的代码都不会执行,retrun一旦执行函数自动结束
6、文档字符串
help()是Python中内置函数,通过help()函数可以查询Python中函数的用法
在定义函数时,可以在函数内部编写文档字符串,文档字符串就是对函数的说明
当我们编写了文档字符串时,就可以通过help()函数来查看函数的说明
def fn(a:bool,b:int,c:str)-> int:
'''
这是一个文档字符串的示例
这个函数的作用.......
函数的参数
:param a: 作用 类型 默认值...
:param b: 作用 类型 默认值...
:param c: 作用 类型 默认值...
:return: 需要/不需要
'''
return 123
help(fn)
7、函数的作用域
作用域指的是变量生效的区域
7.1全局作用域
全局作用域在程序执行时创建,在程序执行结束时销毁
所有函数以外的区域都是全局作用域
在全局作用域中定义的变量,都是全局变量,全局变量可以在程序的任意位置进行访问
7.2局部作用域(函数作用域)
函数作用域在函数调用时创建,在调用结束时销毁
函数每调用一次就会产生一个新的函数作用域
在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问
8、命名空间
命名空间实际上就是一个字典,是一个专门用来存储变量的字典
locals()用来获取当前作用域的命名空间
如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间
返回值是一个字典
向scope里面添加了一个key-value
scope[‘c’] = 789
print( c )打印的是c的value(全局变量下可以使用,局部变量下不可以使用)
9 、递归函数
递归是解决问题的一种方式,它的整体思想,是将一个大问题分解为一个个的小问题,直到问题无法分解时,在去解决问题
递归式函数有2个条件
- 基线条件: 问题可以被分解为最小问题,当满足基线条件时,递归就不执行了
- 递归条件: 可以将问题继续分解的条件
例如: 定义一个函数来实现任意数的阶乘
# 定义一个函数来实现任意数的阶乘
def fn(i):
if i==1:
return i
return i*fn(i-1)
print(fn(10))#3628800
# 验证结果是不是正确的
n=10
for m in range(1,10):
n *= m
print(n)#3628800
10、高阶函数
①接收一个或多个函数作为参数
fn2就是接受了一个参数的高阶函数
def fn1(x):
return x*x
li = [1,2,3,4]
def fn2(func, li):
li1 = []
for i in li:
li1.append(fn1(i))
return li1
②将函数作为返回值
fn就是将函数作为返回值的高阶函数
li = [1,2,3,4,5,6]
def fn(li):
return lambda x:x%2==1
print(list(filter(fn,li)))
只要满足任意一条就可以叫做高阶函数
11、闭包
闭包:将函数作为返回值的高阶函数
闭包的好处:通过闭包可以创建一些只有当前函数能访问的变量,将一些私有数据藏到闭包中
形成闭包的条件
1.函数嵌套
2.将内部函数作为返回值返回
3.内部函数必须使用到外部函数的变量
def make_fn():
nums2 = []
def fn1(n):
nums2.append(n)
return sum(nums2) / len(nums2)
return fn1
res = make_fn()
print(res(10)) # 10.0
print(res(5)) # 7.5
12、匿名函数
①匿名函数 lambda函数表达式
lambda函数表达式就是专门用来创建一些简单的函数,函数可以提高复用性,但是一些简单的功能没必要创建函数复杂化,用匿名函数,用完就销毁,简单方便不占内存
语法:lambda 参数列表 : 返回值
②filter()过滤函数的使用
参数 1 函数 2 需要过滤的序列(可迭代的) 返回值 过滤后的新的序列(可迭代的结构)
例如过滤列表中的奇数(匿名函数常配合过滤函数使用)
li = [1,2,3,4,5,6,7,8,9]
print(list(filter(lambda x:x%2==1,li)))
13、装饰器
要对函数进行装饰的时候我们可以通过修改函数中的代码来完成这个需求,但是会产生一些问题
问题一 如果要修改的函数过多,修改起来比较麻烦
问题二 不方便后期的维护
问题三 违反开闭原则(ocp) 程序的设计思想 要求开发对程序的扩展,要关闭对程序的修改
import time
# 设计一个函数计算程序运行的时间,如果每个函数都写一样的
# 功能太麻烦,为了避免重复造轮子,就使用引用的方式
def fn1(func):
start = time.time()
func()
time.sleep(2)
end = time.time()
print(f'spend {end - start}')
def demo1():
print('i am demo1')
def demo2():
print('i am demo2')
fn1(demo1) # 如果函数名变了还要修改函数名才能实现相关功能,为了OCP原则还得继续改进,往下看
通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展在开发中,我们都是通过装饰器来扩展函数的功能的(装饰器只有满足闭包才可以实现,不管函数有没有被调用,装饰器都会在装饰,有几个语法糖就会装饰几次,在函数调用前就被装饰了)
import time
# 设计一个函数计算程序运行的时间,如果每个函数都写一样的
# 功能太麻烦,为了避免重复造轮子,就使用引用的方式
def fn1(func):
start = time.time()
func()
time.sleep(2)
end = time.time()
print(f'spend {end - start}')
@fn1
def demo1():
print('i am demo1')
def demo2():
print('i am demo2')
demo1() # @就是一个语法糖,用fn1去装饰demo1那么调用的时候就相当于把fn1上面的功能加到了demo1上,这样即使demo1变了,fn1也有对应的功能