您好,我是码农飞哥,感谢您浏览本文,欢送一键三连哦 。
本文将介绍 Python 函数的高级知识点:重点介绍函数参数传递机制以及函数式编程。
干货满满,倡议珍藏,须要用到时常看看。小伙伴们如有问题及须要,欢送踊跃留言哦~ ~ ~。
前言
上一篇文章其实也介绍了 Python 函数,不过都是些函数根本知识点。还没看的小伙伴抽空看下【Python 从入门到精通】(十一)Python 的函数的方方面面【珍藏下来保障有用!!!】。这篇文章将重点介绍函数参数传递机制,lambda 表达式以及函数式编程等深刻一点的知识点。从入门到精通,不能老是搞些简略的常识,也要来点硬货。更深刻的扎进常识的汪洋中。
Python 函数参数传递机制
上一篇文章咱们说到 Python 函数参数传递机制有两种:别离是值传递和援用传递。那么这两种形式有啥区别呢?各自具体的参数传递机制又是啥呢?这个章节就未来解答这两个问题。首先来看看值传递。如下代码定义了一个 swap 函数,有两个入参 a,b。这个函数的工作就是替换入参 a,b 的值。
def swap(a, b):
a, b = b, a
print("形参 a =", a, 'b=', b)
return a, b
a, b = '码农飞哥', '加油'
print("调用函数前实参的 a =", a, 'b=', b)
swap(a, b)
print("调用函数后实参的 a =", a, 'b=', b)
运行后果是:
调用函数前实参的 a = 码农飞哥 b= 加油
形参 a = 加油 b= 码农飞哥
调用函数后实参的 a = 码农飞哥 b= 加油
能够看出形参被胜利的扭转了,然而并没有影响到实参。这到底是为啥呢?这其实是因为 swap 函数中形参 a,b 的值别离是实参 a,b 值的正本,也就是说在调用 swap 之后 python 会对入参 a,b 别离 copy 一份给 swap 函数的形参。对正本的扭转当然不影响原来的数值啦。 语言的形容是空洞的,画个图阐明下吧:在 Python 中一个办法对应一个栈帧,栈是一种后进先出的构造。下面说的过程能够用上面的调用图来示意:
能够看出当执行 a, b = '码农飞哥', '加油'
代码是,Python 会在 main 函数栈中初始化 a,b 的值。当调用 swap 函数时,又把 main 函数中 a,b 的值别离 copy 一份传给 swap 函数栈。当 swap 函数对 a,b 的值进行替换时,也就只影响到 a,b 的正本了,而对 a,b 自身没影响。
然而对于列表,字典这两的数据类型的话,因为数据是存储在堆中,栈中只存储了援用,所以在批改形参数据时实参会扭转。。如下代码演示:
def swap(dw):
# 上面代码实现 dw 的 a、b 两个元素的值替换
dw['a'], dw['b'] = dw['b'], dw['a']
print("swap 函数里,a =", dw['a'], "b =", dw['b'])
dw = {'a': '码农飞哥', 'b': '加油'}
print("调用函数前内部 dw 字典中,a =", dw['a'], "b =", dw['b'])
swap(dw)
print("调用函数后内部 dw 字典中,a =", dw['a'], "b =", dw['b'])
运行后果是:
调用函数前内部 dw 字典中,a = 码农飞哥 b = 加油
swap 函数里,a = 加油 b = 码农飞哥
调用函数后内部 dw 字典中,a = 加油 b = 码农飞哥
能够清晰的看出调用函数之后传入的实参 dw 的值的确扭转了。这阐明他是援用传递的,那么援用传递与值传递有啥区别呢?
从上图能够看出字典的数据是存储在堆中的,在 main 函数的栈中通过援用来指向字典存储的内存区域,当调用 swap 函数时,python 会将 dw 的援用复制一份给形参,当然复制的援用指向的是同一个字典存储的内存区域。当通过正本援用来操作字典时,字典的数据当然也扭转。综上所述:援用传递实质上也是值传递,只不过这个值是指援用指针自身,而不是援用所指向的值。 为了验证这个论断咱们能够略微革新下下面的代码:
def swap(dw):
# 上面代码实现 dw 的 a、b 两个元素的值替换
dw['a'], dw['b'] = dw['b'], dw['a']
print("swap 函数里,a =", dw['a'], "b =", dw['b'])
dw = None
print("删除形参对字典的援用",dw)
dw = {'a': '码农飞哥', 'b': '加油'}
print("调用函数前内部 dw 字典中,a =", dw['a'], "b =", dw['b'])
swap(dw)
print("调用函数后内部 dw 字典中,a =", dw['a'], "b =", dw['b'])
运行的后果是:
调用函数前内部 dw 字典中,a = 码农飞哥 b = 加油
swap 函数里,a = 加油 b = 码农飞哥
删除形参对字典的援用
调用函数后内部 dw 字典中,a = 加油 b = 码农飞哥
删除了形参对字典的援用后,实参还是能获取到字典的值。这就充分说明了传给形参的是实参的援用的正本。
递归函数
递归函数置信不少小伙伴都不生疏。递归函数是指在函数外部调用它本身的函数。递归函数经常被使用到数列计算,遍历省市区,遍历文件夹等场景下。须要留神的是:递归函数必须可能在满足某个条件下不再调用他本身。不然的话就可能会呈现一直调用,陷入死循环的地步。 比方:当初有一个数列:f(0) = 1,f(1) = 4,f(n + 2) = 2*f(n+ 1) +f(n),其中 n 是大于 0 的整数,求 f(5) 的值。这道题能够应用递归来求得。上面程序将定义一个 fn() 函数,用于计算 f(5) 的值。依据 f(n + 2) = 2*f(n+ 1) +f(n)
公式能够推导失去 f(n)=2*f(n-1)+f(n-2)
公式。
def fn(n):
if n == 0:
return 1
if n == 1:
return 4
return 2 * fn(n - 1) + fn(n - 2)
print(fn(5))
运行后果是 128
,上面来推导下函数执行过程:
n= 5 时,函数返回的是 2*fn(4)+f(3)。
n= 4 时,函数返回的是 2*fn(3)+f(2)。
n= 3 时,函数返回的是 2*fn(2)+f(1)。
n= 2 时,函数返回的是 2*fn(1)+f(0)
n= 1 时,函数返回 4
n= 0 时,函数返回 1。
所以,fn(2)=9,f(3)=22,f(4)=53,f(5)=128。他的调用过程是层层递归的直到失去对里层的后果。而后反推失去外层的后果。
变量作用域
变量的作用域 -> 说白了就是变量能作用的范畴 。Python 中变量分两种:局部变量和全局变量。
定义在函数外部的变量被称为局部变量,其作用域在函数外部,随函数生随函数死。就像一线员工一样,你的权责范畴就在本部门(函数),部门外的事件你管不到。
局部变量的初始化过程是:当函数执行时,Python 会为其调配一块长期的存储空间,所有在函数外部定义的变量都会被存储在这块空间中。当函数执行结束之后,这块长期存储空间随即被开释并回收。该空间中存储的变量天然也就无奈再被应用。
正如下面代码中的 obj 变量和 name 变量,在函数外部能够失常应用,在函数内部则会提醒 NameError: name 'obj' is not defined
。 所以能够得出局部变量不能在函数外应用并且形参变量也是局部变量的论断。
定义在函数内部的变量被称为全局变量,其作用域在整个应用程序,即全局变量既能够在各个函数的内部应用,也能够在各个函数外部应用。就像老板能够对全公司(整个应用程序)发号施令一样,那各个小部门(函数)必定也得听他的话。就像上面代码中的 name 变量这样,在函数 param_test1 内外都能应用。
name='码农飞哥'
def param_test1(obj):
print('name=', name)
print('name=', name)
获取指定作用域范畴中的变量
- 通过
global()
函数获取 python 文件内的全局变量,其后果是一个字典。 - 在函数外部通过调用
locals()
函数能够获取该函数内的局部变量。举个例子吧!
def param_test1(obj):
name = '张三'
print('局部变量有 =', locals())
return obj + name
param_test1('李四')
name2 = '张三'
print('全局变量有', globals())
运行后果是:
局部变量有 = {'name': '张三', 'obj': '李四'}
全局变量有 {'__name__': '__main__', '__doc__': '@date: 2021/7/19 21:23\n@desc: \n', '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000226B627B208>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/python_demo_1/demo/function/param_test_fun.py', '__cached__': None, 'param_test1': <function param_test1 at 0x00000226B6221EA0>, 'name2': '张三'}
如何在函数中应用同名的全局变量
当函数外部的局部变量和函数内部的全局变量同名时,在函数外部,局部变量会”遮蔽“同名的全局变量。正所谓强龙不压地头蛇,在函数外部局部变量就是地头蛇,全局变量这个强龙的风头也会被它压住。
name = "张三"
def test_1():
# 拜访全局变量
print(name)
name = "李四"
test_1()
运行该函数会报如下谬误:
Traceback (most recent call last):
File "python_demo_1/demo/function/param_test_fun.py", line 33, in <module>
test_1()
File "/python_demo_1/demo/function/param_test_fun.py", line 31, in test_1
print(name)
UnboundLocalError: local variable 'name' referenced before assignment
下面的谬误提醒通知咱们在执行 print(name)
时,name 还没有定义,因为 name 是在第五行定义的。其实咱们冀望在第四行打印全局变量 name 的值,然而因为第五行函数中定义了一个同名的局部变量 name(Python 语法规定,在函数外部对不存在的变量赋值时,默认就是从新定义新的局部变量)。而局部变量 name” 遮蔽 ” 了全局变量 name。同时局部变量 name 在 print(name)
后才被初始化,违反了 ” 先定义后应用 ” 的准则,因而程序会报错。
如何避免 ” 遮蔽 ” 的状况呢?
那么如何避免在函数外部全局变量被同名的局部变量 ” 遮蔽 ” 呢?这里有两种形式:
- 间接拜访被遮蔽的全局变量,如果心愿程序仍然能拜访 name 全局变量,且在函数中可从新定义 name 局部变量,能够通过 globals()函数来实现。
name = "张三"
def test_1():
name = "李四"
print('局部变量 name=', name)
# 拜访全局变量
print('全局变量 name=', globals()['name'])
test_1()
运行后果是:
局部变量 name= 李四
全局变量 name= 张三
通过globals()['name']
间接拜访全局变量 name,就实现了和局部变量 name 井水不犯河水的成果。
- 在函数中通过 global 关键字申明全局变量,为了防止在函数中对全局变量赋值(不是从新定义局部变量), 可应用 global 语句来申明全局变量。
name = "码农飞哥"
def test_2():
global name
# 拜访全局变量
print('全局变量 name=', name)
name = "小伟伟"
print('局部变量 name=', name)
print('全局变量 name=', globals()['name'])
test_2()
运行后果是:
全局变量 name= 码农飞哥
局部变量 name= 小伟伟
全局变量 name= 小伟伟
通过 global 关键字同样能够局部变量遮蔽同名的全局变量。通过 global 润饰全局变量之后,在同名的局部变量定义之前,都应用的是全局变量。
函数的高级用法
函数赋值给其余变量
函数不仅仅能够间接调用,还能够间接将函数赋值给其余变量。就像上面定义了一个名为 my_fun 的函数,首先将函数名 my_fun 赋值给变量 other,而后通过 other 来间接调用 my_fun()函数。
def my_fun():
print("正在执行 my_fun 函数")
# 将函数赋值给其余变量
other = my_fun
# 间接调用 my_fun()函数
other()
运行后果是:正在执行 my_fun 函数
将函数以参数的模式传递给其余函数
Python 还反对将函数以参数的模式传递给其余函数,也就是说将能够将函数作为一个形参传递。举个栗子吧!
def my_fun1():
return '加油,你肯定行'
def my_fun2(name, my_fun1):
return name + my_fun1
print(my_fun2('码农飞哥', my_fun1()))
运行后果是 码农飞哥加油,你肯定行
。
下面代码首先定义了一个函数 my_fun1,接着将函数 my_fun1 作为参数传递给函数 my_fun2。代码能够失常运行。
部分函数(外部函数)及用法
Python 不仅反对在函数外部定义局部变量,还反对在函数外部定义函数。这类函数就称之为部分函数。举个例子吧!
# 全局函数
def outer_fun():
def inner_fun():
print('调用部分函数')
return inner_fun
# 调用部分函数
new_inner_fun = outer_fun()
new_inner_fun()
下面代码首先定义了全局函数 outer_fun,而后在该函数外部定义了一个部分函数 inner_fun。接着返回该部分函数。依据后面函数能够赋值给变量的常识,就能够顺利调用到部分函数 inner_fun。
须要留神的是,部分函数中定义有和所在函数中变量同名的变量,也会产生”遮蔽“的问题,防止这种问题的形式不再是应用 global 关键字,而 须要通过 nonlocal 关键字。就像上面这样!
# 全局函数
def outer_fun():
name = '码农飞哥'
def inner_fun():
print('调用部分函数')
nonlocal name
print('全局变量 name=', name)
name = '小伟伟'
print('局部变量 name=', name)
return inner_fun
# 调用部分函数
new_inner_fun = outer_fun()
new_inner_fun()
运行后果是:
调用部分函数
全局变量 name= 码农飞哥
局部变量 name= 小伟伟
lambda 表达式
Python 是反对 lambda 表达式的。lambda 表达式又称为匿名函数,罕用来示意外部蕴含 1 行表达式的函数。其语法结构是:
name=lambda [list]: 表达式
其中, 定义 lambda 表达式,必须应用 lambda 关键字;[list]作为可选参数
,等同于定义函数是指定的参数列表;name 为该表达式的名称。例如当初有如下一个 add 函数,入参是 x,y。返回是 x+y。
def add(x, y):
return x + y
能够将该函数改写成一个 lambda 表达式,其后果是lambda x,y:x+y
。而后将其赋值给一个 add 变量。就像上面这样。
add = lambda x, y: x + y
print(add(2, 3))
当然,lambda 表达式远不止这点技能,它与接下来介绍的函数式编程联合会产生别样的成果。接下来就来看看函数式编程吧。
函数式编程
一般的函数当入参是列表或者字典时,当对形参进行批改时,则实参也会扭转。就像上面这样:
test_list = [1, 2, 3, 4]
def second_multi(test_list):
for i in range(len(test_list)):
test_list[i] = test_list[i] * 2
return test_list
for i in range(3):
print('第 {0} 次运行的后果是:'.format(str(i)), second_multi(test_list))
运行后果是:
第 0 次运行的后果是: [2, 4, 6, 8]
第 1 次运行的后果是: [4, 8, 12, 16]
第 2 次运行的后果是: [8, 16, 24, 32]
能够看出每次传入雷同的参数 test_list,3 次失去的后果都不一样。这是因为函数外部对 test_list 做了扭转。要想防止这种状况,只能在函数外部从新定义一个新的列表 new_list,函数只对 new_list 进行批改。
test_list = [1, 2, 3, 4]
def second_multi(test_list):
new_list = []
for i in range(len(test_list)):
new_list.append(test_list[i] * 2)
return new_list
for i in range(3):
print('第 {0} 次运行的后果是:'.format(str(i)), second_multi(test_list))
运行后果是:
第 0 次运行的后果是: [2, 4, 6, 8]
第 1 次运行的后果是: [2, 4, 6, 8]
第 2 次运行的后果是: [2, 4, 6, 8]
不过当初有了函数式编程,能够通过函数式编程来实现上述代码的成果。函数式编程是指代码中每一块都是不可变的,是由纯函数的模式组成。这里的纯函数,是指函数自身互相独立,互不影响,对于雷同的输出,总会有雷同的输入。将下面的函数革新成函数式编程会如何呢?上述函数等价于上面的函数式编程map(lambda x: x * 2, test_list)
test_list = [1, 2, 3, 4]
for i in range(3):
print('第 {0} 次运行的后果是:'.format(str(i)), list(map(lambda x: x * 2, test_list)))
运行后果是:
第 0 次运行的后果是: [2, 4, 6, 8]
第 1 次运行的后果是: [2, 4, 6, 8]
第 2 次运行的后果是: [2, 4, 6, 8]
从上述后果能够看出,函数式编程对于雷同的输出总会有雷同的输入。
Python 有如下三个函数用于函数式编程。
map()函数
map()函数的性能 是对可迭代对象中的每个元素,都调用指定的函数,并返回一个 map 对象 。然而这个 map 对象是不能间接输入的,能够通过 for 循环或者 list() 函数来示意。
map()函数的语法格局是:map(function,iterable)
其中,function 参数示意要传入一个函数,其能够是内置函数,自定义函数或者 lambda 匿名函数,iterable 示意一个或多个可迭代对象,能够是列表,字符串等。就像上面这样:
test_list1 = [1, 3, 5]
test_list2 = [2, 4, 6]
new_map = map(lambda x, y: x + y, test_list1, test_list2)
print(list(new_map))
运行后果是:[3, 7, 11]
下面的代码是对两个列表中的每个元素进行求和,失去一个新的列表。相当于上面的函数
def add(test_list1, test_list2):
new_list = []
for index in range(len(test_list1)):
new_list.append(test_list1[index] + test_list2[index])
return new_list
print(add(test_list1, test_list2))
运行后果是[3, 7, 11]
filter()函数
filter()函数的性能是 对 iterable 中每个元素,都应用 function 函数判断,并返回 True 或者 False,最初将返回 True 的元素组成一个新的可遍历的汇合 filter 对象。同样这个 filter 对象是不能间接输入的,能够通过 for 循环或者 list()函数来示意。filter 函数个别用于过滤数据的场景 其语法格局是:filter(function,iterable)
其中,function 参数示意要传入一个函数,其能够是内置函数,自定义函数或者 lambda 函数;iterable 示意一个或多个可迭代的对象,能够是列表,字符串等。上面就是过滤出列表中的所有偶数。
test_list3 = [1, 2, 3, 4, 5]
new_filter = filter(lambda x: x % 2 == 0, test_list3)
print(list(new_filter))
运行后果是:[2, 4]
reduce()函数
reduce()函数通常用于对一个汇合做一些累积操作。其语法结构是:
import functools
functools.reduce(function, iterable)
其中,function 参数示意要传入一个函数,其能够是内置函数,自定义函数或者 lambda 函数;iterable 示意一个或多个可迭代的对象,能够是列表,字符串等。
import functools
test_list4 = [1, 2, 3, 4, 5]
product = functools.reduce(lambda x, y: x + y, test_list4)
print(product)
运行后果是:15
。
总结
本文具体介绍了 Python 函数一些常见的高级知识点,包含函数参数的传递机制,变量的作用域,lambda 表达式,以及函数式编程。都是些对理论开发有帮忙的知识点。心愿对读者敌人们有所帮忙,也欢送大家一件三连。
我是码农飞哥,再次感谢您读完本文 。
全网同名【码农飞哥】。不积跬步,无以至千里,享受分享的高兴
我是码农飞哥,再次感谢您读完本文。