deepcopy и Python - подсказки для избегания использования его?

У меня есть очень простая стандартная программа Python, которая включает циклическое повторение через список примерно 20 000 широт, координат долготы и вычисления расстояния каждой точки к контрольной точке.

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 <=100. Таким образом, я решил портировать его на Python, потому что я хотел использовать SqlAlchemy, чтобы сделать некоторый другой материал.

Я сначала портировал его без deepcopy операторов, которые теперь перчат метод, и это заставило результаты быть 'нечетными', или частично неправильными, потому что некоторые точки просто становились скопированными как ссылки (я предполагаю? Я думаю?) - но это было все еще в большой степени с такой скоростью, как версия C.

Теперь с добавленными вызовами deepcopy, стандартная программа делает это - задание правильно, но это подверглось экстремальной потере производительности и теперь занимает несколько секунд, чтобы сделать то же задание.

Это походит на довольно общее задание, но я ясно не делаю его pythonic путь. Как я должен делать это так, чтобы я все еще получил корректные результаты, но не должен был включать 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

Так в основном я просто делаю полную копию входа и добавляю новое значение - расстояние от контрольной точки. Затем я просто подаю заявку 'отсортированный' к получающемуся списку, указывая, что ключ сортировки должен быть свойством расстояния объекта PointDistance.

Это намного быстрее, чем использование deepcopy, хотя я признаюсь, что действительно не понимаю почему. Я предполагаю, что это до эффективного Python реализаций C, "отсортировал"?

14
задан si28719e 15 June 2010 в 08:50
поделиться

1 ответ

Хорошо, сначала самое простое:

  1. deepcopy в целом медленный, так как он должен выполнять много внутренней бухгалтерии для копирования патологических случаев, таких как объекты, содержащие себя разумным образом. См., Например, эту страницу или взгляните на исходный код deepcopy в copy.py , который находится где-то на вашем пути к Python.

  2. sorted работает быстро, поскольку реализован на C. Намного быстрее, чем эквивалентный sort в Python.

А теперь еще кое-что о поведении подсчета ссылок в Python, как вы просили в своем комментарии. В Python переменные - это ссылки. Когда вы говорите a = 1 , представьте, что у него есть 1 как объект, существующий сам по себе, а a - это просто тег, прикрепленный к нему. В некоторых других языках, таких как C, переменные являются контейнерами (а не тегами), и когда вы делаете a = 1 , вы фактически помещаете 1 в a . Это не относится к 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) создает новый кортеж из существующего кортежа, на который указывает a плюс еще один кортеж (1, 2) , который создается «на лету», и a корректируется так, чтобы указывать на новый кортеж, в то время как конечно b по-прежнему относится к старому кортежу. То же самое происходит с простыми числовыми сложениями, такими как a = a + 2 : в этом случае число, на которое изначально указывает a , никоим образом не изменяется, Python «конструирует» новый число и перемещает a , чтобы указать на новый номер. Итак, в двух словах: числа, строки и кортежи неизменяемы; списки, словари и наборы изменяемы. Определяемые пользователем классы, как правило, изменяемы, если вы явно не гарантируете, что внутреннее состояние не может быть изменено. И есть frozenset , неизменный набор. Плюс ко многим другим, конечно :)

Я не знаю, почему ваш исходный код не работал, но, вероятно, вы столкнулись с поведением, связанным с фрагментом кода, который я показал в списках в качестве вашего PointDistance также по умолчанию является изменяемым.Альтернативой может быть класс namedtuple из коллекций , который создает объект, подобный кортежу, поля которого также могут быть доступны по именам. Например, вы могли бы сделать это:

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

Это создает для вас класс PointDistance с двумя именованными полями: point и distance . В вашем основном цикле for вы можете назначить их соответствующим образом. Поскольку точечные объекты, на которые указывают поля point , не будут изменены в течение цикла for , а расстояние является числом (т. Е. по определению неизменяемый), поступая таким образом, вы должны быть в безопасности. Но да, в целом кажется, что простое использование sorted быстрее, поскольку sorted реализовано в C. Вам также может повезти с модулем heapq , который реализует структуру данных кучи, поддерживаемую обычным списком Python, поэтому он позволяет вам легко находить верхние k элементы, не сортируя остальные. Однако, поскольку heapq также реализован в Python, скорее всего, sorted будет работать лучше, если у вас нет большого количества точек.

Наконец, я хотел бы добавить, что мне никогда не приходилось использовать deepcopy , поэтому я думаю, что в большинстве случаев есть способы избежать этого.

33
ответ дан 1 December 2019 в 07:05
поделиться
Другие вопросы по тегам:

Похожие вопросы: