前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >19. 再说函数~那些不得不知道的事儿

19. 再说函数~那些不得不知道的事儿

作者头像
大牧莫邪
发布2018-08-27 17:49:17
4570
发布2018-08-27 17:49:17
举报

前面的课程中,我们已经对函数有了简单的了解 函数的声明、函数的的调用、函数的参数以及返回值等等

本节内容主要对函数中的一些高级操作进行讲解,方便大家在项目操作过程中对函数的操作更加灵活一些

  • 函数递归
  • 函数变量赋值
  • 参数中的函数
  • 匿名函数
  • 返回值中的函数:闭包
  • 偏函数
  • 装饰器

1. 函数递归

函数的递归,就是让在函数的内部调用函数自身的情况,这个函数就是递归函数。 递归函数其实是另外一种意义的循环 如:计算一个数字的阶乘操作,将这个功能封装成函数fact(num) 提示:阶乘算法是按照小于等于当前数字的自然数进行乘法运算 计算5的阶乘:5 X 4 X 3 X 2 X1 计算n的阶乘:n X (n - 1) X ... X 3 X 2 X 1

代码语言:javascript
复制
# 定义一个递归函数
def fact(num):
    if n == 1:
        return n
    return n * fact(n - 1)
# 执行函数
>>> fact(1)
1
>>> fact(2)
2
>>> fact(3)
6
>>> fact(4)
24
>>> fact(5)
120
>>> fact(9)
362880

递归操作,整个计算过程如下 计算5的阶乘:fact(5) fact(5) ->5 X fact(5 - 1) ->5 X (4 X fact(4 - 1)) ->5 X (4 X (3 X fact(3 - 1))) ->5 X (4 X (3 X (2 X fact(2 - 1))))) =>5 X (4 X (3 X (2 X 1))) =>5 X (4 X (3 X 2)) =>5 X (4 X 6) =>5 X 24 =>120

我们在之前说过,递归就是另外一种特殊的循环:函数级别的循环 所以递归函数也可以使用循环来进行实现 但是循环的实现思路没有递归清晰。

使用递归函数时一定需要注意:递归函数如果一旦执行的层数过多就会导致内存溢出程序崩溃。

有一种做法是将递归函数的返回值中,不要添加表达式,而是直接返回一个函数,这样的做法旨在进行尾递归优化,大家如果有兴趣的话可以上网自行查询一下;由于不同的解释器对于函数递归执行的不同的处理,所以递归的使用请慎重分析和操作。

2. 函数变量赋值

函数,是一种操作行为 函数名称,其实是这种操作行为赋值的变量 调用函数,其实是通过这个赋值的变量加上一堆圆括号来进行函数的执行

代码语言:javascript
复制
# 定义了一个函数,函数命名为printMsg
def printMsg (msg):
    print("you say :" + msg)
# 通过变量printMsg来进行函数的调用
printMsg("my name is jerry!")

既然函数名称只是一个变量,变量中存放了这样的一个函数对象 我们就可以将函数赋值给另一个变量

代码语言:javascript
复制
# 将函数赋值给变量pm
pm = printMsg;
# 就可以通过pm来进行函数的执行了
pm(" my name is tom!")

3. 参数中的函数

函数作为一个对象,我们同样可以将函数当成一个实际参数传递给另一个函数进行处理

代码语言:javascript
复制
# 系统内置求绝对值函数abs(),赋值给变量f
f = abs;
# 定义一个函数,用于获取两个数据绝对值的和
def absSum(num1, num2, fn):
    return fn(num1) + fn(num2)
# 调用执行函数
res = absSum(-3, 3, f)
# 执行结果
~ 6

函数作为参数进行传递,极大程度的扩展了函数的功能,在实际操作过程中有非常广泛的应用。

4. 匿名函数

在一个函数的参数中,需要另一个函数作为参数进行执行:

代码语言:javascript
复制
def printMsg(name, fn):
    print(name)
    fn()

常规做法是我们定义好自己的函数,然后将函数名称传递给参数进行调用

代码语言:javascript
复制
def f():
    print("日志记录:函数执行完成")
printMsg("jerry", f)
重点在这里

我们通过如下的方式来调用函数

代码语言:javascript
复制
printName("tom", lambda:print("函数执行完成..."))
# 执行结果
tom
函数执行完成

在printName函数调用时,需要一个函数作为参数的地方,出现了lambda这样一个词汇和后面紧跟的语句

lambda是一种表达式,一种通过表达式来实现简单函数操作的形式,lambda表达式可以看成是一种匿名函数 常规的lambda表达式的语法结构是

代码语言:javascript
复制
lambda 参数列表:执行代码

如下面这样的lambda表达式

代码语言:javascript
复制
lambda x, y: x * y
# 就是定义了类似如下的代码:
def test(x, y):
    x * y

lambda表达式已经在后端开发的各种语言中出现了,以其简洁的风格和灵活的操作风靡一时,但是需要注意,lambda表达式简化了简单函数的定义,但是同时也降低了代码的可读性 所以这样的lambda表达式,可以使用,但是要慎重使用,切记不能滥用,否则造成非常严重的后果:你的代码由于极差的可读性就会变成一次性的!

5. 返回值中的函数:闭包

函数作为对象,同样也可以出现在返回值中,其实就是在函数中又定义了另外的函数 在一个函数中定义并使用其他的函数,这样的方式在不同的编程语言中有不同的管理方式,在Python中,这样的方式也成为闭包。

代码语言:javascript
复制
# 在一个函数outerFn中定义了一个函数innerFn
def outerFn():
    x = 12;
    def innerFn():
        x = x *12
    return innerFn;
# 执行函数
f = outerFn();
f()
# 执行结果:144

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 什么是闭包,闭包就是在函数A中添加定义了另一个函数B
# 最后将函数B返回,通过函数B就可以直接使用局部变量,扩大了局部变量的作用域
# 
# 为什么要使用闭包,闭包就是为了再多人协同开发项目过程中,同时会有多个人写多
# 个python文件并且要互相引入去使用,此时如果不同的开发人员定义的全局变量出现
# 名称相同,就会出现变量值覆盖引起的数据污染,也称为变量的全局污染。为了避免
# 出现这样的情况,我们通常通过闭包来管理当前文件中变量的使用。
#
# 怎么使用闭包,闭包函数中可以定义其他的任意多个变量和函数,在闭包函数执行的
# 时候这些函数都会执行,也就是将函数的执行从程序加载执行->迁移->闭包函数执行的
# 过程
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

6. 偏函数

常规函数操作中,我们在函数的参数中可以添加参数的默认值来简化函数的操作,偏函数也可以做到这一点,偏函数可以在一定程度上更加方便的管理我们的函数操作 偏函数通过内置模块functools的partial()函数进行定义和处理

如之前我们学习过的一个类型转换函数int(str),用于将一个字符串类型的数字转换成整数,同样的,可以在类型转换函数中指定将一个字符串类型的数字按照指定的进制的方式进行转换

代码语言:javascript
复制
# 将一个字符串类型的123转换成整数类型的123
int("123")  # 123
# 将一个字符串12按照16进制转换成十进制的整数
int("12", base=16)  # 18
# 将一个字符串17按照8进制转换成十进制的整数
int("17", base=8)  15
# 将一个字符串1110按照2进制转换成十进制的整数
int("1110", base=2) 14

# 注意:上述要转换的字符串的整数必须满足对应的进制,否则会转换报错
# 按照八进制转换,但是要转换的字符串中的数字不是8进制数字
int("9", base=8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 8: '9'
# 按照2进制转换,但是要转换的字符串不是2进制数字
int("3", base=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 2: '3'

上述这样的操作方式,通过一个命名关键字参数base的方式来指定转换进制,可读性较好,但是操作起来稍显繁琐,我们可以通过functools的partial()函数进行改造如下:

代码语言:javascript
复制
# functools.partial()函数语法结构
新函数名称 = functools.partial(函数名称, 默认赋值参数)

通过对指定的函数进行参数的默认赋值,然后将这样的一个新的函数保存在变量中,通过这个新函数就可以执行更加简洁的操作了

代码语言:javascript
复制
# 原始的2进制数据转换
int("111", base=2)
~执行结果:7
# 引入我们需要的模块functools
import functools
# 通过偏函数扩展一个新的函数int2
int2 = functools.partial(int, base=2)
# 使用新的函数,新的函数等价于上面的int("111", base=2)
int2("111")
~执行结果:7

系统内置函数可以通过上述的形式进行封装,那么我们自定义函数是否也可以封装呢

代码语言:javascript
复制
# 定义一个函数,可以根据用户输入的类型来遍历数据
def showData(data, *, type=1):
    if type == 1:     #打印字符串
        print(data)
    elif type ==2:   # 遍历列表、元组、集合
        for x in data:
            print(x)
    elif type == 3: # 遍历字典
        for k, v in data.items():
            print(k, v)
# 打印字符串
showData("hello functools partial");
# 打印列表
showData([1,2,3,4,5], type=2)
# 打印元组
showData((1,2,3,4,5), type=2)
# 打印集合
showData({1,2,3,4,5}, type=2)
# 打印字典
showData({"1":"a", "2":"b", "3":"c"}, type=3)

# 使用偏函数进行改造
import functools
showString = functools.partial(showData, type=1)
showList = functools.partial(showData, type = 2)
showDict = functools.partial(showData, type = 3)
# 打印字符串
showString ("hello functools partial");
# 打印列表
showList ([1,2,3,4,5])
# 打印元组
showList ((1,2,3,4,5))
# 打印集合
showList ({1,2,3,4,5})
# 打印字典
showDict ({"1":"a", "2":"b", "3":"c"})
# * * * * * * * * * * * * * * * * * * * * * *
# 整个世界,清净了...
# * * * * * * * * * * * * * * * * * * * * * *

7. 装饰器函数处理

装饰器是在不修改函数本身的代码的情况下,对函数的功能进行扩展的一个手段

装饰器,整个名词是从现实生活中抽象出来的一个概念 所谓装饰,生活中其实就是不改造原来的物体的情况下给物体增加额外的一些功能的手段,比如一个房子盖好了~但是不喜欢房子现在的墙壁颜色,不喜欢房子原始地板的颜色,就可以通过装修的形式,给房子额外增加一些装饰,让房子更加的豪华温馨 此时:房子->装修->额外的样式

我们定义一个简单的函数,用于进行数据的遍历

代码语言:javascript
复制
# 定义一个函数,可以根据用户输入的类型来遍历数据
def showData(data, *, type=1):
    if type == 1:     #打印字符串
        print(data)
    elif type ==2:   # 遍历列表、元组、集合
        for x in data:
            print(x)
    elif type == 3: # 遍历字典
        for k, v in data.items():
            print(k, v)

此时,我们想要给这个函数增加额外的功能,在函数执行之前和函数执行后增加额外的日志的记录,记录函数执行的过程,大致功能如下

代码语言:javascript
复制
print("遍历函数开始执行")
showData("hello my name is showData")
print("遍历函数执行完成")

这样的代码也是能满足我们的需要的,但是这个函数的调用如果可能出现在很多地方呢?是不是就需要在每次调用的时候都要在函数的前后写这样的代码呢?肯定不太现实

我们通过如下的方式来定义一个函数,包装我们的showData()函数

代码语言:javascript
复制
# 定义一个包装函数
def logging(func):
    def wrapper(*args, **kw):
        print("遍历函数开始执行----")
        res = func(*args, **kw)
        print("遍历函数执行完成----")
        return res;
    return wrapper
# 在我们原来的函数前面添加一个注解
@logging
def showData(data, *, type=1):
    if type == 1:     #打印字符串
        print(data)
    elif type ==2:   # 遍历列表、元组、集合
        for x in data:
            print(x)
    elif type == 3: # 遍历字典
        for k, v in data.items():
            print(k, v)

# 执行函数,我们会发现在函数执行时,出现了额外的添加的功能。
showData("my name is jerry!")
# 执行结果
~ 遍历函数开始执行----
~ my name is jerry!
~ 遍历函数执行完成----

装饰器函数执行的全过程解析 一、定义过程 1.首先定义好了一个我们的功能处理函数showData(data, * , type = 1) 2.然后定了一个功能扩展函数logging(func),可以接受一个函数作为参数 3.使用python的语法@符号,给功能处理函数增加一个标记,将@logging 添加到功能处理函数的前面 二、执行过程 1.直接调用执行showData("my name is jerry!")

2.python检查到函数顶部声明了@logging,将当前函数作为参数传递给 logging()函数,就是首先执行logging(showData)

3.功能处理函数的参数"my name is jerry",传递给功能扩展函数的闭包函数wrapper(*args, **kw)

4.在闭包函数wrapper中,可以通过执行func(*args, **kw)来执行我们的> 功能处理函数showData(),这样就可以在执行func(*args,**kw)之前和之后添加我们自己需要扩展的功能 [备注:函数中的参数,不论传递什么参数,都可以通过(*args, **kw)来接收,请参考函数参数部分内容]

5.执行过程如下图所示:

装饰器函数执行过程图解


本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.05.19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 函数递归
  • 2. 函数变量赋值
  • 3. 参数中的函数
  • 4. 匿名函数
  • 5. 返回值中的函数:闭包
  • 6. 偏函数
  • 7. 装饰器函数处理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档