403 lines
480 KiB
HTML
403 lines
480 KiB
HTML
|
<!DOCTYPE html>
|
|||
|
<html><head><title>Python工匠:案例、技巧与工程实践</title><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta property="og:title" content="Python工匠:案例、技巧与工程实践"/><meta property="og:description" content="本书基于受欢迎的“Python工匠”系列开源文章。全书从工程实践角度出发,通过剖析核心知识、展示典型案例与总结实用技巧,帮助大家系统进阶Python,写好工程代码,做好实践项目。 本书共计13章,分为五大部分:变量与基础类型、语法结构、函数与装饰器、面向对象编程、总结与延伸,涵盖Python编程的方方面面。本书的写作方式别具一格,核心知识点都会通过三大板块来阐述:基础知识、案例故事、编程建议。其中基础知识帮助大家快速回顾Python基础;案例故事由作者经历的编程项目与案例改编而来,兼具实战性与趣味性;编程建议以大家喜闻乐见的条目式知识点呈现,短小精悍,可直接应用于自己的编程实践中。."/><meta property="og:image" content="https://wiki.7wate.com/static/og-image.png"/><meta property="og:width" content="1200"/><meta property="og:height" content="675"/><link rel="icon" href="../../../static/icon.png"/><meta name="description" content="本书基于受欢迎的“Python工匠”系列开源文章。全书从工程实践角度出发,通过剖析核心知识、展示典型案例与总结实用技巧,帮助大家系统进阶Python,写好工程代码,做好实践项目。 本书共计13章,分为五大部分:变量与基础类型、语法结构、函数与装饰器、面向对象编程、总结与延伸,涵盖Python编程的方方面面。本书的写作方式别具一格,核心知识点都会通过三大板块来阐述:基础知识、案例故事、编程建议。其中基础知识帮助大家快速回顾Python基础;案例故事由作者经历的编程项目与案例改编而来,兼具实战性与趣味性;编程建议以大家喜闻乐见的条目式知识点呈现,短小精悍,可直接应用于自己的编程实践中。."/><meta name="generator" content="Quartz"/><link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="preconnect" href="https://fonts.gstatic.com"/><script async src="https://umami.7wate.com/script.js" data-website-id="c061efdc-95dd-4d21-9d04-a1ffda0a85b9"></script><script>
|
|||
|
var _hmt = _hmt || [];
|
|||
|
(function() {
|
|||
|
var hm = document.createElement("script");
|
|||
|
hm.src = "https://hm.baidu.com/hm.js?94d8ccb156eb7c65abf317e6e01cdba9";
|
|||
|
var s = document.getElementsByTagName("script")[0];
|
|||
|
s.parentNode.insertBefore(hm, s);
|
|||
|
})();
|
|||
|
</script><script async src="https://www.googletagmanager.com/gtag/js?id=G-MHMEL0F832"></script><script>
|
|||
|
(function() {
|
|||
|
window.dataLayer = window.dataLayer || [];
|
|||
|
function gtag() {
|
|||
|
window.dataLayer.push(arguments);
|
|||
|
}
|
|||
|
gtag('js', new Date());
|
|||
|
gtag('config', 'G-MHMEL0F832');
|
|||
|
})();
|
|||
|
</script><link href="../../../index.css" rel="stylesheet" type="text/css" spa-preserve/><link href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css" rel="stylesheet" type="text/css" spa-preserve/><link href="https://fonts.googleapis.com/css2?family=IBM Plex Mono&family=Schibsted Grotesk:wght@400;700&family=Source Sans Pro:ital,wght@0,400;0,600;1,400;1,600&display=swap" rel="stylesheet" type="text/css" spa-preserve/><script src="../../../prescript.js" type="application/javascript" spa-preserve></script><script type="application/javascript" spa-preserve>const fetchData = fetch(`../../../static/contentIndex.json`).then(data => data.json())</script></head><body data-slug="Personal/Book/计算机/Python工匠:案例、技巧与工程实践"><div id="quartz-root" class="page"><div id="quartz-body"><div class="left sidebar"><h1 class="page-title "><a href="../../..">📚 X·Eden</a></h1><div class="spacer mobile-only"></div><div class="search "><div id="search-icon"><p>Search</p><div></div><svg tabIndex="0" aria-labelledby="title desc" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.9 19.7"><title id="title">Search</title><desc id="desc">Search</desc><g class="search-path" fill="none"><path stroke-linecap="square" d="M18.5 18.3l-5.4-5.4"></path><circle cx="8" cy="8" r="7"></circle></g></svg></div><div id="search-container"><div id="search-space"><input autocomplete="off" id="search-bar" name="search" type="text" aria-label="Search for something" placeholder="Search for something"/><div id="results-container"></div></div></div></div><div class="darkmode "><input class="toggle" id="darkmode-toggle" type="checkbox" tabIndex="-1"/><label id="toggle-label-light" for="darkmode-toggle" tabIndex="-1"><svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" version="1.1" id="dayIcon" x="0px" y="0px" viewBox="0 0 35 35" style="enable-background:new 0 0 35 35;" xmlSpace="preserve"><title>Light mode</title><path d="M6,17.5C6,16.672,5.328,16,4.5,16h-3C0.672,16,0,16.672,0,17.5 S0.672,19,1.5,19h3C5.328,19,6,18.328,6,17.5z M7.5,26c-0.414,0-0.789,0.168-1.061,0.439l-2,2C4.168,28.711,4,29.086,4,29.5 C4,30.328,4.671,31,5.5,31c0.414,0,0.789-0.168,1.06-0.44l2-2C8.832,28.289,9,27.914,9,27.5C9,26.672,8.329,26,7.5,26z M17.5,6 C18.329,6,19,5.328,19,4.5v-3C19,0.672,18.329,0,17.5,0S16,0.672,16,1.5v3C16,5.328,16.671,6,17.5,6z M27.5,9 c0.414,0,0.789-0.168,1.06-0.439l2-2C30.832,6.289,31,5.914,31,5.5C31,4.672,30.329,4,29.5,4c-0.414,0-0.789,0.168-1.061,0.44 l-2,2C26.168,6.711,26,7.086,26,7.5C26,8.328,26.671,9,27.5,9z M6.439,8.561C6.711,8.832,7.086,9,7.5,9C8.328,9,9,8.328,9,7.5 c0-0.414-0.168-0.789-0.439-1.061l-2-2C6.289,4.168,5.914,4,5.5,4C4.672,4,4,4.672,4,5.5c0,0.414,0.168,0.789,0.439,1.06 L6.439,8.561z M33.5,16h-3c-0.828,0-1.5,0.672-1.5,1.5s0.672,1.5,1.5,1.5h3c0.828,0,1.5-0.672,1.5-1.5S34.328,16,33.5,16z M28.561,26.439C28.289,26.168,27.914,26,27.5,26c-0.828,0-1.5,0.672-1.5,1.5c0,0.414,0.168,0.789,0.439,1.06l2,2 C28.711,30.832,29.086,31,29.5,31c0.828,0,1.5-0.672,1.5-1.5c0-0.414-0.168-0.789-0.439-1.061L28.561,26.439z M17.5,29 c-0.829,0-1.5,0.672-1.5,1.5v3c0,0.828,0.671,1.5,1.5,1.5s1.5-0.672,1.5-1.5v-3C19,29.672,18.329,29,17.5,29z M17.5,7 C11.71,7,7,11.71,7,17.5S11.71,28,17.5,28S28,23.29,28,17.5S23.29,7,17.5,7z M17.5,25c-4.136,0-7.5-3.364-7.5-7.5 c0-4.136,3.364-7.5,7.5-7.5c4.136,0,7.5,3.364,7.5,7.5C25,21.636,21.636,25,17.5,25z"></path></svg></label><label id="toggle-label-dark" for="darkmode-toggle" tabIndex="-1"><svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" version="1.1" id="nightIcon" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background='new 0 0 100 100'" xmlSpace="preserve"><title>Dark mode</title><path d="M96.76,66.458c-0.853-0.852-2.15-1.064-3.23-0.534c-6.063,2.991-12.858,4.571-19.655,4.571 C62.022,70.495,50.88,65.88,42.5,57.5C29.043,44.043,25.658,23.536,34.076,6.47c0.532-1.08,0.318-2.379-0.534-3.23 c-0.851-0.852-2.15-1.064-3.23-0.534c-4.918,2.427-9.375,5.619-13.246,9.491c-9.447,9.447-14.65,22.008-
|
|||
|
<ul>
|
|||
|
<li><strong>书名</strong>:《Python工匠:案例、技巧与工程实践》</li>
|
|||
|
<li><strong>作者</strong>: 朱雷</li>
|
|||
|
<li><strong>分类</strong>: 计算机-编程设计</li>
|
|||
|
<li><strong>ISBN</strong>:9787115584045</li>
|
|||
|
<li><strong>出版社</strong>:人民邮电出版社有限公司</li>
|
|||
|
</ul>
|
|||
|
<h2 id="概述">概述<a aria-hidden="true" tabindex="-1" href="#概述" class="internal"> §</a></h2>
|
|||
|
<p>本书基于受欢迎的“Python工匠”系列开源文章。全书从工程实践角度出发,通过剖析核心知识、展示典型案例与总结实用技巧,帮助大家系统进阶Python,写好工程代码,做好实践项目。 本书共计13章,分为五大部分:变量与基础类型、语法结构、函数与装饰器、面向对象编程、总结与延伸,涵盖Python编程的方方面面。本书的写作方式别具一格,核心知识点都会通过三大板块来阐述:基础知识、案例故事、编程建议。其中基础知识帮助大家快速回顾Python基础;案例故事由作者经历的编程项目与案例改编而来,兼具实战性与趣味性;编程建议以大家喜闻乐见的条目式知识点呈现,短小精悍,可直接应用于自己的编程实践中。</p>
|
|||
|
<h2 id="划线">划线<a aria-hidden="true" tabindex="-1" href="#划线" class="internal"> §</a></h2>
|
|||
|
<blockquote>
|
|||
|
<p>即便两个人实现同一个功能,最终效果看上去也一模一样,但代码质量却可能有着云泥之别。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>我前前后后读过一些书——《代码大全》《重构》《设计模式》《代码整洁之道》——毫无疑问,它们都是领域内首屈一指的经典好书,我从中学到了许多知识,至今受益匪浅。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>编程是一个通过代码来表达思想的过程。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>变量与注释是作者表达思想的基础,是读者理解代码的第一道门,它们对代码质量的贡献毋庸置疑。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>除了上面的普通解包外,Python还支持更灵活的动态解包语法。只要用星号表达式(*variables)作为变量名,它便会贪婪[插图]地捕获多个值对象,并将捕获到的内容作为列表赋值给variables。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在常用的诸多变量名中,单下划线_是比较特殊的一个。它常作为一个无意义的占位符出现在赋值语句中。_这个名字本身没什么特别之处,这算是大家约定俗成的一种用法。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>相比编写Sphinx格式文档,我其实更推荐使用类型注解,因为它是Python的内置功能,而且正在变得越来越流行。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>因此,我强烈建议在多人参与的中大型Python项目里,至少使用一种类型注解方案——Sphinx格式文档或官方类型注解都行。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>计算机科学领域只有两件难事:缓存失效和命名。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>描述代码为什么要这么做,而不是简单复述代码本身。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>指引性注释。这种注释并不直接复述代码,而是简明扼要地概括代码功能,起到“代码导读”的作用。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>这些变化让整段代码变得更易读,也让整个算法变得更好理解。所以,哪怕是一段不到10行代码的简单函数,对变量和注释的不同处理方式,也会让代码发生质的变化</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>喜欢把所有变量初始化定义写在一起,放在函数最前面</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>总是从代码的职责出发,而不是其他东西。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>直接翻译业务逻辑的代码,大多不是好代码。优秀的程序设计需要在理解原需求的基础上,恰到好处地抽象,只有这样才能同时满足可读性和可扩展性方面的需求</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>定义一个临时变量”是诸多方式里不太起眼的一个,但用得恰当的话效果也很巧妙。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>函数内变量的数量太多,通常意味着函数过于复杂,承担了太多职责。只有把复杂函数拆分为多个小函数,代码的整体复杂度才可能实现根本性的降低。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>这样的代码就像删掉赘语的句子,变得更精练、更易读。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在编写了许多函数以后,我总结出了一个值得推广的好习惯:先写注释,后写代码。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在写出一句有说服力的接口注释前,别写任何函数代码。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在定义数值字面量时,如果数字特别长,可以通过插入_分隔符来让它变得更易读</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>True和False这两个布尔值可以直接当作1和0来使用</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>f-string格式化方式用起来最方便</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>首先创建一个空列表,然后把需要拼接的字符串都放进列表,最后调用str.join来获得大字符串</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>把数字字面量改成常量和枚举类型后,我们就能很好地规避输入错误问题。同样,把字符串字面量改写成枚举类型,也可以获得这种好处</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>如果需要验证某个“经验之谈”,dis和timeit两个优秀的工具可以帮到你:前者能让你直接查看编译后的字节码,后者则能让你方便地做性能测试。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>Python里的字典在底层使用了哈希表(hash table)数据结构。当你往字典里存放一对key: value时,Python会先通过哈希算法计算出key的哈希值——一个整型数字;然后根据这个哈希值,决定数据在表里的具体位置。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>集合只能存放可哈希对象</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>当我们把某个对象放进集合或者作为字典的键使用时,解释器都需要对该对象进行一次哈希运算,得到哈希值,然后再进行后面的操作。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>虽然都是返回结果,但yield和return的最大不同之处在于,return的返回是一次性的,使用它会直接中断整个函数执行,而yield可以逐步给调用方生成结果:</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>我们有时会过于喜欢用not关键字,反倒忘记了运算符本身就可以表达否定逻辑。最后,代码里会出现许多下面这种判断语句:</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>异常处理对于我来说,就是一些不想做却又不得不做的琐事</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在Python世界里,EAFP指不做任何事前检查,直接执行操作,但在外层用try来捕获可能发生的异常。如果还用下雨举例,这种做法类似于“出门前不看天气预报,如果淋雨了,就回家后洗澡吃感冒药”。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>Python里的函数可以一次返回多个值(通过返回一个元组实现)。所以,当我们要表明函数执行出错时,可以让它同时返回结果与错误信息。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>新函数拥有更稳定的返回值类型,它永远只会返回Item类型或是抛出异常。· 虽然我们鼓励使用异常,但异常总是会不可避免地让人“感到惊讶”,所以,最好在函数文档里说明可能抛出的异常类型。· 不同于返回值,异常在被捕获前会不断往调用栈上层汇报。因此create_item()的直接调用方也可以完全不处理CreateItemError,而交由更上层处理。异常的这个特点给了我们更多灵活性,但同时也带来了更大的风险。具体来说,假如程序缺少一个顶级的统一异常处理逻辑,那么某个被所有人忽视了的异常可能会层层上报,最终弄垮整个程序。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>上下文管理器功能强大、用处很多,其中最常见的用处之一,就是简化异常处理工作。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>除了应该避免抛出高于当前抽象级别的异常外,我们同样应该避免泄露低于当前抽象级别的异常</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在数据校验这块,pydantic模块是一个不错的选择。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在编写代码时,我们应当尽量避免手动校验任何数据。因为数据校验任务独立性很强,所以应该引入合适的第三方校验模块(或者自己实现),让它们来处理这部分专业工作。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>可迭代对象不一定是迭代器,但迭代器一定是可迭代对象;· 对可迭代对象使用iter()会返回迭代器,迭代器则会返回其自身;· 每个迭代器的被迭代过程是一次性的,可迭代对象则不一定;· 可迭代对象只需要实现__iter__方法,而迭代器要额外实现__next__方法。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>生成器(generator)利用其简单的语法,大大降低了迭代器的使用门槛,是优化循环代码时最得力的帮手。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>enumerate()是Python的一个内置函数,它接收一个可迭代对象作为参数,返回一个不断生成(当前下标,当前元素)的新可迭代对象。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>“修饰可迭代对象”是指用生成器(或普通的迭代器)在循环外部包装原本的循环主体,完成一些原本必须在循环内部执行的工作——比如过滤特定成员、提供额外结果等,以此简化循环代码。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>product()接收多个可迭代对象作为参数,然后根据它们的笛卡儿积不断生成结果:</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>用product()优化函数里的嵌套循环</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>takewhile(predicate, iterable)会在迭代第二个参数iterable的过程中,不断使用当前值作为参数调用predicate()函数,并对返回结果进行真值测试,如果为True,则返回当前值并继续迭代,否则立即中断本次迭代。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>for循环(和while循环)后的else关键字,代表如果循环正常结束(没有碰到任何break),便执行该分支内的语句。因此,老式的“循环+标记变量”代码,就可以利用该特性简写为“循环+else分支”</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>因为Python语言不支持“带标签的break”语句[插图],无法用一个break跳出多层循环。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>为了规避这个问题,使用None来替代可变类型默认值是比较常见的做法:</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>当你要调用参数较多(超过3个)的函数时,使用关键字参数模式可以大大提高代码的可读性。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>每个函数只返回一种类型,变得更简单易用。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>适合返回None的函数需要满足以下两个特点:(1)函数的名称和参数必须表达“结果可能缺失”的意思;(2)如果函数执行无法产生结果,调用方也不关心具体原因。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>除了“搜索”“查询”几个场景外,对绝大部分函数而言,返回None并不是一个好的做法。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在编写函数时,请不要纠结函数是不是应该只有一个return,只要尽早返回结果可以提升代码可读性,那就多多返回吧。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>为了简化函数调用,让代码更简洁,我们其实可以定义一个接收单个参数的double()函数,让它通过multiply()完成计算:def double(value): # 返回 multiply 函数调用结果 return multiply(2, value)# 调用代码变得更简单result = double(value)val = double(number)</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>原来,在使用re.sub(pattern, repl, string)函数时,第二个参数repl不光可以是普通字符串,还可以是一个可调用的函数对象。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>截至上一个问题,小R所写的mosaic_matchobj()函数只是一个无状态函数。但为了满足新需求,小R需要调整mosaic_matchobj()函数,把它从一个无状态函数改为有状态函数。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>闭包是一种非常有用的工具,非常适合用来实现简单的有状态函数。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>权衡了这三种方案的利弊后</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>别写太复杂的函数</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>Python里的递归因为缺少语言层面的优化,局限性较大。当你想用递归来实现某个算法时,请先琢磨琢磨是否能用循环来改写。如果答案是肯定的,那就改成循环吧。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>装饰器并不提供任何独特的功能,它所做的,只是让我们可以在函数定义语句上方,直接添加用来修改函数行为的装饰器函数</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>装饰器是一种通过包装目标函数来修改其行为的特殊高阶函数,绝大多数装饰器是利用函数的闭包原理实现的。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>添加@wraps(wrapped)来装饰decorated函数后,wraps()首先会基于原函数func来更新包装函数decorated的名称、文档等内置属性,之后会将func的所有额外属性赋值到decorated上</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>所以,装饰器的优势并不在于它提供了动态修改函数的能力,而在于它把影响函数的装饰行为移到了函数头部,降低了代码的阅读与理解成本。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>(1) 运行时校验:在执行阶段进行特定校验,当校验通不过时终止执行。· 适合原因:装饰器可以方便地在函数执行前介入,并且可以读取所有参数辅助校验。· 代表样例:Django框架中的用户登录态校验装饰器@login_required。(2) 注入额外参数:在函数被调用时自动注入额外的调用参数。· 适合原因:装饰器的位置在函数头部,非常靠近参数被定义的位置,关联性强。· 代表样例:unittest.mock模块的装饰器@patch。(3) 缓存执行结果:通过调用参数等输入信息,直接缓存函数执行结果。· 适合原因:添加缓存不需要侵入函数内部逻辑,并且功能非常独立和通用。· 代表样例:functools模块的缓存装饰器@lru_cache。(4) 注册函数:将被装饰函数注册为某个外部流程的一部分。· 适合原因:在定义函数时可以直接完成注册,关联性强。· 代表样例:Flask框架的路由注册装饰器@app.route。(5)替换为复杂对象:将原函数(方法)替换为更复杂的对象,比如类实例或特殊的描述符对象(见12.1.3节)。· 适合原因:在执行替换操作时,装饰器语法天然比foo = staticmethod(foo)的写法要直观得多。· 代表样例:静态类方法装饰器@staticmethod。在设计新的装饰器时,你可以先参考上面的常见装饰器功能列表,琢磨琢磨自己的设计是否能很好地发挥装饰器的优势。切勿滥用装饰器技术,设计出一些天马行空但难以理解的API。吸取前人经验,同时在设计上保持克制,才能写出更好用的装饰器。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>私有属性是“君子协定”</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在Python里,所有的类属性和方法默认都是公开的,不过你可以通过添加双下划线前缀__的方式把它们标示为私有。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>和普通方法相比,静态方法不需要访问实例的任何状态,是一种与状态无关的方法,因此静态方法其实可以改写成脱离于类的外部普通函数。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>使用@property装饰器,你可以把上面的get_basename()方法变成一个虚拟属性,然后像使用普通属性一样使用它</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>@property是个非常有用的装饰器,它让我们可以基于方法定义类属性,精确地控制属性的读取、赋值和删除行为,灵活地实现动态属性等功能。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在超过90%的情况下,你能找到的合理的Python代码就如上所示:没有任何类型检查,想做什么就直接做。你肯定想问,假如调用方提供的fp参数不是文件对象怎么办?答案是:不怎么办,直接报错就好。示例如下。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>总结一下,抽象类通过__subclasshook__钩子和.register()方法,实现了一种比继承更灵活、更松散的子类化机制,并以此改变了isinstance()的行为。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>super()使用的其实不是当前类的父类,而是它在MRO链条里的上一个类。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>大多数情况下,你需要的并不是多重继承,而也许只是一个更准确的抽象模型,在该模型下,最普通的继承关系就能完美解决问题。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>元类控制着类的创建行为,就像普通类控制着实例的创建行为一样。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>但继承是一种类与类之间紧密的耦合关系。让子类继承父类,虽然看上去毫无成本地获取了父类的全部能力,但同时也意味着,从此以后父类的所有改动都可能影响子类。继承关系越复杂,这种影响就越容易超出人们的控制范围。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>针对事物的行为建模,而不是对事物本身建模。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在多数情况下,基于事物的行为来建模,可以孵化出更好、更灵活的模型设计。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>即使B和A是同类,那它们真的需要用继承来表明类型关系吗?要知道,Python是鸭子类型的,你不用继承也能实现多态。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>多态(polymorphism)是面向对象编程的基本概念之一。它表示同一个方法调用,在运行时会因为对象类型的不同,产生不同效果。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>SOLID单词里的5个字母,分别代表5条设计原则。· S:single responsibility principle(单一职责原则,SRP)。· O:open-closed principle(开放–关闭原则,OCP)。· L:Liskov substitution principle(里式替换原则,LSP)。· I:interface segregation principle(接口隔离原则,ISP)。· D:dependency inversion principle(依赖倒置原则,DIP)。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>单一职责是面向对象领域的设计原则,通常用来形容类。而在Python中,单一职责的适用范围不限于类——通过定义函数,我们同样能让上面的代码符合单一职责原则。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>这世间唯一不变的,只有变化本身。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>虽然继承功能强大,但它并非通往OCP的唯一途径。除了继承外,我们还可以采用另一种思路:组合(composition)。更具体地说,使用基于组合思想的依赖注入(dependency injection)技术</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>但是,如果少了PostFilter抽象类,当编写HNTopPostsSpider类的__init__方式时,我就无法给post_filter增加类型注解了——post_filter: Optional[这里写什么?],因为我根本找不到一个具体的类型。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>但数据驱动也有一个缺点:它的可定制性不如其他两种方式。举个例子,假如我想以“链接是否以某个字符串结尾”来进行过滤,现在的数据驱动代码就做不到。影响每种方案可定制性的根本原因在于,各方案所处的抽象级别不一样。比如,在依赖注入方案下,我选择抽象的内容是“条目过滤行为”;而在数据驱动方案下,抽象内容则是“条目过滤行为的有效站点地址”。很明显,后者的抽象级别更低,关注的内容更具体,所以灵活性不如前者。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>LSP认为,所有子类(派生类)对象应该可以任意替代父类(基类)对象使用,且不会破坏程序原本的功能。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>在Python 3.8版本里,类型注解typing模块增加了一个名为“协议”(Protocol)的类型。从各种意义上来说,Protocol都比抽象类更接近传统的“接口”。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>更丰富的接口协议,意味着更高的实现成本,也更容易给实现方带来麻烦。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>描述符(descriptor)是Python对象模型里的一种特殊协议,它主要和4个魔法方法有关: <strong>get</strong>、<strong>set</strong>、<strong>delete__和__set_name</strong>。从定义上来说,除了最后一个方法__set_name__以外,任何一个实现了__get__、__set__或__delete__的类,都可以称为描述符类,它的实例则叫作描述符对象。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>现在你应该明白了,一个对象的__del__方法,并非在使用del语句时被触发,而是在它被作为垃圾回收时触发。del语句无法直接回收任何东西,它只是简单地删掉了指向当前对象的一个引用(变量名)而已</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>TDD(test-driven development,测试驱动开发)是由Kent Beck提出的一种软件开发方式。在TDD工作流下,要对软件做一个改动,你不会直接修改代码,而会先写出这个改动所需要的测试用例。TDD的工作流大致如下:(1)写测试用例(哪怕测试用例引用的模块根本不存在);(2)执行测试用例,让其失败;(3)编写最简单的代码(此时只关心实现功能,不关心代码整洁度);(4)执行测试用例,让测试通过;(5)重构代码,删除重复内容,让代码变得更整洁;(6)执行测试用例,验证重构;(7)重复整个过程。</p>
|
|||
|
</blockquote>
|
|||
|
<blockquote>
|
|||
|
<p>你应该了解这些理论,越多越好,但是千万不要陷入教条主义。因为在现实世界里,每个人参与的项目千差万别,别人的理论不一定适用于你,如果盲目遵从,反而会给自己增加麻烦。</p>
|
|||
|
</blockquote>
|
|||
|
<h2 id="笔记">笔记<a aria-hidden="true" tabindex="-1" href="#笔记" class="internal"> §</a></h2>
|
|||
|
<blockquote>
|
|||
|
<p>编程最初带给我们的快乐已悄然远去,写代码这件事现在变得有些痛苦。更有甚者,一想到项目里的烂代码,每天起床后最想干的一件事就是辞职。</p>
|
|||
|
</blockquote>
|
|||
|
<p>💭 哈哈哈真实</p>
|
|||
|
<blockquote>
|
|||
|
<p>如果某个函数的圈复杂度超过10,就代表它已经太复杂了,代码编写者应该想办法简化。优化写法或者拆分成子函数都是不错的选择。</p>
|
|||
|
</blockquote>
|
|||
|
<p>💭 代码复杂度</p>
|
|||
|
<blockquote>
|
|||
|
<p>所以简单来说,抽象就是一种选择特征、简化认知的手段。接下来,我们看看抽象与软件开发的关系。</p>
|
|||
|
</blockquote>
|
|||
|
<p>💭 抽离出来普遍具有的特征现象</p>
|
|||
|
<blockquote>
|
|||
|
<p>当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以称为鸭子。
|
|||
|
——来自“鸭子类型”的维基百科词条</p>
|
|||
|
</blockquote>
|
|||
|
<p>💭 鸭子类型</p>
|
|||
|
<blockquote>
|
|||
|
<p>所有与数据模型有关的方法,基本都以双下划线开头和结尾,它们通常被称为魔法方法(magic method)。</p>
|
|||
|
</blockquote>
|
|||
|
<p>💭 魔法方法</p>
|
|||
|
<blockquote>
|
|||
|
<p>所以,写单元测试不是浪费时间,也不会降低开发效率。你在单元测试上花费的那点儿时间,会在未来的日子里为项目的所有参与者节约不计其数的时间。</p>
|
|||
|
</blockquote>
|
|||
|
<p>💭 重要的单元测试</p>
|
|||
|
<blockquote>
|
|||
|
<p>不要掉进完美主义的陷阱。</p>
|
|||
|
</blockquote>
|
|||
|
<p>💭 不要让完美主义成为你的绊脚石!</p>
|
|||
|
<h2 id="书评">书评<a aria-hidden="true" tabindex="-1" href="#书评" class="internal"> §</a></h2>
|
|||
|
<h2 id="点评">点评<a aria-hidden="true" tabindex="-1" href="#点评" class="internal"> §</a></h2></article></div><div class="right sidebar"><div class="graph "><h3>Graph View</h3><div class="graph-outer"><div id="graph-container" data-cfg="{"drag":true,"zoom":true,"depth":1,"scale":1.1,"repelForce":0.5,"centerForce":0.3,"linkDistance":30,"fontSize":0.6,"opacityScale":1,"showTags":true,"removeTags":[]}"></div><svg version="1.1" id="global-graph-icon" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 55 55" fill="currentColor" xmlSpace="preserve"><path d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
|
|||
|
s-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4
|
|||
|
c0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562
|
|||
|
C8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829
|
|||
|
c1.106,0.86,2.44,1.436,3.898,1.619v10.16c-2.833,0.478-5,2.942-5,5.91c0,3.309,2.691,6,6,6s6-2.691,6-6c0-2.967-2.167-5.431-5-5.91
|
|||
|
v-10.16c1.458-0.183,2.792-0.759,3.898-1.619l7.669,7.669C41.215,39.576,41,40.26,41,41c0,2.206,1.794,4,4,4s4-1.794,4-4
|
|||
|
s-1.794-4-4-4c-0.74,0-1.424,0.215-2.019,0.567l-7.669-7.669C36.366,28.542,37,26.846,37,25s-0.634-3.542-1.688-4.897l9.665-9.665
|
|||
|
C46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z M11,9c0-1.103,0.897-2,2-2s2,0.897,2,2s-0.897,2-2,2
|
|||
|
S11,10.103,11,9z M6,51c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S8.206,51,6,51z M33,49c0,2.206-1.794,4-4,4s-4-1.794-4-4
|
|||
|
s1.794-4,4-4S33,46.794,33,49z M29,31c-3.309,0-6-2.691-6-6s2.691-6,6-6s6,2.691,6,6S32.309,31,29,31z M47,41c0,1.103-0.897,2-2,2
|
|||
|
s-2-0.897-2-2s0.897-2,2-2S47,39.897,47,41z M49,10c-2.206,0-4-1.794-4-4s1.794-4,4-4s4,1.794,4,4S51.206,10,49,10z"></path></svg></div><div id="global-graph-outer"><div id="global-graph-container" data-cfg="{"drag":true,"zoom":true,"depth":-1,"scale":0.9,"repelForce":0.5,"centerForce":0.3,"linkDistance":30,"fontSize":0.6,"opacityScale":1,"showTags":true,"removeTags":[]}"></div></div></div><div class="toc desktop-only"><button type="button" id="toc" class><h3>Table of Contents</h3><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="fold"><polyline points="6 9 12 15 18 9"></polyline></svg></button><div id="toc-content"><ul class="overflow"><li class="depth-0"><a href="#简介" data-for="简介">简介</a></li><li class="depth-0"><a href="#概述" data-for="概述">概述</a></li><li class="depth-0"><a href="#划线" data-for="划线">划线</a></li><li class="depth-0"><a href="#笔记" data-for="笔记">笔记</a></li><li class="depth-0"><a href="#书评" data-for="书评">书评</a></li><li class="depth-0"><a href="#点评" data-for="点评">点评</a></li></ul></div></div><div class="backlinks "><h3>Backlinks</h3><ul class="overflow"><li>No backlinks found</li></ul></div><div class="explorer mobile-only"><button type="button" id="explorer" data-behavior="collapse" data-collapsed="collapsed" data-savestate="true" data-tree="[{"path":"Journal","collapsed":true},{"path":"Journal/2024","collapsed":true},{"path":"Journal/2024/W33","collapsed":true},{"path":"Obsidian","collapsed":true},{"path":"Obsidian/Templates","collapsed":true},{"path":"Personal","collapsed":true},{"path":"Personal/Blog","collapsed":true},{"path":"Personal/Blog/2018","collapsed":true},{"path":"Personal/Blog/2020","collapsed":true},{"path":"Personal/Blog/2021","collapsed":true},{"path":"Personal/Blog/2022","collapsed":true},{"path":"Personal/Blog/2023","collapsed":true},{"path":"Personal/Blog/2024","collapsed":true},{"path":"Personal/Book","collapsed":true},{"path":"Personal/Book/个人成长","collapsed":true},{"path":"Personal/Book/医学健康","collapsed":true},{"path":"Personal/Book/历史","collapsed":true},{"path":"Personal/Book/哲学宗教","collapsed":true},{"path":"Personal/Book/心理","collapsed":true},{"path":"Personal/Book/政治军事","collapsed":true},{"path":"Personal/Book/教育学习","collapsed":true},{"path":"Personal/Book/文学","collapsed":true},{"path":"Personal/Book/生活百科","collapsed":true},{"path":"Personal/Book/社会文化","collapsed":true},{"path":"Personal/Book/科学技术","collapsed":true},{"path":"Personal/Book/经济理财","collapsed":true},{"path":"Personal/Book/艺术","collapsed":true},{"path":"Personal/Book/计算机","collapsed":true},{"path":"Personal/Journal","collapsed":true},{"path":"Personal/Journal/2022","collapsed":true},{"path":"Personal/Journal/2022/W34","collapsed":true},{"path":"Personal/Journal/2022/W35","collapsed&qu
|
|||
|
function toggleCallout() {
|
|||
|
const outerBlock = this.parentElement;
|
|||
|
outerBlock.classList.toggle(`is-collapsed`);
|
|||
|
const collapsed = outerBlock.classList.contains(`is-collapsed`);
|
|||
|
const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight;
|
|||
|
outerBlock.style.maxHeight = height + `px`;
|
|||
|
let current = outerBlock;
|
|||
|
let parent = outerBlock.parentElement;
|
|||
|
while (parent) {
|
|||
|
if (!parent.classList.contains(`callout`)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
const collapsed2 = parent.classList.contains(`is-collapsed`);
|
|||
|
const height2 = collapsed2 ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight;
|
|||
|
parent.style.maxHeight = height2 + `px`;
|
|||
|
current = parent;
|
|||
|
parent = parent.parentElement;
|
|||
|
}
|
|||
|
}
|
|||
|
function setupCallout() {
|
|||
|
const collapsible = document.getElementsByClassName(
|
|||
|
`callout is-collapsible`
|
|||
|
);
|
|||
|
for (const div of collapsible) {
|
|||
|
const title = div.firstElementChild;
|
|||
|
if (title) {
|
|||
|
title.removeEventListener(`click`, toggleCallout);
|
|||
|
title.addEventListener(`click`, toggleCallout);
|
|||
|
const collapsed = div.classList.contains(`is-collapsed`);
|
|||
|
const height = collapsed ? title.scrollHeight : div.scrollHeight;
|
|||
|
div.style.maxHeight = height + `px`;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
document.addEventListener(`nav`, setupCallout);
|
|||
|
window.addEventListener(`resize`, setupCallout);
|
|||
|
</script><script type="module">
|
|||
|
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
|
|||
|
const darkMode = document.documentElement.getAttribute('saved-theme') === 'dark'
|
|||
|
mermaid.initialize({
|
|||
|
startOnLoad: false,
|
|||
|
securityLevel: 'loose',
|
|||
|
theme: darkMode ? 'dark' : 'default'
|
|||
|
});
|
|||
|
document.addEventListener('nav', async () => {
|
|||
|
await mermaid.run({
|
|||
|
querySelector: '.mermaid'
|
|||
|
})
|
|||
|
});
|
|||
|
</script><script src="https://cdn.jsdelivr.net/npm/katex@0.16.7/dist/contrib/copy-tex.min.js" type="application/javascript"></script><script src="../../../postscript.js" type="module"></script></html>
|