2009-07-03 13 views
13

Digamos que tiene un objeto que fue instanciado de una clase dentro de un módulo. Ahora, recarga ese módulo. Lo siguiente que le gustaría hacer es hacer que la recarga afecte a esa clase.En Python, ¿cómo se cambia un objeto instanciado después de una recarga?

mymodule.py 
--- 
class ClassChange(): 
    def run(self): 
     print 'one' 

myexperiment.py 
--- 
import mymodule 
from mymodule import ClassChange # why is this necessary? 
myObject = ClassChange() 
myObject.run() 
>>> one 
### later, i changed this file, so that it says print 'two' 

reload(mymodule) 
# trick to change myObject needed here 
myObject.run() 
>>> two 

¿Tiene que hacer un nuevo objeto ClassChange, copiar myObject en eso, y eliminar el viejo myObject? ¿O hay una manera más simple?

Editar: El método run() parece un método de estilo de clase estático, pero eso fue solo por brevedad. Me gustaría que el método run() opere con datos dentro del objeto, por lo que una función de módulo estático no funcionaría ...

+2

para responder a sus preguntas adicionales indicadas "¿Por qué es necesario?": No se trata. import mymodule pone mymodule al espacio de nombres, pero mymodule es en sí mismo un espacio de nombres, que contiene ClassChange, por lo que tiene que abordarlo con mymodule.ClassChange – balpha

+0

Gracias balpha, me he estado preguntando por un tiempo :) – user129975

+0

Todas estas respuestas son una gran comida para pensar para mí. Todavía no sé qué respuesta es la correcta, así que jugaré un poco con las soluciones antes de seleccionar. ¡Gracias! :) – user129975

Respuesta

6

Tienes que hacer un objeto nuevo. No hay forma de actualizar mágicamente los objetos existentes.

Lea el reload builtin documentation - está muy claro. Aquí está el último párrafo:

Si un módulo de instancia instancias de una clase, volver a cargar el módulo que define la clase no afecta a las definiciones de métodos de los casos - que siguen utilizando la definición de clase de edad. Lo mismo es cierto para las clases derivadas.

Existen otras advertencias en la documentación, por lo que realmente debería leerlas, y considerar alternativas. Tal vez quiera comenzar una nueva pregunta con por qué desea usar reload y solicitar otras formas de lograr lo mismo.

+0

Gracias por la respuesta, aunque una sombrío. Quiero que el usuario pueda reescribir el código de un objeto y hacer que ese código entre en vigencia. Entonces, por ejemplo, tienes un objeto planetario y criaturas en el planeta. Desea cambiar el código del planeta, por lo que el clima en el planeta cambia el comportamiento. Todas las criaturas tienen punteros a ese planeta, y los datos del planeta son enormes, por lo que borrarlos/crearlos sería doloroso. Tal vez la recarga no es apropiada para este escenario ... – user129975

+0

No seas __demasiado sombrío, aidave - mira mi respuesta ... siempre que sepas qué instancias quieres actualizar, y te muestro una forma de lograr eso , asignar su __class__ o ejecutarlos a través de un método para posiblemente agregar o eliminar algunos atributos es bastante factible (si desea utilizar reload para obtener el nuevo objeto de clase, o cualquier otro enfoque para el mismo propósito, es un problema bastante secundario). –

+0

La cita de documentación dada no implica que "No hay forma de actualizar mágicamente los objetos existentes". Puede, por ejemplo, recorrer el 'gc.get_referrers (oldclass)' o un subconjunto conocido y volver a clasificar (o incluso volver a trabajar con un pequeño código de corrección) las instancias existentes en la medida de lo posible.Esa es una práctica común prolífica para obtener un ciclo de ejecución de edición pythonic rápido durante el desarrollo. – kxr

2

El siguiente código hace lo que quiere, pero favor no lo use (al menos no hasta que esté muy seguro de que está haciendo lo correcto), he publicado sólo para fines de explicación.

mymodule.py:

class ClassChange(): 
    @classmethod 
    def run(cls,instance): 
     print 'one',id(instance) 

myexperiment.py:

import mymodule 

myObject = mymodule.ClassChange() 
mymodule.ClassChange.run(myObject) 

# change mymodule.py here 

reload(mymodule) 
mymodule.ClassChange.run(myObject) 

Cuando en el código que instanciate myObject, se obtiene una instancia de ClassChange. Esta instancia tiene un método de instancia llamado run. El objeto mantiene este método de instancia (por el motivo explicado por nosklo) incluso al volver a cargar, porque la recarga solo recarga la clase ClassChange.

En mi código anterior, run es un método de clase . Los métodos de clase siempre están vinculados ay funcionan en la clase, no en la instancia (razón por la cual su primer argumento generalmente se llama cls, no self). Wenn ClassChange se vuelve a cargar, este es el método de clase.

Puede ver que también paso la instancia como argumento para trabajar con la instancia correcta (misma) de ClassChange. Puede ver eso porque la misma identificación de objeto se imprime en ambos casos.

+0

Interesante. Eso en realidad puede funcionar para lo que tenía en mente, pero es un enfoque totalmente diferente a lo que había imaginado. – user129975

+0

Tenga en mente todos los buenos consejos que recibió de nosklo y también de Alex Martelli en su pregunta anterior. Este es un tema muy difícil. – balpha

1

No estoy seguro de si esta es la mejor manera de hacerlo, o se acopla con lo que quiere hacer ... pero esto puede funcionar para usted. Si desea cambiar el comportamiento de un método, para todos los objetos de un cierto tipo ... simplemente use una variable de función. Por ejemplo:


def default_behavior(the_object): 
    print "one" 

def some_other_behavior(the_object): 
    print "two" 

class Foo(object): 
    # Class variable: a function that has the behavior 
    # (Takes an instance of a Foo as argument) 
    behavior = default_behavior 

    def __init__(self): 
    print "Foo initialized" 

    def method_that_changes_behavior(self): 
    Foo.behavior(self) 

if __name__ == "__main__": 
    foo = Foo() 
    foo.method_that_changes_behavior() # prints "one" 
    Foo.behavior = some_other_behavior 
    foo.method_that_changes_behavior() # prints "two" 

    # OUTPUT 
    # Foo initialized 
    # one 
    # two 

ahora puede tener una clase que se encarga de cargar de nuevo módulos, y después de la recarga, el establecimiento de Foo.behavior a algo nuevo. Probé este código. Funciona bien :-).

¿Esto funciona para usted?

+0

Gracias por la respuesta, pero ¿qué ocurre cuando se necesita más que un comportamiento estático en toda la clase? Debí haber sido más especifico. La función necesitará utilizar datos específicos para esa instanciación. – user129975

+0

¿Qué quieres decir? Puede cambiar el comportamiento() para tomar un objeto ... como el comportamiento (el_objeto), y pasarlo a él. es decir, comportamiento de Foo (self) (en method_that_changes_behavior) – Tom

+0

De esta forma puede usar el estado que es específico del objeto. – Tom

13

Para actualizar todas las instancias de una clase, es necesario realizar un seguimiento de esas instancias, normalmente mediante referencias débiles (el valor débil es la más práctica y general) por lo que la funcionalidad "mantener el seguimiento" no detendrá las instancias innecesarias de irse, por supuesto!

Normalmente, desea mantener un contenedor de este tipo en el objeto de la clase, pero, en este caso, dado que va a volver a cargar el módulo, obtener el objeto de la clase anterior no es trivial; es más sencillo trabajar a nivel de módulo.

Por lo tanto, digamos que un "módulo actualizable" tiene que definir, en su inicio, un valor dict débil (y un auxiliar "llave junto a utilizar" int) con, por ejemplo, nombres convencionales:

import weakref 
class _List(list): pass # a weakly-referenceable sequence 
_objs = weakref.WeakValueDictionary() 
_nextkey = 0 
def _register(obj): 
    _objs[_nextkey] = List((obj, type(obj).__name__)) 
    _nextkey += 1 

Cada clase del módulo debe tener, por lo general en __init__, una llamada _register(self) para registrar nuevas instancias.

Ahora la "función de recarga" puede obtener la lista de todas las instancias de todas las clases en este módulo obteniendo una copia de _objs antes de volver a cargar el módulo.

Si todo lo que se necesita es cambiar el código, entonces la vida es razonablemente fácil:

def reload_all(amodule): 
    objs = getattr(amodule, '_objs', None) 
    reload(amodule) 
    if not objs: return # not an upgraable-module, or no objects 
    newobjs = getattr(amodule, '_objs', None) 
    for obj, classname in objs.values(): 
     newclass = getattr(amodule, classname) 
     obj.__class__ = newclass 
     if newobjs: newobjs._register(obj) 

Por desgracia, uno normalmente no quieren dar la nueva clase la oportunidad de actualizar un objeto de la clase de edad a sí mismo más finamente, por ejemplo por un método de clase adecuado. Eso no es demasiado duro, ya sea:

def reload_all(amodule): 
    objs = getattr(amodule, '_objs', None) 
    reload(amodule) 
    if not objs: return # not an upgraable-module, or no objects 
    newobjs = getattr(amodule, '_objs', None) 
    for obj, classname in objs: 
     newclass = getattr(amodule, classname) 
     upgrade = getattr(newclass, '_upgrade', None) 
     if upgrade: 
      upgrade(obj) 
     else: 
      obj.__class__ = newclass 
     if newobjs: newobjs._register(obj) 

Por ejemplo, dicen que la nueva versión de la clase Zap ha cambiado el nombre de un atributo de foo a bar. Este podría ser el código del nuevo Zap:

class Zap(object): 
    def __init__(self): 
     _register(self) 
     self.bar = 23 

    @classmethod 
    def _upgrade(cls, obj): 
     obj.bar = obj.foo 
     del obj.foo 
     obj.__class__ = cls 

esto no es todo - hay mucho más que decir sobre el tema - Pero, es la esencia, y la respuesta es mucho el tiempo suficiente ya (y yo, agotado lo suficiente ;-).

+0

Guau, muy interesante, lea :-). Creo que mi respuesta todavía tiene algún mérito si el objetivo es muy simple: cambiar el comportamiento de una sola función. Sin embargo, su respuesta es obviamente mucho más general y flexible con respecto a las cosas que puede cambiar. Para eso, +1. Y si pudiera, te daría más +1 ya que aprendo al menos una cosa interesante de tus publicaciones todos los días :-). – Tom

+0

@Tom, de acuerdo, estoy de acuerdo en que su respuesta podría indicar una arquitectura alternativa adecuada para objetivos más simples, y "lo más simple posible, pero no más simple" es seguro cerca de la regla de oro! -) Tx para el +1, y elogios - He luchado con tales problemas (y en algún momento los problemas ganaron la pelea ;-) por tanto tiempo, es justo para mí "pagar hacia adelante" la ayuda que obtuve cuando _I_ tenía menos experiencia y hago mi mejor esfuerzo para ayudar a otros! -) –

+0

Esta es una excelente idea Alex. Gracias. Estoy haciendo un seguimiento de mis objetos, eso no es un problema. De hecho, esto es más flexible, porque luego el usuario puede optar por volver a cargar todos los objetos, solo uno, algunos o ninguno y tener objetos futuros que usen el nuevo código. Le daré una oportunidad a su solución esta semana e informaré aquí. ¡Gracias de nuevo! – user129975

1

Hay trucos para hacer lo que quiera posible.

Alguien ya ha mencionado que puede tener una clase que mantiene una lista de sus instancias, y luego cambiar la clase de cada instancia a la nueva al volver a cargar.

Sin embargo, eso no es eficiente. Un mejor método es cambiar la clase anterior para que sea igual a la nueva clase.

Eche un vistazo a un artículo antiguo que escribí que es probablemente lo mejor que puede hacer para una recarga que modificará los objetos viejos.

http://www.codexon.com/posts/a-better-python-reload

4

Mi aproximación a esto es la siguiente:

  1. Mire a través de todos los módulos importados y volver a cargar sólo aquellos con un nuevo archivo .py (en comparación con el archivo existente .pyc)
  2. Para cada método de función y clase que se recarga, configure old_function .__ code__ = new_function .__ code__.
  3. Para cada clase reloaded, use gc.get_referrers para listar instancias de la clase y establecer su atributo __class__ en la nueva versión.

Las ventajas de este enfoque son:

  • Por lo general, no hay necesidad de recargar los módulos en un orden particular
  • Por lo general, sólo tiene que volver a cargar los módulos con código cambiado y no más
  • no hacer necesita modificar las clases para realizar un seguimiento de sus instancias

Puede leer sobre la técnica (y sus limitaciones) aquí: http://luke-campagnola.blogspot.com/2010/12/easy-automated-reloading-in-python.html

Y se puede descargar el código aquí: http://luke.campagnola.me/code/downloads/reload.py

+0

Sugerí esto para la recarga automática de iPython: https://github.com/ipython/ipython/issues/461#issuecomment-54382721 – dashesy

3

Tienes que conseguir que la nueva clase del módulo fresco y asignarlo a la instancia.

Si pudiera desencadenar esta operación en cualquier momento que utiliza una instancia con este mixin:

import sys 

class ObjDebug(object): 
    def __getattribute__(self,k): 
    ga=object.__getattribute__ 
    sa=object.__setattr__ 
    cls=ga(self,'__class__') 
    modname=cls.__module__ 
    mod=__import__(modname) 
    del sys.modules[modname] 
    reload(mod) 
    sa(self,'__class__',getattr(mod,cls.__name__)) 
    return ga(self,k) 
Cuestiones relacionadas