重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
在Python语言中,可以在函数中定义函数。 这种在函数中嵌套定义的函数也叫内部函数。我们来看下面的代码:
创新互联主营双江网站建设的网络公司,主营网站建设方案,app软件开发公司,双江h5微信平台小程序开发搭建,双江网站营销推广欢迎双江等地区企业咨询
上述代码中,定义了函数greet,在函数greet内部又定义了一个函数inner_func, 并调用该函数打印了一串字符。
我们可以看到,内部函数inner_func的定义和使用与普通函数基本相同。需要注意的是变量的作用域,在上述代码中,函数参数name对于全局函数greet是局部变量,对内部函数inner_func来说则是非局部变量。内部函数对于非局部变量的访问规则类似于标准的外部函数访问全局变量。
从这个例子我们还可以看到内部函数的一个作用,就是通过定义内部函数的方式将一些功能隐藏起来,防止外部直接调用。常见的场景是,在一个复杂逻辑的函数中,将一些小的任务定义成内部函数,然后由这个外层函数使用,这样可以使代码更为清晰,易于维护。这些内部函数只会在这个外层函数中使用,不能被其他函数或模块使用。
在Python语言中, 函数也是对象,它可以被创建、赋值给变量,或者作为函数的返回值。我们来看下面这个例子。
在上述代码中,在函数gen_greet内部定义了inner_func函数,并返回了一个inner_func函数对象。外部函数gen_greet返回了一个函数对象,所以像gen_greet这样的函数也叫工厂函数。
在内部函数inner_func中,使用了外部函数的传参greet_words(非局部变量),以及函数的参数name(局部变量),来打印一个字符串。
接下来,调用gen_greet("Hello")创建一个函数对象say_hello,紧接着调用say_hello("Mr. Zhang"),输出的结果为:Hello, Mr. Zhang!
同样的,调用gen_greet("Hi")创建一个函数对象say_hi,调用say_hello("Mr. Zhang"),输出的结果为:Hi,Tony!
我们可以发现,gen_greet返回的函数对象具有记忆功能,它能够把所需使用的非局部变量保存下来,用于后续被调用的时候使用。这种保存了非局部变量的函数对象被称作闭包(closure)。
那么闭包是如何实现的呢?其实并不复杂,函数对象中有一个属性__closure__,它就是在创建函数对象时用来保存这些非局部变量的。
__closure__属性是一个元组或者None类型。在上述代码中,我们可以通过下面方式查看:
函数的嵌套所实现的功能大都可以通过定义类的方式来实现,而且类是更加面向对象的代码编写方式。
嵌套函数的一个主要用途是实现函数的装饰器。我们看下面的代码:
在上述代码中,logger函数返回函数with_logging,with_logging则是打印了函数func的名称及传入的参数,然后调用func, 并将参数传递给func。其中的@wraps(func)语句用于复制函数func的名称、注释文档、参数列表等等,使得with_logging函数具有被装饰的函数func相同的属性。
代码中接下来用@logger对函数power_func进行修饰,它的作用等同于下面的代码:
可见,装饰器@符其实就是上述代码的精简写法。
通过了解了嵌套函数和闭包的工作原理,我们在使用过程中就能够更加得心应手了。
之前 分析了装饰器的语法,由此可以直接推导出其基本框架。但为了写出一个功能完整的装饰器,还需要了解一个概念——闭包。
闭包(closure) ,是引用了自由变量的函数。 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
看下面的例子
对 f 内部的函数 g 来说,参数 a 既不是它的参数,也不是它的局部变量,而是它的自由变量。该自由变量可以
闭包和嵌套函数的概念有所区别。闭包当然是嵌套函数,但没有引用自由变量的嵌套函数却不是闭包。
Python 的函数有一个只读属性 __closure__ ,存储的就是函数所引用的自由变量,
如果仅仅是嵌套函数,它的 __closure__ 应该是 None 。
闭包有个重要的特性:内部函数只能引用而不能修改外部函数中定义的自由变量。试图直接修改只有两种结果,要么运行出错,要么你以为你修改了,实际并没有。
不能修改不是因为 Python 设计者故意限制,不给它权限,而是外部的自由变量被内部的局部变量覆盖了;被覆盖了也不是闭包独有的特性,从普通函数内部同样也不能直接修改全局变量。Python 命名空间的查找规则简写为 LEGB,四个字母分别代表 local、enclosed、global 和 build-in,闭包外层函数的命名空间就是 enclosed。Python 在检索变量时,按照 L - E - G - B 的顺序依次查找,如果在 L 中找到了变量,就不会继续向后查找。
在示例 1 中,你的本意是修改自由变量 number ,然而并不能:由于存在对 number 的赋值语句( number += 1 ),Python 会认为 number 是 printer 的局部变量,可是在局部变量字典中又查找不到它的定义,只好抛出异常。抛出的异常不是因为不能修改自由变量,而是局部变量还没赋值就被引用了。
在示例 2 中,Python 成功地在 printer 内定义了局部变量 number ,并覆盖了同名自由变量,你可能以为自己成功修改了 print_msg 中的 number ,然而并没有。
怎么才能修改呢?
一种做法是利用可变类型(mutable)的特性,把变量存放在列表(List)之中。对可变的列表的修改并不需要对列表本身赋值, number[0] = 3 只是修改了列表元素。虽然列表发生了变化,但引用列表的变量却并没有改变,巧妙地“瞒”过了 Python。见示例3。
Python 3 引入了 nonlocal 关键字,明确告诉解释器:这不是局部变量,要找上外头找去。在示例 4 中, nonlocal 帮助我们实现了所期望的对自由变量的修改。
其实,在 Python 2 中,用 global 代替 nonlocal ,也能达到类似的效果,但由于全局变量的不易控制,这种做法不被提倡。
下面的例子很好地展示了自由变量的特点:与引用它的函数一同存在,而想要修改它,得小心谨慎。
装饰器 rate_limit 的作用,是限制被装饰的函数每秒内最多被访问 max_per_sec 次。为此,需要维护一个变量用以记录上次被调用的时刻,它独立于函数之外,和被修饰的函数一同存在,还能在每次被调用的时候更新。 last_time_called 就是这样的变量。为了正确地更新, last_time_called 以列表的形式存在。如果在 Python 3 中,它也可以直接存为 float ,只要在内部函数中声明为 nonlocal ,也可以达到同样的目的。
在函数中可以定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。
闭包可以用来在一个函数与一组私有变量之间创建关联关系。
在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。
形成闭包的三个条件
必须有一个内嵌函数—这对应函数之间的嵌套;
内嵌函数必须引用一个定义在闭合范围内的变量—内部函数引用外部变量;
外部函数必须返回内嵌函数—必须返回内部函数。
换句话来说:闭包的概念很简单,一个可以引用在函数闭合范围内变量的函数,即内部函数,只有那个内部函数才有所谓的__closure__属性。
闭包的原理
形成闭包之后,闭包函数会获得一个非空的_Closure_属性,这个属性是一个元组。
组里面的对象为cell对象,而访问cell对象的cell_contents属性则可以得到闭包变量的当前值。
而随着闭包的继续调用,变量会进行再次更新。由此可见,一般形成闭包之后,Python确定会将_closure_和闭包函数绑定作为储存闭包变量的场所。
闭包的好处是什么?
其实,闭包并不是必须的。
没有闭包的话,Python的功能不会受到任何影响;但有了闭包之后,可以提供一种额外的解决方案。
在python中,函数可以被嵌套定义,也就是说,函数中可以定义函数。该函数还可以将其内部定义的函数作为返回值返回。
闭包的定义:一般来说,我们可以认为,如果一个函数可以读取其他函数中的局部变量,那么它们就构成了闭包。
注意 :闭包的定义不是特别清晰,但大体上的意思是这样的。
我们知道,普通的函数是可以使用全局变量的
类似的,函数中定义的函数,也是可以使用外部函数的变量的。因此,满足了函数读取了其他函数局部变量的这一条件,他们因此构成了闭包。
在闭包的使用中,我们可以先给外部的函数赋予不同的局部变量,然后再调用其中内部的函数时,就可以读取到这些不同的局部变量了。
外部变量的使用 在普通函数中,虽然可以直接使用全局变量,但是不可以直接修改全局变量。从变量的作用域来说,一旦你尝试修改全局变量,那么就会尝试创建并使用一个同名的局部变量。因此,如果你需要在普通函数中修改全局变量,需要使用global
同样的,如果你希望通过定义在内部的函数去修改其外部函数的变量,那么必须使用nonlocal