2010-08-12 9 views
28

necesito para extender el paquete python NetworkX y añadir unos métodos a la clase Graph para mi necesidad particularmoldeada a pitón clase derivada (o de forma más Pythonic de las clases que se extienden)

La forma en que pensé en hacer esto es simplemente derivar una nueva clase, digamos NewGraph, y agregar los métodos requeridos.

Sin embargo, hay varias otras funciones en networkx que crean y devuelven objetos Graph (por ejemplo, generar un gráfico aleatorio). Ahora necesito convertir estos objetos Graph en objetos NewGraph para que pueda usar mis nuevos métodos.

¿Cuál es la mejor manera de hacerlo? ¿O debería abordar el problema de una manera completamente diferente?

Respuesta

39

Si se acaba de agregar comportamiento, y no en función de valores de instancia adicionales, puede asignar a __class__ del objeto:

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) 

Lienzo:

10 
314.159265359 
<__main__.Circle object at 0x00A0E270> 
20 
62.8318530718 
<__main__.CirclePlus object at 0x00A0E270> 

Esto es lo más cercano a un "molde" que se puede encontrar en Python, y al igual que en la fundición C, no debe hacerse sin pensarlo un poco. He publicado un ejemplo bastante limitado, pero si puedes mantenerte dentro de las restricciones (solo agrega comportamiento, no nuevos vars de instancia), entonces esto podría ayudar a resolver tu problema.

+1

Ok, entonces, ¿qué sucede cuando _necesitas agregar variables? –

+0

Puede agregar/establecer variables de instancia en tiempo de ejecución. Sin embargo, ten en cuenta que no te confundas con la variable de instancia agregada por CirclePlus __init__ que olvidaste agregar porque este método de conversión omite __init__, supongo. Por cierto, dado que el sistema de tipos de Python se puede anular, este método de conversión no siempre funcionará. –

+0

Si encuentra que también necesita agregar variables de instancia, entonces creo que está yendo más allá del dominio del código que se puede mantener: tiempo para repensar su diseño, probablemente utilizando alguna forma de contención y/o delegación. – PaulMcG

0

Si una función crea objetos Graph, no puede convertirlos en objetos NewGraph.

Otra opción es que NewGraph tenga un gráfico en lugar de ser un gráfico. Delegar los métodos gráfica de la gráfico de objetos que tiene, y se puede envolver cualquier objeto gráfico en un nuevo 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): 
     .... 
+2

Gracias leí en otro lugar que solo puedo cambiar el atributo __class__. p.ej. MyRandomGraphObject .__ class__ = NewGraph. Y realmente funciona. ¿Mala práctica? – zenna

12

Aquí se explica cómo reemplazar "mágicamente" una clase en un módulo con una subclase hecha a medida sin tocar el módulo. Son solo unas pocas líneas extra de un procedimiento de subclases normal, y por lo tanto te da (casi) todo el poder y la flexibilidad de la subclasificación como bonificación. Por ejemplo, esto le permite agregar nuevos atributos, si lo desea.

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() 

Hasta ahora esto es exactamente como una subclase normal. Ahora tenemos que enganchar esta subclase en el módulo networkx para que todas las instancias de nx.Graph tengan como resultado un objeto NewGraph. Esto es lo que normalmente ocurre cuando se instancia un objeto nx.Graph con 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 a sustituir nx.Graph.__new__ y hacerlo volver NewGraph lugar. En él, llamamos al método __new__ de object en lugar del método __new__ de NewGraph, porque este último es simplemente otra forma de llamar al método que estamos reemplazando, y por lo tanto daría como resultado una recursión sin 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() 

En la mayoría de los casos, esto es todo lo que necesita saber, pero hay una gotcha. Nuestra anulación del método __new__ solo afecta a nx.Graph, no a sus subclases. Por ejemplo, si llama al nx.gn_graph, que devuelve una instancia de nx.DiGraph, no tendrá ninguna de nuestras extensiones de lujo.Debe subclasificar cada una de las subclases de nx.Graph con las que desea trabajar y agregar los métodos y atributos necesarios. Usar mix-ins puede facilitar la extensión consistente de las subclases al tiempo que se cumple el principio DRY.

Aunque este ejemplo puede parecer bastante directo, este método de enganche en un módulo es difícil de generalizar de una manera que cubra todos los pequeños problemas que puedan surgir. Creo que es más fácil adaptarlo al problema en cuestión. Por ejemplo, si la clase en la que está enganchando define su propio método personalizado __new__, debe almacenarlo antes de reemplazarlo y llamar a este método en lugar de object.__new__.

+0

¿Puedo hacer esto con un built-in? Si, por ejemplo, quiero convertir 'set' en' SpecialSet', ¿puedo cambiar el comportamiento '__new__' del built-in? – GrantJ

+1

@GrantJ Eso no funcionará. La mayoría de los builtins de python se implementan en C, y como tales no son tan maleables como las clases puras de python. Obtendrá este error: 'TypeError: no puede establecer los atributos del tipo de extensión/integrado 'set''. –

0

Para su caso simple también podría escribir su subclase __init__ de esta manera y asignar los punteros desde las estructuras de datos de Gráficos a sus datos de subclase.

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) 

También se puede utilizar la copia() o deepcopy() en las asignaciones, pero si usted está haciendo eso que también podría utilizar

G=MyGraph() 
G.add_nodes_from(R) 
G.add_edges_from(R.edges()) 

para cargar los datos del gráfico.

+0

Esto funcionó para mí. ¿Pero cómo hacerlo con métodos de doble subrayado? – GrantJ

-1

¿Han intentado [Python] cast base class to derived class

he probado, y parece que funciona. También creo que este método es un poco mejor que debajo de uno ya que debajo uno no ejecuta init función de función derivada.

c.__class__ = CirclePlus 
+0

Lea todas las soluciones existentes antes de publicar la misma. – wieczorek1990

0

Usted puede simplemente crear una nueva NewGraph derivado de Graph objeto y tienen la función __init__ incluir algo como self.__dict__.update(vars(incoming_graph)) como la primera línea, antes de definir sus propias propiedades. De esta forma, básicamente copia todas las propiedades del Graph que tiene en un nuevo objeto, derivado de Graph, pero con su salsa 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) 
Cuestiones relacionadas