Python 进阶指南(编程轻松进阶):六、编写 Python 风格的代码

原文:http://inventwithpython.com/beyond/chapter6.html

强大对于编程语言来说是一个没有意义的形容词。每种编程语言都称自己长处。官方 Python 教程开头就说 Python 是一种简单易学、功能强大的编程语言。但是没有一种语言可以做另一种语言不能做的算法,也没有量化编程语言“能力”的度量单位(尽管你可以用编程需要在程序员中受欢迎的成都来度量)。

但是每种语言都有自己的设计模式和缺陷,展现了它的优点和缺点。要像真正编写 Python 风格的 Python 代码,你需要知道的不仅仅是语法和标准库,进一步还学习它的习惯用法,或者专门的 Python 的编码实践。某些 Python 语言的特性有助于您编写 Python 风格的代码。

在这一章中,我将提供几种编写地道 Python 代码的常用方法以及相应案例。不同的程序员认为 Python 风格是不同的,但是它通常包括我在这里讨论的例子和实践。有经验的 Python 程序员使用这些技术,所以熟悉它们可以让您在现实世界的代码中识别它们。

Python 之禅

Tim Peters 的《Python 之禅》是一套 20 条关于 Python 语言和 Python 程序设计的指南。您的 Python 代码不一定要遵循这些指导方针,但是记住它们是有好处的。Python 之禅也是一个复活节彩蛋,或者隐藏的笑话,当你运行import this时出现:

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
`--snip--`

不可思议的是,只有 19 条指导方针被写了下来。据报道,Python 的创始人吉多·范·罗苏姆说,丢失的第 20 句格言是“蒂姆·彼得斯开的一个奇怪的玩笑”,他让 Gudio 去填补空白,而他似乎从来没有抽出时间去做这件事。


最后,这些指导方针是大多数程序员支持或反对的观点。像所有好的道德准则一样,它们看似矛盾,但却提供最大的灵活性。以下是我对这些格言的解读:

  1. 漂亮总比丑陋好。漂亮的代码被认为是易读易懂的。程序员经常快速编写代码,而不考虑可读性。计算机可以运行不可读的代码,但不可读的代码对于人类程序员来说很难维护和调试。美丽是主观的,但是不考虑如何理解的代码对其他人来说通常是丑陋的。Python 受欢迎的原因是它的语法不像其他语言那样充斥着晦涩的标点符号,这使得它很容易使用。
  2. 显性比隐性好。如果我只写“这是不言自明的”,我会为这句格言提供一个糟糕的解释。同样,在代码中,最好是详细和明确的。您应该避免将代码的功能隐藏在晦涩难懂的语言特性后面,这些特性需要对语言有很深的了解才能完全理解。
  3. 简单胜于复杂。复杂总比蛮干好。这两句格言提醒我们,我们可以用简单或复杂的技术建造任何东西。如果你有一个需要铲子的简单问题,使用 50 吨的液压推土机就太大材小用了。但是对于一项巨大的工作来说,操作一台推土机的复杂性要比协调一个 100 人的团队的复杂性更可取。喜欢简单胜过复杂,但要知道简单的局限。
  4. 扁平比嵌套好。程序员喜欢将他们的代码组织成类别,尤其是包含子类别的类别,子类别包含其他子子类别。这些等级制度与其说增加了组织,不如说增加了官僚主义。只在一个顶级模块或数据结构中编写代码是可以的。如果你的代码看起来像spam.eggs.bacon.ham()spam['eggs']['bacon']['ham'],那么你的代码太复杂了。
  5. 稀不如密。程序员通常喜欢将尽可能多的功能塞进尽可能少的代码中,就像下面这样:print('\n'.join("%i bytes = %i bits which has %i possiblevalues." % (j, j*8, 256**j-1) for j in (1 << i for i in range(8))))。虽然像这样的代码可能会给他们的朋友留下深刻印象,但它会激怒他们的同事,他们不得不试图理解它。不要让你的代码一次做太多事情。分散在多行中的代码通常比密集的一行代码更容易阅读。这句格言大致和简单比复杂好一样。
  6. 可读性很重要。尽管对于从 20 世纪 70 年代就开始用 C 语言编程的人来说,strcmp()可能意味着“字符串比较”函数,但现代计算机有足够的内存来写出完整的函数名。不要从你的名字中去掉字母或者写过于简洁的代码。花点时间为变量和函数想出描述性的、具体的名字。代码各部分之间的空行可以起到与书籍中的段落分隔符相同的作用,让读者知道哪些部分应该一起阅读。这句格言大致和美丽胜于丑陋一样。
  7. 特例很少十,提倡以特殊违反规则。虽然实用性战胜了纯粹性。这两句格言互相矛盾。编程中充满了程序员应该在代码中努力实现的“最佳实践”。绕过这些实践进行快速破解可能很诱人,但可能会写出不一致、不可读的代码。另一方面,竭尽全力遵守规则会导致高度抽象、不可读的代码。例如,Java 编程语言试图让所有代码都符合其面向对象的范例,这通常会导致即使是最小的程序也有大量的样板代码。随着经验的积累,在这两个格言之间游走变得更加容易。假以时日,你就会明白“规则是用来打破的”这个道理。
  8. 错误永远不会悄无声息地过去。除非您选择沉默。仅仅因为程序员经常忽略错误信息并不意味着程序应该停止发出错误警告信息。当函数返回错误代码或None而不是引发异常时,可能会发生无声错误。这两句格言告诉我们,对于一个程序来说,让它快速失败和崩溃比选择漠视它要好。后来不可避免地发生的错误将更难调试,因为它们是在最初更容易被检测分析到。尽管你总是可以决定明确地忽略程序引起的错误,但是要心里明白你自己忽略了那些错误。
  9. 面对模棱两可的问题时,不要瞎猜。电脑让人类变得迷信:为了驱除电脑中的恶魔,我们会举行一个神圣的仪式,关掉电脑,然后再打开。据说这将解决任何神秘的问题。但是电脑不是魔术。如果你的代码不能工作,那是有原因的,只有仔细的、批判性的思考才能解决问题。拒绝盲目尝试解决方案的诱惑,直到事情似乎奏效;通常,你只是掩盖了问题,而不是解决了问题。
  10. 应该有一种——最好只有一种——显而易见的方法来做这件事。这是对 Perl 编程语言格言“有不止一种方法可以做到这一点”的观点是完全对立的,用三种或四种不同的方式来编写完成相同任务的代码是一把双刃剑:您可以灵活地编写代码,但现在您必须学习每种可能的方式来阅读其他人的代码。这种灵活性不值得花更多的精力去学习一门编程语言。
  11. 虽然除非你是荷兰人,否则一开始这种方式并不明显。这句话是个笑话。Python 之父吉多·范·罗苏姆是荷兰人。
  12. 现在总比没有好。尽管从来没有比现在更好。这两句格言告诉我们,运行慢的代码明显不如运行快的代码。但是等待你的程序完成,总比过早完成的错误程序要好。
  13. 如果实现很难解释,就尽量不要这样做。如果实现很容易解释,这可能是一个好主意。随着时间的推移,许多事情变得越来越复杂:税法、浪漫关系、Python 编程书籍。软件也不例外。这两句格言提醒我们,如果代码复杂到程序员无法理解和调试,那么它就是糟糕的代码。但是仅仅因为向别人解释一个程序的代码很容易,并不意味着它是好代码。不幸的是,弄清楚如何使代码尽可能简单,而不是更简单,这通常很难做到。
  14. 命名空间是一个非常棒的想法——让我们多做一些吧!命名空间是标识符的独立容器,以防止命名冲突。比如open()内置函数和webbrowser.open()函数同名但引用不同的函数。导入webbrowser不会覆盖内置的open()函数,因为两个open()函数存在于不同的名称空间中:分别是内置的名称空间和webbrowser模块的名称空间。但是请记住,扁平的比嵌套的好:尽管名称空间很大,但是您应该只使用它们来防止命名冲突,而不是添加不必要的分类。

和所有关于编程的观点一样,你可以反驳我在这里列出的观点,或者你看后无感。争论应该如何编写代码或者什么才算“Python 风格化”意义并不大。(除非你正在写一本充满编程观点的书。)

学会使用缩进

我从来自其他语言的程序员那里听到的关于 Python 的最常见的担忧是,Python 的有效缩进(经常被误称为有效空格)是怪异和陌生的。在 Python 中,一行代码开头的缩进量是有意义的,因为它决定了哪些代码行在同一个代码块中。

使用缩进对 Python 代码块进行分组可能看起来很奇怪,因为其他语言用大括号{}来声明代码块的开始和结束。但是非 Python 语言的程序员通常也缩进他们的块,就像 Python 程序员一样,以使他们的代码更可读。例如,Java 编程语言没有明显的缩进。Java 程序员不需要缩进代码块,但为了可读性,他们经常这样做。下面的例子有一个名为main()的 Java 函数,它包含一个对println()函数的调用:

// Java Example
public static void main(String[] args) {
    System.out.println("Hello, world!");
}

如果println()行没有缩进,这段 Java 代码仍然可以运行,因为括号是 Java 中标记代码块的开始和结束,而不是缩进。Python 不允许缩进是可选的,而是强制代码具有一致的可读性。但是注意 Python 没有有效空格这个概念,因为 Python 并没有限制你如何使用非有效空格(两个2 + 22+2都是有效的 Python 表达式)。

一些程序员认为左大括号应该和开始语句在同一行,而另一些人认为应该在下一行。程序员会争论他们喜欢的风格的优点,直到时间的尽头。Python 巧妙地避开了这个问题,根本不使用大括号,让 Python 编码者避开无意义的讨论, 回到更高效的工作中。我开始希望所有的编程语言都采用 Python 的方法对代码块进行分组。

但是有些人仍然渴望大括号,并希望将它们添加到 Python 的未来版本中——尽管这种想法是多么不合时宜。Python 的__future__模块将特性反向移植到早期的 Python 版本,如果你试图将括号特性导入 Python,你会发现一个隐藏的复活节彩蛋:

>>> from __future__ import braces
SyntaxError: not a chance

我不指望大括号会很快被加入 Python。

经常被误用的语法

如果 Python 不是你的第一编程语言,你可以用和其他编程语言一样的策略来编写你的 Python 代码。或者,您可能学习了一种不寻常的编写 Python 代码的方法,因为您不知道有更多已确立的最佳实践。这段笨拙的代码可以工作,但是通过学习编写 Python 风格代码的更标准的方法,您可以节省一些时间和精力。本节解释了程序员常犯的错误,以及应该如何编写代码。

使用enumerate()而不是range()

当循环遍历一个列表或其他序列时,一些程序员使用range()len()函数来生成从0到序列长度的索引整数,但不包括序列长度。在这些for循环中使用变量名i(用于索引)是很常见的。例如,在交互式 Shell 中输入以下非单调示例:

>>> animals = ['cat', 'dog', 'moose']
>>> for i in range(len(animals)):
...    print(i, animals[i])
...
0 cat
1 dog
2 moose

约定很简单,但是不太理想,因为它可能很难阅读。相反,将列表或序列传递给内置的enumerate()函数,该函数将返回索引和该索引处的项目的整数。例如,您可以编写以下 Python 风格代码:

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for i, animal in enumerate(animals):
...    print(i, animal)
...
0 cat
1 dog
2 moose

enumerate()代替range(len()),你写的代码会稍微干净一点。如果只需要条目而不需要索引,仍然可以用 Python 的方式直接遍历列表:

>>> # Pythonic Example
>>> animals = ['cat', 'dog', 'moose']
>>> for animal in animals:
...    print(animal)
...
cat
dog
moose

调用enumerate()并直接迭代一个序列比使用老式的range(len())约定更好。

使用with语句代替open()close()函数

函数将返回一个包含读写文件方法的文件对象。完成后,file对象的close()方法会关闭该文件,使该文件可供其他程序读写。您可以单独使用这些函数。但是这样做是不严谨的。例如,在交互式 Shell 中输入以下内容来编写文本'Hello, world!'到一个名为spam.txt的文件:

>>> # Unpythonic Example
>>> fileObj = open('spam.txt', 'w')
>>> fileObj.write('Hello, world!')
13
>>> fileObj.close()

如果在try块中发生错误,程序跳过对close()的调用,以这种方式编写代码会导致文件不关闭。例如:

>>> # Unpythonic Example
>>> try:
...    fileObj = open('spam.txt', 'w')
...    eggs = 42 / 0    # A zero divide error happens here.
...    fileObj.close()  # This line never runs.
... except:
...    print('Some error occurred.')
...
Some error occurred.

在到达零除错误时,执行移动到except块,跳过close()调用并保持文件打开。这可能导致文件受损错误,以后排错很难追溯到try块。

相反,当执行离开with语句的块时,可以使用with语句自动调用close()。下面的 Python 风格示例与本节中的第一个示例是等价的:

>>> # Pythonic Example
>>> with open('spam.txt', 'w') as fileObj:
...    fileObj.write('Hello, world!')
...

即使没有对close()的显式调用,当执行离开块时,with语句也会自动调用它。

使用is而不是==None进行比较,

==相等运算符比较两个对象的值,而is相同运算符比较两个对象的标识。第 7 章涵盖了值和标识。两个对象可以存储相等的值,但是作为两个独立的对象意味着它们有独立的标识。然而,每当你比较一个值和None时,你应该总是使用is操作符而不是==操作符。

在某些情况下,表达式spam == None可以计算为True,即使spam只包含None。这可能是由于==操作符过载造成的,第 17 章对此有更详细的介绍。但是spam is None会检查spam变量中的值是否是字面上的None。因为NoneNoneType数据类型的唯一值,所以任何 Python 程序中都只有一个None对象。如果变量被设置为None,则is None比较将总是求值为True。第 17 章描述了重载==操作符的细节,但下面是这种行为的一个例子:

>>> class SomeClass:
...    def __eq__(self, other):
...        if other is None:
...            return True
...
>>> spam = SomeClass()
>>> spam == None
True
>>> spam is None
False

一个类以这种方式重载==操作符的可能性很小,但是为了以防万一,总是使用is None而不是== None已经成为 Python 的习惯用法。

最后,你不应该使用带有值TrueFalseis操作符。您可以使用==相等运算符将一个值与TrueFalse进行比较,例如spam == Truespam == False。更常见的是完全省略操作符和布尔值,编写类似于if spam:if not spam:的代码,而不是if spam == True:if spam == False:

格式化字符串

字符串出现在几乎所有的计算机程序中,不管是哪种语言。这种数据类型很常见,所以有许多方法来操作和格式化字符串也就不足为奇了。本节重点介绍几个最佳案例。

如果您的字符串有许多反斜杠,请使用原始字符串

转义字符允许您将文本插入到字符串字面值中,否则将无法在文本中包含转移字符。例如,您需要'Zophie\'s chair'中的\,因此 Python 将第二个引号解释为字符串的一部分,而不是标记字符串结尾的符号。因为反斜杠有这个特殊的转义含义,如果你想在字符串中放一个实际的反斜杠字符,你必须输入它作为\\

原始字符串是带有前缀r的字符串,它们不将反斜杠字符视为转义字符。相反,他们只是把反斜杠放到字符串中。例如,Windows 文件路径的这个字符串需要几个转义反斜杠,这不是很 Python 风格化:

>>> # Unpythonic Example
>>> print('The file is in C:\\Users\\Al\\Desktop\\Info\\Archive\\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

这个原始字符串(注意前缀r)产生相同的字符串值,同时可读性更好:

>>> # Pythonic Example
>>> print(r'The file is in C:\Users\Al\Desktop\Info\Archive\Spam')
The file is in C:\Users\Al\Desktop\Info\Archive\Spam

原始字符串是一种特殊的字符串数据类型;它们只是一种输入包含几个反斜杠字符的字符串的便捷方式。我们经常使用原始字符串来键入正则表达式或 Windows 文件路径的字符串,这些字符串中经常有几个反斜杠字符,用\\逐个转义会很麻烦。

用 F 字符串格式化字符串

字符串格式化,或字符串插值,是创建包含其他字符串的字符串的过程,在 Python 中有很长的历史。最初,+操作符可以将字符串连接在一起,但这导致代码中有许多引号和加号:'Hello, ' + name + '. Today is ' + day + ' and it is ' + weather + '.'%s转换说明符使语法变得简单了一点:'Hello, %s. Today is %s and it is %s.' % (name, day, weather)。这两种技术都将把namedayweather变量中的字符串插入到字符串字面值中,以计算一个新的字符串值,就像这样:'Hello, Al. Today is Sunday and it is sunny.'

format()字符串方法添加了格式规范迷你语言docs.python.org/3/library/string.html#formatspec),这涉及到以类似于%s转换说明符的方式使用{}大括号对。然而,这种方法有些复杂,会产生不可读的代码,所以我不鼓励使用它。

但是从 Python 3.6 开始, F 字符串格式字符串的缩写)提供了一种更方便的方法来创建包含其他字符串的字符串。就像原始字符串在第一个引号前加前缀r一样,F 字符串也加前缀f。您可以在 F 字符串的大括号中包含变量名,以插入存储在这些变量中的字符串:

>>> name, day, weather = 'Al', 'Sunday', 'sunny'
>>> f'Hello, {name}. Today is {day} and it is {weather}.'
'Hello, Al. Today is Sunday and it is sunny.'

大括号也可以包含整个表达式:

>>> width, length = 10, 12
>>> f'A {width} by {length} room has an area of {width * length}.'
'A 10 by 12 room has an area of 120.'

如果需要在 F 字符串中使用大括号字面值,可以用一个额外的大括号进行转义:

>>> spam = 42
>>> f'This prints the value in spam: {spam}'
'This prints the value in spam: 42'
>>> f'This prints literal curly braces: {{spam}}'
'This prints literal curly braces: {spam}'

因为您可以将变量名和表达式内联到字符串中,所以您的代码比使用旧的字符串格式化方法更具可读性。

所有这些格式化字符串的不同方法都违背了 Python 的格言:应该有一种——最好只有一种——显而易见的方法来做某事。但是格式化函数是对语言的一种改进(在我的看来),正如另一条指导方针所说,实用性胜过纯粹性。如果只为 Python 3.6 或更高版本编写代码,请使用 F 格式化字符串。如果您正在编写早期 Python 版本运行的代码,请坚持使用format()字符串方法或%s转换说明符。

制作列表的浅层副本

切片语法可以很容易地从现有的字符串或列表中创建新的字符串或列表。在交互式 Shell 中输入以下内容,看看它是如何工作的:

>>> 'Hello, world!'[7:12] # Create a string from a larger string.
'world'
>>> 'Hello, world!'[:5] # Create a string from a larger string.
'Hello'
>>> ['cat', 'dog', 'rat', 'eel'][2:] # Create a list from a larger list.
['rat', 'eel']

冒号(:)分隔要放入正在创建的新列表中的项目的开始和结束索引。如果省略冒号前的起始索引,如在'Hello, world!'[:5]中,起始索引默认为0。如果省略冒号后的结束索引,如['cat', 'dog', 'rat', 'eel'][2:],结束索引默认为列表的末尾。

如果省略两个索引,起始索引是0(列表的开始),结束索引是列表的结尾。这实际上创建了列表的副本:

>>> spam = ['cat', 'dog', 'rat', 'eel']
>>> eggs = spam[:]
>>> eggs
['cat', 'dog', 'rat', 'eel']
>>> id(spam) == id(eggs)
False

请注意,spameggs中的列表标识是不同的。eggs = spam[:]行创建了spam中列表的浅副本,而eggs = spam将只拷贝对列表的引用。但是[:]看起来确实有点奇怪,使用copy模块的copy()函数来生成列表的浅层副本更具可读性:

>>> # Pythonic Example
>>> import copy
>>> spam = ['cat', 'dog', 'rat', 'eel']
>>> eggs = copy.copy(spam)
>>> id(spam) == id(eggs)
False

你应该知道这种少见的语法,以防你碰到使用它的 Python 代码,但是我不建议用你自己的代码写它。请记住,[:]copy.copy()都可以创建浅层副本。

使用字典的 Python 风格方法

字典是许多 Python 程序的核心,因为键值对(将在第 7 章中进一步讨论)通过将一段数据映射到另一段数据提供了灵活性。因此,了解 Python 代码常用的一些字典习惯用法是很有用的。

关于字典的更多信息,请参考 Python 程序员 Brandon Rhodes 关于字典及其工作原理的精彩演讲:2010 年 PyCon 上的“强大的字典”,可在invpy.com/mightydictionary观看;2017 年 PyCon 上的“更强大的字典”,可在invpy.com/dictionaryevenmightier观看。

对字典使用get()setdefault()

试图访问一个不存在的字典键会导致一个KeyError错误,所以程序员通常会编写啰嗦的代码来避免这种情况,就像这样:

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' in numberOfPets: # Check if 'cats' exists as a key.
...    print('I have', numberOfPets['cats'], 'cats.')
... else:
...    print('I have 0 cats.')
...
I have 0 cats.

这段代码检查字符串'cats'是否作为关键字存在于numberOfPets字典中。如果是,调用print()访问numberOfPets['cats']作为给用户的消息的一部分。如果没有,另一个print()调用在没有访问numberOfPets['cats']的情况下打印一个字符串,所以它不会引发KeyError

这种模式经常发生,以至于字典中有一个get()方法,当字典中不存在某个键时,该方法允许您指定一个要返回的默认值。以下 Python 风格代码相当于前面的示例:

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> print('I have', numberOfPets.get('cats', 0), 'cats.')
I have 0 cats.

调用numberOfPets.get('cats', 0)检查关键字'cats'是否存在于numberOfPets字典中。如果是,方法调用返回'cats'键的值。如果没有,它将返回第二个参数0。使用get()方法为不存在的键指定默认值比使用if-else语句更短,可读性更好。

相反,如果一个键不存在,您可能希望设置一个默认值。例如,如果numberOfPets中的字典没有'cats'键,指令numberOfPets['cats'] += 10将导致KeyError错误。您可能希望添加代码来检查该键是否存在并设置默认值:

>>> # Unpythonic Example
>>> numberOfPets = {'dogs': 2}
>>> if 'cats' not in numberOfPets:
...    numberOfPets['cats'] = 0
...
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

但是因为这种模式也很常见,所以字典有一个更 Python 风格化的setdefault()方法。以下代码等效于前面的示例:

>>> # Pythonic Example
>>> numberOfPets = {'dogs': 2}
>>> numberOfPets.setdefault('cats', 0) # Does nothing if 'cats' exists.
0
>>> numberOfPets['cats'] += 10
>>> numberOfPets['cats']
10

如果您正在编写if语句来检查字典中是否存在某个键,如果该键不存在,则设置默认值,请使用setdefault()方法。

为默认值使用collections.defaultdict

您可以使用collections.defaultdict类来完全消除KeyError错误。这个类允许您通过导入collections模块并调用collections.defaultdict()来创建一个默认字典,向其传递一个数据类型以用作默认值。例如,通过将int传递给collections.defaultdict(),您可以创建一个类似字典的对象,它使用0作为不存在的键的默认值。在交互式 Shell 中输入以下内容:

>>> import collections
>>> scores = collections.defaultdict(int)
>>> scores
defaultdict(<class 'int'>, {})
>>> scores['Al'] += 1 # No need to set a value for the 'Al' key first.
>>> scores
defaultdict(<class 'int'>, {'Al': 1})
>>> scores['Zophie'] # No need to set a value for the 'Zophie' key first.
0
>>> scores['Zophie'] += 40
>>> scores
defaultdict(<class 'int'>, {'Al': 1, 'Zophie': 40})

注意,您传递的是int()函数,而不是调用它,所以您省略了collections.defaultdict(int)int后面的括号。也可以通过list使用空列表作为默认值。在交互式 Shell 中输入以下内容:

>>> import collections
>>> booksReadBy = collections.defaultdict(list)
>>> booksReadBy['Al'].append('Oryx and Crake')
>>> booksReadBy['Al'].append('American Gods')
>>> len(booksReadBy['Al'])
2
>>> len(booksReadBy['Zophie']) # The default value is an empty list.
0

如果您需要每个可能的键的默认值,使用collections.defaultdict()比使用常规字典并不断调用setdefault()方法要容易得多。

使用字典代替switch语句

Java 之类的语言有一个switch语句,这是一种if-elif-else语句,它根据判定变量包含的众多值中的哪一个来运行代码。Python 没有switch语句,所以 Python 程序员有时会编写类似下面这个例子的代码,根据season变量包含的值运行不同的赋值语句:

# All of the following if and elif conditions have "season ==":
if season == 'Winter':
    holiday = 'New Year\'s Day'
elif season == 'Spring':
    holiday = 'May Day'
elif season == 'Summer':
    holiday = 'Juneteenth'
elif season == 'Fall':
    holiday = 'Halloween'
else:
    holiday = 'Personal day off'

这段代码不一定不好看,但是有点冗长。默认情况下,Java switch语句具有“跳转”功能,要求每个块以一个break语句结束。否则,执行将继续到下一个块。忘记添加这个break语句是一个常见的错误来源。但是在我们的 Python 例子中,所有的if-elif语句都是重复的。一些 Python 程序员更喜欢设置一个字典值,而不是使用if-elif语句。以下简洁的 Python 风格代码相当于前面的示例:

holiday = {'Winter': 'New Year\'s Day',
           'Spring': 'May Day',
           'Summer': 'Juneteenth',
           'Fall':   'Halloween'}.get(season, 'Personal day off')

这段代码只是一条赋值语句。存储在holiday中的值是get()方法调用的返回值,它返回season被设置的键的值。如果season键不存在,get()返回'Personal day off'。使用字典会使代码更简洁,但也会使代码更难阅读。是否使用该特性由您决定。

条件表达式:Python 的“丑陋”三元运算符

三元运算符(正式名称为条件表达式,有时在 Python 中称为三元选择表达式)根据条件将表达式计算为两个值之一。通常,您会用 Python 风格的if-else语句来实现这一点:

>>> # Pythonic Example
>>> condition = True
>>> if condition:
...    message = 'Access granted'
... else:
...    message = 'Access denied'
...
>>> message
'Access granted'

三元简单地表示一个有三个输入的操作符,但在编程中,它与条件表达式同义。条件表达式也为符合这种模式的代码提供了更简洁的一行程序。在 Python 中,它们是通过ifelse关键字的奇怪排列来实现的:

>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
>>> message = valueIfTrue if condition else valueIfFalse # 1
>>> message
'Access granted'
>>> print(valueIfTrue if condition else valueIfFalse) # 2
'Access granted'
>>> condition = False
>>> message = valueIfTrue if condition else valueIfFalse
>>> message
'Access denied'

如果condition变量为True,表达式valueIfTrue if condition else valueIfFalse 1 的计算结果为valueIfTrue。当condition变量为False时,表达式计算结果为valueIfFalse。吉多·范·罗苏姆开玩笑地将三元运算符是最丑的代码,然后是真值,最后是假值。您可以在任何可以使用表达式或值的地方使用条件表达式,包括作为函数调用 2 的参数。

为什么 Python 会在 Python2.5 中引入这种语法,尽管它打破了漂亮比难看好的第一条准则?不幸的是,尽管有些不可读,但许多程序员热衷于使用三元运算符,并希望 Python 支持这种语法。有可能滥用布尔运算符短路来创建一种三元运算符。如果conditionTrue,表达式condition and valueIfTrue or valueIfFalse将计算为valueIfTrue,如果conditionFalse,则计算为valueIfFalse(除了一个重要的情况)。在交互式 Shell 中输入以下内容:

>>> # Unpythonic Example
>>> valueIfTrue = 'Access granted'
>>> valueIfFalse = 'Access denied'
>>> condition = True
>>> condition and valueIfTrue or valueIfFalse
'Access granted'

这种condition and valueIfTrue or valueIfFalse风格的伪三进制运算符有一个微妙的 bug:如果valueIfTrue是一个 false 值(如0FalseNone或空白字符串),那么如果conditionTrue,则表达式意外地计算为valueIfFalse

但是程序员还是继续使用这个假的三元运算符,还有“为什么 Python 没有三元运算符?”成为 Python 核心开发人员的一个长期问题。创建条件表达式是为了让程序员不再要求三元运算符,也不会使用容易出错的伪三元运算符。但是条件表达式也很丑陋,足以阻止程序员使用它们。虽然漂亮可能比难看好,但 Python 的“难看”三元运算符是实用性战胜纯粹性的一个例子。

条件表达式不完全是 Python 风格的,但也不是非 python 式的。如果您确实要使用它们,请避免将条件表达式嵌套在其他条件表达式中:

>>> # Unpythonic Example
>>> age = 30
>>> ageRange = 'child' if age < 13 else 'teenager' if age >= 13 and age < 18 else 'adult'
>>> ageRange
'adult'

嵌套的条件表达式是一个很好的例子,说明密集的一行程序在技术上是正确的,但在阅读时却令人沮丧。

使用变量值

您经常需要检查和修改变量存储的值。Python 有几种方法可以做到这一点。让我们看几个例子。

链接赋值和比较运算符

当您必须检查一个数字是否在某个范围内时,您可以像这样使用布尔and运算符:

# Unpythonic Example
if 42 < spam and spam < 99:

但是 Python 允许你链接比较操作符,所以你不需要使用and操作符。以下代码等效于前面的示例:

# Pythonic Example
if 42 < spam < 99:

这同样适用于链接=赋值操作符。您可以在一行代码中将多个变量设置为相同的值:

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> print(spam, eggs, bacon)
string string string

要检查这三个变量是否都相同,可以使用and操作符,或者更简单地说,将==比较操作符链接起来以确保相等。

>>> # Pythonic Example
>>> spam = eggs = bacon = 'string'
>>> spam == eggs == bacon == 'string'
True

在 Python 中,链接操作符是一个小而有用的快捷方式。然而,如果你滥用它们,则会引起问题。第 8 章展示了一些使用它们会在你的代码中引入意想不到的错误的例子。

检查变量是否是许多值中的一个

有时,您可能会遇到与上一节中描述的情况相反的情况:检查单个变量是否是多个可能值中的一个。您可以使用or操作符来做到这一点,比如在表达式spam == 'cat' or spam == 'dog' or spam == 'moose'中。所有那些多余的“spam ==”部分让这个表达有点笨拙。

相反,您可以将多个值放入一个元组中,并使用in运算符检查该元组中是否存在变量值,如下例所示:

>>> # Pythonic Example
>>> spam = 'cat'
>>> spam in ('cat', 'dog', 'moose')
True

根据timeit的说法,这个方式不仅更容易理解,而且速度也稍快。

总结

所有编程语言都有自己的习惯用法和最佳实践。本章重点介绍 Python 程序员编写“Python”代码的特殊方式,以充分利用 Python 的语法。

Python 代码的核心是来自 Python 禅宗的 20 条格言,它们是编写 Python 的粗略指南。这些格言只是观点,对于编写 Python 代码来说并不是绝对必要的,但是记住它们是有好处的。

Python 的显著缩进(不要与显著的空白混淆)引起了刚入坑 Python 程序员的最大抗议。虽然几乎所有的编程语言都使用缩进来使代码可读,但是 Python 将缩进作用提升了一个层次,编译器对缩进是有语义解释的。

尽管许多 Python 程序员对for循环默认使用range(len()),但是enumerate()函数提供了一种更简洁的方法来获取索引和值,同时对序列进行迭代。同样,与手动调用open()close()相比,with语句是一种更干净、更不容易出错的文件处理方式。with语句确保无论何时执行跳出with语句块,都会调用close()

Python 有几种插入字符串的方法。最初的方法是使用%s转换说明符来标记字符串应该包含在原始字符串中的位置。Python 3.6 的现代方式是使用 F 字符串。F 字符串以字母f作为字符串的前缀,并使用大括号来标记可以在字符串中放置字符串(或整个表达式)的位置。

制作浅层列表副本的语法看起来有点奇怪,不一定是 Python 风格的,但它已经成为快速创建浅层列表的常用方法。

字典有一个get()setdefault()方法来处理不存在的键。或者,collections.defaultdict字典将对不存在的键使用默认值。另外,虽然 Python 中没有switch语句,但是使用字典是一种简洁的方法来实现它的等价语句,而不需要使用几个if-elif-else语句,并且在两个值之间求值时可以使用三元运算符。

一系列的==操作符可以检查多个变量是否相等,而in操作符可以检查一个变量是否是许多可能值中的一个。

本章讲述了几个 Python 语言习惯用法,为您提供了如何编写更多 Python 代码的提示。在下一章中,您将了解一些 Python 初学者容易掉入的陷阱。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/7660.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Robosense激光雷达Linux配置

文章目录1.1 速腾rs16连接&#xff1a;1.2 网络配置1&#xff09;官方说明2&#xff09;设置网络3&#xff09;检查是否连接成功2.1 激光雷达ROS包下载/编译1)下载ROS包2&#xff09;安装libpcap依赖3&#xff09;修改编译模式4&#xff09;config文件配置5&#xff09;编译并运…

AI-TestOps —— 软件测试工程师的一把利剑

写在前面软件测试的前世今生测试工具开始盛行AI-TestOps 云平台● AI-TestOps 功能模块● AI-TestOps 自动化测试流程写在前面 最近偶然间看到一句话&#xff1a;“软件测试是整个 IT 行业中最差的岗位”。这顿时激起了我对软件测试领域的兴趣&#xff0c;虽然之前未涉及过软件…

《Flutter进阶》flutter升级空安全遇到的一些问题及解决思路

空安全出来挺久了&#xff0c;由于业务需求较紧&#xff0c;一直没时间去升级空安全&#xff0c;最近花了几天去升级&#xff0c;发现其实升级也挺简单的&#xff0c;不要恐惧&#xff0c;没有想象中的多BUG。 flutter版本从1.22.4升到3.0.5&#xff1b; compileSdkVersion从1…

日撸 Java 三百行day11-13

文章目录说明day11-day12 顺序表1.面向过程面向对象区别2.代码2.1 面向过程2.2 面向对象day13 链表1.成员内部类2.链表的插入删除3.代码说明 闵老师的文章链接&#xff1a; 日撸 Java 三百行&#xff08;总述&#xff09;_minfanphd的博客-CSDN博客 自己也把手敲的代码放在了…

【51单片机】:LED任务及汇编解释任务

学习目标&#xff1a; 1、用汇编或者c语言实现D1 D3 D5 D7 为一组 &#xff1b;D2 D4 D6 D8 为一组 &#xff0c;两组实现 1&#xff09;一组亮约一秒 另一组灭一秒&#xff0c;这样的互闪现象五次后 25分 2&#xff09;所有灯灭约一秒后&#xff0c; …

关于ChatGPT的一些随笔

大家好&#xff0c;我是老三&#xff0c;最近几个月关于ChatGPT的信息可以说是铺天盖地。 “王炸&#xff0c;ChatGPT……” “xxx震撼发布……” “真的要失业了&#xff0c;xxx来袭……” “普通如何利用ChatGPT……” …… 不过老三前一阵比较忙&#xff0c;对ChatGPT…

ElasticSearch简介

第一章 ElasticSearch简介 1.1 什么是ElasticSearch Elaticsearch&#xff0c;简称为es&#xff0c; es是一个开源的高扩展的分布式全文检索引擎&#xff0c;它可以近乎实时的存储、检索数据&#xff1b;本身扩展性很好&#xff0c;可以扩展到上百台服务器&#xff0c;处理PB…

【数据结构与算法】树与二叉树

目录一.树1.树的定义2.结点的分类与关系3.树的相关概念4.树的表示方法二.二叉树1.二叉树的定义2.特殊二叉树3.二叉树的性质4.二叉树的顺序结构5.二叉树的链式结构(1)链式结构的创建(2)结点的创建(3)二叉树的手动构建(4)前中后序遍历(5)二叉树结点个数(6)二叉树的高度(7)第k层的…

Docker目录迁移

介绍 在docker的使用中随着下载镜像越来越多&#xff0c;构建镜像、运行容器越来越多, 数据目录必然会逐渐增大&#xff1b;当所有docker镜像、容器对磁盘的使用达到上限时&#xff0c;就需要对数据目录进行迁移。 如何避免&#xff1a; 1.在安装前对/var/lib/docker&#x…

如何3步精读《PMBOK指南》(含PMP备考资料)

初学者学习《PMBOK指南》的确有点吃亏&#xff0c;比不得那些项目管理专业以及相关专业的毕业生&#xff0c;哪怕只稍微接触过项目的都比初学者强。 所以&#xff0c;有计划性的阅读就显得尤为重要&#xff0c;要克服的不仅是阅读上的枯燥&#xff0c;还有专业知识的理解&…

Java——JDK动态代理

1.动态代理 1.1什么是动态代理&#xff1f; 动态代理(理解) 基于反射机制 举个例子&#xff0c;生活中一般在打官司的时候都会请代理律师&#xff0c;为什么要请律师呢&#xff1f;是因为开庭的时候大部人对于打官司没有经验&#xff0c;只会说出自己案件的陈述&#xff0c;并不…

软硬皆施,WMS仓库管理系统+PDA,实现效率狂飙

人工经验Excel表格&#xff0c;是传统第三方仓储企业常用的管理模式。在这种管理模式下&#xff0c;对仓库员工的Excel操作能力、业务经验和工作素养要求极高。一旦员工的经验能力不足&#xff0c;就会导致仓库业务运行不顺畅&#xff0c;效率低下&#xff0c;而员工也会因长时…

【MySQL】基于GTID的半同步主从复制(实践)

一、GTID简介 什么是GTID? 全局事务标识符GTID的全称为Global Transaction Identifier&#xff0c;是在整个复制环境中对一个事务的唯一标识。 它是MySQL 5.6加入的一个强大特性&#xff0c;目的在于能够实现主从自动定位和切换&#xff0c;而不像以前需要指定文件和位置。 …

ArduPilot飞控之DIY-F450计划

ArduPilot飞控之DIY-F450计划1. 历史2. 源由3. 计划3.1 硬件3.2 软件4. 动手4.1 接线4.1.1 ELRS nano接收机4.1.2 BN880 GPS模块4.1.3 Radio Telemetry4.2 配置4.2.1 选择四轴机型4.2.2 电源参数调整4.2.3 校准加速度计4.2.4 校准磁力计4.2.5 遥控器校准4.2.6 电机设置4.2.7 电…

企业电子招投标采购系统——功能模块功能描述+数字化采购管理 采购招投标

​ 功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外…

Melis4.0[D1s]:4.测试笔记 - 内嵌的显示命令

文章目录1.配置将显示测试源码包含进工程&#xff08;默认是包含了&#xff09;2.不要启动melis桌面系统3.开始测试3.1 disp 命令3.1.1 disp不带参数时&#xff0c;打印显示信息&#xff1a;3.1.2 disp -c 0 8 测试4种颜色3.2 disp_layer_cfg 命令3.3 disp_mem 对显示内存写入内…

全球自动驾驶竞争力最新排行榜,4家中国企业上榜

发展至今&#xff0c;自动驾驶技术不仅是汽车行业的一个主战场&#xff0c;更是全球科技领域中备受关注和充满竞争的一个重要领域。近年来&#xff0c;各大汽车制造商和科技公司都在投入大量财力物力人力进行自动驾驶技术的研发&#xff0c;并进一步争夺市场份额。 当然&#…

人工智能前沿——「小海带」超全视觉注意力机制资源分享(附下载链接)

&#x1f4da;&#x1f4da; 人工智能 | 计算机视觉 —— 致力于目标检测领域科研Tricks改进与推荐 | 主要包括主干网络改进、轻量化网络、注意力机制、检测头部改进、空间金字塔池化、损失函数及NMS改进、ICCV/CVPR/ECCV视觉顶会创新点改进、各类数据集资源分享以及算法训练相…

智云通CRM:如何给客户创造尽可能安全的成交环境?

销售人员要想和客户顺利成交&#xff0c;给对方创造尽可能安全的成交环境尤为重要。销售人员的每一句话和每一个观点&#xff0c;应当都是客户所想的。客户是非常聪明的&#xff0c;有任何风吹草动&#xff0c;他们都会提高警惕&#xff0c;这就是客户跟销售人员之间关系的现状…

笔记:关于使用vitepress 制作静态站点并托管到gitee

笔记&#xff1a;关于使用vitepress 制作静态站点并托管到giteegiteejcLee95&#xff1a;https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/129419979…