Item 5: Write Helper Functions Instead of Complex Expressions

Python’s pithy syntax makes it easy to write single-line expressions that implement a lot of logic. For example, say that I want to decode the query string from a URL. Here, each query string parameter represents an integer value:

Python简洁的语法使得编写实现大量逻辑的单行表达式变得很容易。例如,假设我想要解码URL中的查询字符串。这里,每个查询字符串参数表示一个整数值:

from urllib.parse import parse_qs
my_values = parse_qs('red=5&blue=0&green=', keep_blank_values=True)
print(repr(my_values))

>>>
{'red': ['5'], 'blue': ['0'], 'green': ['']}

Some query string parameters may have multiple values, some may have single values, some may be present but have blank values, and some may be missing entirely. Using the get method on the result dictionary will return different values in each circumstance:

一些查询字符串参数可能有多个值,一些可能只有一个值,一些可能存在但有空白值,还有一些可能完全缺失。在结果字典上使用get方法将在每种情况下返回不同的值:

print('Red: ', my_values.get('red'))
print('Green: ', my_values.get('green'))
print('Opacity: ', my_values.get('opacity'))
>>>
Red: ['5']
Green: ['']
Opacity: None

It’d be nice if a default value of 0 were assigned when a parameter isn’t supplied or is blank. I might choose to do this with Boolean expressions because it feels like this logic doesn’t merit a whole if statement or helper function quite yet.

如果在没有提供参数或为空时,赋值为默认值0就好了。我可能会选择使用布尔表达式,因为这种简单的逻辑还不值得使用整个if语句或辅助函数。

Python’s syntax makes this choice all too easy. The trick here is that the empty string, the empty list, and zero all evaluate to False implicitly. Thus, the expressions below will evaluate to the subexpression after the or operator when the first subexpression is False:

Python的语法让这个选择太容易了。这里的技巧是空字符串、空列表和零都隐式地计算为False。因此,当第一个子表达式为False时,下面的表达式将求出or操作符之后的子表达式:

# For query string 'red=5&blue=0&green='
red = my_values.get('red', [''])[0] or 0
green = my_values.get('green', [''])[0] or 0
opacity = my_values.get('opacity', [''])[0] or 0
print(f'Red: {red!r}')
print(f'Green: {green!r}')
print(f'Opacity: {opacity!r}')
>>>
Red: '5'
Green: 0
Opacity: 0

The red case works because the key is present in the my_values dictionary. The value is a list with one member: the string '5'. This string implicitly evaluates to True, so red is assigned to the first part of the or expression.

red案例可以正常运行,是因为键存在于my_values字典中。该值是一个包含一个成员的列表:字符串'5'。这个字符串隐式地计算为True,因此red被赋值给or表达式的第一部分。

The green case works because the value in the my_values dictionary is a list with one member: an empty string. The empty string implicitly evaluates to False, causing the or expression to evaluate to 0.

green案例可以正常运行,是因为my_values字典中的值是一个包含一个成员的列表:一个空字符串。空字符串隐式计算为False,因此or表达式计算为0。

The opacity case works because the value in the my_values dictionary is missing altogether. The behavior of the get method is to return its second argument if the key doesn’t exist in the dictionary (see Item 16: “Prefer get Over in and KeyError to Handle Missing Dictionary Keys”). The default value in this case is a list with one member: an empty string. When opacity isn’t found in the dictionary, this code does exactly the same thing as the green case.

opacity可以正常运行,是因为my_values字典中的值完全丢失了。get方法的行为是: 如果键在字典中不存在,则返回它的第二个参数(参见第16项:“Prefer get Over in and KeyError to Handle Missing dictionary Keys”)。在本例中,默认值是一个包含一个成员的列表:一个空字符串。当在字典中没有找到opacity时,这段代码所做的事情与green案例完全相同。

However, this expression is difficult to read, and it still doesn’t do everything I need. I’d also want to ensure that all the parameter values are converted to integers so I can immediately use them in mathematical expressions. To do that, I’d wrap each expression with the int built-in function to parse the string as an integer:

然而,这个表达式很难理解,而且它不能完成我需要的所有操作。我还希望确保所有的参数值都被转换成整数,以便可以立即在数学表达式中使用它们。为此,我将用内置的int函数包装每个表达式,以将字符串解析为整数:

red = int(my_values.get('red', [''])[0] or 0)

This is now extremely hard to read. There’s so much visual noise. The code isn’t approachable. A new reader of the code would have to spend too much time picking apart the expression to figure out what it actually does. Even though it’s nice to keep things short, it’s not worth trying to fit this all on one line.

现在读起来非常困难,因为代码有太多的视觉干扰,不易理解。代码的新读者将不得不花费大量的时间来分析表达式,以弄清楚它实际做了什么。尽管保持简短很好,但不值得将所有内容都写在一行中。

Python has if/else conditional—or ternary—expressions to make cases like this clearer while keeping the code short:

Python有if/else条件表达式或三元表达式,在保持代码简短的同时,使这样的情况更清楚:

red_str = my_values.get('red', [''])
red = int(red_str[0]) if red_str[0] else 0

This is better. For less complicated situations, if/else conditional expressions can make things very clear. But the example above is still not as clear as the alternative of a full if/else statement over multiple lines. Seeing all of the logic spread out like this makes the dense version seem even more complex:

这会好一点,对于不太复杂的情况,if/else条件表达式可以使事情非常清楚。但是上面的例子仍然不像在多行中使用完整的if/else语句那样清晰。如果所有的逻辑都像这样展开,这个密集的版本看起来会更加复杂:

green_str = my_values.get('green', [''])
if green_str[0]:
   green = int(green_str[0])
else:
   green = 0

If you need to reuse this logic repeatedly—even just two or three times, as in this example—then writing a helper function is the way to go:

如果你需要重复使用这个逻辑——即使只是两到三次,像这个例子一样——去编写一个辅助函数:

def get_first_int(values, key, default=0):
   found = values.get(key, [''])
   if found[0]:
       return int(found[0])
   return default

The calling code is much clearer than the complex expression using or and the two-line version using the if/else expression:

调用辅助函数比使用or的复杂表达式和if/else表达式的两行版本要清晰得多:

green = get_first_int(my_values, 'green')

As soon as expressions get complicated, it’s time to consider splitting them into smaller pieces and moving logic into helper functions. What you gain in readability always outweighs what brevity may have afforded you. Avoid letting Python’s pithy syntax for complex expressions from getting you into a mess like this. Follow the DRY principle: Don’t repeat yourself.

一旦表达式变得复杂,就应该考虑将它们分成更小的部分,并将代码逻辑移动到辅助函数中。你在可读性上所获得的总是比简洁所能提供给你的要多。避免让Python用于复杂表达式的简洁语法使您陷入这样的混乱之中。遵循DRY原则:不要重复你自己。

Things to Remember
要记住的事

✦ Python’s syntax makes it easy to write single-line expressions that are overly complicated and difficult to read.
✦ Move complex expressions into helper functions, especially if you need to use the same logic repeatedly.
✦ An if/else expression provides a more readable alternative to using the Boolean operators or and and in expressions.

✦ Python的语法让我们很容易编写过于复杂和难以阅读的单行表达式。
✦ 将复杂的表达式移到辅助函数中,特别是当你需要重复使用相同的逻辑时。
✦ 与使用布尔操作符or或and相比,if/else表达式提供了更具可读性的选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容