关于python:Python列表解析式到底该怎么用

32次阅读

共计 6502 个字符,预计需要花费 17 分钟才能阅读完成。

Python 是一种极其多样化和弱小的编程语言!当须要解决一个问题时,它有着不同的办法。在本文中,将会展现列表解析式(List Comprehension)。咱们将探讨如何应用它?什么时候该或不该应用它?

列表解析式的劣势

  • 比循环更节省时间和空间。
  • 须要更少的代码行。
  • 可将迭代语句转换为公式。

如何在 Python 中创立列表

列表解析式是一种基于现有列表创立列表的语法结构。让咱们来看看创立列表的不同实现

循环

循环是创立列表的传统形式。不论你应用什么样的循环。要以这种形式创立列表,您应该:

  1. 实例化一个空列表。
  2. 循环遍历一个可迭代的(如 range)的元素。
  3. 将每个元素附加到列表的开端。

    numbers = []
    for number in range(10):
     numbers.append(number)
     
    print(numbers)

    输入:

    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    在此示例中,您实例化了一个空列表 numbers。而后应用 for 循环迭代 range(10) 并应用 append() 办法将每个数字附加到列表的开端。

map() 对象

map() 是创立列表的另一种办法。您须要向 map() 传递一个函数和一个可迭代对象,之后它会创立一个对象。该对象蕴含应用指定函数执行每个迭代元素所取得的输入。

例如,咱们将出现在某些产品的价格中减少增值税的工作。

VAT_PERCENT = 0.1  # 10%


def add_vat(price):
    return price + (price * VAT_PERCENT)
    

prices = [10.03, 8.6, 32.85, 41.5, 22.64]
grand_prices = map(add_vat, prices)
print(grand_prices)
grand_prices = list(grand_prices)
print(grand_prices)

您曾经构建了 add_vat() 函数并创立了 prices 可迭代对象。您将这两个参数都传递给 map() 并收集生成的 map 对象 grand_prices,或者您能够应用 list() 轻松地将其转换为列表。

输入:

<map object at 0x7f18721e7400>  # map(add_vat, prices)
[11.03, 9.46, 36.14, 45.65, 24.9]  # list(grand_prices)

列表解析式

当初,让咱们看一下列表解析式办法!这的确是 Python 格调,并且是创立列表的更好办法。为了弄清楚这种办法有多弱小,咱们用一个单行代码来重写那个循环示例。

numbers = [number for number in range(10)]
print(numbers)

输入

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

正如您所见,这是一种不堪设想的办法!列表解析式看起来足够可读,您不须要编写更多代码,而只需一行。

为了更好地了解列表,请查看以下语法格局:

new_list = [expression for member in iterable]

哪种办法更无效

好的,咱们曾经学习了如何应用循环、map() 和列表解析式来创立列表,在您的脑海中可能会提出“哪种办法更无效”的问题。咱们来剖析一下吧!

import random
import timeit


VAT_PERCENT = 0.1
PRICES = [random.randrange(100) for x in range(100000)]


def add_vat(price):
    return price + (price * VAT_PERCENT)
    

def get_grand_prices_with_map():
    return list(map(add_vat, PRICES))
    

def get_grand_prices_with_comprehension():
    return [add_vat(price) for price in PRICES]
    

def get_grand_prices_with_loop():
    grand_prices = []
    for price in PRICES:
        grand_prices.append(add_vat(price))
    return grand_prices
    

print(timeit.timeit(get_grand_prices_with_map, number=100))
print(timeit.timeit(add_grand_prices_with_comprehension, number=100))
print(timeit.timeit(get_grand_prices_with_loop, number=100))

输入:

0.9833468980004909  # with_map
1.197223742999995   # with_comprehension
1.3564663889992516  # with_loop

正如咱们当初所看到的,创立列表的最优的办法是 map(),排在第二位的是列表解析式,最初是循环。

然而,办法的抉择应取决于您想要实现的指标。

  • 应用 map() 能够使你的代码更高效。
  • 应用循环能够使代码的思路展示更加清晰。
  • 应用列表解析式能够您使代码更加紧凑,且较高效。这是创立列表的最佳形式,因为这种形式可读性最强。

高级解析式

条件逻辑

早些时候,我向您展现了这个公式:

new_list = [expression for member in iterable]

公式可能有些不残缺。对解析式的更加残缺形容减少了对可选条件的反对。将条件逻辑增加到列表解析式的最常见办法是在表达式的开端增加条件:

new_list = [expression for member in iterable (if conditional)]

在这里,您的条件语句正好位于左边的括号中。

条件很重要,因为它们容许列表解析式过滤掉不须要的值,这在个别状况下也能够调用 filter():

numbers = [number for number in range(20) if number % 2 == 0]
print(numbers)

输入:

[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

正如您所看到的那样,这个解析式收集了可被 2 整除且没有余数的数字。

如果您须要更简单的过滤器,那么您甚至能够将条件逻辑挪动到独自的函数中。

def is_prime(number):
    if number > 1:
        for el in range(2, int(number/2)+1):
            if (number % el) == 0:
                return False 
        else:
            return True
          

numbers = [number for number in range(20) if is_prime(number)]
print(numbers)

输入:

[2, 3, 5, 7, 11, 13, 17, 19]

您构建 is_prime(number) 以确定是否是素数并返回布尔值。接下来,您应该将函数增加到解析式的条件中。

该公式容许您应用条件逻辑从几个可能的输入选项中进行抉择。例如,您有一个产品价格表,若有正数,您应将其转换为负数:

price_list = [1.34, 19.01, -4.2, 6, 8.78, -1,1]
normalized_price_list = [price if price > 0 else price*-1 for price in price_list]
print(normalized_price_list)

输入:

[1.34, 19.01, 4.2, 6, 8.78, 1,1]

在这里,您的表达式 price 有一个条件语句,如果 price > 0 else price*-1。这会通知 Python,如果价格为正,则输入价格值;但如果价格为负,则将价格转换为正值。该性能很弱小,思考将条件逻辑视为其本身的函数确实是很有用的:

def normalize_price(price):
    return price if price > 0 else price*-1
    

price_list = [1.34, 19.01, -4.2, 6, 8.78, -1,1]
normalized_price_list = [normalize_price(price) for price in price_list]
print(normalized_price_list)

输入:

[1.34, 19.01, 4.2, 6, 8.78, 1,1]

汇合解析式

您还能够创立一个汇合解析式!它根本与列表解析式雷同。不同之处在于汇合解析式不蕴含反复项。您能够通过应用花括号取代方括号来创立汇合解析式:

string = "Excellent"
unique_string = {letter for letter in string}
print(unique_string)

输入:

{"E", "e", "n", "t", "x", "c", "l"}

你的汇合解析式只蕴含惟一的字母。这与列表不同,汇合不保障我的项目将以特定顺序存储数据。这就是为什么汇合输入的第二个字母是 e,即便字符串中的第二个字母是 x。

字典解析式

字典解析式也是是相似的,但须要定义一个键:

string = "Words are but wind"
word_order = {el: ind+1 for ind, el in enumerate(string.split())}
print(word_order)

输入:

{"Words": 1, "are": 2, "but": 3, "wind": 4}

要创立 word_order 字典,请在表达式中应用花括号 ({}) 以及键值对 (el: ind+1)。

海象运算符

Python 3.8 中引入的海象运算符容许您一次解决两个问题:为变量赋值,返回该值。

假如您须要对将返回温度数据的 API 利用十次。您想要的只是 100 华氏度以上的后果。而每个申请可能都会返回不同的数据。在这种状况下,没有方法在 Python 中应用列表解析式来解决问题。可迭代成员(如果有条件)的公式表达式无奈让条件将数据调配给表达式能够拜访的变量。

海象运算符解决了这个问题。它容许您在执行表达式的同时将输入值调配给变量。以下示例显示了这是如何实现的,应用 get_weather_data() 生成伪天气数据:

import random


def get_weather_data():
    return random.randrange(90, 110)
    

hot_temps = [temp for item in range(20) if (temp := get_weather_data()) >= 100]
print(hot_temps)

输入:

[108, 100, 106, 103, 108, 106, 103, 104, 109, 106]

什么时候不要应用解析式

列表解析式十分有用,它能够帮忙您编写清晰且易于浏览和调试的代码。但在某些状况下,它们可能会使您的代码运行速度变慢或应用更多内存。如果它让您的代码效率更低或更难了解,那么能够思考抉择另一种形式。

留神嵌套的解析式

能够通过嵌套解析式以创立列表、字典和汇合的组合汇合(译者注:这个汇合不是指 set 对象类型,而是 collection,泛指容器)。例如,假如一家公司正在跟踪一年中五个不同城市的支出。存储这些数据的完满数据结构能够是嵌套在字典解析式中的列表解析式。

cities = ['New York', 'Oklahoma', 'Toronto', 'Los Angeles', 'Miami']
budgets = {city: [0 for x in range(12)] for city in cities}
print(budgets)

输入:

{"NewYork": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "Oklahoma": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "Toronto": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "LosAngeles": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    "Miami": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}

您应用字典解析式创立了 budgets 容器。该表达式是一个键值对,其中蕴含另一个解析式。此代码将疾速生成城市中每个 city 的数据列表。

嵌套列表是创立矩阵的罕用办法,通常用于数学目标。查看上面的代码块:

matrix = [[x for x in range(7)] for y in range(6)]
print(matrix)

输入:

[[0, 1, 2, 3, 4, 5, 6],
    [0, 1, 2, 3, 4, 5, 6],
    [0, 1, 2, 3, 4, 5, 6],
    [0, 1, 2, 3, 4, 5, 6],
    [0, 1, 2, 3, 4, 5, 6],
    [0, 1, 2, 3, 4, 5, 6]
]

内部列表解析式 [… for y in range(6)] 创立了六行,而外部列表解析式 [x for x in range(7)] 将用值填充这些行中的每一行。

到目前为止,每个嵌套解析式的指标都是真正且直观的。然而,还有一些其余状况,例如创立扁平化的嵌套列表,其中的逻辑能够使您的代码十分难以浏览。让咱们看上面的例子,应用嵌套列表解析式来展平一个矩阵:

matrix = [[0, 1, 0],
    [1, 0, 1],
    [2, 1, 2],
]
flat = [num for row in matrix for num in row]
print(flat)

输入:

[0, 1, 0, 1, 0, 1, 2, 1, 2]

扁平化矩阵的代码的确很简洁,然而太难了解了,您应该花点工夫弄清楚它是如何工作的。另一方面,如果您应用 for 循环来展平雷同的矩阵,那么您的代码将更加简略易读:

matrix = [[0, 1, 0],
    [1, 0, 1],
    [2, 1, 2],
]
flat = []
for row in matrix:
    for num in row:
        flat.append(num)
print(flat)

输入:

[0, 1, 0, 1, 0, 1, 2, 1, 2]

当初,您能够看到代码一次遍历矩阵的一行,在挪动到下一行之前取出该行中的所有元素。

尽管嵌套列表解析式可能看起来更具备 Python 格调,但对于可能编写出您的团队能够轻松了解和批改的代码来才是更加最重要的。当抉择一个办法时,您应该依据解析式是有助于还是有损于可读性来做出相应的判断。

为大型数据集应用生成器

Python 中的列表解析式通过将整个列表存储到内存中来工作。对于小型至中型列表这通常很好。如果您想将前一千个整数相加,那么列表解析式将轻松地解决此工作:

summary = sum([x for x in range(1000)])
print(summary)

输入:499500

然而,如果您须要对十亿个数字求和呢?您能够尝试执行此操作,但您的计算机可能不会有响应。这是可能因为计算机中调配大量内存。兴许您是因为计算机没有如此多的内存资源。

例如,你想要一些第一个十亿整数,那么让咱们应用生成器!这可能多须要一些工夫,但计算机应该能够克服它:

summary = sum((x for x in range(1000000000)))
print(summary)

输入:

499999999500000000

让咱们来比照一下哪种办法是更优的!

import timeit


def get_sum_with_map():
    return sum(map(lambda x: x, range(1000000000)))


def get_sum_with_generator():
    return sum((x for x in range(1000000000)))


print(timeit.timeit(get_sum_with_map, number=100))
print(timeit.timeit(get_sum_with_generator, number=100))

输入:

4940.844053814  # get_sum_with_map
3464.1995523349997  # get_sum_with_generator
正如您所见,生成器比 map() 高效得多。

总结

本文向您介绍了列表解析式,以及如何应用它来解决简单的工作,而不会使您的代码变得过于艰难。

当初你:

  • 学习了几种创立列表的代替办法。
  • 找出每种办法的长处。
  • 能够简化循环和 map() 调用列表解析式。
  • 了解了一种将条件逻辑增加到解析式中的办法。
  • 能够创立汇合和字典解析式。
  • 学会了何时不应用解析式。

正文完
 0