提高Python编码水平的小技巧

2020-05-09 14:18发布

各个学科领域和行业都在经历一场“Python热”。在观察生物医学领域中Python编程的运用情况之后,笔者意识到相当多的Python程序员,都有不同的编程使用经验,如Matlab、C语言、C++、Java、JavaScript和Swift,也有一些之前没有编程经验的人。

Python成了程序员的“外语”,他们可能没经过系统的Python编码培训,也可能并不知道Python开发的惯用方法。

虽然程序员依然可以通过不同的方式实现同样的功能,编写出优秀的代码,只要代码能够满足预期目的就OK。编写非惯用Python程序也没有问题。但就像我们不断练习英文的口音一样,也有一些人也想让自己的Python代码变得更地道。

本文中,我将分享自己在过去几年中积累的一些习惯用法,希望对提高你的Python编码水平有所帮助。

1.分割序列

常见的序列类型有列表、元组和字符串。通过分割另一个序列,可以创建一个新序列。以下功能用列表作为示例,不过它们也可以用于元组、字符串和字节等其他序列类型。

图源:unsplash

>>> 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.检查序列是否为空

只有序列不为空时,列表、元组等操作才行得通,因此需要在操作之前检查序列是否为空。

图源:unsplash

为此,可以用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中的生成器是创建迭代器的一种简便方法。因为生成器是“惰性的”(也就是说,只有当发出请求时才能生成需要的项)。生成器非常节省内存。

创建生成器的一种特殊方法称为生成器表达式。除了用圆括号而非方括号这一点外,生成器表达式在语法上与列表解析式类似。

图源:unsplash

下面的例子中,当生成器直接用于迭代函数时,圆括号可有可无。

>>> 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参数。

图源:unsplash

除此之外,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())