关于python:Python从入门到精通十二Python函数的高级知识点更深入的吸收知识收藏下来保证有用

您好,我是码农飞哥,感谢您浏览本文,欢送一键三连哦
本文将介绍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)

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

  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井水不犯河水的成果。

  1. 在函数中通过 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表达式,以及函数式编程。都是些对理论开发有帮忙的知识点。心愿对读者敌人们有所帮忙,也欢送大家一件三连。

我是码农飞哥,再次感谢您读完本文
全网同名【码农飞哥】。不积跬步,无以至千里,享受分享的高兴
我是码农飞哥,再次感谢您读完本文

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理