“Pythonic”方式“重置”一个对象的变量?

时间:2011-02-01 18:40:35

标签: variables python global local

(“变量”在这里指的是“名称”,我认为,不完全确定pythonistas使用的定义)

我有一个对象和一些方法。这些方法都需要并且都会更改对象的变量。我怎样才能在最苛刻和最好的方面尊重OOP技术,实现方法所使用的对象变量,同时保留其他方法的原始值?

每次调用方法时都应该复制对象吗?我应该保存原始值并使用reset()方法在每次方法需要时重置它们吗?或者有更好的方法吗?

编辑:我被问到伪代码。由于我更感兴趣的是理解这个概念,而不仅仅是专门解决我遇到的问题,我将试着举个例子:

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

以上是我的Player对象类(例如假设他是篮球对象)。该对象有三种不同的方法,它们都为玩家的统计数据赋值。

所以,让我们说球队有两场联赛,然后是杯赛。我必须打这些电话:

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

很明显,当进行第二次和第三次调用时,需要重置先前更改的玩家统计数据。为此,我可以编写一个重置方法,将所有变量设置为0,或者为每次调用复制对象。或者做一些完全不同的事情。

这就是我的问题所在,最好的方法是什么,python和oop明智?

更新:我怀疑我有这个问题,我可以通过在函数中使用局部变量轻松解决我的问题。但是,如果我在另一个函数中有一个函数会发生什么,我可以在内部函数中使用外部函数的本地函数吗?

10 个答案:

答案 0 :(得分:7)

不确定它是否足够“Pythonic”,但你可以定义一个“可重置”装饰器 对于__init__方法创建副本对象的__dict__并添加reset()方法,将当前__dict__切换为原始方法。

编辑 - 这是一个示例实现:

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:添加了一个带继承的测试

答案 1 :(得分:7)

我不确定“pythonic”,但为什么不在你的对象中创建一个reset方法来进行任何需要的重置?将此方法称为__init__的一部分,这样您就不会复制数据(即:始终(重新)在一个位置初始化它 - reset方法)

答案 2 :(得分:3)

我会创建一个default dict作为包含所有默认值的数据成员,然后在__dict__.update(self.default)期间执行__init__,然后在稍后的某个时间再次将所有值拉回来

更一般地说,您可以使用__setattr__挂钩来跟踪已更改的每个变量,然后使用该数据重置它们。

答案 3 :(得分:2)

听起来你想知道你的班级应该是immutable object。这个想法是,一旦创建,就不会/不应该改变不可变对象。

Python上,inttuple个实例等内置类型是不可变的,由语言强制执行:

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

作为另一个例子,每次添加两个整数时都会创建一个新实例:

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

id() function返回int对象12000的地址。每次添加a+b时,都会创建一个新的12000对象实例。

用户定义的不可变类必须手动强制执行,或者只是作为带有源代码注释的约定完成:

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

无论哪种方式,都可以为每个操作创建一个新实例。

答案 4 :(得分:1)

如果您想要恢复以前的状态,请参阅Memento Design Pattern;如果您希望对象看起来像 pristine ,请参阅Proxy Design Pattern,就像刚刚创建的那样。在任何情况下,您都需要在引用的内容和状态之间放置某些内容

如果您需要一些代码,请发表评论,但如果您将设计模式名称用作关键字,我相信您会在网上找到很多代码。

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

答案 5 :(得分:1)

听起来总的来说你的设计需要一些改造。那个跟踪所有内容的PlayerGameStatistics类怎么样?PlayerGame会保存这些对象的集合呢?

此外,您展示的代码是一个良好的开端,但是您是否可以展示更多与Player类交互的代码?我只是很难看到为什么单个Player对象应该有PlayXGame种方法 - 单个Player在玩游戏时不会与其他Player进行互动,或为什么特定的Player玩游戏?

答案 6 :(得分:1)

一个简单的重置方法(在__init__中调用并在必要时重新调用)很有意义。但是这里有一个我认为有趣的解决方案,如果有点过度设计:创建一个上下文管理器。我很好奇人们对此的看法......

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

为了过度工程,您可以创建一个@resetme装饰器

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

因此,您不必明确使用with,而只需使用装饰器:

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

答案 7 :(得分:0)

听起来我觉得你需要重新修改你的模型,至少要包含一个单独的“PlayerGameStats”类。

有些事情:

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)

要回答编辑中的问题,是嵌套函数可以看到存储在外部作用域中的变量。您可以在教程中阅读更多相关信息:http://docs.python.org/tutorial/classes.html#python-scopes-and-namespaces

答案 8 :(得分:0)

感谢好的输入,因为我遇到了类似的问题。我在init方法上使用钩子来解决它,因为我希望能够重置为对象具有的任何初始状态。这是我的代码:

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

要更新初始状态,您可以构建一些方法,例如将数据写入_tool_init_states的reset(...)。我希望这有助于某人。如果没有元类,这是可能的,请告诉我。

答案 9 :(得分:0)

我喜欢(并尝试过)PaoloVictor的最佳答案。但是,我发现它&#34;重置&#34;本身,也就是说,如果你第二次调用reset()会引发异常。

我发现它可以通过以下实现重复进行

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__