简单 12 步理解 Python 装饰器
admin
2023-07-31 00:45:15
0

好吧,我标题党了。作为 Python 教师,我发现理解装饰器是学生们从接触后就一直纠结的问题。那是因为装饰器确实难以理解!想弄明白装饰器,需要理解一些函数式编程概念,并且要对Python中函数定义和函数调用语法中的特性有所了解。使用装饰器非常简单(见步骤10),但是写装饰器却很复杂。

虽然我没法让装饰器变得简单,但也许通过将问题进行一步步的讲解,可以帮助你更容易理解装饰器。由于装饰器较为复杂,文章会比较长,请坚持住!我会尽量使每个步骤简单明了,这样如果你理解了各个步骤,就能理解装饰器的原理。本文假定你具备最基础的 Python 知识,另外本文对工作中大量使用 Python 的人将大有帮助。

此外需要说明的是,本文中 Python 代码示例是用 doctest 模块来执行的。代码看起来像是交互式 Python 控制台会话(>>> 表示 Python 语句,输出则另起一行)。偶然有以“doctest”开头的“奇怪”注释——那些只是 doctest 的指令,可以忽略。

1. 函数

在 Python 中,使用关键字 def 和一个函数名以及一个可选的参数列表来定义函数。函数使用 return 关键字来返回值。定义和使用一个最简单的函数例子:

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

函数体(和 Python 中所有的多行语句一样)由强制性的缩进表示。在函数名后面加上括号就可以调用函数。

2. 作用域

在 Python 函数中会创建一个新的作用域。Python 高手也称函数有自己的命名空间。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。

1234567 >>> 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 函数来打印全部变量,结果将是我们希望的那样:

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

#1 处,Python 在函数 foo 中搜索局部变量 a_string,但是没有找到,然后继续搜索同名的全局变量。

另一方面,如果尝试在函数里给全局变量赋值,结果并不是我们想要的那样:

12345678 >>> 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. 变量生命周期

值得注意的是,变量不仅是在命名空间中有效,它们也有生命周期。思考下面的代码:

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

这个问题不仅仅是因为 #1 处的作用域规则(虽然那是导致 NameError 的原因),也与 Python 和很多其他语言中函数调用的实现有关。没有任何语法可以在该处取得变量 x 的值——它确确实实不存在!函数 foo 的命名空间在每次函数被调用时重新创建,在函数结束时销毁。

5. 函数的实参和形参

Python 允许向函数传递参数。形参名在函数里为局部变量。

1234 >>> 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 关键字来返回值。定义和使用一个最简单的函数例子:

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

函数体(和 Python 中所有的多行语句一样)由强制性的缩进表示。在函数名后面加上括号就可以调用函数。

2. 作用域

在 Python 函数中会创建一个新的作用域。Python 高手也称函数有自己的命名空间。也就是说,当在函数体中遇到变量时,Python 会首先在该函数的命名空间中寻找变量名。Python 有几个函数用来查看命名空间。下面来写一个简单函数来看看局部变量和全局变量的区别。

1234567 >>> 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 函数来打印全部变量,结果将是我们希望的那样:

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

#1 处,Python 在函数 foo 中搜索局部变量 a_string,但是没有找到,然后继续搜索同名的全局变量。

另一方面,如果尝试在函数里给全局变量赋值,结果并不是我们想要的那样:

12345678 >>> 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. 变量生命周期

值得注意的是,变量不仅是在命名空间中有效,它们也有生命周期。思考下面的代码:

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

这个问题不仅仅是因为 #1 处的作用域规则(虽然那是导致 NameError 的原因),也与 Python 和很多其他语言中函数调用的实现有关。没有任何语法可以在该处取得变量 x 的值——它确确实实不存在!函数 foo 的命名空间在每次函数被调用时重新创建,在函数结束时销毁。

5. 函数的实参和形参

Python 允许向函数传递参数。形参名在函数里为局部变量。

1234 >>> def foo(x):...     print locals()>>> foo(1){\’x\’: 1}

Python 有一些不同的方法来定义和传递函数参数。想要深入的了解,请参考 Python 文档关于函数的定义。来说一个简单版本:函数参数可以是强制的位置参数或者可选的有默认值的关键字参数。

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
项目管理和工程管理的区别 项目管理 项目管理,顾名思义就是专注于开发和完成项目的管理,以实现目标并满足成功标准和项目要求。 工...