最近在学习python装饰器,本篇是根据很多博客学习整理而来,做个学习笔记!!
1. 函数
在python中,函数通过def关键字、函数名和可选的参数列表定义。通过return关键字返回值。我们举例来说明如
何定义和调用一个简单的函数:
方法体是必须的,通过缩进来表示,在方法名的后面加上双括号()就能够调用函数.
2. 作用域
在python中,函数会创建一个新的作用域。python开发者可能会说函数有自己的命名空间,差不多一个意思。这意味着
在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。
内置的函数globals返回一个包含所有python解释器知道的变量名称的字典,当调用了函数foo把函数内部本地作用域里面
的内容打印出来。我们能够看到,函数foo有自己独立的命名空间,虽然暂时命名空间里面什么都还没有。
3. 变量解析规则
当然这并不是说我们在函数里面就不能访问外面的全局变量。在python的作用域规则里面,创建变量一定会一定会在当前
作用域里创建一个变量,但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合
的作用域里面进行查看找。所以如果我们修改函数foo的实现让它打印全局的作用域里的变量也是可以的:
python解释器会尝试查找变量a_string,当然在函数的本地作用域里面是找不到的,所以接着会去上层的作用域里面去查找。
但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:
我们能够看到,全局变量能够被访问到(如果是可变数据类型(像list,dict这些)甚至能够被更改)但是赋值不行。在函数内部
我们实际上新创建了一个局部变量,隐藏全局作用域中的同名变量。我们可以通过打印出局部命名空间中的内容得出这个结论。
我们也能看到打印出来的变量a_string的值并没有改变依旧是定义的全局变量的值。
4. 变量生存周期
值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:
发生的错误不仅仅是因为作用域规则导致的(尽管这是抛出了NameError的错误的原因)它还和python以及其它很多编程
语言中函数调用实现的机制有关。在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量x的值,因为它
这个时候压根不存在!函数foo的命名空间随着函数调用开始而开始,结束而销毁。
5. 函数参数
python允许我们向函数传递参数,参数会变成本地变量存在于函数内部。
函数的参数可以是必须的位置参数或者是可选的命名,默认参数。
6. 嵌套函数
Python允许创建嵌套函数。这意味着我们可以在函数里面定义函数而且现有的作用域和变量生存周期依旧适用。
想一想在#1发生了什么:python解释器需找一个叫x的本地变量,查找失败之后会继续在上层的作用域里面寻找,这个上层
的作用域定义在另外一个函数里面。对函数outer来说,变量x是一个本地变量,但是如先前提到的一样,函数inner可以访问
封闭的作用域(至少可以读和修改)。在#2处,我们调用函数inner,非常重要的一点是,inner也仅仅是一个遵循python变量
解析规则的变量名,python解释器会优先在outer的作用域里面对变量名inner查找匹配的变量.
7.函数的属性,python一切皆对象,函数在python里面也是对象
简单的函数传参:
8.闭包:
所有的东西都在python的作用域规则下进行工作:“x是函数outer里的一个局部变量。当函数inner在#1处打印x的时候,
python解释器会在inner内部查找相应的变量,当然会找不到,所以接着会到封闭作用域里面查找,并且会找到匹配。
但是从变量的生存周期来看,该怎么理解呢?我们的变量x是函数outer的一个本地变量,这意味着只有当函数outer正在运行的
时候才会存在。根据我们已知的python运行模式,我们没法在函数outer返回之后继续调用函数inner,在函数inner被调用的时候,
变量x早已不复存在,可能会发生一个运行时错误。
记住,每次函数outer被调用的时候,函数inner都会被重新定义。现在变量x的值不会变化,所以每次返回的函数inner会是同样
的逻辑,假如我们稍微改动一下呢?
从这个例子中你能够看到闭包 – 被函数记住的封闭作用域 – 能够被用来创建自定义的函数,本质上来说是一个硬编码的
参数。事实上我们并不是传递参数1或者2给函数inner,我们实际上是创建了能够打印各种数字的各种自定义版本。
装饰器
装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数。
写一个有用的装饰器:
想象我们有一个库,这个库能够提供类似坐标的对象,也许它们仅仅是一些x和y的坐标对。不过可惜的是这些坐标对象不支持
数学运算符,而且我们也不能对源代码进行修改,因此也就不能直接加入运算符的支持。我们将会做一系列的数学运算,所以
我们想要能够对两个坐标对象进行合适加减运算的函数,这些方法很容易就能写出:
如果不巧我们的加减函数同时也需要一些边界检查的行为那该怎么办呢?搞不好你只能够对正的坐标对象进行加减操作,
任何返回的值也都应该是正的坐标。所以现在的期望是这样:
将输出里面负值的x和y替换成0,写一个边界检查装饰器!
10. 使用 @ 标识符将装饰器应用到函数
第一个函数one只是简单地讲任何传递过来的位置参数全部打印出来而已,在代码#1处我们只是引用了函数内的变量args,
*args仅仅只是用在函数定义的时候用来表示位置参数应该存储在变量args里面。Python允许我们制定一些参数并且通过
args捕获其他所有剩余的未被捕捉的位置参数,就像#2处所示的那样。
*操作符在函数被调用的时候也能使用,意义基本是一样的。当调用一个函数的时候,一个用*标志的变量意思是变量里面的
内容需要被提取出来然后当做位置参数被使用。同样的,来看个例子:
#1处的代码和#2处的代码所做的事情其实是一样的,*args要么是表示调用方法大的时候额外的参数可以从一个可迭代
列表中取得,要么就是定义方法的时候标志这个方法能够接受任意的位置参数。
接下来提到的**会稍多更复杂一点,**代表着键值对的字典,和*所代表的意义相差无几,也很简单对不对:
当我们定义一个函数的时候,我们能够用**kwargs来表明,所有未被捕获的关键字参数都应该存储在kwargs的字典中。
如前所诉,argshe kwargs并不是python语法的一部分,但在定义函数的时候,使用这样的变量名算是一个不成文的约定。
和*一样,我们同样可以在定义或者调用函数的时候使用**。
我们定义的函数inner,它能够接受任意数量和类型的参数并把它们传递给被包装的方法,这让我们能够用这个装饰器
来装饰任何方法。随便调用我们定义的哪个方法,相应的日志也会打印到输出窗口,和我们预期的一样。