好吧,我标题党了。作为 Python 教师,我发现理解装饰器是学生们从接触后就一直纠结的问题。那是因为装饰器确实难以理解!想弄明白装饰器,需要理解一些函数式编程概念,并且要对Python中函数定义和函数调用语法中的特性有所了解。使用装饰器非常简单(见步骤10),但是写装饰器却很复杂。
虽然我没法让装饰器变得简单,但也许通过将问题进行一步步的讲解,可以帮助你更容易理解装饰器。由于装饰器较为复杂,文章会比较长,请坚持住!我会尽量使每个步骤简单明了,这样如果你理解了各个步骤,就能理解装饰器的原理。本文假定你具备最基础的 Python 知识,另外本文对工作中大量使用 Python 的人将大有帮助。
此外需要说明的是,本文中 Python 代码示例是用 doctest
模块来执行的。代码看起来像是交互式 Python 控制台会话(>>>
和 …
表示 Python 语句,输出则另起一行)。偶然有以“doctest”开头的“奇怪”注释——那些只是 doctest
的指令,可以忽略。
1. 函数
在 Python 中,使用关键字 def
和一个函数名以及一个可选的参数列表来定义函数。函数使用 return
关键字来返回值。定义和使用一个最简单的函数例子:
1 2 3 4 |
>>> def foo(): ... return 1 >>> foo() 1 |
函数体(和 Python 中所有的多行语句一样)由强制性的缩进表示。在函数名后面加上括号就可以调用函数。
2. 作用域
在 Python 函数中会创建一个新的作用域。Python 高手也称函数有自己的命名空间。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。
1 2 3 4 5 6 7 |
>>> a_string = "This is a global variable" >>> def foo(): ... print locals() >>> print globals() # doctest: +ELLIPSIS {..., 'a_string': 'This is a global variable'} >>> foo() # 2 {} |
内建函数 globals
返回一个包含所有 Python 能识别变量的字典。(为了更清楚的描述,输出时省略了 Python 自动创建的变量。)在注释 #2
处,调用了 foo
函数,在函数中打印局部变量的内容。从中可以看到,函数 foo
有自己单独的、此时为空的命名空间。
3. 变量解析规则
当然,以上并不意味着我们不能在函数内部使用全局变量。Python 的作用域规则是, 变量的创建总是会创建一个新的局部变量但是变量的访问(包括修改)在局部作用域查找然后是整个外层作用域来寻找匹配。所以如果修改 foo
函数来打印全部变量,结果将是我们希望的那样:
1 2 3 4 5 |
>>> a_string = "This is a global variable" >>> def foo(): ... print a_string # 1 >>> foo() This is a global variable |
在 #1
处,Python 在函数 foo
中搜索局部变量 a_string
,但是没有找到,然后继续搜索同名的全局变量。
另一方面,如果尝试在函数里给全局变量赋值,结果并不是我们想要的那样:
1 2 3 4 5 6 7 8 |
>>> 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' |
从上面代码可见,全部变量可以被访问(如果是可变类型,甚至可以被修改)但是(默认)不能被赋值。在函数 #1
处,实际上是创建了一个和全局变量相同名字的局部变量,并且“覆盖”了全局变量。通过在函数 foo
中打印局部命名空间可以印证这一点,并且发现局部命名空间有了一项数据。在 #2
处的输出可以看到,全局命名空间里变量 a_string
的值并没有改变。
4. 变量生命周期
值得注意的是,变量不仅是在命名空间中有效,它们也有生命周期。思考下面的代码:
1 2 3 4 5 6 7 |
>>> 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 允许向函数传递参数。形参名在函数里为局部变量。
1 2 3 4 |
>>> def foo(x): ... print locals() >>> foo(1) {'x': 1} |
Python 有一些不同的方法来定义和传递函数参数。想要深入的了解,请参考 Python 文档关于函数的定义。来说一个简单版本:函数参数可以是强制的位置参数或者可选的有默认值的关键字参数。
好吧,我标题党了。作为 Python 教师,我发现理解装饰器是学生们从接触后就一直纠结的问题。那是因为装饰器确实难以理解!想弄明白装饰器,需要理解一些函数式编程概念,并且要对Python中函数定义和函数调用语法中的特性有所了解。使用装饰器非常简单(见步骤10),但是写装饰器却很复杂。
虽然我没法让装饰器变得简单,但也许通过将问题进行一步步的讲解,可以帮助你更容易理解装饰器。由于装饰器较为复杂,文章会比较长,请坚持住!我会尽量使每个步骤简单明了,这样如果你理解了各个步骤,就能理解装饰器的原理。本文假定你具备最基础的 Python 知识,另外本文对工作中大量使用 Python 的人将大有帮助。
此外需要说明的是,本文中 Python 代码示例是用 doctest
模块来执行的。代码看起来像是交互式 Python 控制台会话(>>>
和 …
表示 Python 语句,输出则另起一行)。偶然有以“doctest”开头的“奇怪”注释——那些只是 doctest
的指令,可以忽略。
1. 函数
在 Python 中,使用关键字 def
和一个函数名以及一个可选的参数列表来定义函数。函数使用 return
关键字来返回值。定义和使用一个最简单的函数例子:
1 2 3 4 |
>>> def foo(): ... return 1 >>> foo() 1 |
函数体(和 Python 中所有的多行语句一样)由强制性的缩进表示。在函数名后面加上括号就可以调用函数。
2. 作用域
在 Python 函数中会创建一个新的作用域。Python 高手也称函数有自己的命名空间。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。
1 2 3 4 5 6 7 |
>>> a_string = "This is a global variable" >>> def foo(): ... print locals() >>> print globals() # doctest: +ELLIPSIS {..., 'a_string': 'This is a global variable'} >>> foo() # 2 {} |
内建函数 globals
返回一个包含所有 Python 能识别变量的字典。(为了更清楚的描述,输出时省略了 Python 自动创建的变量。)在注释 #2
处,调用了 foo
函数,在函数中打印局部变量的内容。从中可以看到,函数 foo
有自己单独的、此时为空的命名空间。
3. 变量解析规则
当然,以上并不意味着我们不能在函数内部使用全局变量。Python 的作用域规则是, 变量的创建总是会创建一个新的局部变量但是变量的访问(包括修改)在局部作用域查找然后是整个外层作用域来寻找匹配。所以如果修改 foo
函数来打印全部变量,结果将是我们希望的那样:
1 2 3 4 5 |
>>> a_string = "This is a global variable" >>> def foo(): ... print a_string # 1 >>> foo() This is a global variable |
在 #1
处,Python 在函数 foo
中搜索局部变量 a_string
,但是没有找到,然后继续搜索同名的全局变量。
另一方面,如果尝试在函数里给全局变量赋值,结果并不是我们想要的那样:
1 2 3 4 5 6 7 8 |
>>> 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' |
从上面代码可见,全部变量可以被访问(如果是可变类型,甚至可以被修改)但是(默认)不能被赋值。在函数 #1
处,实际上是创建了一个和全局变量相同名字的局部变量,并且“覆盖”了全局变量。通过在函数 foo
中打印局部命名空间可以印证这一点,并且发现局部命名空间有了一项数据。在 #2
处的输出可以看到,全局命名空间里变量 a_string
的值并没有改变。
4. 变量生命周期
值得注意的是,变量不仅是在命名空间中有效,它们也有生命周期。思考下面的代码:
1 2 3 4 5 6 7 |
>>> 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 允许向函数传递参数。形参名在函数里为局部变量。
1 2 3 4 |
>>> def foo(x): ... print locals() >>> foo(1) {'x': 1} |
Python 有一些不同的方法来定义和传递函数参数。想要深入的了解,请参考 Python 文档关于函数的定义。来说一个简单版本:函数参数可以是强制的位置参数或者可选的有默认值的关键字参数。