匿名函数 函数嵌套 函数递归 作用域

例题引导:

一个由元组构成的列表:a = [(‘b’,3), (‘a’,2), (‘d’,4), (‘c’,1)]

要求:使用lambda表达式和函数sorted(),分别按照元组第一个元素排序(即abcd排序),按照元组第二个元素排序(即1234排序),输出相应的列表结果。

要点:了解lambda表达式和排序函数sorted()用法

参考答案:

a = [('b',3), ('a',2), ('d',4), ('c',1)]

print(sorted(a, key=lambda x:x[0]))

print(sorted(a, key=lambda x:x[1]))

学习引导:

  • 匿名函数
  • 函数的返回值
  • 函数的说明文档
  • 函数的嵌套
  • 变量作用域
  • 函数递归

匿名函数

  • python可以使用lambda来创建匿名函数

  • 所谓匿名,即不再使用def这样的标准语句来专门定义函数

  • lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda中封装有限的逻辑进去。

  • lambda函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间中的参数。

  • 有名函数的定义规则为【def+函数名+参数】;而匿名函数则是用【lambda+参数+表达式】的方式定义函数

lambda的语法只包含一个语句,格式如下:

lambda [arg1[,arg2,...,argN]]:expression

下面用几个例子对两种函数进行对比,以便大家理解。

用def格式写

#用def格式写
def func(x,y):
    return x*y
func(2,3)
#6

用匿名函数写

#用匿名函数写
func=lambda x,y:x*y
func(3,4)
#12

从上面例子可以看出匿名函数相较标准函数的优点有

  • 不用取名称,因为给函数取名是比较头疼的一件事,特别是函数比较多的时候
    可以直接在使用的地方定义,如果需要修改,直接找到修改即可,方便以后代码的维护工作
  • 语法结构简单

函数的返回值

return[表达式]语句用于退出函数,选择性的向调用方返回一个表达式。不带参数值的return语句返回None。

下面演示一下return语句的用法:

def sum(arg1,arg2):
    "返回两个参数的和"
    total=arg1+arg2
    print('函数内:',total)
    return total

#调用sum函数
total=sum(23,32)
print('函数外:',total)
#函数内: 55
#函数外: 55

函数的说明文档

  • 定义:即对函数进行简单的解释说明(一般对参数和返回值进行说明)

  • 作用:方便他人理解和自己日后的复读

语法结构如下:

def 函数名(参数):
    '函数的说明文档内容'
    函数体
    return 返回值

对于函数的内置函数,可以使用help()函数查看其内置文档。

函数的嵌套

在函数中再定义一个函数

语法结构如下:

def outer():
    def inner():
        print('inner')
    print('outer')
    inner()
outer()
# inner()  #该句会报错
#outer
#inner

一个案例

#一个案例
def outer():
    str='Hello World'
    def inner():
        print(str)
    return inner

info=outer()
info()      
#Hello World

变量作用域

一个标识符的可见范围,就是标识符的作用域。一般常说的是变量的作用域。

  • 函数中的局部作用域:local

  • 嵌套函数中父级函数的局部作用域:enclosing

  • 全局作用域:global

  • 系统内置的变量:如 int、str、list 等关键字

  • 局部变量:在某个函数内部定义,作用在函数内部。生命周期:从变量被创建开始到函数结束死亡。

  • 全局变量:定义在.py模块内部,作用在整个.py模块。生命周期:从变量被创造开始到.py模块结束死亡。

案例演示

def Demo1():
    num=1
    print(num)

Demo1()
print(num)    #该句报错。因为num是局部作用,所以在函数外面是找不到num这个变量的。
#1
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-56-d3aaf0ba8dea> in <module>
      5 
      6 Demo1()
----> 7 print(num)
NameError: name 'num' is not defined

对上例的解释

num = 0
def Demo1():
    num = 1
    print("在demo1中的结果:",num)
def Demo2():
    print("在demo2中的结果:",num)

Demo1()
Demo2()
#在demo1中的结果: 1
#在demo2中的结果: 0

从结果可以看出,我们在demo1中num = 1,只在demo1内部起作用,而全局变量num仍然是0;因为在python中,在函数内部改变全局变量的值,会默认的在函数内部创建一个新的变量,全局变量并没有改变。

要想要改变全局变量的值,需要在函数内部用global声明。

现修改上面代码如下:

num = 0
def Demo1():
    global num
    num = 1
    print("在demo1中的结果:",num)
def Demo2():
    print("在demo2中的结果:",num)

Demo1()
Demo2()
print("在函数外面中的结果:",num)
#在demo1中的结果: 1
#在demo2中的结果: 1
#在函数外面中的结果: 1

还有一种情况是当全局变量是可变数据类形,我们可以通过修改可变数据类型的方法,修改可变类型的内容,从而实现修改全局变量。
(这里因为可变数据类型改变时,会在内存中改变数据的值)

num_list = [1,2,3]

def Demo1(demo1_list):
    demo1_list.append(4)
    print("在demo1中的结果:",demo1_list)

def Demo2():

    print("在demo2中的结果:",num_list)

Demo1(num_list)
Demo2()

print("在函数外面中的结果:",num_list)
#在demo1中的结果: [1, 2, 3, 4]
#在demo2中的结果: [1, 2, 3, 4]
#在函数外面中的结果: [1, 2, 3, 4]

列表 += 与 + 的区别

#+:

num_list = [1,2,3]

def Demo1(demo1_list):
    demo1_list = demo1_list + [4,5,6]
    print("在demo1中的结果:",demo1_list)

def Demo2():
    print("在demo2中的结果:",num_list)

Demo1(num_list)
Demo2()

print("在函数外面中的结果:",num_list)
#在demo1中的结果: [1, 2, 3, 4, 5, 6]
#在demo2中的结果: [1, 2, 3]
#在函数外面中的结果: [1, 2, 3]

#+=:

num_list = [1,2,3]

def Demo1(demo1_list):
    demo1_list +=  [4,5,6]
    print("在demo1中的结果:",demo1_list)

def Demo2():

    print("在demo2中的结果:",num_list)

Demo1(num_list)
Demo2()

print("在函数外面中的结果:",num_list)
#在demo1中的结果: [1, 2, 3, 4, 5, 6]
#在demo2中的结果: [1, 2, 3, 4, 5, 6]
#在函数外面中的结果: [1, 2, 3, 4, 5, 6]

因为:

对于+号操作,可变对象和不可变对象调用的都是__add__操作

对于+=号操作,可变对象调用add__,不可变对象调用的是__iadd(不可变对象没有iadd) __iadd__是原地修改

函数递归

  • 定义:在调用一个函数的过程中直接或间接的调用该函数本身,称之为递归调用。

  • 递归调用最多能调用999层。

#基础模型一
def func():
    print('from func')
    func()    #直接调用自身

func()

#基础模型二
def func():
    print('from func')
    bar()    #间接调用自身

def bar():
    print("from bar")
    func()

func()

虽然以上两中方式为函数递归的基础模型,但往往不能直接这样使用。因为没有一个函数的结束条件,仅仅相当于一个死循环。

递归分为两个重要的阶段: 递推+回溯
  •  递推:函数不断减少问题规模直至最终的终止条件。

  •  回溯:拿到最终明确的值后,返回给上次调用进行处理,直至初始层。

下面为一个练习题:解决年龄问题,求出Jack的年龄

"""
Jack 他比小王 大两岁。  4   age(3) + 2
小王 他比大枫 大两岁。  3   age(2) + 2
大枫 他比美丽 大两岁。  2   age(1)  + 2
美丽:我今年18.         1   18
"""

def age(n):
    if n == 1:
        return 18
    else:
        return age(n-1) + 2

print(age(4))
#24

注意在Python:

    1、递归调用必须有一个明确的结束条件

    2、在python中没有尾递归优化,递归调用的效率不高

    3、进入下一次递归时,问题的规模必须降低

再来一个例子
输入任意一个数,让其除以2,直到不能再除

def  cal(num):
    if  num%2==0:#先判断能不能整除
        num=num//2
        return cal(num)
    else:
        return num
print(cal(8))
#1

小作业:

  1. 定义并调用两个函数*fib(n)PrintFN(m,n)*,输出指定的斐波那契数信息(斐波那契数列:1,1,2,3,5,8,…不了解的自行百度)。函数要求:

    fib(n)*:返回斐波那契数列的第n个值

    PrintFN(m,n)*:用列表返回[m, n]中的所有斐波那契数。

    例如输入:20 100 6,输出:

    fib(6) = 13
    4
  2. 解决汉诺塔问题(汉诺塔规则请自行百度):

    有ABC三根柱子,将A柱上的n个盘子移动到C柱。控制台输入盘子个数n,输出移动的所有步骤,格式样例“A–>C”。

  3. 假设你正在爬楼梯。需要 n 阶你才能到达楼顶(给定n为正整数),每次可以爬 1 或 2 个台阶。编写函数,返回有多少种不同的方法可以爬到楼顶。

    def climbStairs(n):
        dp = []
        dp.append(1) # 初始状态,只有1阶的时候有一种走法
        dp.append(2) # 有2阶的时候有两种走法
        if n==1:        #终止条件
            return 1        
        if n==2:        #终止条件
            return 2
        for i in range(2,n):
            dp.append(dp[i-1]+dp[i-2])      #递归
    
        return dp[-1]
  4. 以下代码是否能执行,原因是?如何修改?

    def fun1():
        x = 3
        def fun2():
            x *= x
            return x
        return fun2()
    
    print(fun1())
  5. 写出以下代码的执行结果:

def fun2():
    print(2)
    def fun3():
        print(6)
    print(4)
    fun3()
    print(8)
print(3)
fun2()
print(5)

评论