2009-07-03 10 views
13

Supponiamo di avere un oggetto istanziato da una classe all'interno di un modulo. Adesso ricarichi quel modulo. La prossima cosa che vorresti fare è far sì che la ricarica influenzi quella classe.In Python, come si modifica un oggetto istanziato dopo una ricarica?

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 

Dovete fare un nuovo oggetto Classchange, copiare myObject in quella, e cancellare il vecchio myObject? O c'è un modo più semplice?

Modifica: il metodo run() sembra un metodo di stile di classe statico, ma solo per brevità. Mi piacerebbe che il metodo run() operi sui dati all'interno dell'oggetto, quindi una funzione del modulo statico non farebbe ...

+2

per rispondere alla tua subquestion "Perché questo è necessario?": E non è. import mymodule mette mymodule nello spazio dei nomi, ma mymodule è esso stesso un namespace, che contiene ClassChange, quindi devi affrontarlo con mymodule.ClassChange – balpha

+0

Grazie balpha, mi sono chiesto per un po ':) – user129975

+0

Tutte queste risposte sono ottimo cibo per il pensiero per me. Non so ancora quale risposta sia quella giusta, quindi farò un po 'di gioco con le soluzioni prima di selezionare. Grazie! :) – user129975

risposta

6

Devi creare un nuovo oggetto. Non c'è modo di aggiornare magicamente gli oggetti esistenti.

Leggi il reload builtin documentation - è molto chiaro. Ecco l'ultimo paragrafo:

Se un modulo istanzia le istanze di una classe, ricaricare il modulo che definisce la classe non influenza le definizioni dei metodi delle istanze - continuano a usare la vecchia definizione di classe. Lo stesso vale per le classi derivate.

ci sono altri avvertimenti nella documentazione, in modo da realmente dovrebbe leggerlo, e considerare le alternative. Forse vuoi iniziare una nuova domanda con perché vuoi usare reload e chiedere altri modi per ottenere la stessa cosa.

+0

Grazie per la risposta, anche se un cupo. Voglio che l'utente sia in grado di riscrivere il codice per un oggetto e far sì che il codice sia messo in questione. Quindi, ad esempio, hai un pianeta oggetto e creature sul pianeta. Vuoi cambiare il codice del pianeta, quindi il tempo sul pianeta cambia comportamento. Tutte le creature hanno indicazioni su quel pianeta, e i dati del pianeta sono enormi, quindi eliminarlo/crearlo sarebbe doloroso. Forse ricaricare non è appropriato per questo scenario ... – user129975

+0

Non essere __too__ cupo, aidave - vedi la mia risposta ... purché tu sappia quali istanze vuoi aggiornare, e ti mostro un modo per ottenerlo , assegnare loro __class__ o eseguirli attraverso un metodo per aggiungere o rimuovere alcuni attributi è abbastanza fattibile (se si desidera utilizzare la ricarica per ottenere il nuovo oggetto di classe, o qualsiasi altro approccio per lo stesso scopo, è piuttosto un problema secondario). –

+0

La citazione di documentazione fornita non implica che "Non c'è modo di aggiornare magicamente gli oggetti esistenti". Puoi ad es. cammina il 'gc.get_referrers (oldclass)' o un sottoinsieme noto e ri-classifica (o anche ri-lavora con un piccolo codice di correzione) le istanze esistenti il ​​più lontano possibile.Questa è una pratica comune prolifica per ottenere un rapido ciclo di edit-pythonic durante lo sviluppo. – kxr

2

Il seguente codice fa quello che si vuole, ma si prega di non utilizzare lo (almeno non fino a quando si è molto che si sta facendo la cosa giusta), sto postando per solo scopo esplicativo.

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) 

Quando nel codice si instanciate myObject, si ottiene un'istanza di ClassChange. Questa istanza ha un metodo di istanza chiamato run. L'oggetto mantiene questo metodo di istanza (per la ragione spiegata da nosklo) anche durante il ricaricamento, poiché il ricaricamento si ricarica solo nella classe ClassChange.

In mio codice sopra, run è un metodo di classe . I metodi di classe sono sempre vincolati e operano sulla classe, non sull'istanza (motivo per cui il loro primo argomento è solitamente chiamato cls, non self). Wenn ClassChange viene ricaricato, così è questo metodo di classe.

Si può vedere che passo anche l'istanza come argomento per lavorare con la corretta (stessa) istanza di ClassChange. Lo puoi vedere perché lo stesso ID oggetto è stampato in entrambi i casi.

+0

Interessante. Potrebbe funzionare per quello che avevo in mente, ma è un approccio totalmente diverso da quello che avevo immaginato! – user129975

+0

Tieni a mente tutti i buoni consigli che hai ricevuto da nosklo e anche da Alex Martelli nella tua domanda precedente. Questo è un argomento molto difficile. – balpha

1

Non sono sicuro se questo è il modo migliore per farlo, o si integra con quello che vuoi fare ... ma questo potrebbe funzionare per te. Se vuoi cambiare il comportamento di un metodo, per tutti gli oggetti di un certo tipo ... basta usare una variabile di funzione. Per esempio:


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 

ora è possibile avere una classe che è responsabile per la ricarica dei moduli, e dopo aver ricaricato, l'impostazione Foo.behavior a qualcosa di nuovo. Ho provato questo codice. Funziona benissimo :-).

Funziona per voi?

+0

Grazie per la risposta, ma cosa succede quando hai bisogno di qualcosa di più del comportamento statico in tutta la classe? Avrei dovuto essere più specifico La funzione dovrà utilizzare i dati specifici per tale istanza. – user129975

+0

Cosa intendi? Puoi cambiare comportamento() per prendere un oggetto ... come il comportamento (the_object) e passare a se stesso. es. - Foo.behavior (self) (in method_that_changes_behavior) – Tom

+0

In questo modo è possibile utilizzare lo stato specifico dell'oggetto. – Tom

13

Per aggiornare tutte le istanze di una classe, è necessario tenere traccia da qualche parte su tali istanze, in genere tramite riferimenti deboli (il valore debole è più maneggevole e generale), quindi la funzionalità "tenere traccia" non interrompe le istanze non necessarie dall'andare via, ovviamente!

Normalmente si desidera conservare un tale contenitore nell'oggetto di classe, ma, in questo caso, dal momento che si ricaricherà il modulo, ottenere il vecchio oggetto di classe non è banale; è più semplice lavorare a livello di modulo.

Quindi, diciamo che un "modulo aggiornabile" ha bisogno di definire, al suo inizio, un valore dict debole (e un ausiliario "tasto accanto a usare" int) con, diciamo, i nomi convenzionali:

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 

Ogni classe nel modulo deve avere, in genere in __init__, una chiamata _register(self) per registrare nuove istanze.

Ora la "funzione di ricarica" ​​può ottenere il roster di tutte le istanze di tutte le classi in questo modulo ottenendo una copia di _objs prima di ricaricare il modulo.

Se tutto ciò che serve è quello di cambiare il codice, allora la vita è ragionevolmente facile:

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) 

Ahimè, uno di solito non vuole dare la nuova classe la possibilità di aggiornare un oggetto della vecchia classe a se stesso più finemente, ad es con un metodo di classe adatto. Non è troppo difficile o:

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) 

Per esempio, dicono che la nuova versione di classe Zap ha rinominato un attributo da foo a bar. Questo potrebbe essere il codice del nuovo 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 

questo non è tutto - c'è molto di più da dire su questo argomento - Ma, è l'essenza, e la risposta è abbastanza lungo cammino già (ed io, abbastanza stanco ;-).

+0

Wow, una lettura molto interessante :-). Penso che la mia risposta abbia ancora qualche merito se l'obiettivo è molto semplice: cambiare il comportamento di una singola funzione. Tuttavia, la tua risposta è ovviamente molto più generale e flessibile rispetto alle cose che puoi cambiare. Per questo, +1. E se potessi, ti darei più +1 perché imparo ogni giorno almeno una cosa interessante dai tuoi post :-). – Tom

+0

@Tom, assolutamente, sono d'accordo che la tua risposta potrebbe indicare un'architettura alternativa adatta per obiettivi più semplici - e "il più semplice possibile, ma non più semplice" è sicuramente vicino alla regola d'oro! -) Tx per il +1, e i complimenti - ho lottato con tali problemi (e qualche volta i problemi hanno vinto l'incontro ;-) per così tanto tempo, è giusto per me "pagare in avanti" l'aiuto che ho ottenuto quando ho avuto meno esperienza e faccio del mio meglio per aiutare gli altri fuori! -) –

+0

Questa è un'idea eccellente Alex. Grazie. Sto tenendo traccia dei miei oggetti già, non è un problema. In effetti, questo è più flessibile, perché in tal caso l'utente può scegliere di ricaricare tutti gli oggetti, solo uno, alcuni o nessuno e avere oggetti futuri che usano il nuovo codice. Questa settimana darò una soluzione alla tua soluzione e riferirò qui. Grazie ancora! – user129975

1

Ci sono trucchi per rendere ciò che desideri.

Qualcuno ha già detto che è possibile avere una classe che mantiene un elenco delle sue istanze e quindi cambiare la classe di ogni istanza in quella nuova al momento della ricarica.

Tuttavia, ciò non è efficiente. Un metodo migliore consiste nel cambiare la vecchia classe in modo che sia uguale alla nuova classe.

Dai un'occhiata a un vecchio articolo che ho scritto che è probabilmente il meglio che puoi fare per una ricarica che modificherà i vecchi oggetti.

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

4

Il mio approccio a questo è il seguente:

  1. Guardare attraverso tutti i moduli importati e ricaricare solo quelli con un nuovo file .py (rispetto al file .pyc esistente)
  2. Per ogni funzione e metodo di classe che viene ricaricato, impostare old_function .__ code__ = new_function .__ code__.
  3. Per ogni classe reloaded, utilizzare gc.get_referrers per elencare le istanze della classe e impostare l'attributo __class__ sulla nuova versione.

vantaggi di questo approccio sono:

  • Di solito non c'è bisogno di ricaricare i moduli in un ordine particolare
  • Di solito solo bisogno di ricaricare i moduli con codice modificato e non più
  • Non è necessario modificare le classi per tenere traccia delle istanze

Qui è possibile leggere la tecnica (ei relativi limiti): http://luke-campagnola.blogspot.com/2010/12/easy-automated-reloading-in-python.html

E si può scaricare il codice qui: http://luke.campagnola.me/code/downloads/reload.py

+0

Ho suggerito questo per il suo caricamento automatico di iPython: https://github.com/ipython/ipython/issues/461#issuecomment-54382721 – dashesy

3

Dovete ottenere la nuova classe dal modulo fresco e assegnarlo di nuovo all'istanza.

Se si potesse innescare questo momento operazione si utilizza un'istanza con questo 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) 
Problemi correlati