Cast clase base a clase derivada python (o forma más pitónica de extender clases)


Necesito extender el paquete python Networkx y agregar algunos métodos a la clase Graph para mi necesidad particular

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

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

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

Author: zenna, 2010-08-12

6 answers

Si solo está agregando comportamiento, y no depende de valores de instancia adicionales, puede asignar al __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)

Impresiones:

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

Esto es lo más cercano a un "cast" que se puede obtener en Python, y como el casting en C, no se debe hacer sin pensar en el asunto. He publicado un ejemplo bastante limitado, pero si puede mantenerse dentro de las restricciones (solo agregue comportamiento, no vars de instancia nueva), esto podría ayudar a resolver su problema.

 41
Author: PaulMcG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-12 01:39:13

Aquí está cómo "mágicamente" reemplazar una clase en un módulo con una subclase personalizada sin tocar el módulo. Es solo unas pocas líneas adicionales de un procedimiento normal de subclase, y por lo tanto le da (casi) toda la potencia y flexibilidad de la subclase como un bono. 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 la subclase normal. Ahora necesitamos enganchar esta subclase en el módulo networkx para que toda la instanciación de nx.Graph resulta en un objeto NewGraph en su lugar. Esto es lo que sucede normalmente cuando instancias 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

Reemplazaremos nx.Graph.__new__ y haremos que vuelva NewGraph en su lugar. En él, llamamos al método __new__ de object en lugar del método __new__ de NewGraph, porque este último es solo otra forma de llamar al método que estamos reemplazando, y por lo tanto resultaría en 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 necesitas saber, pero hay una gotcha. Nuestro la sobreescritura del método __new__ solo afecta a nx.Graph, no a sus subclases. Por ejemplo, si llamas a nx.gn_graph, que devuelve una instancia de nx.DiGraph, no tendrá ninguna de nuestras extensiones de fantasía. Necesita subclasificar cada una de las subclases de nx.Graph con las que desea trabajar y agregar sus métodos y atributos requeridos. Usar mix-ins puede hacer más fácil extender consistentemente las subclases mientras se obedece el principio DRY.

Aunque este ejemplo puede parecer sencillo suficiente, este método de enganchar en un módulo es difícil de generalizar de una manera que cubre todos los pequeños problemas que pueden surgir. Creo que es más fácil adaptarlo al problema en cuestión. Por ejemplo, si la clase a la que te estás conectando define su propio método personalizado __new__, necesitas almacenarlo antes de reemplazarlo, y llamar a este método en lugar de object.__new__.

 12
Author: Lauritz V. Thaulow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:10

Si una función está creando objetos Graph, no puede convertirlos en objetos NewGraph.

Otra opción para NewGraph es tener un Gráfico en lugar de ser un Gráfico. Delega los métodos de gráfico al objeto de gráfico que tiene, y puede envolver cualquier objeto de 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):
        ....
 0
Author: Ned Batchelder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-12 01:23:31

Para su caso simple, también podría escribir su subclase __init__ de esta manera y asignar los punteros de las estructuras de datos del gráfico a los datos de su 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 puedes usar copy () o deepcopy () en las asignaciones, pero si estás haciendo eso, también puedes usar

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

Para cargar los datos del gráfico.

 0
Author: Aric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-17 23:11:52

Simplemente podría crear un nuevo NewGraph derivado de Graph objeto y hacer que la función __init__ incluya algo como self.__dict__.update(vars(incoming_graph)) como primera línea, antes de definir sus propias propiedades. De esta manera, básicamente copia todas las propiedades de 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)
 0
Author: cjbarth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-06 20:48:41

¿Habéis intentado [Python] cast clase base a clase derivada

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

c.__class__ = CirclePlus
 -1
Author: YuanZheCSYZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-25 00:05:46