numerica - Lanza la clase base a la clase de python derivada(o forma más pitonica de extender clases)
introduccion al calculo con python (6)
¿Han intentado [Python] lanzar la clase base a la clase derivada
Lo he probado y parece que funciona. También creo que este método es un poco mejor que uno inferior, ya que debajo uno no ejecuta la función init de la función derivada.
c.__class__ = CirclePlus
Necesito extender el paquete de python Networkx y agregar algunos métodos a la clase Graph
para mi necesidad particular
La forma en que pensé hacer esto es simplemente derivar una nueva clase, decir NewGraph
, y agregar los métodos necesarios.
Sin embargo, hay 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 poder usar mis nuevos métodos.
Cual es la mejor manera de hacer esto? ¿O debería abordar el problema de una manera completamente diferente?
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 subclasificación normal. Ahora tenemos que enganchar esta subclase en el módulo networkx
para que todas las instancias de nx.Graph
en un objeto NewGraph
. nx.Graph
es lo que normalmente sucede cuando nx.Graph
una instancia de 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 lo haremos regresar NewGraph
en NewGraph
lugar. En él, llamamos al método __new__
de object
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 un gotcha. Nuestra anulación del método __new__
solo afecta nx.Graph
, no sus subclases. Por ejemplo, si llama a nx.gn_graph
, que devuelve una instancia de nx.DiGraph
, no tendrá ninguna de nuestras extensiones de lujo. nx.Graph
subclasificar cada una de las subclases de nx.Graph
que desea trabajar y agregar los métodos y atributos necesarios. El uso de mix-ins puede hacer que sea más fácil extender consistentemente las subclases mientras se obedece 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 __new__
personalizado, debe almacenarlo antes de reemplazarlo y llamar a este método en lugar del object.__new__
.
Para su caso simple también podría escribir su subclase __init__
esta manera y asignar los punteros desde las estructuras de datos de Graph 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 podría usar copy () o deepcopy () en las asignaciones, pero si lo hace, también podría usar
G=MyGraph()
G.add_nodes_from(R)
G.add_edges_from(R.edges())
para cargar su información gráfica
Si solo está agregando comportamiento y no depende de valores de instancia adicionales, puede asignarle al objeto __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)
Huellas dactilares:
10
314.159265359
<__main__.Circle object at 0x00A0E270>
20
62.8318530718
<__main__.CirclePlus object at 0x00A0E270>
Esto es lo más parecido a un "elenco" que se puede obtener en Python, y como en C, no se puede hacer 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.
Si una función está creando 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. Delega los métodos Graph al objeto Graph que tiene, y puede envolver cualquier objeto Graph 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):
....
Simplemente podría crear un nuevo NewGraph
derivado del objeto Graph
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 forma, básicamente copia todas las propiedades del Graph
que tiene sobre 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)