Pergunta

Preciso estender o pacote Networkx Python e adicionar alguns métodos ao Graph classe para minha necessidade particular

A maneira como pensei em fazer isso é simplesmente derivar uma nova classe, diga NewGraph, e adicionar os métodos necessários.

No entanto, existem várias outras funções no Networkx que criam e retornam Graph Objetos (por exemplo, geram um gráfico aleatório). Agora preciso virar estes Graph objetos em NewGraph Objetos para que eu possa usar meus novos métodos.

Qual a melhor maneira para fazer isto? Ou devo enfrentar o problema de uma maneira completamente diferente?

Foi útil?

Solução

Se você está apenas adicionando comportamento e não dependendo de valores de instância adicionais, você pode atribuir ao objeto __class__:

from math import pi

class Circle(object):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return pi * self.radius**2

class CirclePlus(Circle):
    def diameter(self):
        return self.radius*2

    def circumference(self):
        return self.radius*2*pi

c = Circle(10)
print c.radius
print c.area()
print repr(c)

c.__class__ = CirclePlus
print c.diameter()
print c.circumference()
print repr(c)

Impressões:

10
314.159265359
<__main__.Circle object at 0x00A0E270>
20
62.8318530718
<__main__.CirclePlus object at 0x00A0E270>

Isso é o mais próximo de um "elenco" que você pode entrar em Python e, como elenco em C, não deve ser feito sem pensar em um pouco de pensamento. Publiquei um exemplo bastante limitado, mas se você puder permanecer dentro das restrições (basta adicionar comportamento, sem vars de nova instância), isso pode ajudar a resolver seu problema.

Outras dicas

Veja como "magicamente" substituir uma classe em um módulo por uma subclasse feita sob medida sem tocar no módulo. São apenas algumas linhas extras de um procedimento normal de subclasse e, portanto, fornece (quase) toda a potência e flexibilidade da subclasse como um bônus. Por exemplo, isso permite adicionar novos atributos, se desejar.

import networkx as nx

class NewGraph(nx.Graph):
    def __getattribute__(self, attr):
        "This is just to show off, not needed"
        print "getattribute %s" % (attr,)
        return nx.Graph.__getattribute__(self, attr)

    def __setattr__(self, attr, value):
        "More showing off."
        print "    setattr %s = %r" % (attr, value)
        return nx.Graph.__setattr__(self, attr, value)

    def plot(self):
        "A convenience method"
        import matplotlib.pyplot as plt
        nx.draw(self)
        plt.show()

Até agora, isso é exatamente como subclasse normal. Agora precisamos conectar esta subclasse no networkx módulo para que toda a instanciação de nx.Graph resulta em a NewGraph objeto em vez disso. Aqui está o que normalmente acontece quando você instancia um nx.Graph objeto com nx.Graph()

1. nx.Graph.__new__(nx.Graph) is called
2. If the returned object is a subclass of nx.Graph, 
   __init__ is called on the object
3. The object is returned as the instance

Vamos substituir nx.Graph.__new__ e fazer isso retornar NewGraph em vez de. Nele, chamamos de __new__ método de object ao invés de __new__ método de NewGraph, porque o último é apenas outra maneira de chamar o método que estamos substituindo e, portanto, resultaria em recursão sem fim.

def __new__(cls):
    if cls == nx.Graph:
        return object.__new__(NewGraph)
    return object.__new__(cls)

# We substitute the __new__ method of the nx.Graph class
# with our own.     
nx.Graph.__new__ = staticmethod(__new__)

# Test if it works
graph = nx.generators.random_graphs.fast_gnp_random_graph(7, 0.6)
graph.plot()

Na maioria dos casos, isso é tudo o que você precisa saber, mas há um Gotcha. Nossa substituição do __new__ O método afeta apenas nx.Graph, não suas subclasses. Por exemplo, se você ligar nx.gn_graph, que retorna uma instância de nx.DiGraph, não terá nenhuma das nossas extensões sofisticadas. Você precisa subclasse cada uma das subclasses de nx.Graph que você deseja trabalhar e adicionar os métodos e atributos necessários. Usando mix-ins pode facilitar a extensão consistente das subclasses enquanto obedece ao SECO princípio.

Embora este exemplo possa parecer direto o suficiente, esse método de conectar a um módulo é difícil de generalizar de uma maneira que abranja todos os pequenos problemas que podem surgir. Eu acredito que é mais fácil adaptar -o ao problema em questão. Por exemplo, se a classe em que você está se conectando define seu próprio costume __new__ método, você precisa armazená -lo antes de substituí -lo e chamar esse método em vez de object.__new__.

Se uma função estiver criando objetos gráficos, você não pode transformá -los em objetos newgraph.

Outra opção é que o NewGraph é ter um gráfico em vez de ser um gráfico. Você delega os métodos gráficos do objeto gráfico que você possui e pode envolver qualquer objeto gráfico em um novo objeto Newgraph:

class NewGraph:
    def __init__(self, graph):
        self.graph = graph

    def some_graph_method(self, *args, **kwargs):
        return self.graph.some_graph_method(*args, **kwargs)
    #.. do this for the other Graph methods you need

    def my_newgraph_method(self):
        ....

Para o seu caso simples, você também pode escrever sua subclasse __init__ Assim e atribua os ponteiros das estruturas de dados do gráfico aos dados da subclasse.

from networkx import Graph

class MyGraph(Graph):
    def __init__(self, graph=None, **attr):
        if graph is not None:
            self.graph = graph.graph   # graph attributes
            self.node = graph.node   # node attributes
            self.adj = graph.adj     # adjacency dict
        else:
            self.graph = {}   # empty graph attr dict
            self.node = {}    # empty node attr dict 
            self.adj = {}     # empty adjacency dict

        self.edge = self.adj # alias 
        self.graph.update(attr) # update any command line attributes


if __name__=='__main__':
    import networkx as nx
    R=nx.gnp_random_graph(10,0.4)
    G=MyGraph(R)

Você também pode usar copy () ou DeepCopy () nas tarefas, mas se estiver fazendo isso, você também pode usar

G=MyGraph()
G.add_nodes_from(R)
G.add_edges_from(R.edges())

para carregar seus dados do gráfico.

Você pode simplesmente criar um novo NewGraph derivado de Graph objeto e ter o __init__ função inclua algo como self.__dict__.update(vars(incoming_graph)) Como a primeira linha, antes de definir suas próprias propriedades. Dessa maneira, você basicamente copia todas as propriedades do Graph você tem em um novo objeto, derivado de Graph, mas com seu molho especial.

class NewGraph(Graph):
  def __init__(self, incoming_graph):
    self.__dict__.update(vars(incoming_graph))

    # rest of my __init__ code, including properties and such

Uso:

graph = function_that_returns_graph()
new_graph = NewGraph(graph)
cool_result = function_that_takes_new_graph(new_graph)

Vocês já tentaramPython] Classe base fundida para a classe derivada

Eu testei e parece que funciona. Também acho que esse método é um pouco melhor do que abaixo um, pois abaixo um não é executado iniciar função da função derivada.

c.__class__ = CirclePlus
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top