在Python中以非常大的状态撤消。可能吗?

在Python中以非常大的状态撤消。可能吗?,python,undo,Python,Undo,这看起来很简单,但我找不到好的解决方案 这是旧的“按引用传递”/“按值传递”/“按对象引用传递”问题。我知道发生了什么,但我找不到好的工作 我知道小问题的解决方案,但我的状态非常大,保存/重新计算非常昂贵。考虑到这些限制,我找不到解决方案 下面是一些简单的伪代码来说明我想做什么(如果Python允许我通过引用传递): 编辑:添加更多的伪代码来显示我希望如何使用上面的代码 test=A(1) # initialise test.g as 1 print(test.g) out: 1 test.

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

这是旧的“按引用传递”/“按值传递”/“按对象引用传递”问题。我知道发生了什么,但我找不到好的工作

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

下面是一些简单的伪代码来说明我想做什么(如果Python允许我通过引用传递):

编辑:添加更多的伪代码来显示我希望如何使用上面的代码

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”操作成本高昂(无法从保存的状态重新计算)
  • Do操作也是非确定性的,依赖于随机输入
  • 撤消操作非常频繁
我有一个想法,那就是确保所有内容都存储在列表中,并编写代码,以便所有内容都存储在这些列表中,从这些列表中读取并写入。然后在上面的代码中,每当我想读/写变量时,我都可以传递列表名和列表索引

本质上,这相当于在Python中构建我自己的内存体系结构和C风格的指针系统!
这是可行的,但似乎有点。。。荒唐的肯定有更好的方法吗?

请检查是否有帮助

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

对于每个更改,您都可以更新更改字典。你唯一需要弄清楚的是“如何在self.change dictionary中为what创建项作为一个键”,我刚刚将其设置为str(x),只需检查类型(what)以及如何在您的情况下将其设置为键。

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

使用exec()的旧代码:

感谢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()版本在更新类中的列表时不会遇到问题,但是第二个版本将失败。

我认为您可以复制what并存储其副本。问题是如何存储“what”的位置,这样我以后就可以从副本中恢复旧值了?而且,如果我没记错的话,当你在Python中向一个函数传递一个参数时,你就是在向这个对象传递引用。当您重新分配变量时,旧对象没有更改。您能提供您的
什么
是什么数据类型吗?我想这就是您想要的-通过它的两个配套配方,它将记录一系列更改并提供撤消/重做。我唯一想添加的是某种“快照”和还原到任何快照的功能。这让我想到使用变量的名称作为参考,谢谢。我想出了一个可行的解决方案,但很难看。。。我现在就把它贴在下面,但是把问题留着,以防有人能想出更干净的东西……不知道为什么需要exec()调用,讨厌。可能您的部分困难在于您试图将正在更改的类与存储更改历史的观察者结合起来。您可以对对象进行pickle以捕获其状态,并将其保存在单独的“撤消堆栈”中,取消对堆栈中需要还原到的任何项的pickle。如果状态非常大,则始终可以将pickle保存到磁盘。exec调用是迄今为止我发现的唯一一种在Python中通过引用引用类中的变量的方法。我真的用它的名字这么做!这确实是一个黑客,但这是我发现的唯一有效的简单方法。我应该更清楚一点——存储状态完全被排除在可能的解决方案之外!这肯定会导致内存使用问题。也许酸洗可以帮上忙。。。但是存储状态最重要的问题是,保存整个模拟状态的速度会非常慢。这是一个大型蒙特卡罗模拟,我需要每秒数千次移动,而不是每5秒移动一次,或者在每次移动之前存储整个状态需要多长时间。使用gettar(self,name)查看
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