一个不上进的 Python 使用者我是一个有 C 语言背景的开发者。最近转做了 Python,平时用 Python 还算 6,这周在给新员工分享工作之后,有个小孩跑来问我:“哥,你是学 C 出身的吧?”我心想,难道我脸上写着呢?后来简单聊聊知道了,原来是这段代码暴露了我的背景。
看出问题来了吗?问题就出现在我写的循环上。用他的话说这是一段非常没有 Python 特色的代码。
说来惭愧,虽说我有编程语言基础,上手 Python 也挺容易的。但一直停留在用得很 6 的程度上,对它一直也没有做过什么深入的研究。之前项目着急,觉得拿来能用就好。可是要想写出高性能的代码,停留在光能用还是不够的,我觉得自己是一个不合格的 Python 使用者。
安利几个 Python 小技巧如果想写出专业、高性能的 Python 代码,还是要多加修炼的。于是,在我准备深入了解些 Python 特性的时候,误打误撞地找到了一个大神的博客。本想随便看看,谁知道挖到了宝,根本停不下来。看到这几个小技巧,你是不是也很熟悉?
老板让我构建一个在线商城
我们在使用 Python 时,有些真正有用的语言特性反而得到的关注不多。比如,Python 内置的 assert 语句就没有受到重视。
Python 的断言语句是一种调试工具,用来测试某个断言条件。如果断言条件为真,则程序将继续正常执行;但如果条件为假,则会引发 AssertionError 异常并显示相关的错误消息。
举个简单例子,假设老板让你用 Python 构建一个在线商店。为了添加优惠券的功能,你编写了下面这个 apply_discount 函数。
注意到 assert 语句了吗?这条语句确保在任何情况下,通过该函数计算的折后价不低于 0,也不会高于产品原价。
那调用该函数能否正确计算折后价?在这个例子中,商店中的产品用普通的字典表示。这样就能够很好地演示断言的使用方法了。当然实际的应用程序可能不会这么做。先来创建一个示例产品,即一双价格为 149 美元的漂亮鞋子:
顺便说一下,这里使用整数来表示以分为单位的价格,以此来避免货币的舍入问题。一般而言,这是个好办法……好吧,有点扯远了。现在如果为这双鞋打七五折,即优惠了 25%,则售价变为 111.75 美元:
可以再尝试使用一些无效的折扣,比如 200% 的“折扣”会让商家向顾客付钱:
从上面可以看到,当尝试使用无效的折扣时,程序会停止并触发一个 AssertionError。
发生这种情况是因为 200% 的折扣违反了在 apply_discount 数中设置的断言条件。
从异常栈跟踪信息中还能得知断言验证失败的具体位置。如果你(或者团队中的另一个开发人员)在测试在线商店时遇到这些错误,那么查看异常回溯就可以轻松地了解是哪里出了问题。
这极大地加快了调试工作的速度,并且长远看来,程序也更易于维护。这就是断言的力量。
一只6条腿还能四处跑的狗
刚接触 Python 的时候,有一些概念给我带来了不少烦恼。我没花什么时间仔细地去了解过这些概念。所以,在我早期的面向对象的代码中,都是充满了令人惊讶的行为还有一些奇奇怪怪的错误。
比如,这两个 Python 对象的数据属性:类变量和实例变量。类变量在类定义内部声明(但位于实例方法之外),不受任何特定类实例的束缚。实例变量总是绑定到特定的对象实例。它的内容不存储在类上,而是存储在每个由类创建的单个对象上。
看这样一段代码,快乐的狗需要什么?四条腿和一个名字:
好吧,就用狗狗示例来描述一下面向对象的形式。首先,创建新的 Dog 实例能正常工作,并且每个实例都会获得一个名为 name 的实例变量:
涉及类变量时,就比较灵活了。在每个 Dog 实例或类本身上可以直接访问 num_legs 类变量:
然而,如果尝试通过类访问实例变量,会失败并抛出 AttributeError。实例变量是特定于每个对象实例的,在运行 __init__ 构造函数时创建,并不位于类本身中。这就是类变量和实例变量之间的核心区别:
是不是到目前为止还行?
假如有一天,一只名为 Jack 的狗在吃晚餐时与微波炉靠得太近,发生变异又长出了一双腿,那么如何在代码中表示呢?第一个想法可能是简单地修改 Dog 类中的 num_legs 变量:
不过,我不希望所有的狗都开始用六条腿四处乱跑。由于修改了类变量,因此现在把所有的狗都变成了超级狗。这会影响到所有的狗,甚至是我之前创建的狗:
看来这种方式不行。原因是修改类名称空间上的类变量会影响类的所有实例。我只能现在撤销这个对类变量的改动,并尝试仅向 Jack 添加额外两条腿:
咦?怎么感觉自己用这种方式创造了一个怪物。
看起来好像“相当不错”(除了可怜的 Jack 多了两条腿)。但这种改动是如何影响 Dog 对象的呢?
这里的难点在于,虽然得到了想要的结果(为 Jack 添加两条腿),但在 Jack 实例中引入了一个 num_legs 实例变量。而新的 num_legs 实例变量“遮盖”了相同名称的类变量,在访问对象实例作用域时覆盖并隐藏类变量:
以上可以看出,类变量没有同步更新,因为写入到 jack.num_legs 创建了一个与类变量同名的实例变量。
其实,这不一定是坏事。重要的是要意识到背后发生的事情。在最终了解 Python 中的类层面和实例层面的作用域规则之前,很容易因为这些问题在程序中引入 bug。
说实话,试图通过对象实例修改类变量时意外地创建了一个名称相同的实例变量,从而隐藏了原来的类变量。有点像是 Python 中的一个 OOP 陷阱。
寿司操作符
Python 的列表对象有方便的切片特性。切片可被视为方括号索引语法的扩展,通常用于访问有序集合中某一范围的元素。例如,将一个大型列表对象分成几个较小的子列表。我们就跟大神一起做一次料理师。
看这个例子,切片使用熟悉 [] 索引语法和如下 [start:stop:step] 模式:
[1:3:1] 索引返回从索引 1 到索引 2 的原始列表切片,步长为一个元素。为了避免多算一个元素的错误,需要记住切片计算方法是算头不算尾。因此 [1:3:1] 切片的子列表是 [2,3]。
如果不提供步长,则默认为1:
步长(step)参数也称为步幅(stride),还可用来做其他有趣的事情。例如,可以创建一个间隔包含原列表元素的子列表:
是不是还行!大神将冒号分隔符称作:寿司操作符,因为这像是一个从侧面切开的美味太卷寿司(maki roll)。除了让人想到美食和访问列表范围之外,切片还有一些鲜为人知的应用。来看几个有趣且有用的列表切片技巧。
除使用切片步长来间隔选择列表中的元素,还有其他用法。比如 [::-1] 切片会得到原始列表的逆序副本:
用 :: 让 Python 提供完整的列表,但将步长设置为 -1 来从后到前遍历所有元素。这种方式很整洁,但大神在大多数情况下会坚持使用 list.reverse() 和内置的 reverse() 函数来反转列表。
还有另一个列表切片技巧,即:使用操作符清空列表中的所有元素,同时不会破坏列表对象本身。
这适用于在程序中有其他引用指向这个列表时清空列表。在这种情况下,通常不能用新的列表对象替换已有列表来清空列表,替换列表不会更新原列表的引用。此时“寿司操作符”就派上用场了:
上面的操作删除了 lst 中的所有元素,但保持列表对象本身不变。在 Python 3 中也可以使用 lst.clear() 完成同样的工作,这种方式在某些情况下可读性更好。但要注意在 Python 2 中无法使用 clear()。
除了清空列表之外,切片还可以用来在不创建新列表对象的情况下替换列表中的所有元素,即手动快速清空列表然后重新填充元素:
前面的示例中替换了列表中的所有元素,不过并未销毁再重新创建列表本身。因此原始列表对象的旧引用仍然是有效的。
“寿司操作符”的另一个作用是创建现有列表的浅副本:
创建浅副本意味着只复制元素的结构,而不复制元素本身。两个列表中的每个元素都是相同的实例。
如果需要复制所有内容(包括元素),则需要创建列表的深副本。此时可以使用 Python 内置的 copy 模块。
怎么样?是不是还蛮好玩的。
格衬衫、秃头男、穿拖鞋,这好像是当代人对我们程序员最大的误解。不过不是所有程序员都是这样的。再说,这样也没什么不好。我们就是喜欢随性一点,难道不行吗?
不过,这位大神有点不一样。小胡子、黑框眼睛、T恤衫,妥妥的文学范。他是谁呢?他是编程界的本尼迪克特·康伯巴奇,玩蛇最 6 的人,Real Python 培训机构的总编——Dan Bader。
他的博客 dbader.org 每月有超过 20 万的浏览量,Real Python 视频累计浏览量超过百万。他影响了全球 1 000 000 以上程序员,是一个不折不扣的大佬。
除此之外,Dan Bader 还致力于分享和建设良好的 Python 生态。他为程序员打造了专属的玩蛇阵地——PythonistaCafe。
这是一个为 Python 开发者们专门打造的付费社区。在这里可以跟来自世界各地的专业开发人员和 Python 爱好者交流。成员们水平各有千秋。用户可以提问、获得答案、分享自己的进度。堪称真正懂你的技术社区。
会玩,又会教,这样的大神给我来一打Dan Bader 爱好广泛,平日里除了喜欢去健身,还喜欢烹饪、远足。有时候还会弹弹吉他来放松自己。同时他还会读很多书。用他的话说,构建一些东西是最能让他感到开心的事情。他不仅会玩,还会教。自从我订阅了大神的邮件,每天准点就会收到他发送来的 Python 学习技能。一天 24 小时,能做这么多事情,如果不是源于对技术的热爱,想必真的很难做到。
如果你翻阅他的博客,基本上不管你处在学习 Python 的什么阶段,Dan Bader 都为你贴心地做了规划并给出建议。毕竟他每天收到那么多的邮件询问建议,相信他已经掌握了大多数开发者的问题和需求。
还有他录制的 Real Python 栏目的视频。我英语其实挺一般的,但是看了他的视频,最大的感觉就是“好懂”,用的词都很直接。连 Native Speaker 都觉得好读的,那基本可以说是白话版了。
除了博客和课程之外,他还写了几本书。其中有一本叫 Python Tricks ,是他从收集到的读者邮件中整理出来的实用 Python 技巧,可以说是最接近读者需求的 Python 技巧了。如果你有 Python 基础,那么三周让代码变得更 6 应该不是问题。
他用最短、最易理解的示例讲述了 Python 最酷的一面。字典示例、用双胞胎猫讲述 is 和== 等等。以“自助餐”的形式来介绍这些性能我还是第一次见。目前这本书的中文版已经上市了,也是时候跟大神一起玩耍一下了。
虽然我没想过有一天可以成为大神,但是这样的愿望还是要有的。如果学什么都只为一时,那么真的很难有更高的成就。这也是在我看完 Dan Bader 课程后最大的心得。
原版豆瓣评分 8.3
作者:Dan Bader
译者:孙波翔
本书致力于帮助 Python 开发人员挖掘这门语言及相关程序库的优秀特性,避免重复劳动,同时写出简洁、流畅、易读、易维护的代码。用好 Python 需要了解的最重要的特性、Python 2 过渡到 Python 3 需要掌握的现代模式。有其他编程语言背景想快速上手 Python 的程序员需要特别注意的问题等等,本书都可以解决。
文末畅聊
留言说说你对 Python 的了解有多少?或者说说你用 Python 的哪些性能用得最 6,以及哪些技术大牛对你的影响颇深?快来畅所欲言吧!