如何避免在列表理解中运行30,000次致命功能?

kin*_*ion 4 python list-comprehension python-3.x

我有两个相同长度的列表.我想检查一个列表中的条件.如果条件为真,则在另一个列表上运行一个非常大的内存/处理密集型函数.

我的第一次尝试是这样的:

records = [(a, deadly_func(b)) for a, b in zip(listA, listB) if a == "condition"]
Run Code Online (Sandbox Code Playgroud)

这立即分配了我桌面上的所有内存,并在我杀了它之前继续了一段时间.显然,它在listB中的所有30,000个项目上运行了deadly_func(b),而意图是使用'if'语句将listB过滤到大约30个项目.

我能够制作一个有效的版本:

records = [(a, i) for a, i in zip(listA, range(len(listB)) if a == "condition"]
records = [(a, deadly_func(listB[i]) for a, i in records] 
Run Code Online (Sandbox Code Playgroud)

为什么我的第一次尝试不起作用?是否有更多的pythonic方式使这项工作?


编辑:谢谢你的回复.这是两个版本的实际代码

不工作:

import shapefile, shapely.geometry as shpgeo

lat = 42.3968243
lon = -71.0313479

sf = shapefile.Reader("/opt/ziplfs/tl_2014_us_zcta510.shp")

records = [(r[0], shpgeo.shape(s.__geo_interface__)) for r, s in zip(sf.records(), sf.shapes()) if haversine(lon, lat, float(r[8]), float(r[7])) < 10]
Run Code Online (Sandbox Code Playgroud)

haversine()是一个用户自制的半正弦函数,取两对lat和long并以km为单位返回距离.

from math import sqrt, sin, cos, radians, asin
def haversine(lon1, lat1, lon2, lat2):
    """
    Calculate the great circle distance between two points 
    on the earth (specified in decimal degrees). Return is in kilometers
    """
    # convert decimal degrees to radians 
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])

    # haversine formula 
    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a)) 
    r = 6371 # Radius of earth in kilometers. Use 3956 for miles
    return c * r
Run Code Online (Sandbox Code Playgroud)

shapefile('tl_2014_us_zcta510.shp')是美国人口普查局的所有拉链码.如果你真的喜欢shapefile,请在这里下载,你的硬盘上有800 MB,你不知道该怎么做.

此脚本应返回一个元组列表,表示美国的所有邮政编码,其中心位于距离马萨诸塞州切尔西10公里的范围内.

对于工作版本,将记录行替换为:

records = [(r[0], i) for r, i in zip(sf.records(), range(len(sf.records()))) if haversine(lon, lat, float(r[8]), float(r[7])) < 10]
shapes = [shpgeo.shape(sf.shape(i).__geo_interface__) for r, i in records]
Run Code Online (Sandbox Code Playgroud)

我做了一些时间测试.'非工作'版本:

$ python test.py 
Time Elapsed: 0:00:14.221533
$ python test.py 
Time Elapsed: 0:00:14.637827
$ python test.py 
Time Elapsed: 0:00:14.253425
Run Code Online (Sandbox Code Playgroud)

和工作版本:

$ python test.py 
Time Elapsed: 0:00:01.887987
$ python test.py 
Time Elapsed: 0:00:01.886635
$ python test.py 
Time Elapsed: 0:00:01.982547
Run Code Online (Sandbox Code Playgroud)

也许每个人都不会"致命",但重复30k次时会很重要.

use*_*559 6

没有repro?此代码并不会运行deadly_func上的所有元素listB.只是那些对应的listA值是True:

listA = [True, False, True, False]
listB = [1, 2, 3, 4]

def deadly_func(x):
    print("Called with {}".format(x))
    return x

print([(a, deadly_func(b)) for a, b in zip(listA, listB) if a])

# Output:
# Called with 1
# Called with 3
# [(True, 1), (True, 3)]
Run Code Online (Sandbox Code Playgroud)

编辑

基于更新的问题,我的猜测是sf.shapes()昂贵的部分.因此sf.shape(i),仅调用所需元素的子集会更有效.

如果我的猜测是正确的,这应该是诀窍:

records = [(r[0], shpgeo.shape(sf.shape(i).__geo_interface__)) for i, r in enumerate(sf.records()) if haversine(lon, lat, float(r[8]), float(r[7])) < 10]
Run Code Online (Sandbox Code Playgroud)

(当然,这主要是你已经做过的.)

  • `for i,r in enumerate(sf.records())` (2认同)