From 45a5b884f382aea6f3dffede0955e8dd21fb64c7 Mon Sep 17 00:00:00 2001 From: piglei Date: Wed, 2 Mar 2022 13:30:23 +0800 Subject: [PATCH 01/16] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c51d56..078f340 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ - [第 6 章 循环与可迭代对象](https://www.zlovezl.cn/book/ch06_loop.html) - [第 10 章 面向对象设计原则](https://www.zlovezl.cn/book/ch10_solid_p1.html) -包含全部内容的纸质书将由人民邮电出版社于 ~~2021 年底~~ 2022 年初出版,敬请期待。 +包含全部内容的纸质书现已正式上市,欢迎购买。 + +[点击购买](https://item.jd.com/13068111.html) | [豆瓣书评](https://book.douban.com/subject/35723705/) | [图灵社区](https://www.ituring.com.cn/book/3007) --- From dfbf4c2042c2048d01fe743c03c3316951ebe5ac Mon Sep 17 00:00:00 2001 From: piglei Date: Thu, 17 Mar 2022 17:32:19 +0800 Subject: [PATCH 02/16] Update README.md: add mirror site link --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 078f340..77b0bb3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - 《Python 工匠》图书首页上线啦!共有 3 章在线内容,欢迎阅读。 - [图书首页](https://www.zlovezl.cn/book/) @@ -39,7 +38,9 @@ - [14. 写好面向对象代码的原则(下)](zh_CN/14-write-solid-python-codes-part-3.md) - [15. 在边界处思考](zh_CN/15-thinking-in-edge-cases.md) -关注我的微信公众号,在第一时间阅读最新文章: +如果你觉得读 GitHub 文件不太方便,也可以访问[这个镜像站点](https://pengzhangzhi.github.io/one-python-craftsman/)阅读所有文章(由 [@pengzhangzhi](https://github.com/pengzhangzhi) 搭建)。 + +也欢迎你关注我的微信公众号,在第一时间阅读最新文章: From 05b583c040d67b7c47f5e653c9b3bdd45972b4ad Mon Sep 17 00:00:00 2001 From: piglei Date: Thu, 17 Mar 2022 17:50:44 +0800 Subject: [PATCH 03/16] Update README.md: add wechat channel name --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77b0bb3..9db6b25 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ 如果你觉得读 GitHub 文件不太方便,也可以访问[这个镜像站点](https://pengzhangzhi.github.io/one-python-craftsman/)阅读所有文章(由 [@pengzhangzhi](https://github.com/pengzhangzhi) 搭建)。 -也欢迎你关注我的微信公众号,在第一时间阅读最新文章: +也欢迎你关注我的微信公众号:piglei,在第一时间阅读最新文章: From c2c747d93f27c78d3ddecdeff7666d0d95f2b7ec Mon Sep 17 00:00:00 2001 From: piglei Date: Thu, 17 Mar 2022 17:56:55 +0800 Subject: [PATCH 04/16] Update 5-function-returning-tips.md --- zh_CN/5-function-returning-tips.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zh_CN/5-function-returning-tips.md b/zh_CN/5-function-returning-tips.md index 053fd7e..9e27fd8 100644 --- a/zh_CN/5-function-returning-tips.md +++ b/zh_CN/5-function-returning-tips.md @@ -149,7 +149,7 @@ def create_for_input(): try: item = create_item(name) except CreateItemError as e: - print(f'create item failed: {err_msg}') + print(f'create item failed: {e}') else: print(f'item<{name}> created') From c47aaeded36b45db8feb0240f37bcc26156adfa2 Mon Sep 17 00:00:00 2001 From: piglei Date: Thu, 17 Mar 2022 17:58:16 +0800 Subject: [PATCH 05/16] Update 8-tips-on-decorators.md --- zh_CN/8-tips-on-decorators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zh_CN/8-tips-on-decorators.md b/zh_CN/8-tips-on-decorators.md index fab7c56..9517c23 100644 --- a/zh_CN/8-tips-on-decorators.md +++ b/zh_CN/8-tips-on-decorators.md @@ -30,7 +30,7 @@ True ``` -函数自然是“可被调用”的对象。但除了函数外,我们也可以让任何一个类(class)变得“可被调用”(callable)。办法很简单,只要自定义类的 `__call__` 魔法方法即可。 +函数自然是“可被调用”的对象。但除了函数外,我们也可以让任何一个类实例变得“可被调用”(callable)。办法很简单,只要自定义类的 `__call__` 魔法方法即可。 ```python class Foo: From dab546c80f0280a82d255a74646a51cb07750c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=B0=E5=86=A2?= <49506152+BruceLee569@users.noreply.github.com> Date: Fri, 22 Apr 2022 15:23:58 +0800 Subject: [PATCH 06/16] Update 2-if-else-block-secrets.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docsify文档需要小写才显示 --- zh_CN/2-if-else-block-secrets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zh_CN/2-if-else-block-secrets.md b/zh_CN/2-if-else-block-secrets.md index 3317a23..58bcf6d 100644 --- a/zh_CN/2-if-else-block-secrets.md +++ b/zh_CN/2-if-else-block-secrets.md @@ -50,7 +50,7 @@ Python 支持最为常见的 `if/else` 条件分支语句,不过它缺少在 但是因为 Python 使用了缩进来代替 `{}`,所以过深的嵌套分支会产生比其他语言下更为严重的后果。比如过多的缩进层次很容易就会让代码超过 [PEP8](https://www.python.org/dev/peps/pep-0008/) 中规定的每行字数限制。让我们看看这段代码: -```Python +```python def buy_fruit(nerd, store): """去水果店买苹果 From f608df74cc0a71f19bebddee1fb8b908fc17ec75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=B0=E5=86=A2?= <49506152+BruceLee569@users.noreply.github.com> Date: Sun, 24 Apr 2022 10:50:46 +0800 Subject: [PATCH 07/16] Update 11-three-tips-on-writing-file-related-codes.md --- zh_CN/11-three-tips-on-writing-file-related-codes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zh_CN/11-three-tips-on-writing-file-related-codes.md b/zh_CN/11-three-tips-on-writing-file-related-codes.md index 6846737..b78264c 100644 --- a/zh_CN/11-three-tips-on-writing-file-related-codes.md +++ b/zh_CN/11-three-tips-on-writing-file-related-codes.md @@ -214,7 +214,7 @@ def count_nine_v3(fname): return count ``` -进行到这一步,代码似乎已经没有优化的空间了,但其实不然。[iter(iterable)](https://docs.python.org/3/library/functions.html#iter) 是一个用来构造迭代器的内建函数,但它还有一个更少人知道的用法。当我们使用 `iter(callable, sentinel)` 的方式调用它时,会返回一个特殊的对象,迭代它将不断产生可调用对象 callable 的调用结果,直到结果为 setinel 时,迭代终止。 +进行到这一步,代码似乎已经没有优化的空间了,但其实不然。[iter(iterable)](https://docs.python.org/3/library/functions.html#iter) 是一个用来构造迭代器的内建函数,但它还有一个更少人知道的用法。当我们使用 `iter(callable, sentinel)` 的方式调用它时,会返回一个特殊的对象,迭代它将不断产生可调用对象 callable 的调用结果,直到结果为 sentinel 时,迭代终止。 ```python def chunked_file_reader(file, block_size=1024 * 8): From 170c44fc54d68f2ad9d1a636e5518ac75b333a8d Mon Sep 17 00:00:00 2001 From: piglei Date: Mon, 2 May 2022 07:50:28 +0800 Subject: [PATCH 08/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9db6b25..738ee87 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ - [第 6 章 循环与可迭代对象](https://www.zlovezl.cn/book/ch06_loop.html) - [第 10 章 面向对象设计原则](https://www.zlovezl.cn/book/ch10_solid_p1.html) -包含全部内容的纸质书现已正式上市,欢迎购买。 +包含全部内容的纸质书现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等章节,总文字量几乎翻倍,欢迎购买。 [点击购买](https://item.jd.com/13068111.html) | [豆瓣书评](https://book.douban.com/subject/35723705/) | [图灵社区](https://www.ituring.com.cn/book/3007) From 5368a68f4fbdc740e2eed91d3c2833b6adb40fe0 Mon Sep 17 00:00:00 2001 From: piglei Date: Mon, 2 May 2022 08:20:31 +0800 Subject: [PATCH 09/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 738ee87..ddd48be 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ - [第 6 章 循环与可迭代对象](https://www.zlovezl.cn/book/ch06_loop.html) - [第 10 章 面向对象设计原则](https://www.zlovezl.cn/book/ch10_solid_p1.html) -包含全部内容的纸质书现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等章节,总文字量几乎翻倍,欢迎购买。 +图书《Python 工匠》现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等核心章节,总内容量几乎翻倍,欢迎购买。 [点击购买](https://item.jd.com/13068111.html) | [豆瓣书评](https://book.douban.com/subject/35723705/) | [图灵社区](https://www.ituring.com.cn/book/3007) From 27e41ddd722c72c837a974a439e4daa1beeeacb3 Mon Sep 17 00:00:00 2001 From: piglei Date: Sun, 23 Oct 2022 11:41:10 +0800 Subject: [PATCH 10/16] Add #16 --- README.md | 426 +++++++++++----------- zh_CN/16-stmt-expr-and-walrus-operator.md | 307 ++++++++++++++++ 2 files changed, 525 insertions(+), 208 deletions(-) create mode 100644 zh_CN/16-stmt-expr-and-walrus-operator.md diff --git a/README.md b/README.md index ddd48be..98bb895 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,8 @@ 『Python 工匠』这个系列文章,是我的一次小小尝试。它专注于分享 Python 编程中的一些偏 **『小』** 的东西。希望能够帮到每一位编程路上的匠人。 - ## 文章列表 - + - 1\. 善用变量改善代码质量 [[图书版:变量与注释](https://www.zlovezl.cn/book/ch01_variables.html)] - [2. 编写条件分支代码的技巧](zh_CN/2-if-else-block-secrets.md) - [3. 使用数字与字符串的技巧](zh_CN/3-tips-on-numbers-and-strings.md) @@ -37,6 +36,7 @@ - [13. 写好面向对象代码的原则(中)](zh_CN/13-write-solid-python-codes-part-2.md) - [14. 写好面向对象代码的原则(下)](zh_CN/14-write-solid-python-codes-part-3.md) - [15. 在边界处思考](zh_CN/15-thinking-in-edge-cases.md) +- [16. 语句、表达式和海象操作符](zh_CN/16-stmt-expr-and-walrus-operator.md) 如果你觉得读 GitHub 文件不太方便,也可以访问[这个镜像站点](https://pengzhangzhi.github.io/one-python-craftsman/)阅读所有文章(由 [@pengzhangzhi](https://github.com/pengzhangzhi) 搭建)。 @@ -50,251 +50,261 @@ ### 1. [善用变量改善代码质量](zh_CN/1-using-variables-well.md) -* 如何为变量起名 - * 1 - 变量名要有描述性,不能太宽泛 - * 2 - 变量名最好让人能猜出类型 - * 『什么样的名字会被当成 bool 类型?』 - * 『什么样的名字会被当成 int/float 类型?』 - * 其他类型 - * 3 - 适当使用『匈牙利命名法』 - * 4 - 变量名尽量短,但是绝对不要太短 - * 使用短名字的例外情况 - * 5 - 其他注意事项 -* 更好的使用变量 - * 1 - 保持一致性 - * 2 - 尽量不要用 globals()/locals() - * 3 - 变量定义尽量靠近使用 - * 4 - 合理使用 namedtuple/dict 来让函数返回多个值 - * 5 - 控制单个函数内的变量数量 - * 6 - 及时删掉那些没用的变量 - * 7 - 能不定义变量就不定义 -* 结语 +- 如何为变量起名 + - 1 - 变量名要有描述性,不能太宽泛 + - 2 - 变量名最好让人能猜出类型 + - 『什么样的名字会被当成 bool 类型?』 + - 『什么样的名字会被当成 int/float 类型?』 + - 其他类型 + - 3 - 适当使用『匈牙利命名法』 + - 4 - 变量名尽量短,但是绝对不要太短 + - 使用短名字的例外情况 + - 5 - 其他注意事项 +- 更好的使用变量 + - 1 - 保持一致性 + - 2 - 尽量不要用 globals()/locals() + - 3 - 变量定义尽量靠近使用 + - 4 - 合理使用 namedtuple/dict 来让函数返回多个值 + - 5 - 控制单个函数内的变量数量 + - 6 - 及时删掉那些没用的变量 + - 7 - 能不定义变量就不定义 +- 结语 ### 2. [编写条件分支代码的技巧](zh_CN/2-if-else-block-secrets.md) -* 最佳实践 - * 1 - 避免多层分支嵌套 - * 2 - 封装那些过于复杂的逻辑判断 - * 3 - 留意不同分支下的重复代码 - * 4 - 谨慎使用三元表达式 -* 常见技巧 - * 1 - 使用“德摩根定律” - * 2 - 自定义对象的“布尔真假” - * 3 - 在条件判断中使用 all() / any() - * 4 - 使用 try/while/for 中 else 分支 -* 常见陷阱 - * 1 - 与 None 值的比较 - * 2 - 留意 and 和 or 的运算优先级 -* 结语 -* 注解 +- 最佳实践 + - 1 - 避免多层分支嵌套 + - 2 - 封装那些过于复杂的逻辑判断 + - 3 - 留意不同分支下的重复代码 + - 4 - 谨慎使用三元表达式 +- 常见技巧 + - 1 - 使用“德摩根定律” + - 2 - 自定义对象的“布尔真假” + - 3 - 在条件判断中使用 all() / any() + - 4 - 使用 try/while/for 中 else 分支 +- 常见陷阱 + - 1 - 与 None 值的比较 + - 2 - 留意 and 和 or 的运算优先级 +- 结语 +- 注解 ### 3. [使用数字与字符串的技巧](zh_CN/3-tips-on-numbers-and-strings.md) -* 最佳实践 - * 1 - 少写数字字面量 - * 使用 enum 枚举类型改善代码 - * 2 - 别在裸字符串处理上走太远 - * 3 - 不必预计算字面量表达式 -* 实用技巧 - * 1 - 布尔值其实也是“数字” - * 2 - 改善超长字符串的可读性 - * 当多级缩进里出现多行字符串时 - * 3 - 别忘了那些 “r” 开头的内建字符串函数 - * 4 - 使用“无穷大” float("inf") -* 常见误区 - * 1 - “value += 1” 并非线程安全 - * 2 - 字符串拼接并不慢 -* 结语 +- 最佳实践 + - 1 - 少写数字字面量 + - 使用 enum 枚举类型改善代码 + - 2 - 别在裸字符串处理上走太远 + - 3 - 不必预计算字面量表达式 +- 实用技巧 + - 1 - 布尔值其实也是“数字” + - 2 - 改善超长字符串的可读性 + - 当多级缩进里出现多行字符串时 + - 3 - 别忘了那些 “r” 开头的内建字符串函数 + - 4 - 使用“无穷大” float("inf") +- 常见误区 + - 1 - “value += 1” 并非线程安全 + - 2 - 字符串拼接并不慢 +- 结语 ### 4. [容器的门道](zh_CN/4-mastering-container-types.md) -* 底层看容器 - * 写更快的代码 - * 1 - 避免频繁扩充列表/创建新列表 - * 2 - 在列表头部操作多的场景使用 deque 模块 - * 3 - 使用集合/字典来判断成员是否存在 -* 高层看容器 - * 写扩展性更好的代码 - * 面向容器接口编程 -* 常用技巧 - * 1 - 使用元组改善分支代码 - * 2 - 在更多地方使用动态解包 - * 3 - 使用 next() 函数 - * 4 - 使用有序字典来去重 -* 常见误区 - * 1 - 当心那些已经枯竭的迭代器 - * 2 - 别在循环体内修改被迭代对象 -* 总结 -* 系列其他文章 -* 注解 +- 底层看容器 + - 写更快的代码 + - 1 - 避免频繁扩充列表/创建新列表 + - 2 - 在列表头部操作多的场景使用 deque 模块 + - 3 - 使用集合/字典来判断成员是否存在 +- 高层看容器 + - 写扩展性更好的代码 + - 面向容器接口编程 +- 常用技巧 + - 1 - 使用元组改善分支代码 + - 2 - 在更多地方使用动态解包 + - 3 - 使用 next() 函数 + - 4 - 使用有序字典来去重 +- 常见误区 + - 1 - 当心那些已经枯竭的迭代器 + - 2 - 别在循环体内修改被迭代对象 +- 总结 +- 系列其他文章 +- 注解 ### 5. [让函数返回结果的技巧](zh_CN/5-function-returning-tips.md) -* 编程建议 - * 1 - 单个函数不要返回多种类型 - * 2 - 使用 partial 构造新函数 - * 3 - 抛出异常,而不是返回结果与错误 - * 4 - 谨慎使用 None 返回值 - * 1 - 作为操作类函数的默认返回值 - * 2 - 作为某些“意料之中”的可能没有的值 - * 3 - 作为调用失败时代表“错误结果”的值 - * 5 - 合理使用“空对象模式” - * 6 - 使用生成器函数代替返回列表 - * 7 - 限制递归的使用 -* 总结 -* 附录 +- 编程建议 + - 1 - 单个函数不要返回多种类型 + - 2 - 使用 partial 构造新函数 + - 3 - 抛出异常,而不是返回结果与错误 + - 4 - 谨慎使用 None 返回值 + - 1 - 作为操作类函数的默认返回值 + - 2 - 作为某些“意料之中”的可能没有的值 + - 3 - 作为调用失败时代表“错误结果”的值 + - 5 - 合理使用“空对象模式” + - 6 - 使用生成器函数代替返回列表 + - 7 - 限制递归的使用 +- 总结 +- 附录 ### 6. [异常处理的三个好习惯](zh_CN/6-three-rituals-of-exceptions-handling.md) -* 前言 -* 三个好习惯 - * 1 - 只做最精确的异常捕获 - * 2 - 别让异常破坏抽象一致性 - * 3 - 异常处理不应该喧宾夺主 -* 总结 -* 附录 +- 前言 +- 三个好习惯 + - 1 - 只做最精确的异常捕获 + - 2 - 别让异常破坏抽象一致性 + - 3 - 异常处理不应该喧宾夺主 +- 总结 +- 附录 ### 7. [编写地道循环的两个建议](zh_CN/7-two-tips-on-loop-writing.md) -* 前言 -* 什么是“地道”的循环? - * enumerate() 所代表的编程思路 -* 建议1:使用函数修饰被迭代对象来优化循环 - * 1 - 使用 product 扁平化多层嵌套循环 - * 2 - 使用 islice 实现循环内隔行处理 - * 3 - 使用 takewhile 替代 break 语句 - * 4 - 使用生成器编写自己的修饰函数 -* 建议2:按职责拆解循环体内复杂代码块 - * 复杂循环体如何应对新需求 - * 使用生成器函数解耦循环体 -* 总结 -* 附录 +- 前言 +- 什么是“地道”的循环? + - enumerate() 所代表的编程思路 +- 建议1:使用函数修饰被迭代对象来优化循环 + - 1 - 使用 product 扁平化多层嵌套循环 + - 2 - 使用 islice 实现循环内隔行处理 + - 3 - 使用 takewhile 替代 break 语句 + - 4 - 使用生成器编写自己的修饰函数 +- 建议2:按职责拆解循环体内复杂代码块 + - 复杂循环体如何应对新需求 + - 使用生成器函数解耦循环体 +- 总结 +- 附录 ### 8. [使用装饰器的技巧](zh_CN/8-tips-on-decorators.md) -* 前言 -* 最佳实践 - * 1 - 尝试用类来实现装饰器 - * 2 - 使用 wrapt 模块编写更扁平的装饰器 -* 常见错误 - * 1 - “装饰器”并不是“装饰器模式” - * 2 - 记得用 functools.wraps() 装饰内层函数 - * 3 - 修改外层变量时记得使用 nonlocal -* 总结 -* 附录 +- 前言 +- 最佳实践 + - 1 - 尝试用类来实现装饰器 + - 2 - 使用 wrapt 模块编写更扁平的装饰器 +- 常见错误 + - 1 - “装饰器”并不是“装饰器模式” + - 2 - 记得用 functools.wraps() 装饰内层函数 + - 3 - 修改外层变量时记得使用 nonlocal +- 总结 +- 附录 ### 9. [一个关于模块的小故事](zh_CN/9-a-story-on-cyclic-imports.md) -* 前言 -* 一个关于模块的小故事 - * 需求变更 - * 解决环形依赖问题 - * 小 C 的疑问 -* 总结 -* 附录 +- 前言 +- 一个关于模块的小故事 + - 需求变更 + - 解决环形依赖问题 + - 小 C 的疑问 +- 总结 +- 附录 ### 10. [做一个精通规则的玩家](zh_CN/10-a-good-player-know-the-rules.md) -* 前言 - * Python 里的规则 -* 案例:从两份旅游数据中获取人员名单 - * 第一次蛮力尝试 - * 尝试使用集合优化函数 - * 对问题的重新思考 - * 利用集合的游戏规则 - * 使用 dataclass 简化代码 - * 案例总结 -* 其他规则如何影响我们 - * 使用 `__format__` 做对象字符串格式化 - * 使用 `__getitem__` 定义对象切片操作 -* 总结 -* 附录 +- 前言 + - Python 里的规则 +- 案例:从两份旅游数据中获取人员名单 + - 第一次蛮力尝试 + - 尝试使用集合优化函数 + - 对问题的重新思考 + - 利用集合的游戏规则 + - 使用 dataclass 简化代码 + - 案例总结 +- 其他规则如何影响我们 + - 使用 `__format__` 做对象字符串格式化 + - 使用 `__getitem__` 定义对象切片操作 +- 总结 +- 附录 ### 11. [高效操作文件的三个建议](zh_CN/11-three-tips-on-writing-file-related-codes.md) -* 前言 -* 建议一:使用 pathlib 模块 - * 使用 pathlib 模块改写代码 - * 其他用法 -* 建议二:掌握如何流式读取大文件 - * 标准做法的缺点 - * 使用 read 方法分块读取 - * 利用生成器解耦代码 -* 建议三:设计接受文件对象的函数 - * 如何编写兼容二者的函数 -* 总结 -* 附录 -* 注解 +- 前言 +- 建议一:使用 pathlib 模块 + - 使用 pathlib 模块改写代码 + - 其他用法 +- 建议二:掌握如何流式读取大文件 + - 标准做法的缺点 + - 使用 read 方法分块读取 + - 利用生成器解耦代码 +- 建议三:设计接受文件对象的函数 + - 如何编写兼容二者的函数 +- 总结 +- 附录 +- 注解 ### 12. [写好面向对象代码的原则(上)](zh_CN/12-write-solid-python-codes-part-1.md) -* 前言 - * Python 对 OOP 的支持 - * SOLID 设计原则 -* SOLID 原则与 Python -* S:单一职责原则 - * 违反“单一职责原则”的坏处 - * 拆分大类为多个小类 - * 另一种方案:使用函数 -* O:开放-关闭原则 - * 如何违反“开放-关闭原则” - * 使用类继承来改造代码 - * 使用组合与依赖注入来改造代码 - * 使用数据驱动思想来改造代码 -* 总结 -* 附录 +- 前言 + - Python 对 OOP 的支持 + - SOLID 设计原则 +- SOLID 原则与 Python +- S:单一职责原则 + - 违反“单一职责原则”的坏处 + - 拆分大类为多个小类 + - 另一种方案:使用函数 +- O:开放-关闭原则 + - 如何违反“开放-关闭原则” + - 使用类继承来改造代码 + - 使用组合与依赖注入来改造代码 + - 使用数据驱动思想来改造代码 +- 总结 +- 附录 ### 13. [写好面向对象代码的原则(中)](zh_CN/13-write-solid-python-codes-part-2.md) -* 前言 -* 里氏替换原则与继承 -* L:里氏替换原则 -* 一个违反 L 原则的样例 - * 不当继承关系如何违反 L 原则 - * 一个简单但错误的解决办法 - * 正确的修改办法 -* 另一种违反方式:子类修改方法返回值法返回值) - * 分析类方法返回结果 - * 如何修改代码 - * 方法参数与 L 原则 -* 总结 -* 附录 +- 前言 +- 里氏替换原则与继承 +- L:里氏替换原则 +- 一个违反 L 原则的样例 + - 不当继承关系如何违反 L 原则 + - 一个简单但错误的解决办法 + - 正确的修改办法 +- 另一种违反方式:子类修改方法返回值法返回值) + - 分析类方法返回结果 + - 如何修改代码 + - 方法参数与 L 原则 +- 总结 +- 附录 ### 14. [写好面向对象代码的原则(下)](zh_CN/14-write-solid-python-codes-part-3.md) -* 前言 -* D:依赖倒置原则 - * 需求:按域名分组统计 HN 新闻数量 - * 为 SiteSourceGrouper 编写单元测试 - * 使用 mock 模块 - * 实现依赖倒置原则 - * 依赖倒置后的单元测试 - * 问题:一定要使用抽象类 abc 吗? - * 问题:抽象一定是好东西吗? -* I:接口隔离原则 - * 例子:开发页面归档功能 - * 问题:实体类不符合 HNWebPage 接口规范 - * 成功违反 I 协议 - * 如何分拆接口 - * 一些不容易发现的违反情况 - * 现实世界中的接口隔离 -* 总结 -* 附录 +- 前言 +- D:依赖倒置原则 + - 需求:按域名分组统计 HN 新闻数量 + - 为 SiteSourceGrouper 编写单元测试 + - 使用 mock 模块 + - 实现依赖倒置原则 + - 依赖倒置后的单元测试 + - 问题:一定要使用抽象类 abc 吗? + - 问题:抽象一定是好东西吗? +- I:接口隔离原则 + - 例子:开发页面归档功能 + - 问题:实体类不符合 HNWebPage 接口规范 + - 成功违反 I 协议 + - 如何分拆接口 + - 一些不容易发现的违反情况 + - 现实世界中的接口隔离 +- 总结 +- 附录 ### 15. [在边界处思考](zh_CN/15-thinking-in-edge-cases.md) -* 前言 -* 第一课:使用分支还是异常? - * 获取原谅比许可简单(EAFP) -* 当容器内容不存在时 - * 使用 defaultdict 改写示例 - * 使用 setdefault 取值并修改 - * 使用 dict.pop 删除不存在的键 - * 当列表切片越界时 -* 好用又危险的 “or” 操作符 -* 不要手动去做数据校验 -* 不要忘记做数学计算 -* 总结 -* 附录 - - +- 前言 +- 第一课:使用分支还是异常? + - 获取原谅比许可简单(EAFP) +- 当容器内容不存在时 + - 使用 defaultdict 改写示例 + - 使用 setdefault 取值并修改 + - 使用 dict.pop 删除不存在的键 + - 当列表切片越界时 +- 好用又危险的 “or” 操作符 +- 不要手动去做数据校验 +- 不要忘记做数学计算 +- 总结 +- 附录 + +### 16. [语句、表达式和海象操作符](zh_CN/16-stmt-expr-and-walrus-operator.md) + +- [表达式的特点 +- [海象操作符 + - 1. 用于分支语句 + - 2. 消除推导式中的重复 + - 3. 捕获推导式的中间结果 + - 4. 赋值表达式的限制 +- 其他建议 + - 1. “更紧凑”不等于“更好” + - 2. 宜少不宜多 diff --git a/zh_CN/16-stmt-expr-and-walrus-operator.md b/zh_CN/16-stmt-expr-and-walrus-operator.md new file mode 100644 index 0000000..87cbe05 --- /dev/null +++ b/zh_CN/16-stmt-expr-and-walrus-operator.md @@ -0,0 +1,307 @@ +让我们从两行最简单的 Python 代码开始。 + +```python +>>> name = 'piglei' +>>> print(f'Hello {name}!') +Hello piglei! +``` + +这是一个“Hello World”程序,你也许已经见过它无数次,对里面的每个字母都了如指掌。但你可能从未意识到,上面两行代码,刚好对应着 Python 语言里的两个重要概念:**语句(statement)** 和 **表达式(expression)**。 + +具体来说,`name = 'piglei'` 是一行赋值语句,它将字符串 `'piglei'` 赋给了 `name` 变量。`print(f'Hello {name}!')` 则是一个表达式,它通过调用内置函数 `print` 往屏幕打印信息。 + +### 表达式的特点 + +编写代码时,语句和表达式是两类最基本的代码单元。 + +虽然在日常表达中,我们会把语句和表达式区分开来,但二者并非完全不同——表达式实际上就是一种特殊的语句。和普通语句比起来,表达式的特别之处在于它拥有一个(或多个)返回值。 + +举例来说,前面的 `print(...)` 表达式就会返回一个值:`None`。你可以像下面这样获取它: + +```python +# print 函数总是返回 None +>>> val = print(f'Hello {name}!') +Hello piglei! +>>> val is None +True +``` + +虽然这么做没啥实际用途,但它足够体现出表达式的独特之处——因为你永远无法对普通语句做出类似的事情。无论是“赋值语句”、“循环语句”,还是一个“条件分支语句”,你永远都无法将其赋值给某个变量,这在语法上无从谈起: + +```python +>>> val = (name = 'piglei') + File "", line 1 + val = (name = 'piglei') + ^ +SyntaxError: invalid syntax #1 +``` + +1. 意料之中,抛出了语法错误(`SyntaxError`) + +不过,Python 3.8 版本发布以后,表达式和语句间的分界线突然变得前所未有的模糊。上面这行错误代码,只要增加一个冒号就可以变得合法: + +```python +>>> val = (name := 'piglei') +>>> val, name +('piglei', 'piglei') +``` + +这便是“海象操作符(walrus operator)”——`:=`——的威力。 + +### 海象操作符 + +也许你会好奇,“海象操作符”这名字是怎么来的,为啥蟒蛇(python)的世界里会突然冒出一头海象(walrus)?假如你把头向左倾斜 90 度,仔细观察 `:=` 符号,就会发现其中的奥秘:它看起来就像一头海象的面部,冒号是鼻孔,等号是它的两根长牙。 + +使用 `:=` 操作符,可以构建出学名为“赋值表达式(Assignment Expressions)”的东西。在赋值表达式出现前,变量的赋值只能通过语句来完成。它出现后,我们便可在一个表达式内完成赋值,同时返回所赋值的变量。 + +```python +>>> val = (name := 'piglei') #1 +``` + +1. `(name := 'piglei')` 就是一个赋值表达式,它同时做到了两件事:将 `'piglei'` 赋值为 `name` 变量;返回 `name` 变量的值。 + +赋值表达式几乎可以被用在任何你能想到的地方,比如条件分支、循环和列表推导式,等等。 + +让我们来看几个典型场景。 + +#### 1. 用于分支语句 + +有一个函数,功能是从一段字符串中找出第一个以字母“w”开头的单词,如未找到,再尝试找以“w”结尾的。代码可以这么写: + +```python +import re + +LEADING_W_WORD = re.compile(r'\bw\w*?\b', re.I) +TRAILING_W_WORD = re.compile(r'\b\w*?w\b', re.I) + +def find_w_word(s): + """找到并打印字符串中第一个以 w 开头的单词,如未找到,再试着找 w 结尾的""" + if LEADING_W_WORD.search(s): + word = LEADING_W_WORD.search(s).group() + print(f'Found word starts with "w": {word}') + elif TRAILING_W_WORD.search(s): + word = TRAILING_W_WORD.search(s).group() + print(f'Found word ends with "w": {word}') +``` + +调用效果如下: + +```python +>>> find_w_word('Guido found several examples where a programmer repeated a subexpression') +Found word starts with "w": where +``` + +上面的代码存在一个小问题,每个负责正则搜索的表达式 `LEADING_W_WORD.search(s)` 分别重复出现了两次:一次在分支判断处,另一次在分支内部。 + +这种重复会让代码更难维护,也会影响程序的执行性能。因此,大部分时候我们会通过定义变量来消除重复: + +```python +def find_w_word_v2(s): + """找到并打印字符串中第一个以 w 开头的单词,如未找到,再试着找 w 结尾的""" + l_match = LEADING_W_WORD.search(s) #1 + if l_match: + word = l_match.group() + print(f'Found word starts with "w": {word}') + else: + t_match = TRAILING_W_WORD.search(s) + if t_match: + word = t_match.group() + print(f'Found word ends with "w": {word}') +``` + +1. 定义一个变量 `l_match` 保存 `.search()` 返回的匹配结果 + +但这样虽然消除了重复,却引入了更深的嵌套层级,还是难以让人满意。 + +有了赋值表达式后,我们可以更进一步,直接在分支判断语句中一次性完成表达式的运算和赋值。于是,代码可以被进一步简化成这样: + +```python +def find_w_word_v3(s): + """找到并打印字符串中第一个以 w 开头的单词,如未找到,再试着找 w 结尾的""" + if l_match := LEADING_W_WORD.search(s): + word = l_match.group() + print(f'Found word starts with "w": {word}') + elif t_match := TRAILING_W_WORD.search(s): + word = t_match.group() + print(f'Found word ends with "w": {word}') +``` + +修改之后,代码变得更扁平,逻辑也更加紧凑了。 + +除了 `if` 条件分支,`while` 循环中也可以使用赋值表达式。比如,下面这种模式的循环代码十分常见: + +```python +while True: + chunk = fp.read(2048) + if not chunk: + break + # 继续后续对 chunk 的处理... +``` + +如果使用赋值表达式,它可以被简化成下面这样: + +```python +while chunk := fp.read(2048): + # 继续后续对 chunk 的处理... +``` + +#### 2. 消除推导式中的重复 + +前面演示了在分支语句中使用赋值表达式,除此之外,你也可以在各类推导式中使用它。 + +举个例子,在构建一个推导式时,我们有时可能会需要同时做到以下两件事: + +1. 预计算每个成员,判断结果是否满足要求 +2. 如满足,将预计算的结果置入新对象 + +下面的代码完成了这个功能: + +```python +# 仅挑选 func(...) > 100 的成员构建新列表 +new_objs = [func(p) for p in objs if func(p) > 100] +``` + +虽然它满足需求,但也有一个严重的问题:`func(p)` 在每次迭代时会被重复执行两次,这很可能会成为一个潜在的性能隐患。 + +在以前,如果你想优化这个问题,除了把表达式拆成普通 `for` 循环外没什么其他办法。但有了赋值表达式,代码可被轻松优化成这样: + +```python +new_objs = [v for p in objs if (v := func(p)) > 100] +``` + +重复的函数调用原地消失了。 + +#### 3. 捕获推导式的中间结果 + +从某种角度上看,赋值表达式是一种有“副作用”的表达式,它的副作用就是在返回值的同时,完成变量赋值。如果你有意地利用这种副作用,就能完成一些相当出人意料的事情。 + +让我来举个例子。`any()` 是 Python 的一个内建函数,它接收一个可迭代对象作为参数,在遍历该对象的过程中,如果发现任何布尔值为真的成员,函数就立刻返回 `True`,否则返回 `False`。 + +一个常见的使用场景如下所示: + +```python +def has_lucky_number(nums): + """判断给定的列表中,是否存在能被 7 整除的数字""" + return any(n % 7 == 0 for n in nums) +``` + +调用示例: + +```python +>>> has_lucky_number([4, 8, 9]) +False +>>> has_lucky_number([4, 8, 21, 9]) +True +``` + +某日,需求变更了。函数不仅需要知道是否存在被 7 整除的数字,还得把这个数字找出来。代码该怎么改?`any(...)` 像是肯定没法再用了,不如写一个平平无奇的 `for` 循环吧。 + +但其实,如果你使用赋值表达式搭配上 `any` 函数的短路执行特性,下面这几行代码也可以达成使命: + +```python +def get_lucky_number(nums): + """返回列表中能被 7 整除的数字,如没有则返回 None""" + if any((ret := n) % 7 == 0 for n in nums): + return ret + return None +``` + +调用示例: + +```python +>>> get_lucky_number([4, 8, 9]) +>>> get_lucky_number([4, 8, 21, 9]) +21 +``` + +和之前相比,新代码最主要的修改在于将 `n` 替换成了 `(ret := n)`——一个有副作用的赋值表达式。在 `any` 函数进行循环遍历 `nums` 列表的过程中,当前被迭代的成员 `n` 会被赋到 `ret` 变量上,如其刚好满足条件,就会直接被当做结果返回。 + +借助赋值表达式的副作用,我们成功捕获了第一个满足条件的成员,只用一行代码就实现了需求。 + +#### 4. 赋值表达式的限制 + +从外观上看,赋值表达式和赋值语句极为相似,仅多了一个冒号 `:`。但如果你继续深入,会发现它其实被施加了许多普通赋值语句所没有的限制。 + +比如,它在作为整句独立使用时,两边必须添加括号: + +``` +>>> x := 1 +SyntaxError: invalid syntax +>>> (x := 1) +1 +``` + +此外,赋值表达式也无法直接操作对象属性(或字典的键): + +```python +# 普通赋值语句 +>>> s.foo = 'bar' +>>> d['foo'] = 'bar' + +# 赋值表达式无法做到 +>>> (s.foo := 'bar') +SyntaxError: cannot use assignment expressions with attribute +>>> (d['foo'] := 'bar') +SyntaxError: cannot use assignment expressions with subscript +``` + +诸如此类的限制,是语言设计者为避免人们滥用赋值表达式而为之。但即便有着这些限制,赋值表达式这个 Python 3.8 中增加的新语法,已然为人们在 Python 中“遣词造句”,带来了巨大的可能性和想象空间。 + +> 如果你想了解更多关于”赋值表达式“的细节,建议阅读官方 PEP: [PEP 572 – Assignment Expressions](https://peps.python.org/pep-0572/)。 + +### 其他建议 + +下面是关于”赋值表达式“的两个使用建议。 + +#### 1. “更紧凑”不等于“更好” + +正如前面所展示的,我们可以像玩积木一样组合使用赋值表达式,写出更精炼、更紧凑的代码。但对于代码而言,“更紧凑”不能和“更好”画上等号。关于这点,我很喜欢 Tim Peters 举过的[一个简单例子](https://peps.python.org/pep-0572/#appendix-a-tim-peters-s-findings)。 + +Tim Peters 说自己不喜欢“匆匆忙忙”的代码,讨厌将概念上无关的逻辑写到同一行代码里。比方说,与其像下面这样写: + +```python +i = j = count = nerrors = 0 +``` + +他更倾向于改成这样: + +```python +i = j = 0 +count = 0 +nerrors = 0 +``` + +第一种写法虽然紧凑,但其实忽视了一件重要的事:这几个变量分属 3 类不同用途(分别是循环索引值、个数和错误数量),它们只是碰巧都为 `0` 而已。将代码拆成 3 行以后,虽没那么紧凑,但概念上实际变得更清晰了。 + +在使用赋值表达式时,我们尤其需要避免掉进盲目追求“精炼”和“紧凑”的陷阱里,多多关注每行代码在逻辑上的联系,而不要整日盯着**字面意义上**的精简。 + +#### 2. 宜少不宜多 + +赋值表达式是 Python 3.8 引入的新特性,已经发布 3 年有余。但就自身感受而言,除了在一些 Python 教程文章中,我在其他项目里极少见到它的身影。 + +人们很少使用赋值表达式,我猜主要出于两方面的原因。 + +其一,Python 3.8 仍是一个相对较新的版本,许多项目尚未完成版本升级。其二,赋值表达式本身非常灵活,适用场景非常多,使用起来难以把控尺度,因此许多开发者对其抱着较为警惕的态度。再加上它本身也不提供任何普通语句做不到的独特功能——不是雪中送炭,只是锦上添花——因此大家不愿尝鲜。 + +上面的第一类原因,随着时间的推移会慢慢得到解决。我们主要看第二类。 + +我认为,大部分开发者的担忧确实有一定道理,赋值表达式在将代码变得紧凑的同时,也带来了更高的理解成本和上手门槛。而且平心而论,一些用了赋值表达式的代码,真的会给我一种“*这么写是不是过于聪明了?*”的感觉。 + +拿之前的这段代码为例: + +```python +if any((ret := n) % 7 == 0 for n in nums): + return ret +``` + +如果是一个私人脚本,也许我会愿意把代码写成上面那样。但在多人参与的真实项目里,我目前可能更愿意用一段平平无奇的 `for` 循环替代它。很多时候,相比“聪明”的代码,“笨”代码才是我们更需要的东西,它们能为项目的参与者省去许多沟通和维护上的成本。 + +总体而言,关于是否应该在项目中使用赋值表达式,我的建议是: + +- 在分支语句的消除重复场景,使用赋值表达式 +- 在推导式的消除重复场景,使用赋值表达式 +- 其他情况下,优先使用普通赋值语句,哪怕这意味着更多代码和少量重复(比如“获取第一个满足条件的成员”场景) + +希望以上的内容对你有所帮助。 + +> 这篇文章属于“Python 工匠”系列,如果你喜欢它,也欢迎了解我的书[《Python工匠:案例、技巧与工程实践》\[试读\]](https://www.zlovezl.cn/book/index.html) | [\[书评\]](https://book.douban.com/subject/35723705/),其中有大量同样风格的 Python 编程进阶知识。 From 295c0609cd258a8ca894e183d3122eb8d4dd358c Mon Sep 17 00:00:00 2001 From: piglei Date: Sun, 23 Oct 2022 11:43:05 +0800 Subject: [PATCH 11/16] Format fix --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 98bb895..401f6fa 100644 --- a/README.md +++ b/README.md @@ -299,8 +299,8 @@ ### 16. [语句、表达式和海象操作符](zh_CN/16-stmt-expr-and-walrus-operator.md) -- [表达式的特点 -- [海象操作符 +- 表达式的特点 +- 海象操作符 - 1. 用于分支语句 - 2. 消除推导式中的重复 - 3. 捕获推导式的中间结果 From de65967babce6b1679ec484d911fe8bc9ec7ff8c Mon Sep 17 00:00:00 2001 From: David Lee <67067729+LordMoMA@users.noreply.github.com> Date: Mon, 1 May 2023 13:08:52 +0800 Subject: [PATCH 12/16] Update 6-three-rituals-of-exceptions-handling.md --- zh_CN/6-three-rituals-of-exceptions-handling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zh_CN/6-three-rituals-of-exceptions-handling.md b/zh_CN/6-three-rituals-of-exceptions-handling.md index cee88f0..fffe99a 100644 --- a/zh_CN/6-three-rituals-of-exceptions-handling.md +++ b/zh_CN/6-three-rituals-of-exceptions-handling.md @@ -69,7 +69,7 @@ if __name__ == '__main__': 问题就藏在这个硕大无比的 `try ... except` 语句块里。假如你把眼睛贴近屏幕,非常仔细的检查这段代码。你会发现在编写函数时,我犯了一个**小错误**,我把获取正则匹配串的方法错打成了 `obj.grop(1)`,少了一个 'u'(`obj.group(1)`)。 -但正是因为那个过于庞大、含糊的异常捕获,这个由打错方法名导致的原本该被抛出的 `AttibuteError` 却被吞噬了。从而给我们的 debug 过程增加了不必要的麻烦。 +但正是因为那个过于庞大、含糊的异常捕获,这个由打错方法名导致的原本该被抛出的 `AttributeError` 却被吞噬了。从而给我们的 debug 过程增加了不必要的麻烦。 异常捕获的目的,不是去捕获尽可能多的异常。假如我们从一开始就坚持:**只做最精准的异常捕获**。那么这样的问题就根本不会发生,精准捕获包括: From cbe9b3f53c5bdd89a9203a0d88ce07ed5259cfb6 Mon Sep 17 00:00:00 2001 From: piglei Date: Wed, 1 Nov 2023 21:20:48 +0800 Subject: [PATCH 13/16] Fix bad domain: zlovezl.cn->piglei.com --- README.md | 14 ++--- zh_CN/10-a-good-player-know-the-rules.md | 18 +++--- ...hree-tips-on-writing-file-related-codes.md | 19 +++--- zh_CN/12-write-solid-python-codes-part-1.md | 19 +++--- zh_CN/13-write-solid-python-codes-part-2.md | 15 +++-- zh_CN/14-write-solid-python-codes-part-3.md | 30 ++++----- zh_CN/15-thinking-in-edge-cases.md | 14 ++--- zh_CN/16-stmt-expr-and-walrus-operator.md | 2 +- zh_CN/4-mastering-container-types.md | 62 +++++++++---------- zh_CN/5-function-returning-tips.md | 51 +++++++-------- .../6-three-rituals-of-exceptions-handling.md | 16 +++-- zh_CN/7-two-tips-on-loop-writing.md | 16 ++--- zh_CN/8-tips-on-decorators.md | 13 ++-- zh_CN/9-a-story-on-cyclic-imports.md | 17 +++-- 14 files changed, 135 insertions(+), 171 deletions(-) diff --git a/README.md b/README.md index 401f6fa..1628b62 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ 《Python 工匠》图书首页上线啦!共有 3 章在线内容,欢迎阅读。 -- [图书首页](https://www.zlovezl.cn/book/) -- [第 1 章 变量与注释](https://www.zlovezl.cn/book/ch01_variables.html) -- [第 6 章 循环与可迭代对象](https://www.zlovezl.cn/book/ch06_loop.html) -- [第 10 章 面向对象设计原则](https://www.zlovezl.cn/book/ch10_solid_p1.html) +- [图书首页](https://www.piglei.com/book/) +- [第 1 章 变量与注释](https://www.piglei.com/book/ch01_variables.html) +- [第 6 章 循环与可迭代对象](https://www.piglei.com/book/ch06_loop.html) +- [第 10 章 面向对象设计原则](https://www.piglei.com/book/ch10_solid_p1.html) 图书《Python 工匠》现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等核心章节,总内容量几乎翻倍,欢迎购买。 @@ -21,18 +21,18 @@ ## 文章列表 -- 1\. 善用变量改善代码质量 [[图书版:变量与注释](https://www.zlovezl.cn/book/ch01_variables.html)] +- 1\. 善用变量改善代码质量 [[图书版:变量与注释](https://www.piglei.com/book/ch01_variables.html)] - [2. 编写条件分支代码的技巧](zh_CN/2-if-else-block-secrets.md) - [3. 使用数字与字符串的技巧](zh_CN/3-tips-on-numbers-and-strings.md) - [4. 容器的门道](zh_CN/4-mastering-container-types.md) - [5. 让函数返回结果的技巧](zh_CN/5-function-returning-tips.md) - [6. 异常处理的三个好习惯](zh_CN/6-three-rituals-of-exceptions-handling.md) -- 7\. 编写地道循环的两个建议 [[图书版:循环与可迭代对象](https://www.zlovezl.cn/book/ch06_loop.html)] +- 7\. 编写地道循环的两个建议 [[图书版:循环与可迭代对象](https://www.piglei.com/book/ch06_loop.html)] - [8. 使用装饰器的技巧](zh_CN/8-tips-on-decorators.md) - [9. 一个关于模块的小故事](zh_CN/9-a-story-on-cyclic-imports.md) - [10. 做一个精通规则的玩家](zh_CN/10-a-good-player-know-the-rules.md) - [11. 高效操作文件的三个建议](zh_CN/11-three-tips-on-writing-file-related-codes.md) -- 12\. 写好面向对象代码的原则(上) [[图书版:面向对象设计原则(上)](https://www.zlovezl.cn/book/ch10_solid_p1.html)] +- 12\. 写好面向对象代码的原则(上) [[图书版:面向对象设计原则(上)](https://www.piglei.com/book/ch10_solid_p1.html)] - [13. 写好面向对象代码的原则(中)](zh_CN/13-write-solid-python-codes-part-2.md) - [14. 写好面向对象代码的原则(下)](zh_CN/14-write-solid-python-codes-part-3.md) - [15. 在边界处思考](zh_CN/15-thinking-in-edge-cases.md) diff --git a/zh_CN/10-a-good-player-know-the-rules.md b/zh_CN/10-a-good-player-know-the-rules.md index 02041e3..ffd3a7e 100644 --- a/zh_CN/10-a-good-player-know-the-rules.md +++ b/zh_CN/10-a-good-player-know-the-rules.md @@ -5,7 +5,7 @@ > 这是 “Python 工匠”系列的第 10 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
编程,其实和玩电子游戏有一些相似之处。你在玩不同游戏前,需要先学习每个游戏的不同规则,只有熟悉和灵活运用游戏规则,才更有可能在游戏中获胜。 @@ -120,11 +120,11 @@ def find_potential_customers_v2(): >>> a - b {1, 7} ``` - + 所以,计算“所有去过普吉岛但没去过新西兰的人”,其实就是一次集合的求差值操作。那么要怎么做,才能把我们的问题套入到集合的游戏规则里去呢? - + ### 利用集合的游戏规则 - + 在 Python 中,如果要把某个东西装到集合或字典里,一定要满足一个基本条件:**“这个东西必须是可以被哈希(Hashable)的”** 。什么是 “Hashable”? 举个例子,Python 里面的所有可变对象,比如字典,就 **不是** Hashable 的。当你尝试把字典放入集合中时,会发生这样的错误: @@ -357,13 +357,11 @@ Python 世界有着一套非常复杂的规则,这些规则的涵盖范围包 ## 附录 - 题图来源: Photo by JESHOOTS.COM on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:异常处理的三个好习惯](https://www.zlovezl.cn/articles/three-rituals-of-exceptions-handling/) -- [Python 工匠:编写地道循环的两个建议](https://www.zlovezl.cn/articles/two-tips-on-loop-writing/) - - +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:异常处理的三个好习惯](https://www.piglei.com/articles/three-rituals-of-exceptions-handling/) +- [Python 工匠:编写地道循环的两个建议](https://www.piglei.com/articles/two-tips-on-loop-writing/) diff --git a/zh_CN/11-three-tips-on-writing-file-related-codes.md b/zh_CN/11-three-tips-on-writing-file-related-codes.md index b78264c..1566cd3 100644 --- a/zh_CN/11-three-tips-on-writing-file-related-codes.md +++ b/zh_CN/11-three-tips-on-writing-file-related-codes.md @@ -1,11 +1,11 @@ -# Python 工匠:高效操作文件的三个建议 +# Python 工匠:高效操作文件的三个建议 ## 前言 > 这是 “Python 工匠”系列的第 11 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
在这个世界上,人们每天都在用 Python 完成着不同的工作。而文件操作,则是大家最常需要解决的任务之一。使用 Python,你可以轻松为他人生成精美的报表,也可以用短短几行代码快速解析、整理上万份数据文件。 @@ -46,7 +46,7 @@ def unify_ext_with_os_path(path): - [`os.path.join(path, filename)`](https://docs.python.org/3/library/os.path.html#os.path.join):组合需要操作的文件名为绝对路径 - [`os.rename(...)`](https://docs.python.org/3/library/os.html#os.rename):重命名某个文件 -上面的函数虽然可以完成需求,但说句实话,即使在写了很多年 Python 代码后,我依然觉得:**这些函数不光很难记,而且最终的成品代码也不怎么讨人喜欢。** +上面的函数虽然可以完成需求,但说句实话,即使在写了很多年 Python 代码后,我依然觉得:**这些函数不光很难记,而且最终的成品代码也不怎么讨人喜欢。** ### 使用 pathlib 模块改写代码 @@ -192,7 +192,7 @@ def count_nine_v2(fname): 假如我们在讨论的不是 Python,而是其他编程语言。那么可以说上面的代码已经很好了。但是如果你认真分析一下 `count_nine_v2` 函数,你会发现在循环体内部,存在着两个独立的逻辑:**数据生成(read 调用与 chunk 判断)** 与 **数据消费**。而这两个独立逻辑被耦合在了一起。 -正如我在[《编写地道循环》](https://www.zlovezl.cn/articles/two-tips-on-loop-writing/)里所提到的,为了提升复用能力,我们可以定义一个新的 `chunked_file_reader` 生成器函数,由它来负责所有与“数据生成”相关的逻辑。这样 `count_nine_v3` 里面的主循环就只需要负责计数即可。 +正如我在[《编写地道循环》](https://www.piglei.com/articles/two-tips-on-loop-writing/)里所提到的,为了提升复用能力,我们可以定义一个新的 `chunked_file_reader` 生成器函数,由它来负责所有与“数据生成”相关的逻辑。这样 `count_nine_v3` 里面的主循环就只需要负责计数即可。 ```python def chunked_file_reader(fp, block_size=1024 * 8): @@ -379,18 +379,15 @@ def parse(self, source, parser=None): ## 附录 - 题图来源: Photo by Devon Divine on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:异常处理的三个好习惯](https://www.zlovezl.cn/articles/three-rituals-of-exceptions-handling/) -- [Python 工匠:编写地道循环的两个建议](https://www.zlovezl.cn/articles/two-tips-on-loop-writing/) - +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:异常处理的三个好习惯](https://www.piglei.com/articles/three-rituals-of-exceptions-handling/) +- [Python 工匠:编写地道循环的两个建议](https://www.piglei.com/articles/two-tips-on-loop-writing/) ## 注解 1. 视机器空闲内存的多少,这个过程可能会消耗比 2GB 更多的内存。 - - diff --git a/zh_CN/12-write-solid-python-codes-part-1.md b/zh_CN/12-write-solid-python-codes-part-1.md index 3279819..00bd16a 100644 --- a/zh_CN/12-write-solid-python-codes-part-1.md +++ b/zh_CN/12-write-solid-python-codes-part-1.md @@ -2,11 +2,10 @@ ## 前言 - > 这是 “Python 工匠”系列的第 12 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
Python 是一门支持多种编程风格的语言,面对相同的需求,拥有不同背景的程序员可能会写出风格迥异的 Python 代码。比如一位习惯编写 C 语言的程序员,通常会定义一大堆函数来搞定所有事情,这是[“过程式编程”](https://en.wikipedia.org/wiki/Procedural_programming)的思想。而一位有 Java 背景的程序员则更倾向于设计许多个相互关联的类*(class)*,这是 [“面向对象编程(后简称 OOP)”](https://en.wikipedia.org/wiki/Object-oriented_programming)。 @@ -36,7 +35,7 @@ Python 语言虽然拥有类、继承、多态等核心 OOP 特性,但和那 接下来,让我用一个真实的 Python 代码样例来分别向你诠释这 5 条设计原则。 > 写在最前面的注意事项: -> +> > 0. “原则”不是“法律”,它只起到指导作用,并非不可以违反 > 1. “原则”的后两条与接口(Interface)有关,而 Python 没有接口,所以对这部分的诠释是我的个人理解,与原版可能略有出入 > 2. 文章后面的内容含有大量代码,请做好心理准备 ☕️ @@ -49,7 +48,7 @@ Python 语言虽然拥有类、继承、多态等核心 OOP 特性,但和那 我经常会去上面看一些热门文章,但我觉得每次打开浏览器访问有点麻烦。所以,我准备编写一个脚本,自动抓取 HN 首页 Top5 的新闻标题与链接,并用纯文本的方式写入到文件。方便自己用其他工具阅读。
- + 图:Hacker News 首页截图
@@ -577,18 +576,14 @@ def main(): [<<<上一篇【11.高效操作文件的三个建议】](11-three-tips-on-writing-file-related-codes.md) - ## 附录 - 题图来源: Photo by Kelly Sikkema on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:让函数返回结果的技巧](https://www.zlovezl.cn/articles/function-returning-tips/) -- [Python 工匠:编写地道循环的两个建议](https://www.zlovezl.cn/articles/two-tips-on-loop-writing/) -- [Python 工匠:高效操作文件的三个建议](https://www.zlovezl.cn/articles/three-tips-on-writing-file-related-codes/) - - - +- [Python 工匠:让函数返回结果的技巧](https://www.piglei.com/articles/function-returning-tips/) +- [Python 工匠:编写地道循环的两个建议](https://www.piglei.com/articles/two-tips-on-loop-writing/) +- [Python 工匠:高效操作文件的三个建议](https://www.piglei.com/articles/three-tips-on-writing-file-related-codes/) diff --git a/zh_CN/13-write-solid-python-codes-part-2.md b/zh_CN/13-write-solid-python-codes-part-2.md index b1800b2..7ab7f80 100644 --- a/zh_CN/13-write-solid-python-codes-part-2.md +++ b/zh_CN/13-write-solid-python-codes-part-2.md @@ -5,10 +5,10 @@ > 这是 “Python 工匠”系列的第 13 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
-在 [上一篇文章](https://www.zlovezl.cn/articles/write-solid-python-codes-part-1/) 里,我用一个虚拟小项目作为例子,讲解了“SOLID”设计原则中的前两位成员:S *(单一职责原则)* 与 O *(开放-关闭原则)*。 +在 [上一篇文章](https://www.piglei.com/articles/write-solid-python-codes-part-1/) 里,我用一个虚拟小项目作为例子,讲解了“SOLID”设计原则中的前两位成员:S *(单一职责原则)* 与 O *(开放-关闭原则)*。 在这篇文章中,我将继续介绍 SOLID 原则的第三位成员:**L(里氏替换原则)**。 @@ -29,6 +29,7 @@ 同前面的 S 与 O 两个原则的命名方式不同,里氏替换原则 *(Liskov Substitution Principle)* 是直接用它的发明者 [Barbara Liskov](https://en.wikipedia.org/wiki/Barbara_Liskov) 命名的,原文看起来像一个复杂的数学公式: > Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T. +> > - 出处: [Liskov substitution principle - Wikipedia](https://en.wikipedia.org/wiki/Liskov_substitution_principle) 如果把它比较通俗的翻译过来,大概是这样:**当你使用继承时,子类(派生类)对象应该可以在程序中替代父类(基类)对象使用,而不破坏程序原本的功能。** @@ -344,13 +345,11 @@ class Admin(User): ## 附录 - 题图来源: Photo by NeONBRAND on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:写好面向对象代码的原则(上)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-1/) -- [Python 工匠:编写地道循环的两个建议](https://www.zlovezl.cn/articles/two-tips-on-loop-writing/) -- [Python 工匠:高效操作文件的三个建议](https://www.zlovezl.cn/articles/three-tips-on-writing-file-related-codes/) - - +- [Python 工匠:写好面向对象代码的原则(上)](https://www.piglei.com/articles/write-solid-python-codes-part-1/) +- [Python 工匠:编写地道循环的两个建议](https://www.piglei.com/articles/two-tips-on-loop-writing/) +- [Python 工匠:高效操作文件的三个建议](https://www.piglei.com/articles/three-tips-on-writing-file-related-codes/) diff --git a/zh_CN/14-write-solid-python-codes-part-3.md b/zh_CN/14-write-solid-python-codes-part-3.md index d605c7c..bd1b51f 100644 --- a/zh_CN/14-write-solid-python-codes-part-3.md +++ b/zh_CN/14-write-solid-python-codes-part-3.md @@ -4,11 +4,10 @@ > 这是 “Python 工匠”系列的第 14 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman) -
- +
- + 在这篇文章中,我将继续介绍 SOLID 原则剩下的两位成员:**I(接口隔离原则)** 和 **D(依赖倒置原则)**。为了方便,这篇文章将会使用先 D 后 I 的顺序。 ## D:依赖倒置原则 @@ -32,7 +31,7 @@ 我想要按照来源域名来分组统计条目数量,这样就能知道哪个站在 HN 上最受欢迎。
- + 图:Hacker News 条目来源截图
@@ -91,7 +90,7 @@ Site: latimes.com | Count: 1 `SiteSourceGrouper` 是我们的核心类。为了完成任务,它需要使用 `requests` 模块获取首页内容、`lxml` 模块解析标题。所以,现在的依赖关系是“正向”的,高层模块 `SiteSourceGrouper` 依赖低层模块 `requests`、`lxml`。
- + 图:SiteSourceGrouper 依赖 requests、lxml
@@ -256,7 +255,7 @@ def main(): 做完这些修改后,让我们再看看现在的模块依赖关系:
- + 图:SiteSourceGrouper 和 RemoteHNWebPage 都依赖抽象层 HNWebPage
@@ -291,7 +290,7 @@ def test_grouper_from_local(): 这样就可以在没有外网的服务器上测试 `SiteSourceGrouper` 类的核心逻辑了。 > Hint:其实上面的测试函数 `test_grouper_from_local` 远远算不上一个合格的测试用例。 -> +> > 如果真要测试 `SiteSourceGrouper` 的核心逻辑。我们应该准备一个虚构的 Hacker News 页面 *(比如刚好包含 5 个 来源自 github.com 的条目)*,然后判断结果是否包含 `assert result['github.com] == 5` ### 问题:一定要使用抽象类 abc 吗? @@ -394,7 +393,7 @@ def get_generated_at(self) -> datetime.datetime: 但是,在给 `LocalHNWebPage` 添加 `get_generated_at` 方法时,我碰到了一个问题。`LocalHNWebPage` 是一个完全基于本地页面文件作为数据来源的类,仅仅通过 “static_hn.html” 这么一个本地文件,我根本就没法知道它的内容是什么时候生成的。 -这时我只能选择让它的 `get_generated_at` 方法返回一个错误的结果 *(比如文件的修改时间)*,或者直接抛出异常。无论是哪种做法,我都可能违反 [里式替换原则](https://www.zlovezl.cn/articles/write-solid-python-codes-part-2/)。 +这时我只能选择让它的 `get_generated_at` 方法返回一个错误的结果 *(比如文件的修改时间)*,或者直接抛出异常。无论是哪种做法,我都可能违反 [里式替换原则](https://www.piglei.com/articles/write-solid-python-codes-part-2/)。 > Hint:里式替换原则认为子类(派生类)对象应该可以在程序中替代父类(基类)对象使用,而不破坏程序原本的功能。让方法抛出异常显然破坏了这一点。 @@ -429,7 +428,7 @@ class SiteAchiever: 代码写到这,让我们回头看看上个例子里的 *条目来源分组类 `SiteSourceGrouper`* 。
- + 图:成功违反了 I 协议
@@ -474,7 +473,7 @@ class HNWebPage(ContentOnlyHNWebPage): 同时,对于 `LocalHNWebPage` 类来说,它也只需要实现那个只返回的文本的 `ContentOnlyHNWebPage` 就行。
- + 图:实施接口隔离后的结果
@@ -534,17 +533,14 @@ def is_new_visitor(cookies: Dict) -> bool: [<<<上一篇【13.写好面向对象代码的原则(中)】](13-write-solid-python-codes-part-2.md) - ## 附录 - 题图来源: Photo by Carolina Garcia Tavizon on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:写好面向对象代码的原则(上)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-1/) -- [Python 工匠:写好面向对象代码的原则(中)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-2/) -- [Python 工匠:写好面向对象代码的原则(下)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-3/) - - +- [Python 工匠:写好面向对象代码的原则(上)](https://www.piglei.com/articles/write-solid-python-codes-part-1/) +- [Python 工匠:写好面向对象代码的原则(中)](https://www.piglei.com/articles/write-solid-python-codes-part-2/) +- [Python 工匠:写好面向对象代码的原则(下)](https://www.piglei.com/articles/write-solid-python-codes-part-3/) diff --git a/zh_CN/15-thinking-in-edge-cases.md b/zh_CN/15-thinking-in-edge-cases.md index f4ed6f1..b9f7657 100644 --- a/zh_CN/15-thinking-in-edge-cases.md +++ b/zh_CN/15-thinking-in-edge-cases.md @@ -5,10 +5,10 @@ > 这是 “Python 工匠”系列的第 15 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
-2016 年,Linux 操作系统的创造者 Linus Torvalds 参加了一场[ TED 访谈节目](https://www.ted.com/talks/linus_torvalds_the_mind_behind_linux/transcript?language=en)。整个节目的前半部分,主要是他在讲如何在家光着膀子写出 Linux 的故事,没有涉及太多编程相关的事情。 +2016 年,Linux 操作系统的创造者 Linus Torvalds 参加了一场[TED 访谈节目](https://www.ted.com/talks/linus_torvalds_the_mind_behind_linux/transcript?language=en)。整个节目的前半部分,主要是他在讲如何在家光着膀子写出 Linux 的故事,没有涉及太多编程相关的事情。 不过在访谈快结束时,突然出现了一个有趣的环节。主持人向 Linus 提问道:“你曾说过更愿意和那些有着好的 **代码品味** 的人共事,那在你眼里,什么才是好的代码品味?” @@ -159,7 +159,7 @@ def counter_by_collections(l): return result ``` -这样的代码既不用“获取许可”,也无需“请求原谅”。 整个函数只有一个主流程,代码更清晰、更自然。 +这样的代码既不用“获取许可”,也无需“请求原谅”。 整个函数只有一个主流程,代码更清晰、更自然。 为什么 `defaultdict` 可以让边界情况消失?因为究其根本,之前的代码就是少了针对 *“键不存在”* 时的默认处理逻辑。所以,当我们用 `defaultdict` 声明了如何处理这个边界情况时,原本需要手动判断的部分就消失了。 @@ -464,12 +464,10 @@ while True: ## 附录 - 题图来源: Photo by Jessica Ruscello on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:写好面向对象代码的原则(上)](https://www.zlovezl.cn/articles/write-solid-python-codes-part-1/) -- [Python 工匠:让函数返回结果的技巧](https://www.zlovezl.cn/articles/function-returning-tips/) - - +- [Python 工匠:写好面向对象代码的原则(上)](https://www.piglei.com/articles/write-solid-python-codes-part-1/) +- [Python 工匠:让函数返回结果的技巧](https://www.piglei.com/articles/function-returning-tips/) diff --git a/zh_CN/16-stmt-expr-and-walrus-operator.md b/zh_CN/16-stmt-expr-and-walrus-operator.md index 87cbe05..692e6a9 100644 --- a/zh_CN/16-stmt-expr-and-walrus-operator.md +++ b/zh_CN/16-stmt-expr-and-walrus-operator.md @@ -304,4 +304,4 @@ if any((ret := n) % 7 == 0 for n in nums): 希望以上的内容对你有所帮助。 -> 这篇文章属于“Python 工匠”系列,如果你喜欢它,也欢迎了解我的书[《Python工匠:案例、技巧与工程实践》\[试读\]](https://www.zlovezl.cn/book/index.html) | [\[书评\]](https://book.douban.com/subject/35723705/),其中有大量同样风格的 Python 编程进阶知识。 +> 这篇文章属于“Python 工匠”系列,如果你喜欢它,也欢迎了解我的书[《Python工匠:案例、技巧与工程实践》\[试读\]](https://www.piglei.com/book/index.html) | [\[书评\]](https://book.douban.com/subject/35723705/),其中有大量同样风格的 Python 编程进阶知识。 diff --git a/zh_CN/4-mastering-container-types.md b/zh_CN/4-mastering-container-types.md index a4c93cf..cb29ec8 100644 --- a/zh_CN/4-mastering-container-types.md +++ b/zh_CN/4-mastering-container-types.md @@ -5,7 +5,7 @@ > 这是 “Python 工匠”系列的第 4 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
-
+
图片来源: "The Humble Mason Jar" by Chiot's Run - 非商业性使用 2.0 通用
@@ -20,29 +20,28 @@ Python 语言自身的内部实现细节也与这些容器类型息息相关。 ### 内容目录 - [Python 工匠:容器的门道](#python-工匠容器的门道) - - [序言](#序言) - - [内容目录](#内容目录) - - [当我们谈论容器时,我们在谈些什么?](#当我们谈论容器时我们在谈些什么) - - [底层看容器](#底层看容器) - - [写更快的代码](#写更快的代码) - - [1. 避免频繁扩充列表/创建新列表](#1-避免频繁扩充列表创建新列表) - - [2. 在列表头部操作多的场景使用 deque 模块](#2-在列表头部操作多的场景使用-deque-模块) - - [3. 使用集合/字典来判断成员是否存在](#3-使用集合字典来判断成员是否存在) - - [高层看容器](#高层看容器) - - [写扩展性更好的代码](#写扩展性更好的代码) - - [面向容器接口编程](#面向容器接口编程) - - [常用技巧](#常用技巧) - - [1. 使用元组改善分支代码](#1-使用元组改善分支代码) - - [2. 在更多地方使用动态解包](#2-在更多地方使用动态解包) - - [3. 使用 next() 函数](#3-使用-next-函数) - - [4. 使用有序字典来去重](#4-使用有序字典来去重) - - [常见误区](#常见误区) - - [1. 当心那些已经枯竭的迭代器](#1-当心那些已经枯竭的迭代器) - - [2. 别在循环体内修改被迭代对象](#2-别在循环体内修改被迭代对象) - - [总结](#总结) - - [系列其他文章](#系列其他文章) - - [注解](#注解) - + - [序言](#序言) + - [内容目录](#内容目录) + - [当我们谈论容器时,我们在谈些什么?](#当我们谈论容器时我们在谈些什么) + - [底层看容器](#底层看容器) + - [写更快的代码](#写更快的代码) + - [1. 避免频繁扩充列表/创建新列表](#1-避免频繁扩充列表创建新列表) + - [2. 在列表头部操作多的场景使用 deque 模块](#2-在列表头部操作多的场景使用-deque-模块) + - [3. 使用集合/字典来判断成员是否存在](#3-使用集合字典来判断成员是否存在) + - [高层看容器](#高层看容器) + - [写扩展性更好的代码](#写扩展性更好的代码) + - [面向容器接口编程](#面向容器接口编程) + - [常用技巧](#常用技巧) + - [1. 使用元组改善分支代码](#1-使用元组改善分支代码) + - [2. 在更多地方使用动态解包](#2-在更多地方使用动态解包) + - [3. 使用 next() 函数](#3-使用-next-函数) + - [4. 使用有序字典来去重](#4-使用有序字典来去重) + - [常见误区](#常见误区) + - [1. 当心那些已经枯竭的迭代器](#1-当心那些已经枯竭的迭代器) + - [2. 别在循环体内修改被迭代对象](#2-别在循环体内修改被迭代对象) + - [总结](#总结) + - [系列其他文章](#系列其他文章) + - [注解](#注解) ### 当我们谈论容器时,我们在谈些什么? @@ -53,7 +52,6 @@ Python 语言自身的内部实现细节也与这些容器类型息息相关。 下面,让我们一起站在这两个不同的层面上,重新认识容器。 - ## 底层看容器 Python 是一门高级编程语言,**它所提供的内置容器类型,都是经过高度封装和抽象后的结果**。和“链表”、“红黑树”、“哈希表”这些名字相比,所有 Python 内建类型的名字,都只描述了这个类型的功能特点,其他人完全没法只通过这些名字了解它们的哪怕一丁点内部细节。 @@ -112,7 +110,7 @@ def validate_name(name): ``` > Hint: 强烈建议阅读 [TimeComplexity - Python Wiki](https://wiki.python.org/moin/TimeComplexity),了解更多关于常见容器类型的时间复杂度相关内容。 -> +> > 如果你对字典的实现细节感兴趣,也强烈建议观看 Raymond Hettinger 的演讲 [Modern Dictionaries(YouTube)](https://www.youtube.com/watch?v=p33CVV29OG8&t=1403s) ## 高层看容器 @@ -316,6 +314,7 @@ user = {**{"name": "piglei"}, **{"movies": ["Fight Club"]}} 除此之外,你还可以在普通赋值语句中使用 `*` 运算符来动态地解包可迭代对象。如果你想详细了解相关内容,可以阅读下面推荐的 PEP。 > Hint:推进动态解包场景扩充的两个 PEP: +> > - [PEP 3132 -- Extended Iterable Unpacking | Python.org](https://www.python.org/dev/peps/pep-3132/) > - [PEP 448 -- Additional Unpacking Generalizations | Python.org](https://www.python.org/dev/peps/pep-0448/) @@ -350,7 +349,7 @@ print(next(i for i in numbers if i % 2 == 0)) ``` > Hint: 在 Python 3.6 中,默认的字典类型修改了实现方式,已经变成有序的了。并且在 Python 3.7 中,该功能已经从 **语言的实现细节** 变成了为 **可依赖的正式语言特性**。 -> +> > 但是我觉得让整个 Python 社区习惯这一点还需要一些时间,毕竟目前“字典是无序的”还是被印在无数本 Python 书上。所以,我仍然建议在一切需要有序字典的地方使用 OrderedDict。 ## 常见误区 @@ -374,7 +373,7 @@ for number in numbers: 而且不光是生成器表达式,Python 3 里的 map、filter 内建函数也都有一样的特点。忽视这个特点很容易导致代码中出现一些难以察觉的 Bug。 -Instagram 就在项目从 Python 2 到 Python 3 的迁移过程中碰到了这个问题。它们在 PyCon 2017 上分享了对付这个问题的故事。访问文章 [Instagram 在 PyCon 2017 的演讲摘要](https://www.zlovezl.cn/articles/instagram-pycon-2017/),搜索“迭代器”可以查看详细内容。 +Instagram 就在项目从 Python 2 到 Python 3 的迁移过程中碰到了这个问题。它们在 PyCon 2017 上分享了对付这个问题的故事。访问文章 [Instagram 在 PyCon 2017 的演讲摘要](https://www.piglei.com/articles/instagram-pycon-2017/),搜索“迭代器”可以查看详细内容。 ### 2. 别在循环体内修改被迭代对象 @@ -422,9 +421,9 @@ print(numbers) ## 系列其他文章 - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:善用变量改善代码质量](https://www.zlovezl.cn/articles/python-using-variables-well/) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:使用数字与字符串的技巧](https://www.zlovezl.cn/articles/tips-on-numbers-and-strings/) +- [Python 工匠:善用变量改善代码质量](https://www.piglei.com/articles/python-using-variables-well/) +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:使用数字与字符串的技巧](https://www.piglei.com/articles/tips-on-numbers-and-strings/) ## 注解 @@ -432,4 +431,3 @@ print(numbers) 2. Python 里没有类似其他编程语言里的“Interface 接口”类型,只有类似的“抽象类”概念。为了表达方便,后面的内容均统一使用“接口”来替代“抽象类”。 3. 有没有只实现了 Mapping 但又不是 MutableMapping 的类型?试试 [MappingProxyType({})](https://docs.python.org/3/library/types.html#types.MappingProxyType) 4. 有没有只实现了 Set 但又不是 MutableSet 的类型?试试 [frozenset()](https://docs.python.org/3/library/stdtypes.html#frozenset) - diff --git a/zh_CN/5-function-returning-tips.md b/zh_CN/5-function-returning-tips.md index 9e27fd8..c6f597c 100644 --- a/zh_CN/5-function-returning-tips.md +++ b/zh_CN/5-function-returning-tips.md @@ -5,7 +5,7 @@ > 这是 “Python 工匠”系列的第 5 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
毫无疑问,函数是 Python 语言里最重要的概念之一。在编程时,我们将真实世界里的大问题分解为小问题,然后通过一个个函数交出答案。函数既是重复代码的克星,也是对抗代码复杂度的最佳武器。 @@ -23,22 +23,22 @@ Python 函数通过调用 `return` 语句来返回结果。使用 `return value` ### 内容目录 - [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-限制递归的使用) - - [总结](#总结) - - [附录](#附录) + - [序言](#序言) + - [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-限制递归的使用) + - [总结](#总结) + - [附录](#附录) ## 编程建议 @@ -161,12 +161,10 @@ def create_for_input(): - 虽然我在这里鼓励使用异常,但“异常”总是会无法避免的让人 **感到惊讶**,所以,最好在函数文档里说明可能抛出的异常类型 - 异常不同于返回值,它在被捕获前会不断往调用栈上层汇报。所以 `create_item` 的一级调用方完全可以省略异常处理,交由上层处理。这个特点给了我们更多的灵活性,但同时也带来了更大的风险。 - > Hint:如何在编程语言里处理错误,是一个至今仍然存在争议的主题。比如像上面不推荐的多返回值方式,正是缺乏异常的 Go 语言中最核心的错误处理机制。另外,即使是异常机制本身,不同编程语言之间也存在着差别。 -> +> > 异常,或是不异常,都是由语言设计者进行多方取舍后的结果,更多时候不存在绝对性的优劣之分。**但是,单就 Python 语言而言,使用异常来表达错误无疑是更符合 Python 哲学,更应该受到推崇的。** - ### 4. 谨慎使用 None 返回值 `None` 值通常被用来表示 **“某个应该存在但是缺失的东西”**,它在 Python 里是独一无二的存在。很多编程语言里都有与 None 类似的设计,比如 JavaScript 里的 `null`、Go 里的 `nil` 等。因为 None 所拥有的独特 *虚无* 气质,它经常被作为函数返回值使用。 @@ -368,7 +366,7 @@ def foo_func(items): yield item ``` -我在 [系列第 4 篇文章“容器的门道”](https://www.zlovezl.cn/articles/mastering-container-types/) 里详细分析过这个模式,更多细节可以访问文章,搜索 “写扩展性更好的代码” 查看。 +我在 [系列第 4 篇文章“容器的门道”](https://www.piglei.com/articles/mastering-container-types/) 里详细分析过这个模式,更多细节可以访问文章,搜索 “写扩展性更好的代码” 查看。 ### 7. 限制递归的使用 @@ -401,14 +399,11 @@ def foo_func(items): ## 附录 - 题图来源: Dominik Scythe on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:善用变量改善代码质量](https://www.zlovezl.cn/articles/python-using-variables-well/) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:使用数字与字符串的技巧](https://www.zlovezl.cn/articles/tips-on-numbers-and-strings/) - - - +- [Python 工匠:善用变量改善代码质量](https://www.piglei.com/articles/python-using-variables-well/) +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:使用数字与字符串的技巧](https://www.piglei.com/articles/tips-on-numbers-and-strings/) diff --git a/zh_CN/6-three-rituals-of-exceptions-handling.md b/zh_CN/6-three-rituals-of-exceptions-handling.md index fffe99a..ab67108 100644 --- a/zh_CN/6-three-rituals-of-exceptions-handling.md +++ b/zh_CN/6-three-rituals-of-exceptions-handling.md @@ -5,7 +5,7 @@ > 这是 “Python 工匠”系列的第 6 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
如果你用 Python 编程,那么你就无法避开异常,因为异常在这门语言里无处不在。打个比方,当你在脚本执行时按 `ctrl+c` 退出,解释器就会产生一个 `KeyboardInterrupt` 异常。而 `KeyError`、`ValueError`、`TypeError` 等更是日常编程里随处可见的老朋友。 @@ -15,7 +15,7 @@ 在这篇文章里,我会分享与异常处理相关的 3 个好习惯。继续阅读前,我希望你已经了解了下面这些知识点: - 异常的基本语法与用法*(建议阅读官方文档 [“Errors and Exceptions”](https://docs.python.org/3.6/tutorial/errors.html))* -- 为什么要使用异常代替错误返回*(建议阅读[《让函数返回结果的技巧》](https://www.zlovezl.cn/articles/function-returning-tips/))* +- 为什么要使用异常代替错误返回*(建议阅读[《让函数返回结果的技巧》](https://www.piglei.com/articles/function-returning-tips/))* - 为什么在写 Python 时鼓励使用异常 *(建议阅读 [“Write Cleaner Python: Use Exceptions”](https://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/))* ## 三个好习惯 @@ -294,7 +294,7 @@ def upload_avatar(request): ``` > Hint:建议阅读 [PEP 343 -- The "with" Statement | Python.org](https://www.python.org/dev/peps/pep-0343/),了解与上下文管理器有关的更多知识。 -> +> > 模块 [contextlib](https://docs.python.org/3/library/contextlib.html) 也提供了非常多与编写上下文管理器相关的工具函数与样例。 ## 总结 @@ -314,13 +314,11 @@ def upload_avatar(request): ## 附录 - 题图来源: Photo by Bernard Hermant on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:善用变量改善代码质量](https://www.zlovezl.cn/articles/python-using-variables-well/) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:让程序返回结果的技巧](https://www.zlovezl.cn/articles/function-returning-tips/) - - +- [Python 工匠:善用变量改善代码质量](https://www.piglei.com/articles/python-using-variables-well/) +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:让程序返回结果的技巧](https://www.piglei.com/articles/function-returning-tips/) diff --git a/zh_CN/7-two-tips-on-loop-writing.md b/zh_CN/7-two-tips-on-loop-writing.md index c7578c3..8db9317 100644 --- a/zh_CN/7-two-tips-on-loop-writing.md +++ b/zh_CN/7-two-tips-on-loop-writing.md @@ -5,7 +5,7 @@ > 这是 “Python 工匠”系列的第 7 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
循环是一种常用的程序控制结构。我们常说,机器相比人类的最大优点之一,就是机器可以不眠不休的重复做某件事情,但人却不行。而**“循环”**,则是实现让机器不断重复工作的关键概念。 @@ -52,7 +52,7 @@ for i, name in enumerate(names): 使用修饰函数处理可迭代对象,可以在各种方面影响循环代码。而要找到合适的例子来演示这个方法,并不用去太远,内置模块 [itertools](https://docs.python.org/3.6/library/itertools.html) 就是一个绝佳的例子。 -简单来说,itertools 是一个包含很多面向可迭代对象的工具函数集。我在之前的系列文章[《容器的门道》](https://www.zlovezl.cn/articles/mastering-container-types/)里提到过它。 +简单来说,itertools 是一个包含很多面向可迭代对象的工具函数集。我在之前的系列文章[《容器的门道》](https://www.piglei.com/articles/mastering-container-types/)里提到过它。 如果要学习 itertools,那么 [Python 官方文档](https://docs.python.org/3.6/library/itertools.html) 是你的首选,里面有非常详细的模块相关资料。但在这篇文章里,侧重点将和官方文档稍有不同。我会通过一些常见的代码场景,来详细解释它是如何改善循环代码的。 @@ -73,7 +73,6 @@ def find_twelve(num_list1, num_list2, num_list3): 对于这种需要嵌套遍历多个对象的多层循环代码,我们可以使用 [product()](https://docs.python.org/3.6/library/itertools.html#itertools.product) 函数来优化它。`product()` 可以接收多个可迭代对象,然后根据它们的笛卡尔积不断生成结果。 - ```python from itertools import product @@ -311,14 +310,11 @@ def notify_nonsleep_users_in_last_30days(): ## 附录 - 题图来源: Photo by Lai man nung on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:容器的门道](https://www.zlovezl.cn/articles/mastering-container-types/) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:异常处理的三个好习惯](https://www.zlovezl.cn/articles/three-rituals-of-exceptions-handling/) - - - +- [Python 工匠:容器的门道](https://www.piglei.com/articles/mastering-container-types/) +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:异常处理的三个好习惯](https://www.piglei.com/articles/three-rituals-of-exceptions-handling/) diff --git a/zh_CN/8-tips-on-decorators.md b/zh_CN/8-tips-on-decorators.md index 9517c23..2e0e93c 100644 --- a/zh_CN/8-tips-on-decorators.md +++ b/zh_CN/8-tips-on-decorators.md @@ -5,7 +5,7 @@ > 这是 “Python 工匠”系列的第 8 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
装饰器 *(Decorator)* 是 Python 里的一种特殊工具,它为我们提供了一种在函数外部修改函数的灵活能力。它有点像一顶画着独一无二 `@` 符号的神奇帽子,只要将它戴在函数头顶上,就能悄无声息的改变函数本身的行为。 @@ -345,17 +345,14 @@ def decorated(*args, **kwargs): [<<<上一篇【7.编写地道循环的两个建议】](7-two-tips-on-loop-writing.md) - ## 附录 - 题图来源: Photo by Clem Onojeghuo on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:异常处理的三个好习惯](https://www.zlovezl.cn/articles/three-rituals-of-exceptions-handling/) -- [Python 工匠:编写地道循环的两个建议](https://www.zlovezl.cn/articles/two-tips-on-loop-writing/) - - +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:异常处理的三个好习惯](https://www.piglei.com/articles/three-rituals-of-exceptions-handling/) +- [Python 工匠:编写地道循环的两个建议](https://www.piglei.com/articles/two-tips-on-loop-writing/) diff --git a/zh_CN/9-a-story-on-cyclic-imports.md b/zh_CN/9-a-story-on-cyclic-imports.md index 86e30c0..0ef3664 100644 --- a/zh_CN/9-a-story-on-cyclic-imports.md +++ b/zh_CN/9-a-story-on-cyclic-imports.md @@ -5,7 +5,7 @@ > 这是 “Python 工匠”系列的第 9 篇文章。[[查看系列所有文章]](https://github.com/piglei/one-python-craftsman)
- +
模块(Module)是我们用来组织 Python 代码的基本单位。很多功能强大的复杂站点,都由成百上千个独立模块共同组成。 @@ -125,7 +125,7 @@ ImportError: cannot import name 'User' from 'fancy_site.users' (.../fancy_site/u 如此一来,整个模块依赖关系成为了环状,程序自然也就没法执行下去了。 -![modules_before](https://www.zlovezl.cn/static/uploaded/2019/05/modules_before.png) +![modules_before](https://www.piglei.com/static/uploaded/2019/05/modules_before.png) 不过,没有什么问题能够难倒一个可以正常访问 Google 的程序员。小 R 随便上网一搜,发现这样的问题很好解决。因为 Python 的 import 语句非常灵活,他只需要 **把在 users 模块内导入 send_sms 函数的语句挪到 `add_notification` 方法内,延缓 import 语句的执行就行啦。** @@ -157,7 +157,7 @@ from .msg_utils import send_sms 新的模块依赖关系如下图所示: -![modules_afte](https://www.zlovezl.cn/static/uploaded/2019/05/modules_after.png) +![modules_afte](https://www.piglei.com/static/uploaded/2019/05/modules_after.png) 在新的模块结构中,整个项目被整齐的分为三层,模块间的依赖关系也变得只有**单向流动**。之前在函数内部 `import` 的“延迟导入”技巧,自然也就没有用武之地了。 @@ -185,14 +185,11 @@ from .msg_utils import send_sms ## 附录 - 题图来源: Photo by Ricardo Gomez Angel on Unsplash -- 更多系列文章地址:https://github.com/piglei/one-python-craftsman +- 更多系列文章地址: 系列其他文章: - [所有文章索引 [Github]](https://github.com/piglei/one-python-craftsman) -- [Python 工匠:编写条件分支代码的技巧](https://www.zlovezl.cn/articles/python-else-block-secrets/) -- [Python 工匠:异常处理的三个好习惯](https://www.zlovezl.cn/articles/three-rituals-of-exceptions-handling/) -- [Python 工匠:编写地道循环的两个建议](https://www.zlovezl.cn/articles/two-tips-on-loop-writing/) - - - +- [Python 工匠:编写条件分支代码的技巧](https://www.piglei.com/articles/python-else-block-secrets/) +- [Python 工匠:异常处理的三个好习惯](https://www.piglei.com/articles/three-rituals-of-exceptions-handling/) +- [Python 工匠:编写地道循环的两个建议](https://www.piglei.com/articles/two-tips-on-loop-writing/) From b51b55cf4deb1368fa3bb6744c374914016d9c30 Mon Sep 17 00:00:00 2001 From: piglei Date: Fri, 15 Dec 2023 19:46:17 +0800 Subject: [PATCH 14/16] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1628b62..99286b3 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ 图书《Python 工匠》现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等核心章节,总内容量几乎翻倍,欢迎购买。 +![image](https://github.com/piglei/one-python-craftsman/assets/731266/256a6af5-2eab-4f32-ad8e-aff590836bc2) + [点击购买](https://item.jd.com/13068111.html) | [豆瓣书评](https://book.douban.com/subject/35723705/) | [图灵社区](https://www.ituring.com.cn/book/3007) --- From 36c8252105938c1d72f7660d259643e0b35f9e85 Mon Sep 17 00:00:00 2001 From: piglei Date: Fri, 15 Dec 2023 19:47:19 +0800 Subject: [PATCH 15/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99286b3..52b8f48 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 图书《Python 工匠》现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等核心章节,总内容量几乎翻倍,欢迎购买。 -![image](https://github.com/piglei/one-python-craftsman/assets/731266/256a6af5-2eab-4f32-ad8e-aff590836bc2) + [点击购买](https://item.jd.com/13068111.html) | [豆瓣书评](https://book.douban.com/subject/35723705/) | [图灵社区](https://www.ituring.com.cn/book/3007) From bb8e9d6f2cafd0f5482d8ce6db8ac41f3726f081 Mon Sep 17 00:00:00 2001 From: piglei Date: Mon, 25 Dec 2023 16:18:12 +0800 Subject: [PATCH 16/16] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 52b8f48..71b7031 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ - [第 6 章 循环与可迭代对象](https://www.piglei.com/book/ch06_loop.html) - [第 10 章 面向对象设计原则](https://www.piglei.com/book/ch10_solid_p1.html) -图书《Python 工匠》现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等核心章节,总内容量几乎翻倍,欢迎购买。 +图书《Python 工匠:案例、技巧与工程实践》现已正式上市。同开源文章相比,图书的知识结构更为流畅,增加了“函数”“面向对象编程”等核心章节,**总内容量几乎翻倍**,欢迎购买。