Fix markdown syntax error

This commit is contained in:
wgzhao 2021-12-05 20:24:52 +08:00
parent d146757842
commit fe874a5e69
14 changed files with 412 additions and 393 deletions

View File

@ -18,25 +18,29 @@
### 内容目录
* [如何为变量起名](#如何为变量起名)
* [1. 变量名要有描述性,不能太宽泛](#1-变量名要有描述性不能太宽泛)
* [2. 变量名最好让人能猜出类型](#2-变量名最好让人能猜出类型)
* [『什么样的名字会被当成 bool 类型?』](#什么样的名字会被当成-bool-类型)
* [『什么样的名字会被当成 int/float 类型?』](#什么样的名字会被当成-intfloat-类型)
* [其他类型](#其他类型)
* [3. 适当使用『匈牙利命名法』](#3-适当使用匈牙利命名法)
* [4. 变量名尽量短,但是绝对不要太短](#4-变量名尽量短但是绝对不要太短)
* [使用短名字的例外情况](#使用短名字的例外情况)
* [5. 其他注意事项](#5-其他注意事项)
* [更好的使用变量](#更好的使用变量)
* [1. 保持一致性](#1-保持一致性)
* [2. 尽量不要用 globals()/locals()](#2-尽量不要用-globalslocals)
* [3. 变量定义尽量靠近使用](#3-变量定义尽量靠近使用)
* [4. 合理使用 namedtuple/dict 来让函数返回多个值](#4-合理使用-namedtupledict-来让函数返回多个值)
* [5. 控制单个函数内的变量数量](#5-控制单个函数内的变量数量)
* [6. 及时删掉那些没用的变量](#6-及时删掉那些没用的变量)
* [7. 能不定义变量就不定义](#7-能不定义变量就不定义)
* [结语](#结语)
- [Python 工匠:善用变量来改善代码质量](#python-工匠善用变量来改善代码质量)
- [『Python 工匠』是什么?](#python-工匠是什么)
- [变量和代码质量](#变量和代码质量)
- [内容目录](#内容目录)
- [如何为变量起名](#如何为变量起名)
- [1. 变量名要有描述性,不能太宽泛](#1-变量名要有描述性不能太宽泛)
- [2. 变量名最好让人能猜出类型](#2-变量名最好让人能猜出类型)
- [『什么样的名字会被当成 bool 类型?』](#什么样的名字会被当成-bool-类型)
- [『什么样的名字会被当成 int/float 类型?』](#什么样的名字会被当成-intfloat-类型)
- [其他类型](#其他类型)
- [3. 适当使用『匈牙利命名法』](#3-适当使用匈牙利命名法)
- [4. 变量名尽量短,但是绝对不要太短](#4-变量名尽量短但是绝对不要太短)
- [使用短名字的例外情况](#使用短名字的例外情况)
- [5. 其他注意事项](#5-其他注意事项)
- [更好的使用变量](#更好的使用变量)
- [1. 保持一致性](#1-保持一致性)
- [2. 尽量不要用 globals()/locals()](#2-尽量不要用-globalslocals)
- [3. 变量定义尽量靠近使用](#3-变量定义尽量靠近使用)
- [4. 合理使用 namedtuple/dict 来让函数返回多个值](#4-合理使用-namedtupledict-来让函数返回多个值)
- [5. 控制单个函数内的变量数量](#5-控制单个函数内的变量数量)
- [6. 及时删掉那些没用的变量](#6-及时删掉那些没用的变量)
- [7. 定义临时变量提升可读性](#7-定义临时变量提升可读性)
- [结语](#结语)
## 如何为变量起名

View File

@ -77,7 +77,7 @@ def find_potential_customers_v1():
`find_potential_customers_v1` 函数通过循环的方式,先遍历所有去过普吉岛的人,然后再遍历新西兰的人,如果在新西兰的记录中找不到完全匹配的记录,就把它当做“潜在客户”返回。
这个函数虽然可以完成任务,但是相信不用我说你也能发现。**它有着非常严重的性能问题**对于每一条去过普吉岛的记录,我们都需要遍历所有新西兰访问记录,尝试找到匹配。整个算法的时间复杂度是可怕的 `O(n*m)`,如果新西兰的访问条目数很多的话,那么执行它将耗费非常长的时间。
这个函数虽然可以完成任务,但是相信不用我说你也能发现。**它有着非常严重的性能问题**对于每一条去过普吉岛的记录,我们都需要遍历所有新西兰访问记录,尝试找到匹配。整个算法的时间复杂度是可怕的 `O(n*m)`,如果新西兰的访问条目数很多的话,那么执行它将耗费非常长的时间。
为了优化内层循环性能,我们需要减少线性查找匹配部分的开销。

View File

@ -258,7 +258,7 @@ print(count_vowels('small_file.txt'))
2. 为了准备测试用例,我要么提供几个样板文件,要么写一些临时文件
3. 而文件是否能被正常打开、读取,也成了我们需要测试的边界情况
**如果,你发现你的函数难以编写单元测试,那通常意味着你应该改进它的设计**上面的函数应该如何改进呢?答案是:*让函数依赖“文件对象”而不是文件路径*。
**如果,你发现你的函数难以编写单元测试,那通常意味着你应该改进它的设计**上面的函数应该如何改进呢?答案是:*让函数依赖“文件对象”而不是文件路径*。
修改后的函数代码如下:
@ -280,7 +280,7 @@ with open('small_file.txt') as fp:
print(count_vowels_v2(fp))
```
**这个改动带来的主要变化,在于它提升了函数的适用面**因为 Python 是“鸭子类型”的,虽然函数需要接受文件对象,但其实我们可以把任何实现了文件协议的 “类文件对象file-like object” 传入 `count_vowels_v2` 函数中。
**这个改动带来的主要变化,在于它提升了函数的适用面**因为 Python 是“鸭子类型”的,虽然函数需要接受文件对象,但其实我们可以把任何实现了文件协议的 “类文件对象file-like object” 传入 `count_vowels_v2` 函数中。
而 Python 中有着非常多“类文件对象”。比如 io 模块内的 [StringIO](https://docs.python.org/3/library/io.html#io.StringIO) 对象就是其中之一。它是一种基于内存的特殊对象,拥有和文件对象几乎一致的接口设计。

View File

@ -127,7 +127,7 @@ def deactivate_users(users: Iterable[User]):
既然为函数增加类型判断无法让代码变得更好,那我们就应该从别的方面入手。
“里氏替换原则”提到,**子类*Admin*应该可以随意替换它的父类*User*,而不破坏程序*deactivate_users*本身的功能**我们试过直接修改类的使用者来遵守这条原则,但是失败了。所以这次,让我们试着从源头上解决问题:重新设计类之间的继承关系。
“里氏替换原则”提到,**子类 *Admin* 应该可以随意替换它的父类 *User*,而不破坏程序 *deactivate_users* 本身的功能**我们试过直接修改类的使用者来遵守这条原则,但是失败了。所以这次,让我们试着从源头上解决问题:重新设计类之间的继承关系。
具体点来说,子类不能只是简单通过抛出异常的方式对某个类方法进行“退化”。如果 *“对象不能支持某种操作”* 本身就是这个类型的 **核心特征** 之一,那我们在进行父类设计时,就应该把这个 **核心特征** 设计进去。
@ -246,7 +246,7 @@ def get_user_posts_count(user: User) -> int:
而现在的设计没做到这点,现在的子类返回值所支持的操作,只是父类的一个子集。`Admin` 子类的 `list_related_posts` 方法所返回的生成器,只支持父类 `User` 返回列表里的“迭代操作”,而不支持其他行为(比如 `len()`)。所以我们没办法随意的用子类替换父类,自然也就无法符合里氏替换原则。
> **注意:**此处说“生成器”支持的操作是“列表”的子集其实不是特别严谨,因为生成器还支持 `.send()` 等其他操作。不过在这里,我们可以只关注它的可迭代特性。
> 注意:此处说“生成器”支持的操作是“列表”的子集其实不是特别严谨,因为生成器还支持 `.send()` 等其他操作。不过在这里,我们可以只关注它的可迭代特性。
### 如何修改代码

View File

@ -191,7 +191,7 @@ type HNWebPage interface {
不过Python 根本没有接口这种东西。那该怎么办呢?虽然 Python 没有接口,但是有一个非常类似的东西:**“抽象类Abstrace Class”**。使用 [`abc`](https://docs.python.org/3/library/abc.html) 模块就可以轻松定义出一个抽象类:
```
```python
from abc import ABCMeta, abstractmethod
@ -206,13 +206,13 @@ class HNWebPage(metaclass=ABCMeta):
抽象类和普通类的区别之一就是你不能将它实例化。如果你尝试实例化一个抽象类,解释器会报出下面的错误:
```
```python
TypeError: Can't instantiate abstract class HNWebPage with abstract methods get_text
```
所以,光有抽象类还不能算完事,我们还得定义几个依赖这个抽象类的实体。首先定义的是 `RemoteHNWebPage` 类。它的作用就是通过 requests 模块请求 HN 页面,返回页面内容。
```
```python
class RemoteHNWebPage(HNWebPage):
"""远程页面,通过请求 HN 站点返回内容"""
@ -226,7 +226,7 @@ class RemoteHNWebPage(HNWebPage):
定义了 `RemoteHNWebPage` 类后,`SiteSourceGrouper` 类的初始化方法和 `get_groups` 也需要做对应的调整:
```
```python
class SiteSourceGrouper:
"""对 HN 页面的新闻来源站点进行分组统计
"""
@ -266,7 +266,7 @@ def main():
再回到之前的单元测试上来。通过引入了新的抽象层 `HNWebPage`,我们可以实现一个不依赖外部网络的新类型 `LocalHNWebPage`
```
```python
class LocalHNWebPage(HNWebPage):
"""本地页面,根据本地文件返回页面内容"""
@ -280,7 +280,7 @@ class LocalHNWebPage(HNWebPage):
所以,单元测试也可以改为使用 `LocalHNWebPage`
```
```python
def test_grouper_from_local():
page = LocalHNWebPage(path="./static_hn.html")
grouper = SiteSourceGrouper(page)

View File

@ -255,7 +255,7 @@ def sum_list(l, limit):
利用这个特点,我们还可以简化一些特定的边界处理逻辑。比如安全删除列表的某个元素:
```
```python
# 使用异常捕获安全删除列表的第 5 个元素
try:
l.pop(5)
@ -295,7 +295,7 @@ if extra_context:
如果使用 `or` 操作符,我们可以让上面的语句更简练:
```
```python
context.update(extra_context or {})
```
@ -409,7 +409,7 @@ def input_a_number_with_pydantic():
很多年前刚接触 Web 开发时,我想学着用 JavaScript 来实现一个简单的文字跑马灯动画。如果你不知道啥是“跑马灯”,我可以稍微解释一下。“跑马灯”就是让一段文字从页面左边往右边不断循环滚动,十几年前的网站特别流行这个。😬
我记得里面有一段逻辑是这样的:*控制文字不断往右边移动,当横坐标超过页面宽度时,重置坐标后继续*我当时写出来的代码,翻译成 Python 大概是这样:
我记得里面有一段逻辑是这样的:*控制文字不断往右边移动,当横坐标超过页面宽度时,重置坐标后继续*我当时写出来的代码,翻译成 Python 大概是这样:
```python
while True:

View File

@ -12,21 +12,25 @@
### 内容目录
* [最佳实践](#最佳实践)
* [1. 避免多层分支嵌套](#1-避免多层分支嵌套)
* [2. 封装那些过于复杂的逻辑判断](#2-封装那些过于复杂的逻辑判断)
* [3. 留意不同分支下的重复代码](#3-留意不同分支下的重复代码)
* [4. 谨慎使用三元表达式](#4-谨慎使用三元表达式)
* [常见技巧](#常见技巧)
* [1. 使用“德摩根定律”](#1-使用德摩根定律)
* [2. 自定义对象的“布尔真假”](#2-自定义对象的布尔真假)
* [3. 在条件判断中使用 all() / any()](#3-在条件判断中使用-all--any)
* [4. 使用 try/while/for 中 else 分支](#4-使用-trywhilefor-中-else-分支)
* [常见陷阱](#常见陷阱)
* [1. 与 None 值的比较](#1-与-none-值的比较)
* [2. 留意 and 和 or 的运算优先级](#2-留意-and-和-or-的运算优先级)
* [结语](#结语)
* [注解](#注解)
- [Python 工匠:编写条件分支代码的技巧](#python-工匠编写条件分支代码的技巧)
- [序言](#序言)
- [内容目录](#内容目录)
- [Python 里的分支代码](#python-里的分支代码)
- [最佳实践](#最佳实践)
- [1. 避免多层分支嵌套](#1-避免多层分支嵌套)
- [2. 封装那些过于复杂的逻辑判断](#2-封装那些过于复杂的逻辑判断)
- [3. 留意不同分支下的重复代码](#3-留意不同分支下的重复代码)
- [4. 谨慎使用三元表达式](#4-谨慎使用三元表达式)
- [常见技巧](#常见技巧)
- [1. 使用“德摩根定律”](#1-使用德摩根定律)
- [2. 自定义对象的“布尔真假”](#2-自定义对象的布尔真假)
- [3. 在条件判断中使用 all() / any()](#3-在条件判断中使用-all--any)
- [4. 使用 try/while/for 中 else 分支](#4-使用-trywhilefor-中-else-分支)
- [常见陷阱](#常见陷阱)
- [1. 与 None 值的比较](#1-与-none-值的比较)
- [2. 留意 and 和 or 的运算优先级](#2-留意-and-和-or-的运算优先级)
- [结语](#结语)
- [注解](#注解)
### Python 里的分支代码
@ -391,4 +395,3 @@ True
> - 2018.04.08:在与 @geishu 的讨论后,调整了“运算优先符”使用的代码样例
> - 2018.04.10:根据 @dongweiming 的建议,添加注解说明 "x and y or c" 表达式的陷阱

View File

@ -15,21 +15,25 @@
### 内容目录
* [最佳实践](#最佳实践)
* [1. 少写数字字面量](#1-少写数字字面量)
* [使用 enum 枚举类型改善代码](#使用-enum-枚举类型改善代码)
* [2. 别在裸字符串处理上走太远](#2-别在裸字符串处理上走太远)
* [3. 不必预计算字面量表达式](#3-不必预计算字面量表达式)
* [实用技巧](#实用技巧)
* [1. 布尔值其实也是“数字”](#1-布尔值其实也是数字)
* [2. 改善超长字符串的可读性](#2-改善超长字符串的可读性)
* [当多级缩进里出现多行字符串时](#当多级缩进里出现多行字符串时)
* [3. 别忘了那些 “r” 开头的内建字符串函数](#3-别忘了那些-r-开头的内建字符串函数)
* [4. 使用“无穷大” float("inf")](#4-使用无穷大-floatinf)
* [常见误区](#常见误区)
* [1. “value = 1” 并非线程安全](#1-value--1-并非线程安全)
* [2. 字符串拼接并不慢](#2-字符串拼接并不慢)
* [结语](#结语)
- [Python 工匠:使用数字与字符串的技巧](#python-工匠使用数字与字符串的技巧)
- [序言](#序言)
- [内容目录](#内容目录)
- [最佳实践](#最佳实践)
- [1. 少写数字字面量](#1-少写数字字面量)
- [使用 enum 枚举类型改善代码](#使用-enum-枚举类型改善代码)
- [2. 别在裸字符串处理上走太远](#2-别在裸字符串处理上走太远)
- [3. 不必预计算字面量表达式](#3-不必预计算字面量表达式)
- [实用技巧](#实用技巧)
- [1. 布尔值其实也是“数字”](#1-布尔值其实也是数字)
- [2. 改善超长字符串的可读性](#2-改善超长字符串的可读性)
- [当多级缩进里出现多行字符串时](#当多级缩进里出现多行字符串时)
- [大数字也可以变得更加可读](#大数字也可以变得更加可读)
- [3. 别忘了那些 “r” 开头的内建字符串函数](#3-别忘了那些-r-开头的内建字符串函数)
- [4. 使用“无穷大” float("inf")](#4-使用无穷大-floatinf)
- [常见误区](#常见误区)
- [1. “value += 1” 并非线程安全](#1-value--1-并非线程安全)
- [2. 字符串拼接并不慢](#2-字符串拼接并不慢)
- [结语](#结语)
## 最佳实践

View File

@ -19,25 +19,29 @@ Python 语言自身的内部实现细节也与这些容器类型息息相关。
### 内容目录
* [底层看容器](#底层看容器)
* [写更快的代码](#写更快的代码)
* [1. 避免频繁扩充列表/创建新列表](#1-避免频繁扩充列表创建新列表)
* [2. 在列表头部操作多的场景使用 deque 模块](#2-在列表头部操作多的场景使用-deque-模块)
* [3. 使用集合/字典来判断成员是否存在](#3-使用集合字典来判断成员是否存在)
* [高层看容器](#高层看容器)
* [写扩展性更好的代码](#写扩展性更好的代码)
* [面向容器接口编程](#面向容器接口编程)
* [常用技巧](#常用技巧)
* [1. 使用元组改善分支代码](#1-使用元组改善分支代码)
* [2. 在更多地方使用动态解包](#2-在更多地方使用动态解包)
* [3. 使用 next() 函数](#3-使用-next-函数)
* [4. 使用有序字典来去重](#4-使用有序字典来去重)
* [常见误区](#常见误区)
* [1. 当心那些已经枯竭的迭代器](#1-当心那些已经枯竭的迭代器)
* [2. 别在循环体内修改被迭代对象](#2-别在循环体内修改被迭代对象)
* [总结](#总结)
* [系列其他文章](#系列其他文章)
* [注解](#注解)
- [Python 工匠:容器的门道](#python-工匠容器的门道)
- [序言](#序言)
- [内容目录](#内容目录)
- [当我们谈论容器时,我们在谈些什么?](#当我们谈论容器时我们在谈些什么)
- [底层看容器](#底层看容器)
- [写更快的代码](#写更快的代码)
- [1. 避免频繁扩充列表/创建新列表](#1-避免频繁扩充列表创建新列表)
- [2. 在列表头部操作多的场景使用 deque 模块](#2-在列表头部操作多的场景使用-deque-模块)
- [3. 使用集合/字典来判断成员是否存在](#3-使用集合字典来判断成员是否存在)
- [高层看容器](#高层看容器)
- [写扩展性更好的代码](#写扩展性更好的代码)
- [面向容器接口编程](#面向容器接口编程)
- [常用技巧](#常用技巧)
- [1. 使用元组改善分支代码](#1-使用元组改善分支代码)
- [2. 在更多地方使用动态解包](#2-在更多地方使用动态解包)
- [3. 使用 next() 函数](#3-使用-next-函数)
- [4. 使用有序字典来去重](#4-使用有序字典来去重)
- [常见误区](#常见误区)
- [1. 当心那些已经枯竭的迭代器](#1-当心那些已经枯竭的迭代器)
- [2. 别在循环体内修改被迭代对象](#2-别在循环体内修改被迭代对象)
- [总结](#总结)
- [系列其他文章](#系列其他文章)
- [注解](#注解)
### 当我们谈论容器时,我们在谈些什么?

View File

@ -22,19 +22,23 @@ Python 函数通过调用 `return` 语句来返回结果。使用 `return value`
### 内容目录
* [编程建议](#编程建议)
* [1. 单个函数不要返回多种类型](#1-单个函数不要返回多种类型)
* [2. 使用 partial 构造新函数](#2-使用-partial-构造新函数)
* [3. 抛出异常,而不是返回结果与错误](#3-抛出异常而不是返回结果与错误)
* [4. 谨慎使用 None 返回值](#4-谨慎使用-none-返回值)
* [1. 作为操作类函数的默认返回值](#1-作为操作类函数的默认返回值)
* [2. 作为某些“意料之中”的可能没有的值](#2-作为某些意料之中的可能没有的值)
* [3. 作为调用失败时代表“错误结果”的值](#3-作为调用失败时代表错误结果的值)
* [5. 合理使用“空对象模式”](#5-合理使用空对象模式)
* [6. 使用生成器函数代替返回列表](#6-使用生成器函数代替返回列表)
* [7. 限制递归的使用](#7-限制递归的使用)
* [总结](#总结)
* [附录](#附录)
- [Python 工匠:让函数返回结果的技巧](#python-工匠让函数返回结果的技巧)
- [序言](#序言)
- [Python 的函数返回方式](#python-的函数返回方式)
- [内容目录](#内容目录)
- [编程建议](#编程建议)
- [1. 单个函数不要返回多种类型](#1-单个函数不要返回多种类型)
- [2. 使用 partial 构造新函数](#2-使用-partial-构造新函数)
- [3. 抛出异常,而不是返回结果与错误](#3-抛出异常而不是返回结果与错误)
- [4. 谨慎使用 None 返回值](#4-谨慎使用-none-返回值)
- [1. 作为操作类函数的默认返回值](#1-作为操作类函数的默认返回值)
- [2. 作为某些“意料之中”的可能没有的值](#2-作为某些意料之中的可能没有的值)
- [3. 作为调用失败时代表“错误结果”的值](#3-作为调用失败时代表错误结果的值)
- [5. 合理使用“空对象模式”](#5-合理使用空对象模式)
- [6. 使用生成器函数代替返回列表](#6-使用生成器函数代替返回列表)
- [7. 限制递归的使用](#7-限制递归的使用)
- [总结](#总结)
- [附录](#附录)
## 编程建议

View File

@ -163,7 +163,7 @@ def process_image(...):
- 我必须引入 `APIErrorCode` 异常类作为依赖来捕获异常
- **哪怕我的脚本和 Django API 根本没有任何关系**
**这就是异常类抽象层级不一致导致的结果**APIErrorCode 异常类的意义,在于表达一种能够直接被终端用户(人)识别并消费的“错误代码”。**它在整个项目里,属于最高层的抽象之一**但是出于方便,我们却在底层模块里引入并抛出了它。这打破了 `image.processor` 模块的抽象一致性,影响了它的可复用性和可维护性。
**这就是异常类抽象层级不一致导致的结果**。`APIErrorCode` 异常类的意义,在于表达一种能够直接被终端用户(人)识别并消费的“错误代码”。**它在整个项目里,属于最高层的抽象之一**但是出于方便,我们却在底层模块里引入并抛出了它。这打破了 `image.processor` 模块的抽象一致性,影响了它的可复用性和可维护性。
这类情况属于“模块抛出了**高于**所属抽象层级的异常”。避免这类错误需要注意以下几点:

View File

@ -8,7 +8,7 @@
<img src="https://www.zlovezl.cn/static/uploaded/2019/05/clem-onojeghuo-142120-unsplash_w1280.jpg" width="100%" />
</div>
装饰器*Decorator* 是 Python 里的一种特殊工具,它为我们提供了一种在函数外部修改函数的灵活能力。它有点像一顶画着独一无二 `@` 符号的神奇帽子,只要将它戴在函数头顶上,就能悄无声息的改变函数本身的行为。
装饰器 *(Decorator)* 是 Python 里的一种特殊工具,它为我们提供了一种在函数外部修改函数的灵活能力。它有点像一顶画着独一无二 `@` 符号的神奇帽子,只要将它戴在函数头顶上,就能悄无声息的改变函数本身的行为。
你可能已经和装饰器打过不少交道了。在做面向对象编程时,我们就经常会用到 `@staticmethod``@classmethod` 两个内置装饰器。此外,如果你接触过 [click](https://click.palletsprojects.com/en/7.x/) 模块就更不会对装饰器感到陌生。click 最为人所称道的参数定义接口 `@click.option(...)` 就是利用装饰器实现的。
@ -32,7 +32,7 @@ True
函数自然是“可被调用”的对象。但除了函数外我们也可以让任何一个类class变得“可被调用”callable。办法很简单只要自定义类的 `__call__` 魔法方法即可。
```
```python
class Foo:
def __call__(self):
print("Hello, __call___")
@ -80,7 +80,7 @@ def delay(duration):
如何使用装饰器的样例代码:
```
```python
@delay(duration=2)
def add(a, b):
return a + b
@ -200,7 +200,7 @@ Foo().print_random_number()
“装饰器模式”是一个完全基于“面向对象”衍生出的编程手法。它拥有几个关键组成:**一个统一的接口定义**、**若干个遵循该接口的类**、**类与类之间一层一层的包装**。最终由它们共同形成一种 *“装饰”* 的效果。
而 Python 里的“装饰器”和“面向对象”没有任何直接联系,**它完全可以只是发生在函数和函数间的把戏**事实上,“装饰器”并没有提供某种无法替代的功能,它仅仅就是一颗[“语法糖”](https://en.wikipedia.org/wiki/Syntactic_sugar)而已。下面这段使用了装饰器的代码:
而 Python 里的“装饰器”和“面向对象”没有任何直接联系,**它完全可以只是发生在函数和函数间的把戏**事实上,“装饰器”并没有提供某种无法替代的功能,它仅仅就是一颗[“语法糖”](https://en.wikipedia.org/wiki/Syntactic_sugar)而已。下面这段使用了装饰器的代码:
```python
@log_time
@ -210,7 +210,7 @@ def foo(): pass
基本完全等同于下面这样:
```
```python
def foo(): pass
foo = log_time(cache_result(foo))