Classe de base Cast à classe dérivée python (ou une façon plus pythonique d'étendre les classes)
j'ai besoin d'étendre le paquet Networkx python et d'ajouter quelques méthodes à la classe Graph
pour mes besoins particuliers
la façon dont j'ai pensé à faire ceci est de dériver simplement une nouvelle classe dire NewGraph
, et d'ajouter les méthodes requises.
cependant, il y a plusieurs autres fonctions dans networkx 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 NewGraph
objets afin que je puisse utiliser mes nouvelles méthodes.
Quelle est la meilleure façon de le faire? Ou devrais-je aborder le problème d'une manière complètement différente?
6 réponses
si vous ajoutez simplement un comportement, et non pas en fonction de valeurs d'instance supplémentaires, vous pouvez attribuer à l'objet __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)
Imprime:
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 casting en C, il ne doit pas être fait sans donner la matière une certaine réflexion. J'ai posté un exemple assez limité, mais si vous pouvez rester dans les contraintes (il suffit d'ajouter behavior, pas de nouvelle instance vars), cela pourrait aider à résoudre votre problème.
Voici comment" magiquement " remplacer une classe dans un module par une sous-classe sur mesure sans toucher le module. Il ne s'agit que de quelques lignes supplémentaires d'une procédure normale de subclassage, et vous donne donc (presque) toute la puissance et la flexibilité de subclassage en bonus. Par exemple, cela 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()
pour l'instant, c'est exactement comme un sous-classement normal. Maintenant nous devons accrocher cette sous-classe dans le networkx
module de façon à ce que toute instanciation de nx.Graph
se traduise par un objet NewGraph
à la place. Voici ce qui se passe normalement lorsque vous instanciez un objet nx.Graph
avec 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
nous remplacerons nx.Graph.__new__
par NewGraph
. Nous y appelons la méthode __new__
de object
au lieu de la méthode __new__
de NewGraph
, parce que cette dernière est juste une autre façon d'appeler la méthode que nous remplaçons, et il en résulterait 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 devez savoir, mais il y a un gotcha. Notre dérogation à la méthode __new__
ne concerne que nx.Graph
, et non ses sous-classes. Par exemple , si vous appelez nx.gn_graph
, qui renvoie une instance de nx.DiGraph
, elle n'aura aucune de nos extensions fantaisistes. Vous avez besoin d'une classe de chacun des sous-classes de nx.Graph
que vous souhaitez travailler avec et ajouter vos méthodes requises et attributs. L'utilisation de mix-ins peut faciliter l'extension constante des sous-classes tout en obéissant au principe DRY .
bien que cet exemple puisse sembler assez simple, cette méthode de branchement dans un module est difficile à généraliser d'une manière qui couvre tous les petits problèmes qui peuvent apparaître. Je crois qu'il est plus facile de l'adapter au problème. Par exemple, si la classe dans laquelle vous vous accrochez 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__
.
si une fonction crée des objets Graph, vous ne pouvez pas les transformer en objets NewGraph.
une autre option pour NewGraph est d'avoir un graphe plutôt que D'être un graphe. Vous déléguez les méthodes graphiques à L'objet graphique que vous avez, et vous pouvez envelopper n'importe quel objet graphique 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):
....
pour votre cas simple, vous pouvez aussi écrire votre sous-classe __init__
comme ceci et assigner les pointeurs des structures de données graphiques à 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 le faites vous pourriez aussi bien utiliser
G=MyGraph()
G.add_nodes_from(R)
G.add_edges_from(R.edges())
pour charger vos données graphiques.
vous pouvez simplement créer un nouveau NewGraph
dérivé de Graph
objet et avoir la fonction __init__
inclure quelque chose comme self.__dict__.update(vars(incoming_graph))
comme première ligne, avant de définir vos propres propriétés. De cette façon, vous copiez toutes les propriétés du 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
Utilisation:
graph = function_that_returns_graph()
new_graph = NewGraph(graph)
cool_result = function_that_takes_new_graph(new_graph)
avez-vous essayé [Python] classe de la base de coulée à la classe dérivée
je l'ai testé, et il semble qu'il fonctionne. Aussi je pense que cette méthode est un peu mieux que ci-dessous un puisque ci-dessous on n'exécute pas init fonction de la fonction dérivée.
c.__class__ = CirclePlus