开发者

"Pythonic" way to "reset" an object's variables?

开发者 https://www.devze.com 2023-02-07 19:01 出处:网络
(\"variables\" here refers to \"names\", I think, not completely sure about the definition pythonistas use)

("variables" here refers to "names", I think, not completely sure about the definition pythonistas use)

I have an object and some methods. These methods all need and all change the object's variables. How can I, in the most pythonic and in the best, respecting the techniques of OOP, way achieve to have the object variables used by the methods but also keep their original values for the other methods?

Should I copy the object everytime a method is called? Should I save the original values and have a reset() method to reset them everytime a method needs them? Or is there an even better way?

EDIT: I was asked for pseudocode. Since I am more interested in understanding the concept rather than just specifically solving the problem I am encountering I am going to try开发者_运维问答 give an example:

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

The above is my class for a Player object (for the example assume he is a basketball one). This object has three different methods that all assign values to the players' statistics.

So, let's say the team has two league games and then a cup game. I'd have to make these calls:

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

It's obvious that when the second and the third calls are made, the previously changed statistics of the player need to be reset. For that, I can either write a reset method that sets all the variables back to 0, or copy the object for every call I make. Or do something completely different.

That's where my question lays, what's the best approach, python and oop wise?

UPDATE: I am suspicious that I have superovercomplicated this and I can easily solve my problem by using local variables in the functions. However, what happens if I have a function inside another function, can I use locals of the outer one inside the inner one?


Not sure if it's "Pythonic" enough, but you can define a "resettable" decorator for the __init__ method that creates a copy the object's __dict__ and adds a reset() method that switches the current __dict__ to the original one.

Edit - Here's an example implementation:

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: Added a test with inheritance


I'm not sure about "pythonic", but why not just create a reset method in your object that does whatever resetting is required? Call this method as part of your __init__ so you're not duplicating the data (ie: always (re)initialize it in one place -- the reset method)


I would create a default dict as a data member with all of the default values, then do __dict__.update(self.default) during __init__ and then again at some later point to pull all the values back.

More generally, you can use a __setattr__ hook to keep track of every variable that has been changed and later use that data to reset them.


Sounds like you want to know if your class should be an immutable object. The idea is that, once created, an immutable object can't/should't/would't be changed.

On Python, built-in types like int or tuple instances are immutable, enforced by the language:

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

As another example, every time you add two integers a new instance is created:

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

The id() function returns the address of the int object 12000. And every time we add a+b a new 12000 object instance is created.

User defined immutable classes must be enforced manually, or simply done as a convention with a source code comment:

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):
        """..."""

Either way, it's OK to create a new instance for every operation.


See the Memento Design Pattern if you want to restore previous state, or the Proxy Design Pattern if you want the object to seem pristine, as if just created. In any case, you need to put something between what's referenced, and it's state.

Please comment if you need some code, though I'm sure you'll find plenty on the web if you use the design pattern names as keywords.

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


It sounds like overall your design needs some reworking. What about a PlayerGameStatistics class that would keep track of all that, and either a Player or a Game would hold a collection of these objects?

Also the code you show is a good start, but could you show more code that interacts with the Player class? I'm just having a hard time seeing why a single Player object should have PlayXGame methods -- does a single Player not interact with other Players when playing a game, or why does a specific Player play the game?


A simple reset method (called in __init__ and re-called when necessary) makes a lot of sense. But here's a solution that I think is interesting, if a bit over-engineered: create a context manager. I'm curious what people think about this...

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

To over-over-engineer, you could then create a @resetme decorator

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

So that instead of having to explicitly use with you could just use the decorator:

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


I liked (and tried) the top answer from PaoloVictor. However, I found that it "reset" itself, i.e., if you called reset() a 2nd time it would throw an exception.

I found that it worked repeatably with the following implementation

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__


It sounds to me like you need to rework your model to at least include a separate "PlayerGameStats" class.

Something along the lines of:

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)

And to answer the question in your edit, yes nested functions can see variables stored in outer scopes. You can read more about that in the tutorial: http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces


thanks for the nice input, as I had kind of a similar problem. I'm solving it with a hook on the init method, since I'd like to be able to reset to whatever initial state an object had. Here's my 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()"

To update the initial state, you could build some method like reset(...) that writes data into _tool_init_states. I hope this helps somebody. If this is possible without a metaclass, please let me know.

0

精彩评论

暂无评论...
验证码 换一张
取 消