11、函数进阶

一、递归函数

1.1 概念
  • 递归是解决问题的一种方式,它的整体思想,是将要给大的问题分解为一个小的问题,直到问题无法分解时,再去解决问题。
  • 递归函数有2个条件:
    • 基线条件 : 问题可以被分解为最小问题,当满足基线条件时,再去解决问题,递归就不再执行了。
    • 递归条件 : 可以将问题继续分解的条件。
1.2 案例分析什么时递归?

分析:求取 10的阶乘
1 的阶乘 1! = 1
2 的阶乘 2! = 1 * 2
3 的阶乘 3! = 1 * 2 * 3
……
10的阶乘 10! = 1 * 2 * 3 * 4……10

n = 1
for i in range(1, 11):
      n *= i
print(10)

def fun(n):
    r = 1
    for i in range(1, 11):
        r *= i
    return r

上面两种方式都不属于递归,就是一个普通的循环。那么什么样才是递归呢 ?

从前有座山,山上有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说……
从前有座山,山上有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说……
从前有座山,山上有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说……

这种情况就是递归:简单理解就是自己调用自己。
总结: 递归函数就是在函数中自己调用自己。

使用递归求 10的 阶乘~
10! = 10 *9!
9! = 9 * 8!
……
2!= 2 *1
1!= 1
推导结论: n! = n * (n-1)!

def fun(n):  # fun(n) 是为了求取n 的阶乘
def  fun(n):
    if n == 1:
        return 1
    return n * fun(n-1)

print(fun(10))

过程分析:
首先 n=10 时,打印的fun(10)的运行结果;
fun(10)= 10 * fun(9), 那么fun(9) 又可以被分解为 9 * fun(8),;
一直到被分解为 n =1时就不能再分解,返回 1。
最终为 9 8 * 7 6 * 5 *4 * 3 *2 *1 的结果。就是10 的阶乘。

1.3 练习

(1)求任意数的任意次幂
分析:
10 ** 5 = 10 * 10 **4
10 ** 4 = 10 * 10 **3
10 ** 3 = 10 * 10 **2
10 **1 = 10

def fun(n, m):
    if m == 1:
      return n
    return  n * fun(n, m-1)

print(fun(10, 6))
----------------输出--------------
1000000

(2)回文字符串

  • 回文字符串,从左到由于从右到左读都是一样的。
    分析:2种情况
    123456789987654321   12345678987654321
     2345678998765432    234567898765432
      34567899876543     3456789876543
         99            9
  • 首先,基线条件,分别从字符串两边开始取值,直到到最后一个,字符串还剩1个或2个一样的,因此我们可以得到递归的基线条件为 len(str) < 2 才行。
  • 其次,递归条件,通过切片取值,拿到最外层的2个索引的值。
def fun(s):
    if len(s) < 2:
      return True
   elif  s[0] != s[-1]:
      return False
   return fun(s[1:-1])   # 返回值,切片取字符串索引 1 到 -1,因为列表特性取不到结尾,所以取得前一个值。即去除最外层2个字符串。

print(fun('123456789987654321'))
print(fun('12345678987654321'))
print(fun('12'))
print(fun('1'))
True
True
False
True

二、高阶函数

2.1 什么是高阶函数 ?
  • 接收函数对象作为参数
  • 将别的函数对象作为返回值返回的函数就是高阶函数。
    满足以上条件之一就是高阶函数。
    比如上面的判断回文字符串的递归函数:
def fun(s):
  if len(s) < 2:
    return True
  elif s[0] != s[1]
    return False
  return fun(s[1:-1])
print('1234567890')

没有接受函数做参数,同时 return = fun(s[1:-1]),返回值就是 f(s) ,因此没有调用其他的函数作为返回值,因此不是高阶函数。

2.2 定义高阶函数
def fun1(i):
    if i % 2 == 0:
        return True

def fun2(fn):
    list1 = []
    for i in  range(1, 101)
        if fn(i):       # fn(i)  这里fn 是形参,形参等于实参,fn=fun1, 那么 fn(i)==fun1(i)
            list1.append(i)
    return list1

print(fun2(fun1))
  • 注意: fn(i) ,fn 是 fun2()的形参,形参等于实参,那么fn = fun1,即 fn(i) = fun1(i)
  • fun2(fun1) 实现了将目标函数 fun1 当作变量传递到了 fun2() 里面来。fun1就是一个对象,里面存储的就是 fun1()函数的功能。
  • 那么 if fn(i): 其实就是执行 fun1(i), 也就是调用 fun1(i), 如果 return True ,就继续执行list1.append(i),
    因此符合高阶函数的要求。


    图片.png

注意:函数调用函数也不一定是 高阶函数!
比如上面的例子:

def fun1(i):
    if i % 2 == 0:
      return True

def fun2(fn):
    list1 = []
    for i in range(1, 101):
        if fun1(i):    #### 这里调用的是 fun1()
            list1.append(i)
    return list1

print(fun())  

同样实现功能,但是不满足高阶函数的2个条件,所以不是高阶函数。

三、匿名函数

3.1 匿名函数介绍
  • 有些简单的函数,实现一些简单功能之后,我们也不会再使用,就会占位,那其他人要去定义的时候还要去查询确认,浪费资源,而实际生生产环境,都是小组协同开发一个项目,为了提高效率,我们尽量使用匿名函数。
    匿名函数的功能:
    1) 防止重名
    2) 不用再去定义函数,使用方便
    3) 可以作为一个传递的工具
3.2 lambda 函数
  • lambda 函数就是匿名函数
    比如,我们之前筛选出偶数,我们就不用去单独定义一个函数实现这个功能了~
  • lambda 语法: lambda 参数:表达式
print((lambda a, b: a + b)(1,2))
# 前面括号 a, b 就是形参,后面括号(1, 2) 就是实参~
  • 前面括号里面 a,b 就是形参,后面括号传递实参。

  • 案例解析:

list1 = [1, 2, 3, 4, 5, 6, 7, 8]
s = lambda a: a % 2 ==0
# print((lambda a: a % 2 ==0)(list1))  # list 无法传递给lambda,需要取处理list 里面的值。
# filter  两个参数,第一个参数是过滤规则,第二个参数是过滤的数据
## 这里就需要使用 filter() 过滤规则,将给出的过滤规则,将过滤数据返回出来。
s = lambda a: a % 2 == 0
print(list(filter(s, list1)))
----------------输出--------------
[2, 4, 6]

四、闭包

  • 将函数作为返回值也是高阶函数,也称为闭包。
  • 闭包的好处
    • 通过闭包可以创建一些只有当前函数能访问的变量
    • 可以将一些私有数据藏到闭包中。
  • 形成闭包的条件
    • 函数嵌套。
    • 将内部函数作为返回值返回。
    • 内部函数和必须要使用到外部函数的变量。

注意: 闭包用多了会导致占用内存较高,生产慎用~

# 定义一个外部函数
def fun_out(num1):
    
    # 定义一个内部函数
    def fun_inner(num2):
          res = num1 + num2
          print(res)

    return fun_inner

f = fun_out(1)
print(f(2))
------------输出-----------
3
None

分析

  1. 因为函数的调用等于函数的返回值,f = fun_out(1) = fun_inner
  2. 那么 f() = fun_inner() = fun_out(1)(),因此 调用到 f()=fun_inner() ,调用内部函数,内部函数没有返回值,结果返回 None。
  3. 内部函数调用 : f(2) = fun_inner(2) = fun_out(1)(2), print(f(2)) 。执行内部函数 print(1+2) =3

综上,有函数嵌套,内部函数作为返回值返回 (return fun_inner), 内部函数使用外部函数的参数 num1,所有上面就是一个 闭包。

特性: 保证被内部使用的外部变量不被销毁

案例2: 修改外部函数的变量。

# 定义外部函数
def fun_out(num1):

    # 定义一个内部函数
    def fun_inner(num2):
        num1 = 10   # 这里num1 = 10 是重新赋值,与形参num1 不是一个同一个概念。
        # 类似于
        # b = 1
        # def fun3(a):  # a = b
        #       a = 0
        #       print(a)
        # fun(b)
        # print(b)
        res = num1 + num2
        return res

    print(num1)
    fun_inner(1)
    print(num1)
    return fun_inner

f = fun_out(1)
-----------输出--------------
1
1

分析:

  1. 首先,程序执行 fun_out(1) , 调用外部函数 fun_out(1), 没有调用内部函数,继续向下执行,print(1) = 1
  2. 然后,fun_inner(1) 调用内部函数 fun_inner(1) ,执行内部函数 res =num1+ num2,return = res=11。
  3. 然后, 继续,print(num1) 。但是此时 num1 并不是外部函数的形参,只是重新赋值,不会被外部函数所使用,所以num1 依然是 1 ,print (1) = 1
  4. 最后,函数执行完成,返回 fun_inner。
图片.png

因此,上面函数中,不满足第三个条件,内部函数没有使用到外部函数的变量,不是闭包。

  • nonlocal 修改外部变量
    作用: 告诉解释器,这里使用的不是本地的变量,而是外部变量。
   def fun_inner(num2):
        # global num1 # 为啥不用 global1 呢? 因为global 成了全局变量。也不符合要求。
        nolocal num1  # 告诉解释器,这里使用的不是本地变量。而是外部变量num1.
        num1 = 10 
        res = num1 + num2
        return res

    print(num1)
    fun_inner(1)
    print(num1)
    return fun_inner

f = fun_out(1)
  • 所以这里才是闭包。

练习:
猴子吃桃问题(递归)
猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,有多吃了一个,第二天早上又将剩下的桃子吃掉了一半,又多吃了一个,以后每天早上都吃了前一天剩下的一半零一个。到了第10天早上想再吃时,见到只剩下一个桃子了,请问一共摘了多少个 桃子 ?

分析:递归方法
第10天剩下的桃子: 1
第9天剩下的桃子: (1+1)*2
第8天剩下的桃子: (( 1 + 1) * 2 +1) * 2
第7天剩下的桃子: ((( 1 + 1) * 2 +1) * 2+1) *2
依次类推
规律:当天剩下的桃子数 = (后面一天的桃子数+1) * 2
即 f(n) = (f(n+1) +1) * 2

因此递归函数就是:

def  count(n):
     if  n == 10:
        return 1
    else:
        return  (count(n+1)  +1  ) * 2

print(count(1)) 
------- 输出----------
1534
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容

  • 1.什么是返回值 """返回值是从函数里面传递到函数外面的数据 怎么返回数据返回值就是 return 关键字后面的...
    Dark_stryyy阅读 84评论 0 1
  • 一、面向对象编程 对象:包括字符串 数组 自定义对象等,万物皆对象对象是单个事物的抽象,包含属性和行为(方法,功能...
    我爱学习111阅读 481评论 0 0
  • 0.日常吐槽 十一小长假真的不要出去玩啊,你会见到你一年也见不到的那么多人,大概就是这样的 这样的 所以还是安安静...
    单嘉伟xx阅读 595评论 0 4
  • 1.函数的定义和调用 1.1 函数的定义方式 1.1.1 函数声明 语法: 1.1.2 函数表达式 语法: 1.1...
    青年心路阅读 264评论 0 0
  • 1.函数的返回值 1.1 什么是返回值 返回值是从函数里面传递到函数外面的数据 怎么返回数据返回值就是 retur...
    宾尼瑞阅读 139评论 0 0