(“变量”在这里指的是“名称”,我认为,不完全确定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明智?
更新:我怀疑我有这个问题,我可以通过在函数中使用局部变量轻松解决我的问题。但是,如果我在另一个函数中有一个函数会发生什么,我可以在内部函数中使用外部函数的本地函数吗?
答案 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上,int
或tuple
个实例等内置类型是不可变的,由语言强制执行:
>>> 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
类怎么样?Player
或Game
会保存这些对象的集合呢?
此外,您展示的代码是一个良好的开端,但是您是否可以展示更多与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__