pyhon:返回函数、闭包

返回函数

python的函数不但可以返回int、str、list、dict等数据类型,还可以返回函数!(嵌套函数:在函数内部再定义一个函数)

def func_1(n):
    def func_2 (num,product):
        if num == 1:
            return product
        return func_2(num - 1,num * product)  #递归函数:返回函数自身
    return func-2(n,1)   #print(fun_1(5))的结果为120

注:外部函数返回的为内部的输出结果,而不是返回内部函数

上面函数就是一个简单的内嵌函数(内部函数没有引用外部函数的变量),其执行过程如下:

func_1(5)->func_2(5,1)

func_2(5,1)->func_2(4,5*4)

func_2(4,20)->func_2(3,20*3)

func_2(3,60)->func_2(2,120)

func_2(2,120)->func_2(1,120*1)

func_2(1,120)->120

例2:

def func_1(*args):
    def func_2():
        x = 0
        for num in args:   #引用外部函数的变量*args
            x += num
        return x          #该return属于函数func_2的,因此缩进要符合func_2
    return func_2         #该return属于函数func_1的,因此缩进要符合func_1,返回的为内部函数
print(func_1)

result = func_1(1,2,3,4)
print(result())

f_1 = func_1(1,2,3)
f_2 = func_1(1,2,3)
print(f_1 == f_2)

#上面代码的输出结果为:
#<function func_1 at 0x0000023BE1900268>
#10
#False

1、从上面的输出结果来看:在执行函数func_1时,没有直返回求和结果,而是返回了一串字符(这个字符其实就是函数),当执行返回的函数时,才返回的为求和的结果

2、在上面例子中,在函数func_1中又定义了函数func_2,并且内部函数func_2可以引用外部函数func_1的参数和局部变量。当func_1返回函数func_2时,相关参数和变量都保存在返回函数中,这种函数成为闭包。

3、当调用func_1函数时, 每次调用都会返回一个新的函数,即使传入相同的参数也是如此(函数的地址不同)

4、内部函数整个作用域都在外部函数之内,就像上面的例子中func_2()整个作用域都在func_1()内,因此只能在func_1函数内调用函数func_2

备注:

1、上面的例子中是将func_1()返回的函数赋值给一个变量result,返回函数指向result(resule就是一个函数),因此执行result()就是执行返回函数(在python中函数也是可以赋值给变量的)

2、上面例子的执行过程为:
第一次执行时:x = 0,num = 1 -> x = 1
第二次执行时:x = 1,num = 2 -> x = 3
第三次执行时:x = 3,num = 3 -> x = 6
第四次执行时:x = 6,num = 4 -> x = 10  
注:由执行过程可以看出:当参数或变量为循环变量(或后续会发生变化的变量)时,执行一次后,会影响到后面的结果。因此函数的最后结果为10
例3:

def line_conf(a, b):
    def line(x):
        return a*x + b

    return line


line1 = line_conf(1, 1)    #返回的函数为:x + 1
line2 = line_conf(4, 5)    #返回的函数为:4x + 5

print(line1(5), line2(5))
print(line_conf(1, 1)(5))  #执行函数返回函数的另一种写法

#上面代码的输出结果为:6,25,6(变量就是固定且不循环的)

闭包:
1、如果在一个内部函数里对外部函数(不是在全局作用域)的变量进行引用,内部函数就被认为是闭包
例4:

def count():
    fs = []
    for i in range(1,4):  #1,2,3
        def f():
            return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count() #f1,f2,f3指向执行count()函数后的返回函数
print(f1(),f2(),f3())  # 执行返回的函数
#上面的代码输出结果为:9 9 9

结果全部都是9. 不是预期的1,4,9。原因是返回函数引用了变量i(i为循环变量),下面来解析一下f1,f2,f3=count()这句的执行过程:
   当i=1, 执行for循环, 结果返回函数f的函数地址,存在列表fs中的第一个位置上。
   当i=2, 由于fs列表中第一个元素所指的函数中的i是count函数的局部变量,i也指向了2;然后执行for循环, 结果返回函数f的函数地址,存在列表fs中的第二个位置上。
   当i=3, 同理,在fs列表第一个和第二个元素所指的函数中的i变量指向了3; 然后执行for循环, 结果返回函数f的函数地址,存在列表fs中的第三个位置上。
所以在调用f1()的时候,函数中的i是指向3的:
 

 f1():
     return 3*3

同理f2(), f3()结果都为9
即:由于返回的函数引用了变量i,但它并非立刻执行。等到三个函数都返回后,他们所引用的变量i已经变成了3,因此最终结果为9

备注:返回闭包时,返回函数不要引用任何循环变量或后续会发生变化的变量。即:包在里面的函数,不要引用外部函数的任何循环变量

2、如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变
例5:

def count():
    fs = []
    for i in range(1,4):
        def f(j):
            def g():
                return j*j
            return g
        fs.append(f(i))
    return fs

f1, f2, f3 = count()
print (f1())
print (f2())
print (f3())
#上面代码的输出结果为:1,4,9

当i=1时,f(1)即让j指向1,
当i=2时,f(2)即让j指向2,此时j不是count的局部变量,不会影响到i=1是f(1)中j的指向。即函数f的参数绑定循环变量当前的值, 而不是循环变量本身。

3、在闭包中,外部函数的局部变量对应内部函数的局部变量,在内部函数中只能对外部函数的局部变量进行访问,但不能对其进行修改,否则会报错
例6:

def fun_1():
    x = 5
    def func_2():
        x += 1
        return x
    return func_2
fun_1()()
#上面代码的输出结果为:UnboundLocalError: local variable 'x' referenced before assignment

4、如果要在内部函数中对外部函数的局部变量进行修改的话可以使用关键字nonlocal,其使用方法与global
例3:

def fun_1():
    x = 5
    def func_2():
        nonlocal x
        x += 1
        return x
    return func_2
print(fun_1()())
#上面代码的输出结果为:6

拓展:
1、python循环中不包含域的概念

例1:

list = []
for i in range(3):
    def func(x):
        return x * i
    list.append(func)
for f in list:
    print(f(2))
#上面代码的输出结果为:4,4,4

按照大家正常的理解,应该输出的是0, 2, 4对吧?但实际输出的结果是:4, 4, 4. 原因是什么呢?loop(循环)在python中是没有域的概念的,list在向列表中添加func的时候,并没有保存i的值,而是当执行f(2)的时候才去取,这时候循环已经结束,i的值是2,所以结果都是4。

闭包的作用:
1、当闭包执行完后,仍然能够保持住当前的运行环境。
例7:

origin = [0, 0]  # 坐标系统原点
legal_x = [0, 50]  # x轴方向的合法坐标
legal_y = [0, 50]  # y轴方向的合法坐标

def create(pos = origin):
    def player(direction, step):
        # 这里应该首先判断参数direction,step的合法性,比如direction不能斜着走,step不能为负等
        # 然后还要对新生成的x,y坐标的合法性进行判断处理,这里主要是想介绍闭包,就不详细写了。
        new_x = pos[0] + direction[0] * step
        new_y = pos[1] + direction[1] * step
        pos[0] = new_x
        pos[1] = new_y
        # 注意!此处不能写成 pos = [new_x, new_y],原因在上文有说过
        return pos

    return player

player = create()  # 创建棋子player,起点为原点
print(player([1, 0], 10))  # 向x轴正方向移动10步
print(player([0, 1], 20) ) # 向y轴正方向移动20步
print(player([-1, 0], 10))  # 向x轴负方向移动10步
#上面代码的输出结果为:
#[10, 0]
#[10, 20]
#[0, 20]

2、目前我对闭包的写法的理解就是:A函数可以实现某个功能,我们用B函数去返回A函数,且A函数的变量是B函数的局部变量

3、后面需要学习的装饰器

一个函数的输出作为另一个函数的输入参数

例1:

def func(a, b):
    return a + b

def func_1(c, d):
    num = c + d
    return num

num_4 = func(1, 2)
print(func_1(3, num_4))

#上面代码的输出结果为:6

例2:

import os
import sys
import subprocess

def get_manifest_xml_path():
    xml_path = input()
    if os.path.exists( xml_path ):
        return xml_path
    else:
        print('AndroidManifest.xml not found!')


def get_out_path( path ):
    return os.path.dirname( os.path.abspath( path ) ) + os.sep + 'AndroidManifest.txt'


def convert_xml_to_txt( xml_path, out_path ):
    convert_cmd = 'java -jar AXMLPrinter2.jar %s>%s' % ( xml_path, out_path )
    subprocess.Popen( convert_cmd, shell=True )

if __name__ == "__main__":
    xml_path = get_manifest_xml_path()
    out_path = get_out_path( xml_path )
    convert_xml_to_txt( xml_path, out_path )

猜你喜欢

转载自blog.csdn.net/qq_39314932/article/details/80386003