0%

[经验总结]一行代码完成一个任务

2020 年的最后一周写一点轻松的东西,熟练使用这些技巧会给日常工作减负不少:本文中会提到几个使用一行代码可以完成简单任务的方法。

替代 if 语句:Ternary Operator

标准用法

先来看一个简单的 if 语句:

1
2
3
4
5
if 5 > 3:
print('5 is more than 3')
else:
print('5 is less than 3')
# '5 is more than 3'

怎么用一行代码完成同样的任务呢?有请 Ternary Operator:

1
True_expression if True_condition else False_expression

意思是这样的:判断 True_condition 是否成立,如果成立,执行 True_expression;如果不成立,执行 False_expression。那么上面的 if 语句可以写成

1
2
'5 is more than 3' if 5 > 3 else '5 is less than 3'
# '5 is more than 3'

Ternary Operator 的变体

  1. Shorthanded Ternary
    1
    expression or another_expression
    如果 expressionTrue,那么 another_expression 则不会执行;反之则会执行。比如:
    1
    2
    3
    4
    True or 'something'
    # True
    False or 'something'
    #'something'
    因为 None 等于 False,这可以用于检查一个函数是否有输出:
    1
    2
    3
    output = None
    msg = output or 'No data returned'
    # 'No data returned'
    也可以用来设定一个函数的动态变量:
    1
    2
    3
    4
    5
    def display_name(name, default=None):
    displayeded_name = dafault or name
    print(displayed_name)
    display_name('Jim') # 'Jim'
    display_name('Tom', 'anonymous123') # 'anonymous123'
  2. 这个变体同样巧妙地应用了 False == 0True == 1 这两个性质。
    1
    (expr_if_False, expr_if_true)[True_or_False]
    这里 (expr_if_False, expr_if_true) 用了 tuple,但是也可以用 list。也可以用 bool() 函数将所有变量转换为 01,因为 False == 0,当 True_or_False 为假时会取第一个元素;当 True_or_False 为真时会取第二个元素。
    1
    2
    3
    output = 'blabla'
    ['Not have an output', 'Has an output'][bool(output)]
    # 'Has an output'

    替代 def 语句:lambda 匿名函数

    再来看一个简单的自定义函数:
    1
    2
    3
    def add(x, y):
    return x + y
    add(1, 2) # 3
    这个简单的函数可以使用 lambda 匿名函数完成。lambda 匿名函数必须在一行内完成,语法为:
    1
    lambda arguments: expression
    那么上面的两数相加的函数也可以写成:
    1
    2
    add = lambda x, y: x + y
    add(1, 2) # 3

    一行代码生成一个容器:解析式

    基础列表解析式

    假如我们希望写一个函数将一个列表中的数字平方在返回一个新列表:
    1
    2
    3
    4
    5
    def square(lst):
    new_lst = []
    for num in lst:
    new_lst.append(num**2)
    return new_lst
    这么一个简单的函数居然要用 5 行代码,尴尬症都要犯了。解析式来救场!解析式又分列表解析式、集合解析式和字典解析式,以列表解析式为例,语法为:
    1
    [expression for var in iterable if condition]
    所以我们可以将上面的函数改写为:
    1
    [var**2 for var in lst]
    一行搞定!要注意的是,只有当元素符合判定条件(if)时才会被处理,解析式里没有 else

    嵌套列表解析式

    更复杂的列表解析式包含多个变量:
    1
    [expression for var1 in iterable1 if condition1 for var2 in iterable2 if condition2]
    我们应该怎么写呢?根据解析式的 PEP202 文档,

    It is proposed to allow conditional construction of list literals using for and if clauses. They would nest in the same way for loops and if statements nest now.

如果我们想写一个 for 循环来做同样的事情,我们可能这样写:

1
2
3
4
5
for var1 in iterable1:
if condition1:
for var2 in iterable2:
if condition2:
expression

使用解析式,我们只需要将正常写法最后的表达式写在最前面,之后依次把嵌套循环的代码依次写在同一行就行了。我们来看一个终极例子:

有一个嵌套单词列表,如果单词有至少两个字母,则返回一个包含所有字母与它所在单词的列表的索引的新列表。

如果我们用常规方法,可能会这么写:

1
2
3
4
5
6
new_lst = []
for idx, lst in enumerate(nested_lst):
for word in lst:
if len(word) >= 2:
for letter in word:
new_lst.append((letter, idx))

使用列表解析式可以这么写:

1
2
3
strings = [ ['foo', 'bar'], ['baz', 'taz'], ['w', 'koko'] ]
[ (letter, idx) for idx, lst in enumerate(strings) for word in lst if len(word)>2 for letter in word]
# [('f', 0), ('o', 0), ('o', 0), ('b', 0), ('a', 0), ('r', 0), ('b', 1), ('a', 1), ('z', 1), ('t', 1), ('a', 1), ('z', 1), ('k', 2), ('o', 2), ('k', 2), ('o', 2)]

集合解析式与字典解析式

集合解析式与列表解析式差不多,区别仅仅是将 [] 换成了 {}。字典解析式与列表解析式的区别在于将 var 换成了键值对key, val。比如在 NLP 应用中,需要生成一个词与索引的 lookup 字典:

1
2
id2word = {idx:word for (idx, word) in enumerate(corpus)}
word2id = {word:id for (id, word) in id2word.items()}

替代 yield 语句:生成器

yield 语句是一种一边循环一边计算的机制,将一个函数中的 return 替换成 yield,使用 next 调用该函数:

1
2
3
4
5
6
7
8
def generator(x):
for i in range(x):
yield i
x = generator(5)
x # <generator object generator at 0x7fee19239c80>
next(x) # 0
next(x) # 1
...

也可以使用一行代码将一个简单的 yield 语句实现,这就是生成器。生成器与列表解析式的语法一样,只是用 () 代替了 []

1
2
3
4
x = (i for i in range(5))
x
next(x) # 0
...

本文中的技巧仅可用于一些简单的任务,如果任务比较复杂,这些技巧要么无法完成,要么可读性大幅下降而变得难以理解与维护。

欢迎关注我的其它发布渠道