前情提要1. 作用域 在python中,函数会创建一个新的作用域。python开发者可能会说函数有自己的命名空间,差不多一个意思。这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。 python中的作用域分4种情况:
搜索变量的优先级顺序依次是:作用域局部 > 外层作用域 > 当前模块中的全局 > python内置作用域,也就是LEGB。 当然,local和enclosing是相对的,enclosing变量相对上层来说也是local。
在Python中,只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如if、try、for等)是不会引入新的作用域的。 让我们写一个简单的函数看一下 global 和 local 有什么不同:
内置的函数 在#2我调用了函数 我们能够看到,函数 global关键字 当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了,当修改的变量是在全局作用域(global作用域)上的,就要使用global先声明一下,代码如下:
nonlocal关键字 global关键字声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量怎么办呢,这时就需要nonlocal关键字了
2. 变量解析规则 当然这并不是说我们在函数里面就不能访问外面的全局变量。 在python的作用域规则里面,创建变量一定会一定会在当前作用域里创建一个变量, 但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域里面进行查看找。 所以如果我们修改函数
在#1处,python解释器会尝试查找变量 但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:
我们能够看到,全局变量能够被访问到(如果是可变数据类型(像list,dict这些)甚至能够被更改)但是赋值不行。 在函数内部的#1处,我们实际上 我们可以通过打印出局部命名空间中的内容得出这个结论。 我们也能看到在#2处打印出来的变量 3. 变量生存周期 值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:
#1处发生的错误不仅仅是因为 它还和python以及其它很多编程语言中函数调用实现的机制有关。 在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量 函数 4. 嵌套函数 Python允许创建嵌套函数。这意味着我们可以在函数里面定义函数而且现有的作用域和变量生存周期依旧适用。
python解释器需找一个叫 这个上层的作用域定义在另外一个函数里面。 对函数 函数 在#2处,我们调用函数
5. 闭包 我们先不急着定义什么是闭包,先来看看一段代码:
不过它会正常的运行吗?我们先来看看作用域规则。 所有的东西都在python的作用域规则下进行工作:“ 当函数 所以接着会到封闭作用域里面查找,并且会找到匹配。 但是从变量的生存周期来看,该怎么理解呢? 我们的变量 根据我们已知的python运行模式,我们没法在函数 万万没想到,返回的函数 Python支持一个叫做 嵌套定义在 这能够通过查看函数的 (只会包含被捕捉到的值,比如 记住,每次函数 闭包用途: ![]() ![]()
装饰器需要掌握知识:
写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
如果需要在 函数执行前 额外执行其他功能的话,我们就可以用到装饰器来实现~
如果我们需要在函数 f1执行前先输出一句 Start ,函数执行后输出一句 End ,那么我们可以这样做:
当写完这段代码后(函数未被执行、未被执行、未被执行),python解释器就会从上到下解释代码,步骤如下: 1.先把 def outer(func) 函数加载到内存 2.执行@outer 执行@outer 时 , 先把 index 函数 加载到内存 ! 并且内部会执行如下操作: 1.执行 outer 函数,将 index 作为参数传递 ( 此时 func = index ) 2.将 outer 函数返回值 ( return inner ),重新赋值给 index (index = inner) 然后执行下面的 result = index() => 相当于 执行了 inner() 函数!!!
问题:如果被装饰的函数如果有参数呢? ![]() ![]() 问题:可以装饰具有处理n个参数的函数的装饰器吗?
多层装饰器问题:一个函数可以被多个装饰器装饰吗?
1.先把 outer_0 、outer_1 和 index 加载到内存 2.执行 @outer_0 时, func = @outer_1 和 index 函数的结合体 3.然后执行 @outer_0 的 inner , 其中包含了 @outer_1 和index => 所以先执行 @outer_1 4.当执行 @outer_1 时, func = index , 并执行 inner -> 再执行 func 即 index 函数 (原理同 只有一个装饰器一样 , 可以 把 @outer_1 和 def index 看成一个 结成的函数 => 当作只有一个 装饰器@outer_0)
带参数的装饰器装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@show_time, 该装饰器唯一的参数就是执行业务的函数。 装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。 import time def time_logger(flag=0): def show_time(func): def wrapper(*args,**kwargs): start_time=time.time() func(*args,**kwargs) end_time=time.time() print('spend %s'%(end_time-start_time)) if flag: print('将这个操作的时间记录到日志中') return wrapper return show_time @time_logger(3) def add(*args,**kwargs): time.sleep(1) sum=0 for i in args: sum+=i print(sum) add(2,7,5) @time_logger(3) 做了两件事: (1)time_logger(3):得到闭包函数show_time,里面保存环境变量flag (2)@show_time :add=show_time(add) 上面的time_logger是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器(一个含有参数的闭包函数)。 当我们使用@time_logger(3)调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。
类装饰器再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。 使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。 import time class Foo(object): def __init__(self, func): self._func = func def __call__(self): start_time=time.time() self._func() end_time=time.time() print('spend %s'%(end_time-start_time)) @Foo #bar=Foo(bar) def bar(): print ('bar') time.sleep(2) bar() #bar=Foo(bar)()>>>>>>>没有嵌套关系了,直接active Foo的 __call__方法 注意 : 使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,: 我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。 from functools import wraps def logged(func): @wraps(func) def wrapper(*args, **kwargs): print (func.__name__ + " was called") return func(*args, **kwargs) return wrapper @logged def cal(x): return x + x * x print(cal.__name__) #cal
|
|