Python中的函数返回值、作用域、说明文档

1.函数的返回值

1.1 函数的返回值

函数也可以具有返回值,该返回值会返回给函数的调用端并且语法上使用return来实现。调用函数时,可以近似的认为是函数的返回值替换掉了函数的调用。

如果在函数内没有显式使用return返回值,则返回值默认为None

示例:

def  sum(a,b):
	return a+b
c = sum(10,20)
print(c)

# output:
# 30

1.2 返回多个值

在函数中执行到return语句时,函数就会终止执行,返回给函数的调用端,这就是说,return之后的语句不会再执行。因此,函数体中不能通过执行多个return返回多个值

然而,函数返回多个值的需求却是普遍存在的,例如,我们要返回两个数的和,差,积,商(4个值)该如何实现呢?实际上,可以通过将多个值放入元组(列表或字典)中,从而返回多个值。

示例:

def func(a,b):
    result = []
    result.append(a+b)
    result.append(a-b)
    result.append(a*b)
    result.append(a/b)
    return result

res = func(10,20)
print(res)

# output:
# [30, -10, 200, 0.5]

2.命名空间与作用域

2.1 命名空间

命名空间,可以认为就是保存命名的一个容器,当我们定义变量,函数,类等结构时,相关的名称就保存在相应的命名空间中。

根据名称在当前模块(文件)中定义的位置,我们可以将名称分为两类

  • 局部名称:定义在函数内的名称(函数的参数,变量,嵌套函数等)。
  • 全局名称:定义在函数、类外的名称(处于当前模块的顶级)。

全局(局部)变量,顶层函数,嵌套函数。

命名空间可以分为如下几类:

  • 内建命名空间:保存内建的名称,例如printsum等内建函数。内建命名空间在Python解释器启动时就会创建,直到程序运行结束。
  • 全局命名空间:保存当前模块(文件)中出现在顶层位置的名称。例如:全局变量,顶层函数等。全局命名空间与模块相关,在读取模块定义时创建,直到程序运行结束。
  • 局部命名空间:保存局部名称。例如局部变量,函数参数,嵌套函数等。局部命名空间在函数执行时创建,在函数执行结束后销毁。

由此可知,我们定义的变量,函数等,其名称处于哪个命名空间完全是由其定义的位置决定的,对于变量,定义的位置就是变量第一次赋值的位置。对于函数,定义的位置就是def出现的位置。

说明:要访问某个名称,必须要在该名称定义后才能访问。

2.2 作用域

作用域,就是命名空间的有效区域。在命名空间的作用域内,我们可以直接访问命名空间中的名称。关于作用域,有如下说明:

  • 内建命名空间,作用域为所有模块(文件)。即在任何模块(文件)中均可直接访问内建命名空间内的名称。
  • 全局命名空间,作用域为当前模块(文件)。在当前模块(文件)中可直接访问全局命名空间内的名称,在其他模块(文件)中,需要先导入该模块,然后才能访问。
  • 局部命名空间,作用域为当前函数,在函数内可访问局部命名空间内的名称,在函数外则无法访问。

2.3 LEGB原则

在访问名称时,会根据LEGB的顺序来搜索名称,即搜索顺序为L -> E -> G -> B。描述如下:

  • L(Local):本地作用域,即包含所访问名称的最小作用域(当前函数的局部命名空间)。
  • E(Enclosing):外围作用域,当函数嵌套时会存在这种情况,即访问外层函数的作用域,如果没有找到,并且外层函数依然还有外层函数(函数多层嵌套),则会继续搜索更外层的函数,直到顶层函数位置(对应外层函数的局部命名空间)。
  • G(Global):全局作用域,当前模块的作用域(全局命名空间)。
  • B(Build-in:内建作用域,所有内建的名称(内建命名空间)。

对名称的访问分以下情况,这几种情况下效果是不同的,名称既可以是变量也可以是函数:

读取名称的值

当读取变量的值时,如果该变量在当前作用域没有发现,则会按照LEGB的方式依次进行查找,以先找到的为准。如果到最后也没有找到,则会产生NameError错误。


为名称赋值

当为变量赋值时,行为有所不同,此时只会在程序执行处的最小作用域内寻找变量,如果变量存在,则修改该变量的绑定,如果该变量不存在,则不会再按照LEGB的顺序查找,而是在当前作用域的命名空间中,新建名称,并绑定所赋予的值。这也就是说,我们无法修改其他命名空间中名称的绑定。

nonlocalglobal:

但是,有时候我们确实期望对外部作用域的名称进行修改,例如,嵌套函数中修改外围函数中定义的名称,或者是修改全局名称。此时,前者,我们可以使用nonlocal,后者我们可以使用global

例如:

# 在局部命名空间中,修改全局命名空间的内容
i=2
def b():
    global i	#用global声明
    i=1
b()
print(i)

# 在局部命名空间中,修改外围命名空间中的变量  
def f():
    k="abc"
    print(locals(),"外层命名空间")
    def inner():
        nonlocal k	#用nonlocal声明
        k="def"
    inner()
    print(k)
f()

# output:
# 1
# {'k': 'abc'} 外层命名空间
# def

细节:nonlocal指定的名称不存在,会产生错误,而global指定的名称不存在则不会产生错误,但是该名称也不会自动创建,如果访问该名称,依然会产生错误。

locals()globals()函数:

print(locals())#找到当前命名空间的名字
print(globals())#全局命名空间中的名字

删除名称

只能删除当前空间内。

3.Lambda表达式

对于函数而言,函数名也是一个名称,该名称绑定函数对象。因此也可以像变量那样,对函数名进行相应操作,这包括:

  • 函数名可以赋值给一个变量。
  • 函数名可以作为实际参数,传递给其他函数。
  • 函数名可以作为另外一个函数的返回值。

Lambda表达式的语法为:

lambda [参数]: 表达式

其中,参数是可选的,表达式的结果值,就是函数的返回值。lambda的主体是一个表达式,而不是一个代码块,lambda表达式的函数体仅能存在一条语句,故lambda表达式适用于功能简单的函数。也因为lambda表达式创建的函数没有名字,因此也称为匿名函数。

示例:返回a+b的值

add = lambda a,b : a+b
a =5 
b = 10
print(add(a,b))

# output:
# 15

注:pep8已经不建议使用lambda,而是推荐使用def去定义一个函数。

4.递归

4.1 递归的含义

递归,就是一个函数直接或者间接调用自身的过程
如果函数A在其函数体内直接调用其自身,则称为直接递归,如果函数A调用函数B,而函数B又调用了函数A(或者函数B再调用其他函数,而其他函数调用函数A),则称为间接递归。不过,函数如果无条件的调用自身,这是没有意义的,最终也会导致超过最大递归深度而产生错误。因此,我们必须能够找出,让递归终止的条件

递归可以分为递推回归。所谓递推,就是解决问题,逐层的进行推理,得出递归的终止点。而回归,则是利用刚才递推取得终止点的条件,再一层层返回,最终取得答案的过程。

4.2 循环与递归

循环与递归具有一定的相似度,二者往往可以实现相同的效果。理论上,能够通过循环解决的问题,使用递归也能够实现。然而,二者还是有一定的差别的。从思想的角度讲,循环是重复性的执行一件相同或者相似的事情,进而得出结果。而递归则是将问题分解为更小的问题,直到该问题能够解决,然后再由最小问题的答案,逐步回归,从而解决更大的问题,最终解决整个问题的过程

以求阶乘的程序为例,对于循环,则是对数据集中的每个数据,依次取出,重复性执行相同的累乘运算,最终得到阶乘的结果。而递归则是,将n的阶乘分解为更小数值的阶乘:

n! = n * (n – 1)!

这个过程会一直进行分解,直到分解成可以解决的问题(递推),即

1! = 1

然后,根据最小问题的答案,逐步解决更大问题(2,3,4的阶乘等),最终推导出最后的结果,即n的阶乘。

对于递归而言,其实现的是函数的逐层调用,而每调用一次函数,就会为函数分配额外的空间资源(自己调用自己也不例外),因此,从效率角度来说,递归不如循环高效。然而,对于一些复杂的问题(例如汉诺塔递归),递归可以更有效的将问题进行分解,以更容易理解的方式解决问题,从这个角度讲,递归优于循环。

4.3 递归的深度

递归的默认深度为1000。

  • sys.getrecursionlimit()
  • sys.setrecursionlimit()

该方法名多少有些误解,将其理解为最大函数调用限制会更好一些。

5.函数描述

5.1 函数说明文档

我们可以为函数编写说明文档。所谓说明文档,就相当于是函数的使用说明书,在文档中可以用来说明函数的功能,参数,返回值类型以及相关注意事项,使用示例等。要为函数编写说明文档,可以在函数体的上方,使用字符串进行标记。按照惯例,作为说明文档的字符串使用三个双引号界定,并且第一行为函数的一个简要说明,接下来是一个空行,然后是函数的具体说明。

def fun():
    """这里是函数的简要说明。

    这是是函数的具体功能说明。(注意存在一个空行)
    """
    # 函数语句

这看起来就像普通的注释,但实则不是。如果是注释内容,则解释器会忽略,但是,作为函数文档,我们可以通过函数的__doc__属性获取这些说明,方便我们进行查看。同时,我们使用help函数来获取帮助时,返回的就是函数的说明文档信息这就能够让我们在不打开函数所在的文件时,也能够方便的获取函数的帮助信息,如果是注释,则必须要定位到相应的函数,才能查看。

5.2 函数注解

Python中,因为变量是没有类型的,因此对于函数来说,我们无法像CJava语言那样,只要观看函数的定义就可以轻松的确定函数的参数与返回值类型。为了弥补这种不足使代码更具有可读性与易用性,可以使用函数注解来进行标注,显式指定函数参数或返回值的类型

def add(a: int, b: int=3) ->int:

参数的说明是在参数后面加上一个然后给出参数的类型。根据惯例与其后的类型使用一个空格分隔。返回值说明是在参数列表后面使用->指出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值