¿La forma “pythonica” de “restablecer” las variables de un objeto?

(“variables” aquí se refiere a “nombres”, creo, no completamente seguro acerca de la definición que usan los pitones)

Tengo un objeto y algunos métodos. Todos estos métodos necesitan y todos cambian las variables del objeto. ¿Cómo puedo, de la manera más sintética y mejor, respetando las técnicas de la POO, lograr que las variables objeto sean utilizadas por los métodos pero también conservar sus valores originales para los otros métodos?

¿Debo copiar el objeto cada vez que se llama un método? ¿Debo guardar los valores originales y tener un método reset () para restablecerlos cada vez que un método los necesita? ¿O hay una manera aún mejor?

EDITAR: Me pidieron pseudocódigo. Ya que estoy más interesado en entender el concepto en lugar de resolver específicamente el problema que estoy encontrando, voy a intentar dar un ejemplo:

class Player(): games = 0 points = 0 fouls = 0 rebounds = 0 assists = 0 turnovers = 0 steals = 0 def playCupGame(self): # simulates a game and then assigns values to the variables, accordingly self.points = K #just an example def playLeagueGame(self): # simulates a game and then assigns values to the variables, accordingly self.points = Z #just an example self.rebounds = W #example again def playTrainingGame(self): # simulates a game and then assigns values to the variables, accordingly self.points = X #just an example self.rebounds = Y #example again 

Lo anterior es mi clase para un objeto Jugador (por ejemplo, supongamos que es uno de baloncesto). Este objeto tiene tres métodos diferentes que asignan valores a las estadísticas de los jugadores.

Entonces, digamos que el equipo tiene dos juegos de liga y luego un juego de copa. Tendría que hacer estas llamadas:

 p.playLeagueGame() p.playLeagueGame() p.playCupGame() 

Es obvio que cuando se realizan las llamadas segunda y tercera, es necesario restablecer las estadísticas cambiadas previamente del jugador. Para eso, puedo escribir un método de restablecimiento que establezca todas las variables de nuevo en 0, o copiar el objeto para cada llamada que haga. O hacer algo completamente diferente.

Ahí es donde se encuentra mi pregunta, ¿cuál es el mejor enfoque? ¿Python y OOP sabiamente?

ACTUALIZACIÓN: sospecho que he supercomplicado esto y puedo resolver fácilmente mi problema usando variables locales en las funciones. Sin embargo, ¿qué sucede si tengo una función dentro de otra función, puedo usar locales de la externa dentro de la interna?

No estoy seguro acerca de “pythonic”, pero ¿por qué no crear un método de reset en su objeto que haga lo que sea necesario? Llame a este método como parte de su __init__ modo que no esté duplicando los datos (es decir, siempre (re) inicialícelos en un solo lugar, el método de reset )

No estoy seguro de si es lo suficientemente “Pythonic”, pero puedes definir un decorador “reseteable” para el método __init__ que crea una copia del __dict__ del objeto y agrega un método reset() que cambia el __dict__ actual al original.

Editar – Aquí hay una implementación de ejemplo:

 def resettable(f): import copy def __init_and_copy__(self, *args, **kwargs): f(self, *args) self.__original_dict__ = copy.deepcopy(self.__dict__) def reset(o = self): o.__dict__ = o.__original_dict__ self.reset = reset return __init_and_copy__ class Point(object): @resettable def __init__(self, x, y): self.x = x self.y = y def __str__(self): return "%d %d" % (self.x, self.y) class LabeledPoint(Point): @resettable def __init__(self, x, y, label): self.x = x self.y = y self.label = label def __str__(self): return "%d %d (%s)" % (self.x, self.y, self.label) p = Point(1, 2) print p # 1 2 px = 15 py = 25 print p # 15 25 p.reset() print p # 1 2 p2 = LabeledPoint(1, 2, "Test") print p2 # 1 2 (Test) p2.x = 3 p2.label = "Test2" print p2 # 3 2 (Test2) p2.reset() print p2 # 1 2 (Test) 

Edit2: Añadido una prueba con herencia

Me gustaría crear un dict default como miembro de datos con todos los valores predeterminados, luego hacer __dict__.update(self.default) durante __init__ y luego otra vez en algún punto posterior para recuperar todos los valores.

De manera más general, puede usar un gancho __setattr__ para realizar un seguimiento de cada variable que se haya cambiado y luego usar esos datos para restablecerlos.

Parece que quieres saber si tu clase debería ser un objeto inmutable . La idea es que, una vez creado, un objeto inmutable no puede / debería / no podría ser cambiado.

En Python , los tipos incorporados, como las instancias int o tuple , son inmutables, impuestas por el lenguaje:

 >>> a=(1, 2, 3, 1, 2, 3) >>> a[0] = 9 Traceback (most recent call last): File "", line 1, in  TypeError: 'tuple' object does not support item assignment 

Como otro ejemplo, cada vez que agrega dos enteros se crea una nueva instancia:

 >>> a=5000 >>> b=7000 >>> d=a+b >>> d 12000 >>> id(d) 42882584 >>> d=a+b >>> id(d) 42215680 

La función id() devuelve la dirección del objeto int 12000 . Y cada vez que agregamos a+b se crea una nueva instancia de 12000 objetos.

Las clases inmutables definidas por el usuario deben imponerse manualmente, o simplemente hacerse como una convención con un comentario de código fuente:

 class X(object): """Immutable class. Don't change instance variables values!""" def __init__(self, *args): self._some_internal_value = ... def some_operation(self, arg0): new_instance = X(arg0 + ...) new_instance._some_internal_operation(self._some_internal_value, 42) return new_instance def _some_internal_operation(self, a, b): """...""" 

De cualquier manera, está bien crear una nueva instancia para cada operación.

Parece que, en general, su diseño necesita algunas modificaciones. ¿Qué pasa con una clase de PlayerGameStatistics que realizaría un seguimiento de todo eso, y un Player o un Game mantendría una colección de estos objetos?

Además, el código que muestra es un buen comienzo, pero ¿podría mostrar más código que interactúe con la clase Player ? Solo estoy teniendo dificultades para ver por qué un solo Player debe tener métodos de PlayXGame : ¿no interactúa un solo Player con otros Player cuando juega un juego, o por qué un Player específico juega el juego?

Un método de reinicio simple (llamado en __init__ y llamado cuando es necesario) tiene mucho sentido. Pero aquí hay una solución que creo que es interesante, aunque un poco diseñada en exceso: crear un administrador de contexto. Tengo curiosidad por lo que la gente piensa acerca de esto …

 from contextlib import contextmanager @contextmanager def resetting(resettable): try: resettable.setdef() yield resettable finally: resettable.reset() class Resetter(object): def __init__(self, foo=5, bar=6): self.foo = foo self.bar = bar def setdef(self): self._foo = self.foo self._bar = self.bar def reset(self): self.foo = self._foo self.bar = self._bar def method(self): with resetting(self): self.foo += self.bar print self.foo r = Resetter() r.method() # prints 11 r.method() # still prints 11 

Para sobre-diseñar, puedes crear un decorador @resetme

 def resetme(f): def rf(self, *args, **kwargs): with resetting(self): f(self, *args, **kwargs) return rf 

De modo que, en lugar de tener que usar explícitamente contigo, solo puedes usar el decorador:

 @resetme def method(self): self.foo += self.bar print self.foo 

Consulte el Patrón de diseño de Memento si desea restaurar el estado anterior, o el Patrón de diseño de proxy si desea que el objeto parezca prístino , como si se hubiera creado. En cualquier caso, debe poner algo entre lo que se menciona y su estado.

Por favor, comente si necesita algún código, aunque estoy seguro de que encontrará un montón en la web si utiliza los nombres de los patrones de diseño como palabras clave.

 # The Memento design pattern class Scores(object): ... class Player(object): def __init__(self,...): ... self.scores = None self.history = [] self.reset() def reset(self): if (self.scores): self.history.append(self.scores) self.scores = Scores() 

Me parece que necesitas volver a trabajar con tu modelo para incluir al menos una clase “PlayerGameStats” separada.

Algo a lo largo de las líneas de:

 PlayerGameStats = collections.namedtuple("points fouls rebounds assists turnovers steals") class Player(): def __init__(self): self.cup_games = [] self.league_games = [] self.training_games = [] def playCupGame(self): # simulates a game and then assigns values to the variables, accordingly stats = PlayerGameStats(points, fouls, rebounds, assists, turnovers, steals) self.cup_games.append(stats) def playLeagueGame(self): # simulates a game and then assigns values to the variables, accordingly stats = PlayerGameStats(points, fouls, rebounds, assists, turnovers, steals) self.league_games.append(stats) def playTrainingGame(self): # simulates a game and then assigns values to the variables, accordingly stats = PlayerGameStats(points, fouls, rebounds, assists, turnovers, steals) self.training_games.append(stats) 

Y para responder a la pregunta en su edición, sí, las funciones anidadas pueden ver las variables almacenadas en los ámbitos externos. Puede leer más sobre eso en el tutorial: http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces

Gracias por la buena entrada, ya que tuve un problema similar. Lo estoy resolviendo con un gancho en el método init, ya que me gustaría poder restablecer el estado inicial que tenía un objeto. Aquí está mi código:

 import copy _tool_init_states = {} def wrap_init(init_func): def init_hook(inst, *args, **kws): if inst not in _tool_init_states: # if there is a class hierarchy, only the outer scope does work _tool_init_states[inst] = None res = init_func(inst, *args, **kws) _tool_init_states[inst] = copy.deepcopy(inst.__dict__) return res else: return init_func(inst, *args, **kws) return init_hook def reset(inst): inst.__dict__.clear() inst.__dict__.update( copy.deepcopy(_tool_init_states[inst]) ) class _Resettable(type): """Wraps __init__ to store object _after_ init.""" def __new__(mcs, *more): mcs = super(_Resetable, mcs).__new__(mcs, *more) mcs.__init__ = wrap_init(mcs.__init__) mcs.reset = reset return mcs class MyResettableClass(object): __metaclass__ = Resettable def __init__(self): self.do_whatever = "you want," self.it_will_be = "resetted by calling reset()" 

Para actualizar el estado inicial, podría crear algún método como restablecer (…) que escribe datos en _tool_init_states. Espero que esto ayude a alguien. Si esto es posible sin una metaclase, hágamelo saber.

Me gustó (y probé) la mejor respuesta de PaoloVictor. Sin embargo, encontré que se “restablece” a sí mismo, es decir, si llama a reset () una segunda vez, se lanzará una excepción.

Encontré que funcionó repetidamente con la siguiente implementación

 def resettable(f): import copy def __init_and_copy__(self, *args, **kwargs): f(self, *args, **kwargs) def reset(o = self): o.__dict__ = o.__original_dict__ o.__original_dict__ = copy.deepcopy(self.__dict__) self.reset = reset self.__original_dict__ = copy.deepcopy(self.__dict__) return __init_and_copy__