python练习生|函数三部曲——再次深造(函数的返回值、递归函数)
我们在上一篇博客简单的认识了函数,了解了函数的语法以及一些基本要素,这篇博客是对上一篇博客:函数三部曲——初来乍到 的再次补充,供我们进一步了解函数相关的知识以及一些简单用途。
一.函数中的返回值(return)
1. 什么是函数的返回值
- 返回值就是调用函数,函数执⾏完成以后返回给用户的结果
- 对于整体的返回值print,我们只能用它在控制台处输出打印。而作为函数中的返回值(return),则更加灵活。
- 我们通过 return 来指定函数的返回值, return后⾯可以跟任意对象,返回值甚⾄可以是⼀个函数。
2.return的简单用法
- 我们在使用函数返回值时,可以用一个变量保存函数的返回值,也可以用于直接输出。
(1).直接打印
def fn():
'意大利炮'
print(fn(),type(fn()))
def fn1():
return"意大利炮"
print(fn1(),type(fn1()))
- 通过两个代码块进行对比我们可以看出,没有return的代码块,其实也有一个“返回值”,它的返回的是none
- 基于这两个函数的代码,以及上面的图片。我们不难发现,return的作用是将return后面的值返回,并使用print打印至控制台。
- 通过第三张图我们可以看出,即使return后没有跟变量,其也有一个“隐藏返回值” NoneType 。
(2).通过赋值变量打印
def fn1():
return"意大利炮"
s = fn1() # 将函数的返回值通过赋值给变量s,对变量s进行打印
print(s)
print(fn1()) # 直接将函数的返回值进行打印
(3).return后面打印的变量
- return后面可以跟各种各样的对象
简单举例:
#return 返回对象多样化
def fn3():
return [1,2,3] # 返回列表
print(fn3(),type(fn3()))
def fn4():
return 'aoligei' # 返回列表
print(fn4(),type(fn4()))
def fn5():
return (1,2,3) # 返回元组
print(fn5(),type(fn5()))
def fn6():
return fn5() # 返回函数
print(fn6(),type(fn6()))
- 你以为就这点东西吗?别着急,后面才是它为所欲为的方式
(4).为所欲为的return
- 让我们来看看return能为我们带来什么惊喜吧。
def fn1(*j):
r = 0
for i in j:
r += i
print(r)
s = fn1(1,2)
print(s,type(s))
print(s+4)
- 没有对比就没有伤害
def fn2(*j):
r = 0
for i in j:
r += i
return r
s = fn2(1,2)
print(s+4)
print(fn2(1,2),type(fn2(1,2)))
print(fn2(1,2)+4)
(5).return与break的区别
- 特别的我们需要去记住的一点是在函数中,一旦你执行了return语句,那么return后面的代码行都不会被执行 。也就是说 return语句一旦执行,函数自动结束 。
- 而break语句,则是在满足条件后,跳出当前循环语句,并不影响其他语句的执行。如果你忘了,不怕,咱有博客写了:break和continue 忘了的老铁可以去看看。
举个栗子:
#return与break的区别
def fn1():
for i in range(5):
if i == 3:
return
print(i)
print("奥利给")
fn1()
def fn2():
for i in range(5):
if i == 3:
break
print(i)
print("奥利给")
fn2()
- 再三强调,函数带括号和不带括号差别很大!
恩, fn 是返回函数对象, fn() 是调用函数
def fn():
print('123')
print('我打印了函数 = ',fn)
print(fn())
二.文档字符串(字符串补充)
- 你是不是很好奇文档字符串为何要出来?主要是它发出了抗议之声,3).字符串补充 你在这里都没给我补充上来,我要给你差评 ==#
- 其实,前期文档字符串对我们并没有多大的帮助,然鹅,现在我们学的越来越多,特别是函数这块,哈哈嗝。如果有小伙伴看不懂,可以通过文档字符串进行函数的注释,从而减少因不懂而导致后期调试的失误。bug越改越多,哈哈嗝,疯了。
- 再说文档字符串之前呢,我们先说一下help()函数
举个栗子:
1.help()函数
- 语法:help(函数对象)
举个栗子:
help(print)
2.文档字符串(长字符串)
def fn1(a,b,c):
'''
文档字符串的使用方法
参数的作用
参数 a : 他能干什么,有什么类别限制...
参数 b : 他能干什么,有什么类别限制...
参数 c : 他能干什么,有什么类别限制...
return:
'''
return '奥利给'
help(fn1) #查看我们自定义的fn1这个函数对象
长字符串用 三个单引号’’’ 或者 三个双引号""" 来表示
- 在定义函数时,可以在函数内部编写⽂档字符串,⽂档字符串就是对函数的说明
三.函数的作用域(scope)
1.什么是作用域
- 作用域(scope):作用域就是限定代码实用范围的一个区域。
2.作用域分类
- 我们简单地把作用域分为:全局作用域、函数作用域
(1).全局作用域
全局作用域:
- 定义的变量不仅可以作用域函数内部,还能在外部调用。我们也可以说,所有 函数以外的区域都是全局作⽤域
(2).函数作用域
函数作用域:
- 在函数作⽤域中定义的变量,都是局部变量,它只能在函数内部被调用,如需在函数外部进行调用,需要一些条件。
- 函数每调⽤⼀次就会产⽣⼀个新的函数作⽤域
简单举例:
#函数作用域
def fn():
a = 10 # 我们在函数fn内部 将10 赋给变量 a
print(a,'在函数fn的内部')
fn()
print(a,'在函数fn的外部')
- 从上述代码块我们可以得知,在外部无法获得函数内部 a 的值。
我们再来看下面的代码:
#函数作用域
b = 100 # 充当全局变量
def fn():
a = 10 # 我们在函数fn内部 将10 赋给变量 a
print(a,'在函数fn的内部')
print(b, '我调用了变量b')
fn()
print(b,'在函数fn的外部')
- 从上面的代码中我们可以得出,变量b的声明对于函数fn来说,属于全局变量,即,可以在函数内部调用。
-在全局作⽤域中定义的变量,都是全局变量,全局变量可以在程序的任意位置进⾏访问
那么如果是 函数嵌套 呢?
#函数的嵌套
def fn1():
a = 100
def fn2():
print('我调用了fn1的函数,a =',a)
fn2()
fn1()
- 从上述代码块中,我们可以得知,函数 fn2 相对于函数 fn1 来说,相当于是在其内部;那么,相应的,函数 fn1 的变量 a 对于函数 fn2来说 相当于是全局变量。所以,函数 fn2 可以调用函数 fn1 中的变量值 。
(3).global 关键字的使用
- 前面我们挖了个坑,现在我们要填坑了,前面在说局部作用域时,我们说了,需要一定条件对内部变量进行“升级”’,使其变成全局变量。
- 现在这个条件(升级方式)已经来了,它就是 global 关键字
举个栗子:
def fn1():
a = 100
def fn2():
global a #把fn2函数的变量a进行"升级"处理,把他变成了全局变量
a = 30
print('我调用了我自己的变量,a =',a,id(a))
fn2()
fn1()
print('a =', a, "我不是100了,而且我的内存地址是=", id(a))
- 从图中我们可以看出,此时的函数fn1中,a的变量值是30
- 从图中我们还可以看出,fn1和fn2函数变量a的地址一样
挖坑:还有个保留字nolocal跟global在某些地方的用法相似,
挖坑:关于global和globals的区别,后面再补
老规矩:插个连接,哈哈嗝。
python官网:关于nolocal的说明
四.函数的命名空间
- 命名空间实际上就是⼀个字典,是⼀个专⻔⽤来存储变量的字典
1.命名空间的获取方式
- 通过使用 locals() ,来获取当前作用域的命名空间
- 若是在全局作⽤域中调⽤locals(),则获取全局命名空间,在函数作⽤域中调⽤locals(),则获取函数命名空间
- 返回值是⼀个字典
2.全局变量命名空间的获取
举点栗子:
a = 30
b = 40
scope = locals()
print(scope)
- 这里的 a,b 都是全局变量,所以这个 scope 也是个全局作用域。
- 从图中我们可以看出该全局作用域的空间命名是个字典类型的
3.局部变量命名空间的获取
#局部变量命名空间的获取
def fn():
a = 10
b = 20
c = '奥利给'
scope = locals()
print(scope)
print(c)
fn()
- 我们可以从图中看出,我们的作用域的空间命名返回的是一个字典
- 能够实现字典的键值对映射
五.递归函数
- 如果一个函数在自己调用自己,那么这个函数就是递归函数。
- 递归是解决问题的⼀种手段,其实就是将⼀个⼤问题逐级拆分,直到问题⽆法分解时,在去解决问题。
我们在了解递归函数之前,先了解一个经典例子:阶乘
哈哈嗝,下面我将举例:
#我们将10的阶乘进行分解:
# 10!= 10*9! 10*9的阶乘
# 9! = 9*8! 9*8的阶乘
# ...
# 2! = 2*1
# 1! = 1
def fn(n):
r = n # 创建一个变量用来保存我们的参数
for i in range(1,n):
r *=i # n-1 的阶乘乘以n 实现n的阶乘
return r # 返回 n的 阶乘
print(fn(10))
- 基于此,我们按照拆分问题的逻辑对这个阶乘进行问题的拆分
1.递归函数的两个基本条件
我们可以总结出递归函数的两个条件:基线条件和递归条件
(1).基线条件
- 基线条件:即问题被逐级拆分后,剩余的最小问题(不能再往下拆分),当满足基线条件时,问题即不可拆分。
(2).递归条件
- 递归条件:问题被逐级拆解时的规律,也就是说将问题逐级分解的条件
(3).递归函数的简单应用
通过上面10的阶乘,我们对条件进行拆分:
10! = 10*9! # 10*9的阶乘
9! = 9*8! # 9*8的阶乘
...
...
2! = 2*1
1! = 1
对于此,我们可以了解到;
基线条件:判断 n ==1的时候,就不执行了
递归条件:通过上述公式,我们可以得出如下公式:
n!=n*(n-1)!
(n-1)! = (n-1)*(n-2)!
...
...
(n-(n-2))!=(n-(n-2))*(n-(n-1)
综上 n! = n*fn(n-1)
代码展示即:
def fn1(n):
r = n
if n == 1:
return 1
return n*fn1(n-1)
print(fn1(10))