Classe base baseada na classe derivada python (ou uma maneira mais pitônica de estender aulas)
-
27-09-2019 - |
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?
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