【Python从入门到精通】(十二)Python函数的高级知识点【收藏下来保证有用】|【生长吧!Python】

举报
码农飞哥 发表于 2021/07/21 15:00:02 2021/07/21
【摘要】 学会参数传递机制,lambda表达式,函数式编程针不戳

您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦
本文将介绍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时,函数返回的是 2fn(4)+f(3)。
n=4时,函数返回的是 2
fn(3)+f(2)。
n=3时,函数返回的是 2fn(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)

获取指定作用域范围中的变量

  1. 通过global() 函数获取python文件内的全局变量,其结果是一个字典。
  2. 在函数内部通过调用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)后才被初始化,违反了"先定义后使用"的原则,因此程序会报错。

如何防止"遮蔽"的情况呢?

那么如何防止在函数内部全局变量被同名的局部变量"遮蔽"呢?这里有两种方式:

  1. 直接访问被遮蔽的全局变量,如果希望程序依然能访问name全局变量,且在函数中可重新定义name局部变量,可以通过globals()函数来实现。
name = "张三"
def test_1():
    name = "李四"
    print('局部变量name=', name)
    # 访问全局变量
    print('全局变量name=', globals()['name'])
test_1()

运行结果是:

局部变量name= 李四
全局变量name= 张三

通过globals()['name'] 直接访问全局变量name,就实现了和局部变量name井水不犯河水的效果。
2. 在函数中通过 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表达式,以及函数式编程。都是些对实际开发有帮助的知识点。希望对读者朋友们有所帮助,也欢迎大家一件三连。
在这里插入图片描述

我是码农飞哥,再次感谢您读完本文

【生长吧!Python】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/278897

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200