各个学科领域和行业都在经历一场“Python热”。在观察生物医学领域中Python编程的运用情况之后,笔者意识到相当多的Python程序员,都有不同的编程使用经验,如Matlab、C语言、C++、Java、JavaScript和Swift,也有一些之前没有编程经验的人。
Python成了程序员的“外语”,他们可能没经过系统的Python编码培训,也可能并不知道Python开发的惯用方法。
虽然程序员依然可以通过不同的方式实现同样的功能,编写出优秀的代码,只要代码能够满足预期目的就OK。编写非惯用Python程序也没有问题。但就像我们不断练习英文的口音一样,也有一些人也想让自己的Python代码变得更地道。
本文中,我将分享自己在过去几年中积累的一些习惯用法,希望对提高你的Python编码水平有所帮助。
1.分割序列
常见的序列类型有列表、元组和字符串。通过分割另一个序列,可以创建一个新序列。以下功能用列表作为示例,不过它们也可以用于元组、字符串和字节等其他序列类型。
>>> a = [0, 2, 4, 6, 8,10, 12, 14, 16, 18, 20]
>>> # Using a range, [start, end)
>>> a[1:3]
[2, 4]
>>> # Using a range with a step
>>> a[1:9:2]
[2, 6, 10, 14]
>>> # Leave out the start = an implicit start of 0
>>> a[:5]
[0, 2, 4, 6, 8]
>>> # Leave out the stop = an implict end to the very last item
>>> a[9:]
[18, 20]
>>> # Entire list
>>> a[:]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
2.使用反向索引访问序列中的元素
如果想在序列的末尾访问一些元素,那么反向计数要容易得多。在Python序列中,最后一个元素的索引是-1,倒数第二个元素的索引是-2,以此类推。
>>> a = 'Hello World!'
>>> # instead of using a[len(a)-1]
>>> a[-1]
'!'
>>> # in combination with slicing
>>> a[-5:-1]
'orld'
3.多重赋值
在给几个变量赋值时,可以使用多重赋值。通过同样的习惯用法,可以交换同一列表中的两个变量或两个元素。这一特征与之后要介绍的元组解包密切相关。
>>> # instead of doing a =8; b = 5
>>> a, b = 8, 5
>>> print(f'a is {a}; b is {b}')
a is 8; b is 5
>>> # Swap two variables
>>> a, b = b, a
>>> print(f'a is {a}; b is {b}')
a is 5; b is 8
>>> # Swap the first and last elements in a list
>>> numbers = [1, 2, 3, 4, 5]
>>> numbers[0], numbers[-1] = numbers[-1], numbers[0]
>>> numbers
[5, 2, 3, 4, 1]
4.颠倒序列
有时需要颠倒序列。虽然可以用for循环语句来实现,但是还有一种更简单直接的方法。与上述情况类似,当某个功能可用于某个序列时,通常意味着字符串、元组和列表也都支持这个功能。
>>> a = (1, 2, 3, 4, 5)
>>> a[::-1]
(5, 4, 3, 2, 1)
>>> b = 'start'
>>> b[::-1]
'trats'
5.检查序列是否为空
只有序列不为空时,列表、元组等操作才行得通,因此需要在操作之前检查序列是否为空。
为此,可以用not关键字来否定序列(例如not[]),只要序列不为空,其值就为True。此外,还可以对另外两种常见的数据类型dict和set执行同样的操作。
>>> empty_list = [(), '',[], {}, set()]
>>> for item in empty_list:
... if not item:
... print(f'Do something with the{type(item)}')
...
Do something with the <class 'tuple'>
Do something with the <class 'str'>
Do something with the <class 'list'>
Do something with the <class 'dict'>
Do something with the <class 'set'>
6.集合推导式
集合推导式的用法与上述列表解析式的用法类似。不同之处在于集合推导式用的是花括号而不是方括号。并且,通过定义set 数据类型,可以删除重复的元素。
7.字典生成式
除了列表解析式和集合推导式外,解析式特征还可用于字典数据类型的创建。dict由键值对组成,因此字典生成式包含指定键和值,二者之间用冒号隔开。
>>> a = [1, 2, 3, 4, 5]
>>> {x: x*x for x in a}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
8.生成器表达式
Python中的生成器是创建迭代器的一种简便方法。因为生成器是“惰性的”(也就是说,只有当发出请求时才能生成需要的项)。生成器非常节省内存。
创建生成器的一种特殊方法称为生成器表达式。除了用圆括号而非方括号这一点外,生成器表达式在语法上与列表解析式类似。
下面的例子中,当生成器直接用于迭代函数时,圆括号可有可无。
>>> sum(x**2 for x inrange(100))
328350
>>> max((x*x for x in range(100)))
9801
9.列表解析式
Python中一个有用的特征是列表解析式。通过列表解析式,可以很方便地构造一个列表。列表解析式的一般格式为[some_expression for element initerable if some_condition]。
>>> a = [1, 2, 3, 4, 5]
>>> [x*2 for x in a]
[2, 4, 6, 8, 10]
>>> [x*3 for x in a if x%2 == 1]
[3, 9, 15]
10.解包元组
元组是Python中十分常见的数据结构。它们是一组组相关的值。元组的常见用法包括访问自身元素。虽然可以使用索引访问这些元素,但是解包是一种更为简便的方法。
与解包的用法有关,可以用下划线来表示不需要的元素,用星号给已命名元素之外的其他元素赋值。
>>> items = (0, 'b','one', 10, 11, 'zero')
>>> a, b, c, d, e, f = items
>>> print(f)
zero
>>> a, *b, c = items
>>> print(b)
['b', 'one', 10, 11]
>>> *_, a, b = items
>>> print(a)
11
11.在for循环语句中使用Reversed()函数
reversed()函数通常用在for循环语句中,是一种以与原始可迭代对象相反的顺序创建迭代器的方法。
>>> tasks = ['laundry','picking up kids', 'gardening', 'cooking']
>>> for task in reversed(tasks):
... print(task)
...
cooking
gardening
picking up kids
laundry
12.Zip()压缩函数
zip()函数在一对一匹配连接多个迭代器方面十分有用。如果某些迭代器超过最短的迭代器,就会被截断。zip()函数返回一个迭代器,因此在迭代中经常使用到。还可以用zip()函数解压缩带星号的迭代器,并将未压缩的项赋值给变量。
>>> students = ('John','Mary', 'Mike')
>>> ages = (15, 17, 16)
>>> scores = (90, 88, 82, 17, 14)
>>> for student, age, score in zip(students, ages, scores):
... print(f'{student}, age: {age},score: {score}')
...
John, age: 15, score: 90
Mary, age: 17, score: 88
Mike, age: 16, score: 82
>>> zipped = zip(students, ages, scores)
>>> a, b, c = zip(*zipped)
>>> print(b)
(15, 17, 16)
13.用Lambdas排序
lambdas表达式为匿名函数,可以用单行表达式接收多个参数。lambdas的一个常见用法是将其设置为内置sorted()函数中的key参数。
除此之外,lambdas还经常用于max(),map()等函数中。在这些函数中,可以用单行表达式来替换使用def关键字的常规函数。
>>> students = [{'name':'John', 'score': 98}, {'name': 'Mike', 'score': 94}, {'name': 'Jennifer','score': 99}]
>>> sorted(students, key=lambda x: x['score'])
[{'name': 'Mike', 'score': 94}, {'name': 'John', 'score': 98}, {'name':'Jennifer', 'score': 99}]
14.速记条件赋值
该特征基本上是个语法糖。在根据特定条件为变量赋值时,可以用以下通用速记赋值:y = x if condition_met elseanother_x。
>>> some_condition = True
>>> # the expanded format
>>> if some_condition:
... x = 5
... else:
... x = 3
>>> print(f'x is {x}')
x is 5
>>> # the shorthand way
>>> x = 5 if some_condition else 3
>>> print(f'x is {x}')
x is 5
15.在for循环语句中使用Enumerate()枚举函数
用enumerate()函数获取可迭代对象来创建迭代器。此外,enumerate()函数还可以跟踪迭代的次数。可以随意设置计数初始值。默认的计数初始值为0。
>>> students = ('John','Mary', 'Mike')
>>> for i, student in enumerate(students):
... print(f'Iteration: {i}, Student:{student}')
...
Iteration: 0, Student: John
Iteration: 1, Student: Mary
Iteration: 2, Student: Mike
>>> for i, student in enumerate(students, 35001):
... print(f'Student Name: {student},Student ID #: {i}')
...
Student Name: John, Student ID #: 35001
Student Name: Mary, Student ID #: 35002
Student Name: Mike, Student ID #: 35003
16.用Get()方法检索字典中的值
通常情况下,可以在方括号中指定键来检索键的值。但是,当键不在字典中时,就可能出错。当然,也可以用try/except异常处理机制来解决这个问题。不过,当键不在字典中时,还可以通过get()方法设置默认值。
>>> number_dict = {0:'zero', 1: 'one', 2: 'two', 3: 'three'}
>>> number_dict[5]
Traceback (most recent call last):
File "<stdin>", line 1,in <module>
KeyError: 5
>>> number_dict.get(5, 'five')
'five'
17.获取字典中最大值对应的键
有时需要在字典中找出最大值对应的键。首先,在所有值列表中找到最大值的索引,然后从另一个存储所有键的列表中找到对应的键。或者,也可以用一种更简单的方法,就是在max()函数中指定key参数。
简便起见,不考虑最大值可能重复的情况。同样地,还可以用min()函数查找最小值对应的键。
>>> model_scores ={'model_a': 100, 'model_z': 198, 'model_t': 150}
>>> # workaround
>>> keys, values = list(model_scores.keys()),list(model_scores.values())