2011-02-01 12 views
12

(« variables » désigne ici « noms », je pense, pas tout à fait sûr les Pythoneux définition utilisent)"Pythonique" façon de "réinitialiser" les variables d'un objet?

J'ai un objet et des méthodes. Ces méthodes ont toutes besoin et toutes changent les variables de l'objet. Comment puis-je, dans le plus pythonique et dans le meilleur, en respectant les techniques de POO, parvenir à avoir les variables d'objet utilisées par les méthodes mais aussi garder leurs valeurs originales pour les autres méthodes?

Dois-je copier l'objet chaque fois qu'une méthode est appelée? Dois-je sauvegarder les valeurs d'origine et avoir une méthode reset() pour les réinitialiser chaque fois qu'une méthode en a besoin? Ou y a-t-il un moyen encore meilleur?

EDIT: Le pseudocode m'a été demandé. Depuis que je suis plus intéressé à comprendre le concept plutôt que de résoudre spécifiquement le problème que je rencontrais, je vais essayer donner un exemple:

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 

Ce qui précède est ma classe pour un objet Player (pour l'exemple suppose qu'il est un ballon de basketball). Cet objet a trois méthodes différentes qui assignent toutes des valeurs aux statistiques des joueurs. Donc, disons que l'équipe a deux matchs de ligue, puis un match de coupe. Je dois faire ces appels:

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

Il est évident que lorsque le deuxième et le troisième appels sont effectués, les statistiques modifiées précédemment du joueur doivent être remis à zéro. Pour cela, je peux soit écrire une méthode de réinitialisation qui remet toutes les variables à 0, ou copier l'objet pour chaque appel que je fais. Ou faire quelque chose de complètement différent.

Voilà où ma question se pose, quelle est la meilleure approche, python et oop sage?

MISE À JOUR: Je suis suspect que j'ai super-compliqué cela et je peux facilement résoudre mon problème en utilisant des variables locales dans les fonctions. Cependant, que se passe-t-il si j'ai une fonction à l'intérieur d'une autre fonction, puis-je utiliser les locaux de l'extérieur à l'intérieur de l'intérieur?

+4

Pouvez-vous expliquer votre intention, le comportement souhaité? Est-ce la méthode de chaînage, comme dans jQuery? –

+1

Peut-être qu'un pseudocode aiderait à expliquer pourquoi il est nécessaire de changer les variables d'objet mais de les changer à la fin de la fonction, plutôt que d'utiliser simplement des variables locales. – tkerwin

+0

J'ai lu ceci plusieurs fois et je ne suis toujours pas sûr de savoir quand une méthode voudra voir les valeurs des membres mises à jour et quand elle ne le sera pas. Cela semble avoir besoin d'un agent, d'une façade ou d'un modèle MVC. –

Répondre

7

Je ne sais pas si elle est « Pythonic » assez, mais vous pouvez définir un « réinitialisés » décorateur pour la méthode __init__ qui crée une copie __dict__ de l'objet et ajoute une méthode reset() qui passe le __dict__ courant à l'original.

Edition - Voici un exemple d'implémentation:

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: Ajout d'un test avec l'héritage

+2

En Python, ils sont appelés des décorateurs, pas des annotations –

+0

+1 Oui! Je les mélange toujours - Chose amusante, c'est qu'en mettant en place je continue à penser "décorateurs", mais j'ai tapé des "annotations" de toute façon. Yikes. – PaoloVictor

+0

-1, L'utilisation de '__dict__' comme source de tous les attributs d'un objet n'est pas fiable. Il ne tient pas compte de l'héritage ou des métaclasses. – Apalala

7

Je ne suis pas sûr « pythonique », mais pourquoi ne pas simplement créer une méthode reset dans votre objet cela fait quoi la réinitialisation est nécessaire? Appelez cette méthode dans le cadre de votre __init__ afin de ne pas dupliquer les données (ie: toujours (ré) initialiser en un seul endroit - la méthode reset)

+0

+1, Bien que l'échange de '__dict__' est intelligent et tout, c'est la méthode la plus explicite (et donc la plus pythonienne à mon humble avis). Un objet immuable est également une bonne option, mais seulement si vos méthodes n'ont pas besoin de changer les valeurs lors du calcul. – senderle

+0

+1 Oubliez _pythonic_. La signification de 'reset()' doit être définie par la classe qui en a besoin. – Apalala

2

Cela semble vouloir dire si votre classe devrait être un immutable object. L'idée est que, une fois créé, un objet immuable ne peut/ne devrait pas/ne devrait pas être changé.

Sur Python, types intégrés comme int ou tuple instances sont immuables, appliquées par la langue:

>>> 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 

Comme autre exemple, chaque fois que vous ajoutez deux entiers une nouvelle instance est créée:

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

Le id() function renvoie l'adresse de l'objet int12000. Et chaque fois que nous ajoutons a+b une nouvelle instance d'objet 12000 est créée.

utilisateur Les classes immuables définies doivent être appliquées manuellement, ou tout simplement fait comme une convention avec un code source commentaire:

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 toute façon, il est OK pour créer une nouvelle instance pour chaque opération.

+1

Merci pour cette réponse, très utile! –

1

Voir la Memento Design Pattern si vous voulez restaurer l'état précédent, ou Proxy Design Pattern si vous voulez que l'objet semble vierge, comme si vous venez de créer. En tout cas, vous devez mettre quelque chose entre ce qui est référencé, et c'est l'état.

S'il vous plaît commenter si vous avez besoin de code, mais je suis sûr que vous trouverez beaucoup sur le web si vous utilisez les noms de modèle de conception comme mots-clés.

# 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

Je voudrais créer un default dict en tant que membre de données avec toutes les valeurs par défaut, puis faites __dict__.update(self.default) pendant __init__, puis de nouveau à un moment plus tard, de tirer toutes les valeurs de retour. Plus généralement, vous pouvez utiliser un hook __setattr__ pour garder une trace de chaque variable qui a été modifiée et plus tard utiliser ces données pour les réinitialiser.

1

Il semble que votre conception ait besoin d'être retravaillée. Qu'en est-il d'une classe PlayerGameStatistics qui garderait une trace de tout cela, et soit un Player ou un Game détiendrait une collection de ces objets?

Aussi le code que vous montrez est un bon début, mais pourriez-vous montrer plus de code qui interagit avec la classe Player? Je ai juste du mal à voir pourquoi un seul objet Player devrait avoir PlayXGame méthodes - est-ce qu'un seul Player interagir avec d'autres Player s lors de la lecture d'un jeu, ou pourquoi un Player spécifique jouer le jeu?

0

Une méthode de réinitialisation simple (appelée en __init__ et rappelée si nécessaire) a beaucoup de sens. Mais voici une solution qui me semble intéressante, si un peu trop technique: créer un gestionnaire de contexte. Je suis curieux de savoir ce que les gens pensent de ça ...

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 

Pour plus-over-ingénieur, vous pouvez ensuite créer un @resetme décorateur

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

Ainsi, au lieu d'avoir à utiliser explicitement with vous pouvez simplement utiliser le décorateur:

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

Je pense que c'est trop complexe. 'def reset (self): ...' est complètement évident sans nécessiter un tas de code supplémentaire. Rien d'autre ne fait qu'ajouter de la complexité sans gain IMO. –

+0

Eh bien, je pense qu'il y a un certain gain, au lieu de revenir à un état fixe qui ne peut pas être changé facilement, les valeurs de foo et de bar retournent à leur état juste avant l'appel de la méthode. Si vous voulez cette fonctionnalité dans une grande classe avec beaucoup de méthodes, cette technique vous évite d'avoir à copier l'état tout le temps. Pourtant, je suis surtout d'accord avec vous :). Je pensais juste que c'était un exercice amusant. – senderle

0

Il me semble que vous devez retravailler votre modèle pour au moins inclure une classe "PlayerGameStats" distincte.

Quelque chose le long des lignes 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) 

Et pour répondre à la question dans votre édition, oui fonctions imbriquées peut voir les variables stockées dans les étendues extérieures. Vous pouvez lire plus à ce sujet dans le tutoriel: http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces

0

merci pour la belle entrée, car j'avais un problème similaire. Je le résous avec un hook sur la méthode init, car j'aimerais pouvoir revenir à l'état initial d'un objet. Voici mon code:

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()" 

Pour mettre à jour l'état initial, vous pourriez construire une méthode comme remise à zéro (...) qui écrit des données dans _tool_init_states. J'espère que cela aide quelqu'un. Si cela est possible sans métaclasse, s'il vous plaît faites le moi savoir.

0

J'ai aimé (et essayé) la meilleure réponse de PaoloVictor. Cependant, j'ai trouvé qu'il se "réinitialisait" lui-même, c'est-à-dire que si vous appeliez reset() une seconde fois, il lancerait une exception.

Je trouve que cela a fonctionné répétable avec la mise en œuvre suivante

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__ 
Questions connexes