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)

没有return

  • 没有对比就没有伤害
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()

return和break的区别

  • 再三强调,函数带括号和不带括号差别很大!
    恩, fn 是返回函数对象, fn() 是调用函数
def fn():
    print('123')
print('我打印了函数 = ',fn)
print(fn())

带括号

二.文档字符串(字符串补充)

  • 你是不是很好奇文档字符串为何要出来?主要是它发出了抗议之声,3).字符串补充 你在这里都没给我补充上来,我要给你差评 ==#
  • 其实,前期文档字符串对我们并没有多大的帮助,然鹅,现在我们学的越来越多,特别是函数这块,哈哈嗝。如果有小伙伴看不懂,可以通过文档字符串进行函数的注释,从而减少因不懂而导致后期调试的失误。bug越改越多,哈哈嗝,疯了。
  • 再说文档字符串之前呢,我们先说一下help()函数
    举个栗子:

1.help()函数

  • 语法:help(函数对象)
    举个栗子:
help(print)

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的说明
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))

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))

递归函数

猜你喜欢

转载自blog.csdn.net/weixin_45095678/article/details/106661359