用循环腌制图形

时间:2012-02-22 18:38:18

标签: python graph pickle

我在python中有一个自定义节点类,它构建在一个图形(这是一个字典)中。由于这些需要花费一些时间来创建,我想腌制它们,这样我每次运行代码时都不必重新构建它们。

不幸的是,因为这个图有周期,所以cPickle会达到最大递归深度:

  

RuntimeError:蚀刻对象时超出了最大递归深度

这是我的节点对象:

class Node:
    def __init__(self, name):
        self.name = name
        self.uid = 0
        self.parents = set()
        self.children = set()

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, that):
        return self.name == that.name

    def __str__(self):
        return "\n".join(["Name: " + self.name,
                          "\tChildren:" + ", ".join([c.name for c in self.children]),
                          "\tParents:" + ", ".join([p.name for p in self.parents])
                          ]
                         )

这是我构建图表的方式:

def buildGraph(input):
    graph = {}
    idToNode = {}

    for line in input:
        ## Input from text line by line looks like
        ## source.node -> target.node
        source, arr, target = line.split()
        if source in graph:
            nsource = graph[source]
        else:
            nsource = Node(source)
            nsource.uid = len(graph)
            graph[source] = nsource
            idToNode[nsource.uid] = nsource

        if target in graph:
            ntarget = graph[target]
        else:
            ntarget = Node(target)
            ntarget.uid = len(graph)
            graph[target] = ntarget
            idToNode[ntarget.uid] = ntarget

        nsource.children.add(ntarget)
        ntarget.parents.add(nsource)
    return graph

然后在我的主要内容中,我有

    graph = buildGraph(input_file)
    bo = cPickle.dumps(graph)

,第二行是我得到递归深度错误的地方。

除了改变Node的结构外还有其他解决方案吗?

3 个答案:

答案 0 :(得分:2)

你需要为pickle准备对象:如果你有一个循环,你需要打破循环并以其他形式存储这些信息。

Pickle 使用方法__getstate__准备pickle(之前调用)和__setstate__初始化对象。

class SomethingPickled(object):
    ## Compress and uncycle data before pickle.
    def __getstate__(self):
        # deep copy object
        state = self.__dict__.copy()
        # break cycles
        state['uncycled'] = self.yourUncycleMethod(state['cycled'])
        del state['cycle']
        # send to pickle
        return state

    ## Expand data before unpickle.
    def __setstate__(self, state):
        # restore cycles
        state['cycle'] = self.yourCycleMethod(state['uncycled'])
        del state['uncycle']
        self.__dict__.update(state)

我相信你会发现如何打破和加入周期:)

答案 1 :(得分:2)

我不认为你的图是循环的这个问题 - pickle(和cPickle)应该处理循环数据结构就好了。我尝试了以下(使用Node的定义)并且它工作得很好:

>>> n1 = Node('a')
>>> n2 = Node('b')
>>> n1.parents.add(n2)
>>> n2.parents.add(n1)
>>> n2.children.add(n1)
>>> n1.children.add(n1)

>>> import cPickle as pickle
>>> pickle.dumps(n1)

实际上,即使是大周期,我也没有遇到问题。例如,这对我来说很好:

>>> def node_cycle(n):
...     start_node = prev_node = Node('node0')
...     for i in range(n):
...         node = Node('node%d' % (i+1))
...         node.parents.add(prev_node)
...         prev_node.children.add(node)
...         prev_node = node
...     start_node.parents.add(node)
...     node.children.add(start_node)

>>> cycle = node_cycle(100000) # cycle of 100k nodes
>>> pickle.dumps(cycle)

(这都是在Python 2.7.1上测试过的)

还有其他原因可以解释为什么pickle可能会以非常深的递归结束,具体取决于数据结构的形状。如果这是真正的问题,那么您可以使用以下内容修复它:

>>> import sys
>>> sys.setrecursionlimit(10000)

答案 2 :(得分:1)

此处,此修改后的节点类仅将对象的名称保存为节点中的字符串,并在检索节点的“children”或“parents”属性时为您提供具有完整“Node”对象的集合

在内部没有循环 - 因此它应该避免无限循环陷阱。您可以实现其他辅助方法以轻松导航。

class Node(object):
    all_nodes = {}
    def __new__(cls, name):
        self = object.__new__(cls)
        cls.all_nodes[name] = self
        return self

    def __getstate__(self):
        self.all_nodes = self.__class__.all_nodes
        return self.__dict__

    def __setstate__(self, dct):
        self.__class__.all_nodes = dct["all_nodes"]
        del dct["all_nodes"]
        self.__dict__ = dct

    def __init__(self, name):
        #self.all_nodes = self.__class__.all_nodes
        self.name = name
        self.uid = 0
        self._parents = set()
        self._children = set()

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, that):
        return self.name == that.name

    def __repr__(self):
        return "\n" + "\n".join(["Name: " + self.name,
                          "\tChildren:" + ", ".join([c.name for c in self.children]),
                          "\tParents:" + ", ".join([p.name for p in self.parents])
                          ]
                         )
    def get_relations(self, which):
        names = getattr(self, which)
        return set(self.__class__.all_nodes[name] for name in names)
    @property
    def children(self):
        return self.get_relations("_children")
    @property
    def parents(self):
        return self.get_relations("_parents")

    def __contains__(self, item):
        return item.name in self._children

    def add(self, child):
        self._children.add(child.name)
        child._parents.add(self.name)
    connect_child = add



#example and testing:

from cPickle import loads, dumps

n1 = Node("n1")
n2 = Node("n2")
n3 = Node("n3")

n1.add(n2)
n2.add(n3)
n3.add(n1)

print n1, n2, n3


p1 = dumps(n1)
Node.all_nodes.clear()
p2 = loads(p1)

print p2
print p2.children
print p2.children.pop().children

print Node.all_nodes

缺点是它维护了一个名为“all_nodes”的类字典,其中引用了所有实际创建的节点。 (Pickle很聪明,只能为给定的图形挑选一次这个字典,因为它被所有Node对象引用)。 类范围“all_nodes”引用的问题是如果你需要pickle和unpickle不同的图形集9let说你用一组节点创建图形g1,在另一个运行中,用另一组节点创建图形g2,并且然后,如果你取消g1,以及后来的g2,g2的unpickling将覆盖g1的节点引用。如果你需要这个工作,请在评论中提问我可以想出一些东西 - 我能想到的更简单的事情是有一个“图”类,它将为所有节点保存一个字典(而不是在Node类中使用它) )