列表推导中用于同时过滤和转换的中间变量

16 投票
4 回答
11721 浏览
提问于 2025-04-16 06:34

我有一个向量列表(在Python中),我想对它们进行归一化,同时去掉那些原本模长很小的向量。

输入列表是,比如说:

a = [(1,1),(1,2),(2,2),(3,4)]

我需要的输出是 (x*n, y*n),其中 n = (x**2+y**2)**-0.5

如果我只需要模长,那用列表推导式就很简单:

an = [ (x**2+y**2)**0.5 for x,y in a ]

如果我只想存储归一化后的x值也很简单,但我想要一个临时变量“n”,用在两个计算中,然后再丢掉它。

我也不能仅仅用一个lambda函数,因为我还需要用n来过滤列表。那么,最好的方法是什么呢?

现在我在这里使用这个嵌套的列表推导式(内部列表中有一个表达式):

a = [(1,1),(1,2),(2,2),(3,4)]

[(x*n,y*n) for (n,x,y) in (( (x**2.+y**2.)**-0.5 ,x,y) for x,y in a) if n < 0.4]

# Out[14]: 
# [(0.70710678118654757, 0.70710678118654757),
#  (0.60000000000000009, 0.80000000000000004)]

内部列表生成带有额外值(n)的元组,然后我用这些值进行计算和过滤。这真的是最好的方法吗?有没有什么效率低下的地方我需要注意的?

4 个回答

4

Python 3.8 开始,引入了 赋值表达式 (PEP 572),也就是 := 这个符号。这样一来,我们就可以在列表推导式中使用局部变量,这样就能避免多次计算同一个表达式。

在我们的例子中,我们可以把 (x**2.+y**2.)**-.5 的计算结果命名为一个变量 n,然后用这个结果来过滤列表,前提是 n 小于 0.4;接着,我们还可以用 n 来生成映射值:

# vectors = [(1, 1), (1, 2), (2, 2), (3, 4)]
[(x*n, y*n) for x, y in vectors if (n := (x**2.+y**2.)**-.5) < .4]
# [(0.7071067811865476, 0.7071067811865476), (0.6000000000000001, 0.8)]
11
Is this really the best way?

嗯,这个方法确实很有效,如果你真的很想写一行代码的话,那这个是你能做到的最好选择。

不过,简单的四行函数也能做到同样的事情,而且看起来更清晰:

def normfilter(vecs, min_norm):
    for x,y in vecs:
        n = (x**2.+y**2.)**-0.5
        if min_norm < n:
            yield (x*n,y*n)

normalized = list(normfilter(vectors, 0.4))

顺便说一下,你的代码或者描述里有个错误——你说你在过滤掉短向量,但你的代码正好相反 :p

撰写回答