程序就是一大堆代码,有些功能可能在程序中反复使用,我们就可以将这些功能代码整合成一个函数,起一个名字,以后使用就调用一下,避免相同代码的重复书写
函数的作用:
- 提高代码重用性
- 拆分代码,易于维护
- 实现函数式编程
1.函数的定义
函数主要由五部组成:
- 定义函数的关键字def
- 函数名字:调用者通过函数名调用
- 函数参数:将需要处理的数据传递给函数
- 函数体:函数的执行部分
- 函数返回值:将函数的处理结果返回给调用者
当然函数参数和返回值是非必须的
2.函数名
函数名和变量名本质上是一样的,都是指向一块内存空间,区别在于变量指向的是数据,,函数名执行代码块
既然函数名字类似变量,那么函数名就可以有多种用法
- 函数名做容器的元素
# 定义一个函数 def fun(): print('我最帅') # 将函数名放在容器中 fun_list = [fun, fun, fun] print(fun_list[0]) # <function fun at 0x0000028192C2D1F0>
- 函数名重新赋值
# 定义一个函数 def fun(): print('我最帅') # 将函数名重新赋值为一个变量 fun = 123 print(fun) # 123
- 函数名做参数
# 定义一个函数 def fun(): print('我最帅') def handler(func): func() # 将函数作为参数传入另一个函数 handler(fun) # 我最帅
- 函数名做返回值
def handler(): def fun(): print('我最帅') return fun handler()() # 我最帅
从上面的例子我们可以了解到,函数名就是一个名字,跟变量名好像没什么区别,其实真没什么区别
3.函数参数
有时候函数执行需要外部的一些数据,这些数据就通过参数进行传递
函数传递参数本质上是变量的内存地址的传递,这样做的特点有:
- 节省内存
- 对于可变类型数据,在函数体作了修改,就可以修改数据本身
def func(name, hobby): print(id(hobby)) # 2230389637952 print(f"大家好,我叫{name}") print('我的兴趣爱好有如下几个:', end=' ') for ele in hobby: print(ele, end=' ') print('\n对了,我还喜欢篮球') hobby.append('篮球') name = 'kunmzhao' hob = ['篮球', '足球'] print(id(hob)) # 2230389637952 func(name, hob) print(hob) # ['篮球', '足球', '篮球']
通过以上案例我们可以发现 hob和hobby的内存地址是一样的,同时在func中队hobby添加元素,hob也会添加了元素
3.1 形式参数
def func(a1, a2): pass
a1和a2都是形式参数,给形式参数赋值有两种方式
- 位置传参
def func(a1, a2): pass
# 1会赋值给a1, 2会赋值给a2
func(1, 2) - 关键字传参
def func(a1, a2): pass func(a2=2,a1=1)
关键字传递参数可以更改参数的传参顺序,当函数有很多参数的时候,我们在调用的时候就可以选用该方法,不用在乎参数位置
3.2 默认参数
函数参数可以赋予默认值,在调用函数的时候如果不传递参数则使用默认值,在一定程度上可以更加人性化,但是也有一定的坑
def func(name, age=18): print(name, age) func('kunmzhao') # kunmzhao 18
注意点:
- 默认参数必须在形式参数后面,否则报错
- 当默认参数是可变容器,需要警惕
def func(a1, a2=[1, 2]): a2.append(a1) return a2 v1 = func(10) print(v1) #[1, 2, 10] v2 = func(20) print(v2) # [1, 2, 10, 20] v3 = func(30, [11, 22]) print(v3) # [11, 22, 30] v4 = func(40) print(v4) # [1, 2, 10, 20, 40] print(v1, v2, v3, v4)# [1, 2, 10, 20, 40] [1, 2, 10, 20, 40] [1, 22, 30] [1, 2, 10, 20, 40]
分析:
对于函数中的默认参数,在创建函数之初,就会开辟内存空间存储默认值,如果默认参数是可变类型,当函数修改了默认参数,那么后续函数中的默认值也会发生改变,在上述案例中,v1,v2, v4都没有传递默认参数,他们都指向同一个内存空间,所以数值都是一样的,而v3
传递了默认参数,则不会在使用默认值了
3.3 动态参数
动态参数可以使得函数不限制参数的个数,有*args和**kwargs两种方式
- *args
可以将多余的实参保存成一个元组,传递给函数
def func(name, *args): print(name, args) func('kunmzhao', 'victor', 18, 'shanghai') # kunmzhao ('victor', 18, 'shanghai')
*也可以打散一个元组或者列表,变成多个参数
print(*(1, 2,)) # 1 2
- **kwargs
可以将多余的关键字传参保存成一个字典传递给函数
def func(name, **kwargs): print(name, kwargs) func('kunmzhao', age=18, gender='man') # kunmzhao {'age': 18, 'gender': 'man'}
**可以打散一个字典,当成多个关键字传参
def func(name, age): print(name, age) # kunmzhao 20 var = {'name':'kunmzhao', 'age':20} func(**var)
- *args, **kwargs
既可以传接收多个位置参数,也可以接收多个关键字参数,这种方式在很多矿建的源码中大量使用,可以极大的扩展函数功能
注意:在定义函数的时候,参数位置为:形式参数,默认参数,*args, **kwargs,否则程序报错
4.函数返回值
函数的返回值可以传递给调用者,返回执行结果,通过关键字return返回
def my_sum(num1, num2): return num1+num2 print(my_sum(100, 88)) # 188
当然,如果函数不需要有返回值,则可以返回None,以下方式都可以返回None
- 没有使用return关键字
- return
- return None
有的时候我们可能返回多个数据值,我们就可以使用return返回多个值即可(使用逗号分割),python就会将多个返回值封装成一个元组返回给调用者
def fun(): return 'kunmzhao', 'age', 18 print(fun()) # ('kunmzhao', 'age', 18)
5.函数中的作用域
函数的作用域即变量的生命周期,变量在自己的作用域可以被调用,否则不能被使用,作用域分为全局和局部
5.1局部作用域
在python的一个函数中,定义了的变量都可以在函数中使用,这点和C语言是不同的
def fun(): a = 100 if a > 100: b = 20 else: b = 30 print(b) # fun() # 30
5.2全局作用域
一般声明在一个文件的头部位置的变量都是改文件的全局变量,可以被文件中的所有函数调用,常用大写来定义变量
COUNT = 100 def fun(): print(COUNT) # 100 fun()
但是局部变量只能对全局变量进行访问,不能做修改(内存地址的更改),如果想做修改,就可以在局部变量使用关键字global声明该全局变量
COUNT = 100 def fun(): global COUNT print(COUNT) # 100 COUNT -=10 fun() print(COUNT) # 90
6.函数的调用
函数的调用是非常简单的,使用函数名字,并传入必要的参数即可,这也是我们创建函数的意义所在
7.函数的嵌套
我们在一个函数中,声明一个或者多个函数,就称之为函数嵌套,函数嵌套也是闭包函数的基础
def run(): name = "kunmzhao" def inner(): print(name) inner()
嵌套函数会引发一个变量作用域的问题,我们只需要记住,变量优先在自己作用域中寻找,没有就向上级寻找,但绝对不会向下级寻找
案例:
name = "kunmzhao" def run(): name = "victor" def inner(): print(name) return inner() run() # victor
8.闭包函数
闭包函数就是一个函数的嵌套,并且内部函数使用外部变量, 从而延长了变量的生命周期
def run(): name = "victor" def inner(): print(name) return inner
run()() # victor
为什么说延长了变量的生命周期呢?
一个变量只能在自己的作用域内存活,超过作用域就会被内存回收,在上述案例中,run()函数执行的时候,会声明name变量,然后返回一个函数,正常情况下,name会随着函数的执行结束而被销毁,但由于嵌套函数中使用了name变量,而延长生命周期直到函数inner执行完毕
9.装饰器
装饰器本质上就是闭包函数的语法糖,什么是语法糖呢?语法糖就是为了简化代码书写方式而实现的
装饰器可以在不改变原有函数的计算出上对函数扩展功能,是非常实用的,在很多优秀的框架中广泛使用
下面我们通过案例介绍装饰器,请在原函数的的基础上添加打印数据的功能
# 原函数 def sum(a1, a2): return a1+a2
版本一:直接修改原函数
def sum(a1, a2): print(a1, a2) return a1+a2
问题:如果函数有多个,难道我们要修改每一个函数吗?,同时,在实际开发中封装好的函数一般是不会轻易改动的
版本二:使用闭包函数
def sum(a1, a2): return a1+a2 def handler(func): def inner(a1, a2): print(a1, a2) res = func(a1, a2) return inner handler(sum)(1, 2) # 1, 2
通过闭包函数我们可以解决版本一的问题,但是这会导致我们在所有之前调用sum函数的地方都要修改为handler,也是很不方便的
版本三:使用闭包函数+函数重新赋值
def sum(a1, a2): return a1+a2 def handler(func): def inner(a1, a2): print(a1, a2) res = func(a1, a2) return inner sum = handler(sum) sum(1, 2) # 1, 2
将sum函数重新赋值,指向一个新的函数,这样对于调用者是无感的,不过我们需要在每个原函数下面要对函数名重新赋值,于是便可以使用装饰器,其实装饰器本质上就是版本三
版本四:装饰器
def handler(func): def inner(a1, a2): print(a1, a2) res = func(a1, a2) return inner
@handler def sum(a1, a2): return a1+a2 sum(1, 2) # 1,2
但是我们会发现一个问题,此时的sum的__name__变成了inner,这不是我们希望的,我们就可以使用functools功能
def handler(func): # @functools.wraps(func) def inner(a1, a2): print(a1, a2) res = func(a1, a2) return inner @handler def sum(a1, a2): return a1+a2 sum(1, 2) # 1,2 print(sum.__name__) # inner
import functools def handler(func): @functools.wraps(func) def inner(a1, a2): print(a1, a2) res = func(a1, a2) return inner @handler def sum(a1, a2): return a1+a2 sum(1, 2) # 1,2 print(sum.__name__) # sum
在面试中经常需要我们手写一个装饰器,以下提供一个完善的写法
import functools def wrapper(func): @functools.wraps(func) def inner(*args, **kwargs): res = func(*args, **kwargs) return res return inner
从上面我们可以发现,装饰器基于闭包函数,闭包函数基于函数的嵌套
10.匿名函数
匿名函数就是一个没有函数名字的函数,使用lambda关键字实现,用于定义一些简单的函数
10.1 定义
res = lambda x, y:x+y print(res(1, 2)) # 3
匿名函数的参数可以是多个,和有名函数的参数一样,但是函数体只能有一行,返回值就是:冒号之后的运算结果
匿名函数在后续一些高阶函数中使用非常广泛,如map, filter,sort, reduce,后续介绍
标签:函数,a1,a2,func,print,def From: https://www.cnblogs.com/victor1234/p/16851852.html