【问题标题】:Undo in Python with a very large state. Is it possible?在 Python 中撤消具有非常大的状态。可能吗?
【发布时间】:2018-05-21 02:49:44
【问题描述】:

这看起来很简单,但我找不到好的解决方案。

这是旧的“按引用传递”/“按值传递”/“按对象引用传递”问题。我了解正在发生的事情,但我找不到一个好的工作。

我知道小问题的解决方案,但我的状态非常大并且保存/重新计算非常昂贵。鉴于这些限制,我找不到解决方案。

这里有一些简单的伪代码来说明我想做的事情(如果 Python 允许我通过引用传递的话):

class A:
    def __init__(self,x):
        self.g=x
        self.changes=[]
    def change_something(self,what,new): # I want to pass 'what' by reference
        old=what # and then de-reference it here to read the value
        self.changes.append([what,old]) # store a reference
        what=new # dereference and change the value
    def undo_changes():
        for c in self.changes:
            c[0]=c[1] # dereference and restore old value

编辑:添加更多伪代码以显示我希望如何使用上述内容

test=A(1) # initialise test.g as 1

print(test.g)
out: 1

test.change_something(test.g,2)
# if my imaginary code above functioned as described in its comments,
# this would change test.g to 2 and store the old value in the list of changes

print(test.g)
out: 2

test.undo_changes()

print(test.g)
out: 1

显然,由于“通过对象引用”,上述代码在 python 中不起作用。此外,我希望能够撤消单个更改,而不仅仅是上面代码中的所有更改。

问题是……我似乎找不到好的工作。有这样的解决方案:

Do/Undo using command pattern in Python

making undo in python

这涉及存储一堆命令。然后,“撤消”涉及删除最后一个命令,然后通过获取初始状态并重新应用除最后一个命令之外的所有内容来重建最终状态。我的状态太大了,这不可行,问题是:

  • 状态非常大。完全保存它非常昂贵。
  • “执行”操作代价高昂(无法从保存的状态重新计算)。
  • Do 操作也是不确定的,依赖于随机输入。
  • 撤消操作非常频繁

我有一个想法,即确保所有内容都存储在列表中,并编写我的代码以便存储、读取和写入这些列表中的所有内容。然后在上面的代码中,每次我想读/写一个变量时,我都可以传递列表名称和列表索引。

基本上这相当于在 Python 中构建我自己的内存架构和 C 风格的指针系统! 这行得通,但似乎有点……荒谬?肯定有更好的方法吗?

【问题讨论】:

  • 我认为您可以复制您的内容并存储其副本。
  • 问题是如何存储“什么”的位置,以便以后从副本中恢复旧值?
  • 另外,如果我没记错的话,当你将参数传递给 Python 中的函数时,你传递的是对对象的引用。然后,当您重新分配变量时,旧对象不会更改。
  • 你能提供你的what是什么数据类型吗?
  • 我认为这就是您想要的 github.com/ActiveState/code/blob/master/recipes/Python/… - 使用它的两个配套配方,它将记录一系列更改并提供撤消/重做。我唯一想添加的是某种“快照”以及恢复到任何快照的能力。

标签: python undo


【解决方案1】:

请检查是否有帮助....

class A:
def __init__(self,x):
    self.g=x
    self.changes={}
    self.changes[str(x)] = {'init':x, 'old':x, 'new':x}   #or make a key by your choice(immutable)
def change_something(self,what,new): # I want to pass 'what' by reference
    self.changes[what]['new'] = new #add changed value to your dict
    what=new # dereference and change the value
def undo_changes():
    what = self.changes[what]['old'] #retrieve/changed to the old value
    self.changes[what]['new'] = self.changes[what]['old'] #change latest new val to old val as you reverted your changes

对于每个更改,您都可以更新 change_dictionary。您必须弄清楚的唯一事情是“如何为 self.change 字典中的键创建条目”,我只是将其设置为 str(x),只需检查类型(什么)以及如何使其成为您的键案例。

【讨论】:

  • 这让我想到了使用变量名作为参考,谢谢。我想出了一个可行的解决方案,但非常难看...我现在将其发布在下面,但如果有人能想出更清洁的东西,请保持开放状态...
【解决方案2】:

好的,所以我想出了一个答案......但它很难看!我怀疑这是最好的解决方案。它使用 exec() 我被告知这是不好的做法,如果可能的话应该避免。编辑:见下文!

使用 exec() 的旧代码:

class A:
    def __init__(self,x):
        self.old=0
        self.g=x
        self.h=x*10
        self.changes=[]
    def change_something(self,what,new):
        whatstr='self.'+what
        exec('self.old='+whatstr)
        self.changes.append([what,self.old]) 
        exec(whatstr+'=new') 
    def undo_changes(self):
        for c in self.changes:
            exec('self.'+c[0]+'=c[1]')
    def undo_last_change(self):
        c = self.changes[-1]
        exec('self.'+c[0]+'=c[1]')
        self.changes.pop()

感谢 barny,这是一个使用 getattr 和 setattr 的更好的版本:

class A:
    def __init__(self,x):
        self.g=x
        self.h=x*10
        self.changes=[]
    def change_something(self,what,new):
        self.changes.append([what,getattr(self,what)])
        setattr(self,what,new) 
    def undo_changes(self):
        for c in self.changes:
            setattr(self,c[0],c[1])
    def undo_last_change(self):
        c = self.changes[-1]
        setattr(self,c[0],c[1])
        self.changes.pop()

为了演示,输入:

print("demonstrate changing one value")
b=A(1)
print('g=',b.g)
b.change_something('g',2)
print('g=',b.g)
b.undo_changes()
print('g=',b.g)

print("\ndemonstrate changing two values and undoing both")
b.change_something('h',3)
b.change_something('g',4)
print('g=', b.g, 'h=',b.h)
b.undo_changes()
print('g=', b.g, 'h=',b.h)

print("\ndemonstrate changing two values and undoing one")
b.change_something('h',30)
b.change_something('g',40)
print('g=', b.g, 'h=',b.h)
b.undo_last_change()
print('g=', b.g, 'h=',b.h)

返回:

demonstrate changing one value
g= 1
g= 2
g= 1

demonstrate changing two values and undoing both
g= 4 h= 3
g= 1 h= 10

demonstrate changing two values and undoing one
g= 40 h= 30
g= 1 h= 30

编辑 2:实际上...经过进一步测试,我使用 exec() 的初始版本比第二个版本有一些优势。如果该类包含第二个类或列表或其他任何内容,则 exec() 版本可以轻松更新类中的类中的列表,但是第二个版本将失败。

【讨论】:

  • 不知道为什么需要 exec() 调用,讨厌。也许您的部分困难在于您正在尝试将正在更改的类以及存储更改历史的观察者结合起来。您可以腌制一个对象以捕获其状态并将其保存在单独的“撤消堆栈”中,取消腌制您需要恢复到的堆栈中的任何项目。
  • 如果状态非常大,那么您可以随时将泡菜保存到磁盘。
  • exec 调用是迄今为止我发现在 Python 中通过引用来引用类中的变量的唯一方法。我实际上是在使用它的名字来做到这一点!这肯定是一种 hack,但它是我发现的唯一有效的简单方法。
  • 我应该更清楚一点 - 完全排除存储状态作为可能的解决方案!它肯定会导致内存使用问题。也许酸洗可以帮助解决这个问题......但存储状态的最重要问题是继续保存模拟的整个状态将非常缓慢。这是针对大型蒙特卡洛模拟的,我每秒需要数千次移动,而不是每 5 秒一次,或者如果我要在每次移动之前存储整个状态需要多长时间。
  • 使用 gettar(self,name) 见stackoverflow.com/questions/1167398/…
猜你喜欢
  • 2011-09-20
  • 1970-01-01
  • 2017-10-14
  • 1970-01-01
  • 1970-01-01
  • 2019-07-15
  • 1970-01-01
  • 1970-01-01
  • 2019-07-13
相关资源
最近更新 更多