Reparto de clase base para pitón clase derivada (o de forma más Pythonic de extender las clases)

StackOverflow https://stackoverflow.com/questions/3464061

Pregunta

Necesito extender el paquete python NetworkX y añadir unos métodos a la clase Graph para mi necesidad particular

La forma en que pensé en hacer esto se simplying derivar una nueva clase NewGraph digamos, y la adición de los métodos necesarios.

Sin embargo, hay varias otras funciones en NetworkX que crean y objetos de retorno Graph (por ejemplo, generar un gráfico aleatorio). Ahora necesito convertir estos objetos en Graph NewGraph objetos de modo que pueda utilizar mis nuevos métodos.

¿Cuál es la mejor manera de hacer esto? O debo abordar el problema de una manera completamente diferente?

¿Fue útil?

Solución

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)

Las 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 "molde" que se puede encontrar en Python, y al igual que la fundición en C, no se va a hacer sin darle al asunto un poco de pensamiento. He publicado un ejemplo bastante limitada, pero si usted puede permanecer dentro de las limitaciones (a añadir comportamiento, sin nueva instancia VARs), entonces esta dirección podría ayudar a su problema.

Otros consejos

Así es como a "mágicamente" en lugar de una clase en un módulo con una subclase medida sin tocar el módulo. Es sólo unas pocas líneas adicionales de un procedimiento de subclases normal, y por lo tanto (casi) da toda la potencia y flexibilidad de la subclasificación como un bono. Por ejemplo, este le permite añadir 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 el momento esto es exactamente igual que la subclasificación normal. Ahora tenemos que conectar esta subclase en el módulo networkx para que todos ejemplificación de los resultados nx.Graph en un objeto NewGraph lugar. 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

reemplazará nx.Graph.__new__ y hacerlo volver NewGraph lugar. En él, llamamos al método __new__ de object en lugar del método de __new__ NewGraph, porque esta última es sólo otra forma de llamar al método estamos reemplazando, y por lo tanto dar lugar a la repetició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. Nuestro primordial del método __new__ sólo afecta nx.Graph, no sus subclases. Por ejemplo, si se llama a nx.gn_graph, que devuelve una instancia de nx.DiGraph, tendrá que ninguna de nuestras extensiones de fantasía. Es necesario subclase cada una de las subclases de nx.Graph que desea trabajar con y agregar sus métodos y atributos requeridos. Utilizando mix-ins puede hacer que sea más fácil de forma consistente extender las subclases obedeciendo principio la DRY .

A pesar de este ejemplo puede parecer bastante sencillo, este método de enganchar en un módulo es difícil generalizar de una manera que cubre todos los pequeños problemas que pueden surgir. Creo que es más fácil simplemente adaptarlo al problema que nos ocupa. Por ejemplo, si la clase que usted está conectando en define su propio método __new__ personalizada, es necesario almacenarlo antes de reemplazarlo, y llamar a este método en lugar de object.__new__.

Si una función es la creación de objetos gráficos, no se puede convertirlos en objetos NewGraph.

Otra opción es que NewGraph es tener un gráfico en lugar de ser un gráfico. Delegar los métodos gráfica para el objeto gráfico 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):
        ....

Para su simple caso también se podría escribir su __init__ subclase de esta manera y asignar los punteros de las estructuras de datos del gráfico de los 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 puede utilizar la copia () o deepcopy () en las asignaciones, pero si usted está haciendo que bien podría utilizar

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

para cargar los datos de gráfico.

Simplemente podría crear un nuevo derivado de NewGraph objeto Graph 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, copiar todas las propiedades de la Graph usted tiene a 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)

¿Han intentado [Python] clase base fundido a clase derivada

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

c.__class__ = CirclePlus
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top