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

例题引导:

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

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

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

参考答案:

1
2
3
4
5
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的语法只包含一个语句,格式如下:

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

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

用def格式写

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

用匿名函数写

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

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

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

函数的返回值

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

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

1
2
3
4
5
6
7
8
9
def sum(arg1,arg2):
"返回两个参数的和"
total=arg1+arg2
print('函数内:',total)
return total

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

函数的说明文档

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

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

语法结构如下:

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

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

函数的嵌套

在函数中再定义一个函数

语法结构如下:

1
2
3
4
5
6
7
def outer():
def inner():
print('inner')
print('outer')
inner()
outer()
# inner() #该句会报错
1
2
#outer
#inner

一个案例

1
2
3
4
5
6
7
8
9
#一个案例
def outer():
str='Hello World'
def inner():
print(str)
return inner

info=outer()
info()
1
#Hello World

变量作用域

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

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

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

  • 全局作用域:global

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

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

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

案例演示

1
2
3
4
5
6
def Demo1():
num=1
print(num)

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

NameError Traceback (most recent call last)

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

对上例的解释

1
2
3
4
5
6
7
8
9
num = 0
def Demo1():
num = 1
print("在demo1中的结果:",num)
def Demo2():
print("在demo2中的结果:",num)

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

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

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

现修改上面代码如下:

1
2
3
4
5
6
7
8
9
10
11
num = 0
def Demo1():
global num
num = 1
print("在demo1中的结果:",num)
def Demo2():
print("在demo2中的结果:",num)

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
1
2
3
#在demo1中的结果: [1, 2, 3, 4]
#在demo2中的结果: [1, 2, 3, 4]
#在函数外面中的结果: [1, 2, 3, 4]

列表 += 与 + 的区别

#+:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)
1
2
3
#在demo1中的结果: [1, 2, 3, 4, 5, 6]
#在demo2中的结果: [1, 2, 3]
#在函数外面中的结果: [1, 2, 3]

#+=:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
1
2
3
#在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层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#基础模型一
def func():
print('from func')
func()    #直接调用自身

func()

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

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

func()

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"""
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))
1
#24

注意在Python:

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

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

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

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

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

小作业:

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

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

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

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

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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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. 以下代码是否能执行,原因是?如何修改?

    1
    2
    3
    4
    5
    6
    7
    8
    def fun1():
    x = 3
    def fun2():
    x *= x
    return x
    return fun2()

    print(fun1())
  5. 写出以下代码的执行结果:

1
2
3
4
5
6
7
8
9
10
def fun2():
print(2)
def fun3():
print(6)
print(4)
fun3()
print(8)
print(3)
fun2()
print(5)

评论