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?

30
demandé sur zenna 2010-08-12 05:20:12

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.

41
répondu PaulMcG 2010-08-12 01:39:13

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__ .

12
répondu Lauritz V. Thaulow 2017-05-23 12:10:10

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):
        ....
0
répondu Ned Batchelder 2010-08-12 01:23:31

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.

0
répondu Aric 2011-01-17 23:11:52

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)
0
répondu cjbarth 2017-06-06 20:48:41

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
-1
répondu YuanZheCSYZ 2013-01-25 00:05:46