Python: обработка высокорекурсивных объектов без использования setrecursionlimit

Я получаю RuntimeError: maximum recursion depth exceeded при попытке обработать высокорекурсивный объект дерева. Во многом как здесь.

Он решил свою проблему, установив более высокий предел рекурсии с помощью sys.setrecursionlimit. Но я не хочу этого делать: я думаю, что это скорее обходной путь, чем решение. Потому что я хочу иметь возможность мариновать свои деревья, даже если в них есть 10 000 узлов. (В настоящее время он терпит неудачу на отметке 200.)

(Кроме того, истинный предел рекурсии для каждой платформы отличается, и я бы очень хотел избежать открытия этой банки с червями.)

Есть ли способ решить эту проблему на фундаментальном уровне? Если бы только модуль pickle обрабатывал с помощью цикла вместо рекурсии, у меня не было бы этой проблемы. Может быть, у кого-то есть идея, как я могу вызвать что-то подобное, не переписывая модуль pickle?

Любая другая идея, как я могу решить эту проблему, будет оценена по достоинству.


person Ram Rachum    schedule 26.05.2010    source источник
comment
Что такое дерево? Зачем его нужно мариновать после 1000 узлов? (просто пытаюсь мыслить нестандартно, но мне нужно больше информации ...)   -  person bwawok    schedule 26.05.2010
comment
Дерево - это временное дерево симуляции. Немного похоже на дерево коммитов системы управления версиями.   -  person Ram Rachum    schedule 26.05.2010
comment
Разве вы не можете итеративно сериализовать его с помощью BFS?   -  person Michael Foukarakis    schedule 02.01.2012


Ответы (4)


Я полагаю, что большинство людей никогда не используют рекурсивные структуры такой глубины. Поскольку самые простые реализации сериализации рекурсивны, вы увидите только их.

На вашем месте я бы не использовал здесь открыто рекурсивную структуру данных. Вместо этого я бы пронумеровал каждый узел и использовал таблицу ссылок, которая эффективно переводит число в узел с этим номером. Каждый узел будет ссылаться на другие узлы (например, его дочерние элементы) через эту таблицу, используя числа. Простое свойство упростило бы это синтаксически. Помимо этих свойств, никакой код, связанный с обходом дерева, менять не нужно. Конструктор узла должен будет выделить номер и поместить себя в таблицу ссылок, что тоже тривиально.

Таблица ссылок может быть просто списком узлов, где индекс в списке служит номером узла; Списки Python, похоже, имеют эффективный доступ по индексу. Если важна скорость вставки, я бы заранее выделил достаточно длинный список, заполненный None; это не займет слишком много места. Если бы узлы сохраняли свои собственные номера, эту структуру можно было бы дешево обходить в обоих направлениях.

Как видите, солить и расправить такое дерево на любой глубине было бы несложно.

person 9000    schedule 05.06.2010
comment
Итак, вы говорите, избегайте указателей от узла к его дочерним и родительским элементам. Это действительно решило бы проблему, но будет очень неприятно отсутствие указателей. Это ставит под угрозу архитектуру данных программы только из-за проблемной реализации pickle. - person Ram Rachum; 05.06.2010
comment
Не совсем. Этот подход будет иметь тот же интерфейс, как если бы указатели были простыми ссылками на Python. Это вопрос простого определения свойства, при котором операция get достаточно эффективна. - person 9000; 16.06.2010
comment
@ 9000 Я знаю, что прошло уже 10 лет, но я попробовал этот метод, и он значительно замедляет работу программы. - person Kaleba KB Keitshokile; 17.03.2021
comment
@KalebaKBKeitshokile: Все меняется, и нет замены измерению реальной производительности. Этот подход не в ускорении, а в работе с чрезмерно глубокими структурами, настолько глубокими, что стек Python переполняется рекурсивным кодом. Не стесняйтесь предлагать лучшее решение, которое касается очень глубокой или, еще лучше, циклической структуры: я с удовольствием проголосую за него! - person 9000; 31.03.2021
comment
@ 9000 В итоге я решил сохранить узлы в одном массиве и их соседей в другом (еще не подключенном), а затем подключать их только при загрузке - person Kaleba KB Keitshokile; 03.04.2021

Чтобы облегчить понимание, вот полный пример, с одной ссылкой для упрощения:

class Node(object):
  linker = [] # one list for all Node instances
  def __init__(self, payload):
    self.payload = payload
    self.__next = None
    self.__index = len(self.linker)
    self.linker.append(self)
  #
  def getNext(self):
    if self.__next is not None:
      return self.linker[self.__next]
  #
  def setNext(self, another):
    if another is not None:
      self.__next = another.__index
    else:
      self.__next = None
  #
  next = property(getNext, setNext)
  #
  def __str__(self):
    return repr(self.payload)


a = Node("One")
b = Node("Two")
c = Node("Three")

b.next = c
a.next = b

# prints "One" "Two" "Three"
print a, a.next, a.next.next

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

person 9000    schedule 15.06.2010
comment
Спасибо. Я все еще считаю, что это слишком взломано. - person Ram Rachum; 16.06.2010
comment
Обновил мой ответ, чтобы удалить некрасивую глобальную переменную. - person 9000; 16.04.2012

Просто не используйте рекурсию. Сделайте стек (список / очередь) с открытыми узлами и обработайте его.

Что-то вроде этого (псевдокод)

stack.add(root)
while not list.empty:
    current = stack.pop
    // process current
    for each child of current:
        stack.add(child)

Это должно сработать

person Mene    schedule 09.07.2010

Я думаю, что хорошее решение - это комбинация ответов Мене и 9000. Учитывая, что узлы имеют глобально уникальные идентификаторы (возможно, адреса памяти могут использоваться как таковые), вы можете это сделать. Конечно, это неаккуратная псевдо-реализация, но с небольшой абстракцией, если она инкапсулирована в древовидный класс, это может быть очень просто.

def all_nodes(node): # walk the tree and get return all nodes as a list
    if node:
        nodes = []
        for child in node.children:
            for sub_child in all_nodes(child):
                nodes.append(sub_child)
        return nodes
    return []


class Node(object):
    def __init__(self, children, id):
        self.children = children
        self.id = id

    def __getstate__(self): #when pickling translate children into IDs
        tmp = self.__dict__.copy()
        children_ids = []
        for child in tmp['children']:
            children_ids.append(child.id)
        tmp['children_ids'] = children_ids
        return tmp


lookup = dict()


for node in all_nodes(rootNode): # put all nodes into a dictionary
    lookup[node.id] = node
#then pickle the dictionary
#then you can unpickle it and walk the dictionary
for id, node in lookup:
    del node.children
    node.children = []
    for child in node.children_ids:
        node.children.append(lookup[child])
#and three should now be rebuilt
person Novikov    schedule 27.08.2010