J'ai besoin d'étendre le paquet python Networkx et d'ajouter quelques méthodes à la classe Graph
pour mon besoin particulier
Pour ce faire, j'ai simplifié la création d'une nouvelle classe en spécifiant NewGraph
et en ajoutant les méthodes requises.
Cependant, networkx comporte plusieurs autres fonctions qui créent et renvoient des objets Graph
(par exemple, générer un graphe aléatoire). Je dois maintenant transformer ces objets Graph
en objets NewGraph
afin de pouvoir utiliser mes nouvelles méthodes.
Quelle est la meilleure façon de faire cela? Ou devrais-je aborder le problème d'une manière complètement différente?
Si vous ne faites qu'ajouter un comportement et que vous ne dépendez pas de valeurs d'instance supplémentaires, vous pouvez affecter le __class__
de l'objet:
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)
Impressions:
10
314.159265359
<__main__.Circle object at 0x00A0E270>
20
62.8318530718
<__main__.CirclePlus object at 0x00A0E270>
C'est aussi proche d'un "casting" que vous pouvez obtenir en Python, et comme pour un casting en C, cela ne doit pas être fait sans réfléchir à la question. J'ai posté un exemple assez limité, mais si vous pouvez rester dans les limites (ajoutez simplement un comportement, pas de nouveaux vars d'instance), cela pourrait aider à résoudre votre problème.
Voici comment remplacer "magiquement" une classe dans un module par une sous-classe personnalisée sans toucher au module. Il ne s'agit que de quelques lignes supplémentaires par rapport à une procédure de sous-classement normale, ce qui vous donne (presque) toute la puissance et la flexibilité du sous-classement en bonus. Par exemple, cela vous permet d'ajouter de nouveaux attributs, si vous le souhaitez.
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()
Jusqu'ici, cela correspond exactement au sous-classement normal. Nous devons maintenant rattacher cette sous-classe au module networkx
afin que toute instanciation de nx.Graph
donne lieu à un objet NewGraph
. Voici ce qui se passe normalement lorsque vous instanciez un objet nx.Graph
avec nx.Graph()
1. nx.Graph .__ new __ (nx.Graph) est appelé 2. Si l'objet renvoyé est une sous-classe de nx.Graph, __Init__ est appelé sur l'objet 3. L'objet est renvoyé en tant qu'instance
Nous allons remplacer nx.Graph.__new__
et le renvoyer NewGraph
à la place. Dans ce document, nous appelons la méthode __new__
de object
au lieu de la méthode __new__
de NewGraph
, car cette dernière est simplement une autre façon d'appeler la méthode que nous remplaçons et entraînerait donc une récursion sans fin.
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()
Dans la plupart des cas, c'est tout ce que vous avez besoin de savoir, mais il y en a un. Notre substitution de la méthode __new__
n'affecte que nx.Graph
, pas ses sous-classes. Par exemple, si vous appelez nx.gn_graph
, qui retourne une instance de nx.DiGraph
, il n’aura aucune de nos extensions fantaisies. Vous devez sous-classer chacune des sous-classes de nx.Graph
avec lesquelles vous souhaitez travailler et ajouter vos méthodes et attributs requis. Utiliser mix-ins peut faciliter l’extension cohérente des sous-classes tout en obéissant au principe DRY .
Bien que cet exemple puisse sembler assez simple, il est difficile de généraliser cette méthode de raccordement à un module de manière à couvrir tous les petits problèmes qui peuvent survenir. Je crois qu'il est plus facile d'adapter le problème au problème en question. Par exemple, si la classe à laquelle vous vous connectez définit sa propre méthode __new__
personnalisée, vous devez la stocker avant de la remplacer et appeler cette méthode au lieu de object.__new__
.
Pour votre cas simple, vous pouvez également écrire votre sous-classe __init__
comme ceci et affecter les pointeurs des structures de données Graphes à vos données de sous-classe.
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)
Vous pouvez également utiliser copy () ou deepcopy () dans les assignations, mais si vous faites cela, vous pouvez aussi bien utiliser
G=MyGraph()
G.add_nodes_from(R)
G.add_edges_from(R.edges())
pour charger vos données graphiques.
Si une fonction crée des objets Graph, vous ne pouvez pas les transformer en objets NewGraph.
Une autre option consiste pour NewGraph à avoir un graphique plutôt qu’à être un graphique. Vous déléguez les méthodes Graph à l'objet Graph que vous avez et vous pouvez envelopper n'importe quel objet Graph dans un nouvel objet 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):
....
Vous pouvez simplement créer une nouvelle variable NewGraph
dérivée de l'objet Graph
et demander à la fonction __init__
d'inclure quelque chose comme self.__dict__.update(vars(incoming_graph))
dans la première ligne, avant de définir vos propres propriétés. De cette manière, vous copiez toutes les propriétés de la Graph
que vous avez sur un nouvel objet, dérivé de Graph
, mais avec votre sauce spéciale.
class NewGraph(Graph):
def __init__(self, incoming_graph):
self.__dict__.update(vars(incoming_graph))
# rest of my __init__ code, including properties and such
Usage:
graph = function_that_returns_graph()
new_graph = NewGraph(graph)
cool_result = function_that_takes_new_graph(new_graph)