Reparto de clase base para pitón clase derivada (o de forma más Pythonic de extender las clases)
-
27-09-2019 - |
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?
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