代码之家  ›  专栏  ›  技术社区  ›  si28719e

deepcopy和python-避免使用它的提示?

  •  12
  • si28719e  · 技术社区  · 14 年前

    我有一个非常简单的python例程,它涉及到在大约20000个纬度、经度坐标的列表中循环,并计算每个点到一个参考点的距离。

    def compute_nearest_points( lat, lon, nPoints=5 ):
        """Find the nearest N points, given the input coordinates."""
    
        points = session.query(PointIndex).all()
        oldNearest = []
        newNearest = []
        for n in xrange(nPoints):
            oldNearest.append(PointDistance(None,None,None,99999.0,99999.0))
            newNearest.append(obj2)
    
        #This is almost certainly an inappropriate use of deepcopy
        #  but how SHOULD I be doing this?!?!
        for point in points:
            distance = compute_spherical_law_of_cosines( lat, lon, point.avg_lat, point.avg_lon )
            k = 0
            for p in oldNearest:
                if distance < p.distance:
                    newNearest[k] = PointDistance(
                        point.point, point.kana, point.english, point.avg_lat, point.avg_lon, distance=distance
                        )
                    break
                else:
                    newNearest[k] = deepcopy(oldNearest[k])
                k += 1
            for j in range(k,nPoints-1):
                newNearest[j+1] = deepcopy(oldNearest[j])
            oldNearest = deepcopy(newNearest)
    
        #We're done, now print the result
        for point in oldNearest:
            print point.station, point.english, point.distance
    
        return
    

    我最初是用C编写的,使用完全相同的方法,在那里工作得很好,对于nPoints基本上是即时的<=所以我决定把它移植到python上因为我想用SqlAlchemy做一些其他的事情。

    我第一次移植它时没有使用deepcopy语句,而deepcopy语句现在在该方法中添加了胡椒粉,这导致结果是“奇怪的”,或者部分不正确,因为一些点只是作为引用被复制(我猜?我想是吧?——但它的速度还是和C版本差不多。

    现在添加了deepcopy调用后,例程可以正确地完成它的工作,但是它已经招致了极大的性能损失,现在执行相同的工作需要几秒钟。

    这似乎是一个很普通的工作,但我显然没有做它的蟒蛇的方式。我应该如何做,这样我仍然得到正确的结果,但不必包括deepcopy无处不在?

    编辑:
    我找到了一个更简单更快的解决方案,

    def compute_nearest_points2( lat, lon, nPoints=5 ):
        """Find the nearest N points, given the input coordinates."""
    
        points = session.query(PointIndex).all()
        nearest = []
    
        for point in points:
            distance = compute_spherical_law_of_cosines( lat, lon, point.avg_lat, point.avg_lon )
            nearest.append( 
                PointDistance(
                    point.point, point.kana, point.english, point.avg_lat, point.avg_lon, distance=distance
                    )
                )
    
        nearest_points = sorted(nearest, key=lambda point: point.distance)[:nPoints]     
        for item in nearest_points:
            print item.point, item.english, item.distance
        return
    

    所以基本上我只是做一个输入的完整副本,并附加一个新的值-到参考点的距离。然后我将“sorted”应用于结果列表,指定sort键应该是PointDistance对象的distance属性。

    这比使用deepcopy快得多,尽管我承认我真的不明白为什么。我想这应该归功于python的高效C实现“排序”?

    2 回复  |  直到 14 年前
        1
  •  35
  •   Tamás    14 年前

    好吧,最简单的事情优先:

    1. deepcopy this page ,或查看 深度复制 copy.py 在你的Python路径中的某个地方。

    2. sorted 是快速的,因为它是用C实现的。比Python中的等价排序快得多。

    a=1 ,想想它有没有 1 作为独立存在的对象,以及 a 只是一个标签。在其他一些语言(如C)中,变量是容器(而不是标记),当您这样做时 a=1 . 这对于Python不适用,因为变量是引用。这有一些有趣的结果,你可能也会偶然发现:

    >>> a = []      # construct a new list, attach a tag named "a" to it
    >>> b = a       # attach a tag named "b" to the object which is tagged by "a"
    >>> a.append(1) # append 1 to the list tagged by "a"
    >>> print b     # print the list tagged by "b"
    [1]
    

    之所以会看到这种行为,是因为列表 可变的 不变的 列表的等价物是元组:

    >>> a = ()      # construct a new tuple, attach a tag named "a" to it
    >>> b = a       # now "b" refers to the same empty tuple as "a"
    >>> a += (1, 2) # appending some elements to the tuple
    >>> print b
    ()
    

    在这里, a += (1, 2) 创建 引用的现有元组中的元组 ,再加上一个元组 (1, 2) 在飞行中建造的,并且 b a = a+2 :在本例中,最初由 指向新号码。所以,简而言之:数字、字符串和元组是不可变的;列表、dict和set是可变的。用户定义的类通常是可变的,除非您明确地确保内部状态不能改变。还有 frozenset ,这是一个不可变的集。当然还有很多其他的:)

    PointDistance 类在默认情况下也是可变的。另一种选择是 namedtuple 上课地点 collections

    from collections import namedtuple
    PointDistance = namedtuple("PointDistance", "point distance")
    

    这将创建一个 点距离 point distance . 在你的主要 for 指向 在您的过程中不会修改字段 对于 循环,和 距离 是一个数字(根据定义,它是不可变的),这样做应该是安全的。但是,总的来说,它似乎只是简单地使用 已排序 比以前快了 已排序 是用C语言实现的。你可能也会幸运地发现 heapq 模块,它实现了一个由普通Python列表支持的堆数据结构,因此它允许您查找 k 元素,而不必对其他元素进行排序。然而,自从 也是用Python实现的,很可能 已排序 效果更好,除非你有很多要点。

    深度复制

        2
  •  6
  •   tvaughan    12 年前

    我知道这并不能直接解决您的问题(我知道这是一个老问题),但是因为有一些关于性能的讨论,所以可能有必要看看 append 操作。您可能需要考虑“预分配”数组。例如:

    array = [None] * num_elements
    for i in range(num_elements):
        array[i] = True
    

    与:

    array = []
    for i in range(num_elements):
        array.append(True)
    

    一个简单的 timeit 如果将数组预分配给中等大小的 num_elements