你的VoAGI贴文-为什么你不应该在Python中过度使用列表推导式
你的VoAGI贴文-为什么过度在Python中使用列表推导式是不明智的
在Python中,列表推导提供了一种简洁的语法,可以从现有列表和其他可迭代对象创建新的列表。然而,一旦你习惯了列表推导,你可能会在不应该使用它们的情况下试图使用它们。
记住,你的目标是编写简单易维护的代码,而不是复杂的代码。通常有助于重新阅读Python的禅宗,它是一组关于编写干净、优雅的Python代码的格言,特别是以下内容:
- 漂亮比丑陋好。
- 简单比复杂好。
- 可读性很重要。
在本教程中,我们将编写三个例子,每个例子比前一个更复杂,其中列表推导使代码变得非常难以维护。然后我们将尝试编写一个更易维护的版本。
- Meta Llama真的是开源的吗?
- 释放ChatGPT AI-1:构建一个先进的基于LLM的系统
- 5 个免费课程,掌握生成式人工智能’ (5 gè miǎnfèi kèchéng, zhǎngwò shēngchéngshì réngōng zhìnéng)
所以让我们开始编码吧!
Python列表推导:快速回顾
让我们先来回顾一下Python中的列表推导。假设你有一个现有的可迭代对象,如列表或字符串。你想要从中创建一个新的列表。你可以遍历可迭代对象,处理每个项目,并将输出附加到新的列表中,如下所示:
new_list = []for item in iterable: new_list.append(output)
但使用列表推导提供了一种简洁的一行替代方法:
new_list = [output for item in iterable]
此外,你还可以添加过滤条件。
下面的代码段:
new_list = []for item in iterable: if condition: new_list.append(output)
可以用下面的列表推导来替代:
new_list = [output for item in iterable if condition]
因此,列表推导可以帮助你编写pythonic代码,通过减少视觉噪音使你的代码更清晰。
现在让我们举三个例子,以了解为什么你不应该在需要超复杂表达式的任务中使用列表推导。因为在这种情况下,列表推导不仅不能使你的代码更优雅,反而使代码难以阅读和维护。
示例1:生成质数
问题:给定一个数字upper_limit
,生成一个列表,其中包含该数字之前的所有质数。
你可以将这个问题分解为两个关键点:
- 检查一个数字是否是质数
- 用所有质数填充一个列表
用列表推导实现此目标的表达式如下所示:
import mathupper_limit = 50 primes = [x for x in range(2, upper_limit + 1) if x > 1 and all(x % i != 0 for i in range(2, int(math.sqrt(x)) + 1))]print(primes)
以下是输出结果:
Output >>>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
乍一看,很难理解发生了什么… 让我们改进一下。
也许,更好一点?
import mathupper_limit = 50 primes = [ x for x in range(2, upper_limit + 1) if x > 1 and all(x % i != 0 for i in range(2, int(math.sqrt(x)) + 1))]print(primes)
更容易阅读了,当然。现在让我们写一个真正更好的版本。
更好的版本
尽管列表推导式实际上是解决这个问题的一个好主意,但是在列表推导式中检查质数的逻辑使得代码变得混乱。
因此,让我们编写一个更易维护的版本,将检查一个数是否为质数的逻辑移动到一个单独的函数is_prime()
中。并在列表推导式的表达式中调用函数is_prime()
:
import mathdef is_prime(num): return num > 1 and all(num % i != 0 for i in range(2, int(math.sqrt(num)) + 1))upper_limit = 50 primes = [ x for x in range(2, upper_limit + 1) if is_prime(x)]print(primes)
输出 >>>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
更好的版本是否足够好? 这样做使得列表推导式的表达式更易理解。现在清楚表达式是收集所有小于upper_limit
的质数(其中is_prime()
返回True)。
示例2:工作中的矩阵
问题: 给定一个矩阵,找到以下内容:
- 所有的质数
- 质数的索引
- 质数的总和
- 按降序排序的质数
为了扁平化矩阵并收集所有质数的列表,我们可以使用与之前示例类似的逻辑。
然而,为了找到索引,我们还有另一个复杂的列表推导式(我对代码进行了格式化,使其易于阅读)。
您可以将检查质数和获取其索引合并为一个单一的推导式。但这并不会使事情变得简单。
下面是代码:
import mathfrom pprint import pprintmy_matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]def is_prime(num): return num > 1 and all(num % i != 0 for i in range(2, int(math.sqrt(num)) + 1))# 扁平化矩阵并过滤只包含质数的元素primes = [ x for row in my_matrix for x in row if is_prime(x)]# 在原矩阵中找到质数的索引prime_indices = [ (i, j) for i, row in enumerate(my_matrix) for j, x in enumerate(row) if x in primes]# 计算质数的总和sum_of_primes = sum(primes)# 按降序对质数进行排序sorted_primes = sorted(primes, reverse=True)# 创建一个包含结果的字典result = { "primes": primes, "prime_indices": prime_indices, "sum_of_primes": sum_of_primes, "sorted_primes": sorted_primes}pprint(result)
相应的输出:
输出 >>>{'primes': [2, 3, 5, 7], 'prime_indices': [(0, 1), (0, 2), (1, 1), (2, 0)], 'sum_of_primes': 17, 'sorted_primes': [7, 5, 3, 2]}
那么什么是更好的版本?
更好的版本
现在,对于更好的版本,我们可以定义一系列函数来分离出关注点。这样,如果出现问题,您就知道应该返回哪个函数并修复逻辑。
import mathfrom pprint import pprintdef is_prime(num): return num > 1 and all(n % i != 0 for i in range(2, int(math.sqrt(num)) + 1))def flatten_matrix(matrix): flattened_matrix = [] for row in matrix: for x in row: if is_prime(x): flattened_matrix.append(x) return flattened_matrixdef find_prime_indices(matrix, flattened_matrix): prime_indices = [] for i, row in enumerate(matrix): for j, x in enumerate(row): if x in flattened_matrix: prime_indices.append((i, j)) return prime_indicesdef calculate_sum_of_primes(flattened_matrix): return sum(flattened_matrix)def sort_primes(flattened_matrix): return sorted(flattened_matrix, reverse=True)my_matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]primes = flatten_matrix(my_matrix)prime_indices = find_prime_indices(my_matrix, primes)sum_of_primes = calculate_sum_of_primes(primes)sorted_primes = sort_primes(primes)result = { "primes": primes, "prime_indices": prime_indices, "sum_of_primes": sum_of_primes, "sorted_primes": sorted_primes}pprint(result)
这段代码输出结果与之前一样。
Output >>>{'primes': [2, 3, 5, 7], 'prime_indices': [(0, 1), (0, 2), (1, 1), (2, 0)], 'sum_of_primes': 17, 'sorted_primes': [7, 5, 3, 2]}
这种改进版本够好吗?虽然这个版本适用于像这个例子中的小矩阵,但是返回一个静态列表通常是不推荐的。对于扩展到更大的维度,您可以使用生成器。
示例3:解析嵌套的JSON字符串
问题:根据条件解析给定的嵌套JSON字符串,并获取所需值的列表。
解析嵌套的JSON字符串很具有挑战性,因为您必须考虑到嵌套的不同级别、JSON响应的动态性以及解析逻辑中的各种数据类型。
让我们以根据条件解析给定的JSON字符串并获取所有满足以下条件的值的列表为例:
- 整数或整数的列表
- 字符串或字符串的列表
您可以使用内置的json模块中的loads
函数将JSON字符串加载到Python字典中。所以我们将有一个嵌套的字典,我们可以使用列表解析。
列表解析使用嵌套循环迭代嵌套的字典。对于每个值,它根据以下条件构造一个列表:
- 如果值不是字典并且键以“inner_key”开头,使用
[inner_item]
。 - 如果值是带有“sub_key”的字典,使用
[inner_item['sub_key']]
。 - 如果值是字符串或整数,使用
[inner_item]
。 - 如果值是字典,使用
list(inner_item.values())
。
请看下面的代码片段:
import jsonjson_string = '{"key1": {"inner_key1": [1, 2, 3], "inner_key2": {"sub_key": "value"}}, "key2": {"inner_key3": "text"}}'# 将JSON字符串解析为Python字典data = json.loads(json_string)flattened_data = [ value if isinstance(value, (int, str)) else value if isinstance(value, list) else list(value) for inner_dict in data.values() for key, inner_item in inner_dict.items() for value in ( [inner_item] if not isinstance(inner_item, dict) and key.startswith("inner_key") else [inner_item["sub_key"]] if isinstance(inner_item, dict) and "sub_key" in inner_item else [inner_item] if isinstance(inner_item, (int, str)) else list(inner_item.values()) )]print(f"Values: {flattened_data}")
这是输出结果:
输出 >>> 值:[[1, 2, 3],'value','text']
如您所见,列表推导式非常难以理解。
请您自己和团队的其他成员善待自己,永远不要编写这样的代码。
更好的版本
我认为下面使用嵌套的for循环和if-elif语句的代码片段更好一些。因为它更容易理解发生了什么。
flatten_data = []for inner_dict in data.values(): for key, inner_item in inner_dict.items(): if not isinstance(inner_item, dict) and key.startswith("inner_key"): flatten_data.append(inner_item) elif isinstance(inner_item, dict) and "sub_key" in inner_item: flatten_data.append(inner_item["sub_key"]) elif isinstance(inner_item, (int, str)): flatten_data.append(inner_item) elif isinstance(inner_item, list): flatten_data.extend(inner_item) elif isinstance(inner_item, dict): flatten_data.extend(inner_item.values())print(f"值:{flatten_data}")
这也给出了预期的输出结果:
输出 >>> 值:[[1, 2, 3],'value','text']
更好的版本足够好吗?嗯,并不是真的。
因为if-elif语句经常被认为是代码异味。您可能会在分支之间重复逻辑,并且添加更多条件只会使代码更难以维护。
但是对于这个例子来说,if-elif语句和嵌套循环的版本比推导表达式更容易理解。
总结
到目前为止,我们编写的示例应该让您了解了过度使用Pythonic特性(如列表推导式)可能会变得过分的情况。这不仅适用于列表推导式(尽管它们是最常用的),还适用于字典和集合推导式。
您应该始终编写易于理解和维护的代码。即使这意味着不使用一些Pythonic特性。继续编码吧!
[Bala Priya C](https://twitter.com/balawc27)是来自印度的开发人员和技术作家。她喜欢在数学,编程,数据科学和内容创作的交叉点上工作。她的兴趣和专长包括DevOps,数据科学和自然语言处理。她喜欢阅读,写作,编码和咖啡!目前,她正在通过撰写教程,指南,观点文章等来学习和与开发者社区分享她的知识。