2011-02-01 13 views
12

("variables" se refiere aquí a "nombres", creo, no del todo seguro acerca de los Pythonistas definición utilizan)"Pitonica" forma de "reiniciar" las variables de un objeto?

Tengo un objeto y algunos métodos. Todos estos métodos necesitan y todos cambian las variables del objeto. ¿Cómo puedo, de la forma más pitónica y en el mejor de los casos, respetar las técnicas de OOP, lograr las variables de objeto utilizadas por los métodos pero también mantener sus valores originales para los otros métodos?

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

EDIT: Me pidieron un pseudocódigo. Desde que estoy más interesado en la comprensión del concepto y no sólo resolver específicamente el problema que estoy encontrando que voy a tratar de 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 Player (para el ejemplo asume 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 hacen el segundo y el tercer llamadas, las estadísticas cambiado previamente de que el jugador deba volver a programar. Para eso, puedo escribir un método de reinicio que restablece todas las variables a 0 o copiar el objeto para cada llamada que hago. O haz algo completamente diferente.

Ahí está mi pregunta, ¿cuál es el mejor enfoque, python y oop wise?

ACTUALIZACIÓN: Sospecho que tengo superovercomplicated esto y puedo resolver fácilmente mi problema mediante el uso de variables locales en las funciones. Sin embargo, ¿qué sucede si tengo una función dentro de otra función, puedo usar los locales del exterior dentro del interior?

+4

Podría explicar su intención, el comportamiento deseado? ¿Es el método de encadenamiento, como en jQuery? –

+1

Quizás algún pseudocódigo ayude a explicar por qué es necesario cambiar las variables del objeto pero cambiarlas al final de la función, en lugar de simplemente usar variables locales. – tkerwin

+0

He leído esto algunas veces y todavía no estoy seguro de cómo saber cuándo un método querrá ver los valores actualizados de los miembros y cuándo no. Parece que necesita un patrón Agent, Facade o MVC. –

Respuesta

7

No está seguro de si se trata de "Pythonic" lo suficiente, pero se puede definir un decorador de "puesta a cero" para el método __init__ que crea una copia de __dict__ el objeto y añade un método reset() que cambia la corriente __dict__ a la original.

Editar - He aquí un ejemplo de implementación:

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 

p.x = 15 
p.y = 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: Se ha añadido una prueba con la herencia

+2

En Python, se llaman decoradores, no anotaciones –

+0

+1 ¡Sí! Siempre los confundí. Lo curioso es que durante la implementación sigo pensando en "decoradores", pero de todos modos escribí "anotaciones". Yikes. – PaoloVictor

+0

-1, Usar '__dict__' como la fuente de todos los atributos de un objeto no es confiable. No tiene en cuenta la herencia o las metaclases. – Apalala

7

No estoy seguro acerca de "Pythonic", pero ¿por qué no crear un método reset en su objetivo que hace lo que se requiere restablecer? Llame a este método como parte de su __init__ para no duplicar los datos (es decir: siempre (re) inicializarlo en un solo lugar - el método reset)

+0

+1, aunque el intercambio de '__dict__' es inteligente y todo, este es el método más explícito (y por lo tanto más en mi humilde opinión) en mi humilde opinión. Un objeto inmutable también es una buena opción, pero solo si sus métodos no necesitan cambiar los valores durante el cálculo. – senderle

+0

+1 Olvídate de _pythonic_. El significado de 'reset()' debe ser definido por la clase que lo necesita. – Apalala

2

Parece que desea saber si su clase debe ser una immutable object. La idea es que, una vez creado, un objeto inmutable no puede/no debe/no se cambiará.

En Python, tipos integrados como int o tuple casos son inmutables, forzada por el lenguaje:

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

Como otro ejemplo, cada vez que añada 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 

El id() function devuelve la dirección del objeto int12000. Y cada vez que agregamos a+b, se crea una nueva instancia de objeto 12000.

usuario clases inmutables definidos deben aplicarse de forma manual, o simplemente hacer como una convención con un comentario 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 para crear una nueva instancia para cada operación.

+1

Gracias por esta respuesta, ¡bastante útil! –

1

Véase el Memento Design Pattern si desea restaurar el estado anterior o el Proxy Design Pattern si desea que el objeto parece prístina, como si acaba de crear. En cualquier caso, debe poner algo entre lo que se hace referencia, y su estado.

Comente si necesita algún código, aunque estoy seguro de que encontrará muchos en la web si utiliza los nombres de 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() 
2

me gustaría crear un diccionario default como un miembro de datos con todos los valores por defecto, y luego hacer __dict__.update(self.default) durante __init__ y luego otra vez en algún momento posterior a tirar de todos los valores de nuevo.

Más en general, puede utilizar un gancho __setattr__ para realizar un seguimiento de cada variable que se ha cambiado y luego utilizar esos datos para restablecerlos.

1

Parece que, en general, su diseño necesita una nueva versión. ¿Qué pasa con una clase PlayerGameStatistics que llevaría un registro de todo eso y un Player o un Game contendría una colección de estos objetos?

También 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 objeto Player debe tener PlayXGame métodos - ¿un solo Player no interactúa con otros Player s al jugar un juego, o por qué un juego específico Player juega?

0

Un método de restauración simple (llamado en __init__ y vuelto a llamar cuando sea necesario) tiene mucho sentido. Pero aquí hay una solución que creo que es interesante, aunque un poco sobrediseñada: cree un administrador de contexto. Tengo curiosidad de lo que la gente piensa sobre 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-sobre-ingeniero, a continuación, puede crear una @resetme decorador

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

Así que en lugar de tener que utilizar explícitamente with usted podría utilizar el decorador:

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

Creo que es demasiado complejo. 'def reset (self): ...' es completamente obvio sin requerir un montón de código adicional. Cualquier otra cosa solo agrega complejidad para que no ganes OMI. –

+0

Bueno, creo que hay algo de ganancia, en que en lugar de volver a un estado fijo que no se puede cambiar fácilmente, los valores de foo y barra vuelven a su estado justo antes de que se llamara el método. Si desea esta funcionalidad en una clase grande con muchos métodos, esta técnica le evita tener que copiar el estado todo el tiempo. Aún así, estoy de acuerdo contigo :) Solo pensé que era un ejercicio divertido. – senderle

0

Me parece que necesita volver a trabajar su modelo para incluir al menos una clase de "PlayerGameStats" por separado.

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 contestar a la pregunta en su edición, funciones anidadas sí puede ver las variables almacenadas en ámbitos exteriores. Puede leer más sobre eso en el tutorial: http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces

0

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 restablecerlo a cualquier estado inicial que tenga 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, se podría construir algún método como el restablecimiento (...) que escribe datos en _tool_init_states. Espero que esto ayude a alguien. Si esto es posible sin una metaclase, házmelo saber.

0

Me gustó (y probé) la respuesta principal de PaoloVictor. Sin embargo, descubrí que se "reiniciaba", es decir, si llamaba a reset() por segunda vez arrojaría una excepción.

encontré que trabajó 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__ 
Cuestiones relacionadas