12步轻松搞定python装饰器

发布时间:2017-09-09 10:41:23
12步轻松搞定python装饰器

我没法让装饰器变得简单,但是通过一步步的剖析,我也许能够让你在理解装饰器的时候更自信一点。因为装饰器很复杂,这篇文章将会很长(自己都说很长,还敢这么多废话blablabla...前戏就不继续翻译直接省略了)

1. 函数

在python中,函数通过def关键字、函数名和可选的参数列表定义。通过return关键字返回值。我们举例来说明如何定义和调用一个简单的函数:

>>> def foo(): ... return 1 >>> foo() 1

方法体(当然多行也是一样的)是必须的,通过缩进来表示,在方法名的后面加上双括号()就能够调用函数

2. 作用域

在python中,函数会创建一个新的作用域。python开发者可能会说函数有自己的命名空间,差不多一个意思。这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。让我们写一个简单的函数看一下 本地作用域 和 全局作用域有什么不同:

>>> a_string = "This is a global variable" >>> def foo(): ... print locals() >>> print globals() {..., 'a_string': 'This is a global variable'} >>> foo() # 2 {}

内置的函数globals返回一个包含所有python解释器知道的变量名称的字典(为了干净和洗的白白的,我省略了python自行创建的一些变量)。在#2我调用了函数 foo 把函数内部本地作用域里面的内容打印出来。我们能够看到,函数foo有自己独立的命名空间,虽然暂时命名空间里面什么都还没有。

3. 变量解析规则

当然这并不是说我们在函数里面就不能访问外面的全局变量。在python的作用域规则里面,创建变量一定会一定会在当前作用域里创建一个变量,但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域里面进行查看找。所以如果我们修改函数foo的实现让它打印全局的作用域里的变量也是可以的:

>>> a_string = "This is a global variable" >>> def foo(): ... print a_string # 1 >>> foo() This is a global variable

在#1处,python解释器会尝试查找变量a_string,当然在函数的本地作用域里面是找不到的,所以接着会去上层的作用域里面去查找。
但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:

>>> a_string = "This is a global variable" >>> def foo(): ... a_string = "test" # 1 ... print locals() >>> foo() {'a_string': 'test'} >>> a_string # 2 'This is a global variable'

我们能够看到,全局变量能够被访问到(如果是可变数据类型(像list,dict这些)甚至能够被更改)但是赋值不行。在函数内部的#1处,我们实际上新创建了一个局部变量,隐藏全局作用域中的同名变量。我们可以通过打印出局部命名空间中的内容得出这个结论。我们也能看到在#2处打印出来的变量a_string的值并没有改变。

4. 变量生存周期

值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:

>>> def foo(): ... x = 1 >>> foo() >>> print x # 1 Traceback (most recent call last): ... NameError: name 'x' is not defined

#1处发生的错误不仅仅是因为作用域规则导致的(尽管这是抛出了NameError的错误的原因)它还和python以及其它很多编程语言中函数调用实现的机制有关。在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量x的值,因为它这个时候压根不存在!函数foo的命名空间随着函数调用开始而开始,结束而销毁。

5. 函数参数

python允许我们向函数传递参数,参数会变成本地变量存在于函数内部。

>>> def foo(x): ... print locals() >>> foo(1) {'x': 1}

在Python里有很多的方式来定义和传递参数,完整版可以查看 python官方文档。我们这里简略的说明一下:函数的参数可以是必须的位置参数或者是可选的命名,默认参数。

>>> def foo(x, y=0): # 1 ... return x - y >>> foo(3, 1) # 2 2 >>> foo(3) # 3 3 >>> foo() # 4 Traceback (most recent call last): ... TypeError: foo() takes at least 1 argument (0 given) >>> foo(y=1, x=3) # 5 2

在#1处我们定义了函数foo,它有一个位置参数x和一个命名参数y。在#2处我们能够通过常规的方式来调用函数,尽管有一个命名参数,但参数依然可以通过位置传递给函数。在调用函数的时候,对于命名参数y我们也可以完全不管就像#3处所示的一样。如果命名参数没有接收到任何值的话,python会自动使用声明的默认值也就是0。需要注意的是我们不能省略第一个位置参数x, 否则的话就会像#5处所示发生错误。
目前还算简洁清晰吧, 但是接下来可能会有点令人困惑。python支持函数调用时的命名参数(个人觉得应该是命名实参)。看看#5处的函数调用,我们传递的是两个命名实参,这个时候因为有名称标识,参数传递的顺序也就不用在意了。
当然相反的情况也是正确的:函数的第二个形参是y,但是我们通过位置的方式传递值给它。在#2处的函数调用foo(3,1),我们把3传递给了第一个参数,把1传递给了第二个参数,尽管第二个参数是一个命名参数。
桑不起,感觉用了好大一段才说清楚这么一个简单的概念:函数的参数可以有名称和位置。这意味着在函数的定义和调用的时候会稍稍在理解上有点儿不同。我们可以给只定义了位置参数的函数传递命名参数(实参),反之亦然!如果觉得不够可以查看官方文档

6. 嵌套函数

企业建站2800元起,携手武汉肥猫科技,做一个有见地的颜值派!更多优惠请戳:天门网站建设 http://tianmen.45qun.com