鱼C论坛

 找回密码
 立即注册
12
返回列表 发新帖
楼主: leftjay

这个函数问题问了很多大神,还是没听明白。。谁能通俗易懂讲清楚啊

[复制链接]
 楼主| 发表于 2018-5-21 18:33:01 | 显示全部楼层
人造人 发表于 2018-5-17 22:47
def FunX(x):
        def FunY(y):
                print(y)

不好意思,前几天出差去了,现在回复不知道你还能看到不,我的疑问是
第2步的第1步,调用FunY
                现在执行FunY
                执行print(y),打印出 3
                FunY执行完毕,返回,如果没有写返回值,默认返回None
执行print(y),打印出 3, FunY执行完毕,返回。对于这句话,执行print(Y)的时候并不知道y=3,那怎么说打印出3的。是不是程序跳过了print(y),先进行的下一步return FunY(3)所以才能解释为什么y=3然后返回到上一步,打印出3?
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2018-5-21 18:47:51 | 显示全部楼层
这是Python 中所谓的闭包,好好看看这篇文章,什么都明白了。

1.闭包(closure)是函数式编程的重要的语法结构

函数式编程是一种编程范式,面向过程编程和面向对象编程也都是编程范式。
在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object),使用函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability)。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。

在Python中,一切皆对象,函数这一语法结构也是一个对象。在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。

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

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5), line2(5))

这个例子中,函数line与环境变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b,说明了这两个环境变量的取值,确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。
如果没有闭包,我们每次创建直线函数的时候,需要同时说明a,b,x,需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函数。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。

2.Python中闭包的实质

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。

一般情况下,如果一个函数结束,函数的内部所有东西都会释放掉,清除所占内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就会把这个临时变量绑定给了内部函数,然后自己再结束。
  
#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
      b = 10
      # inner是内函数
      def inner():
          #在内函数中用到了外函数的临时变量
          print(a+b)
      # 外函数的返回值是内函数的引用
     return inner

if __name__ == '__main__':
     # 在这里我们调用外函数传入参数5
     #此时外函数两个临时变量 a是5, b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
     # 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
     demo = outer(5)
     # 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
     # demo存了外函数的返回值,也就是inner函数的引用。
     demo()     # 15

     demo2 = outer(7)
     demo2()    #17

2.1 引用的理解
在python中一切都是对象,包括整型数据1,字母,函数等其实都是对象。当我们进行a=1的时候,实际上在内存当中有一个地方存了值1,然后用a这个变量名指向了1所在内存的位置。引用就好像c语言里的指针,大家可以把引用理解成地址。a只不过是一个变量名字,a里面存的是1这个数值所在的地址,就是a里面存了数值1的引用。

当我们在python中定义一个函数def demo():的时候,内存当中会开辟一些空间,存下这个函数的代码、内部的局部变量等等。这个demo只不过是一个变量名字,它里面存了这个函数所在位置的引用而已。我们还可以进行x = demo, y = demo, 这样的操作就相当于,把demo里存的东西赋值给x和y,这样x 和y 都指向了demo函数所在的引用,在这之后我们可以用x() 或者 y() 来调用我们自己创建的demo() ,调用的实际上根本就是一个函数,x、y和demo三个变量名存了同一个函数的引用。

对于闭包,在外函数outer中,最后return inner,我们在调用外函数 demo = outer() 的时候,outer返回了inner,inner是一个函数的引用,这个引用被存入了demo中。所以接下来我们再进行demo()()的时候,相当于运行了inner()函数。

一个函数,如果函数名后紧跟一对括号,相当于要调用这个函数;如果不跟括号,则相当于引用一个函数,里面保存了函数所在位置。

2.2 外函数把临时变量绑定给内函数
一般情况下,一个函数结束的时候,会把自己的临时变量都释放,清空所占内存,之后变量都不存在了。但是闭包是一个特别的情况,外函数发现自己的临时变量会在将来的内函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量送给内函数,并绑定在一起。所以外函数已经结束了,调用内函数的时候仍然能够使用外函数的临时变量。

在上面编写的实例中,我两次调用外部函数outer,分别传入的值是5和7。内部函数只定义了一次,我们发现调用的时候,内部函数是能识别外函数的临时变量是不一样的。Python中一切都是对象,虽然函数我们只定义了一次,但是外函数在运行的时候,实际上是按照里面代码执行的,外函数里创建了一个函数,我们每次调用外函数,它都创建一个内函数,虽然代码一样,但是却创建了不同的对象,并且把每次传入的临时变量数值绑定给内函数,再把内函数引用返回。虽然内函数代码是一样的,但我们每次调用外函数,都返回不同的实例对象的引用,他们的功能是一样的,但是它们实际上不是同一个函数对象。

2.3 闭包中内函数修改外函数局部变量
  
在基本的Python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:一是global 声明全局变量。二是全局变量是可变类型数据的时候可以修改。
  
在闭包内函数也是类似的情况,在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候,在python3中,可以用nonlocal关键字声明一个变量,表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
    
#修改闭包变量的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
     b = 10  # a和b都是闭包变量
     
     # inner是内函数
     def inner():
         #内函数中想修改闭包变量
         # nonlocal关键字声明
         nonlocal b
         b+=1
         print(b)
     # 外函数的返回值是内函数的引用
     return inner

if __name__ == '__main__':
      demo = outer(5)
      demo()    #11
在内函数中,对闭包变量进行了修改,打印出来的结果也确实是修改之后的结果。

3.闭包的作用
装饰器:装饰器是做什么的?装饰器能在不改变原来函数的同时,扩充函数功能。例如:我们工作中写了一个登录程序,现在想统计这个程序执行花了多长时间,而又不能改变原来的程序,我们就可以用装饰器装饰这个登录模块,由装饰器帮我们完成登录函数执行之前和之后的时间。
面向对象:从上面的分析,我们可以发现外函数的临时变量送给了内函数。大家回想一下类对象的情况,对象有好多类似的属性和方法,所以我们创建类,用类创建出来的对象都具有相同的属性和方法,因此,闭包也是实现面向对象的方法之一。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2018-5-21 19:39:42 | 显示全部楼层
leftjay 发表于 2018-5-21 18:33
不好意思,前几天出差去了,现在回复不知道你还能看到不,我的疑问是
第2步的第1步,调用FunY
         ...

仔细分析这个输出结果


  1. def FunX(x):
  2.         print("开始执行FunX")
  3.         print("开始定义FunY")
  4.         def FunY(y):
  5.                 print("开始执行FunY")
  6.                 print(y)
  7.                 print("执行完成FunY")
  8.         print("结束定义FunY")
  9.         print("开始返回FunY函数的结果,这实际上是2步,1执行FunY,2返回执行完FunY得到的结果")
  10.         return FunY(3)
  11.         print("这个和下面的 'return FunY(0)'不会被执行")
  12.         return FunY(0)

  13. FunX(0)

  14. '''
  15. 想知道哪一个先执行哪一个后执行很难吗?
  16. 把你认为所有可能的位置都写print,然后运行这个程序,证明你的猜想
  17. '''
复制代码


225140dm22lld5iw661826.png



想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2018-5-21 21:04:33 | 显示全部楼层
人造人 发表于 2018-5-21 19:39
仔细分析这个输出结果

啊,好抽象,还是没能理由。或者你先告诉我程序走到print(y)的时候可以打印出3,是不是因为程序先跳过了这步,看到了下面的return FunY(3)所以知道Y=3,然后又倒退到上一步print(y),把3打印了出来
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2018-5-21 21:24:27 | 显示全部楼层
leftjay 发表于 2018-5-21 21:04
啊,好抽象,还是没能理由。或者你先告诉我程序走到print(y)的时候可以打印出3,是不是因为程序先跳过 ...

1.png
2.png
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2018-5-21 22:08:20 | 显示全部楼层

看到了右边的顺序,我表示完全不理解为什么会是这样。。。。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2018-5-21 22:19:51 | 显示全部楼层
leftjay 发表于 2018-5-21 22:08
看到了右边的顺序,我表示完全不理解为什么会是这样。。。。

这个能理解吗?

1.png
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-4-26 16:58

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表