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?
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.
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__
.
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):
....
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.
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)
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
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