web-dev-qa-db-fra.com

Attribue la classe de base à la classe dérivée python (ou plusieurs méthodes d’extension de classe Pythonic)

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?

33
zenna

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.

45
PaulMcG

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

12
Lauritz V. Thaulow

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.

0
Aric

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):
        ....
0
Ned Batchelder

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)
0
cjbarth