在大学软件构造的课程中,老师反复强调在构造一个数据结构时,要避免表示泄露,这样我们就不会因为外部(例如使用该数据结构的客户端代码)的一些操作而影响到数据结构内部的正确性。
我也非常理解这样可以减少出错的可能性,但是很长一段时间我都觉得,如果内部外部的代码都是由一个人完成,而代码量不大的话,只要稍微留意一点就可以避免误操作影响数据结构内部。
直到今天,我在另一门课程——人工智能——中用python编写一个简单的小程序时,我才意识到之前的想法并不正确,并且为此付出了比较惨痛的代价。其中一个原因是,我已经很长时间没有使用python了,对于python的语法、规则不是很熟悉,不知道如何将变量标记为private,于是干脆就全部使用了public的变量。
人工智能给我的任务是,通过产生式的知识表示,用python编写一个“猴子摘香蕉”的求解方法。对于其中的具体要求这里就不详细阐述了,只需要关注其中的一点:我构造了一个名为State的Class用来表示某时刻的状态,State包含一个list类型的变量route:
然后State中还有一个方法action(),作用是记录来到这个状态所经过的动作。我的想法是,参数before传入来到上一个状态所经过的动作列表,再将新动作通过参数act传进来,将二者一结合就得到了来到当前状态的route。
下面再来看一下我是怎么使用这个方法的:
我实例化了一个新的State类型的变量new,并将旧状态s的route当作参数传了进去。这里就出现了问题,s.route是一个list,是可变数据类型,当s.route被当作参数传入后执行语句:
"self.route = before"
于是,new.route和s.route都指向了同一个内存地址。当我们再执行语句
"self.route.append(act)"
这时候,s.route也一起被改变了,显然不是我们所希望的结果,这就导致了数据出错。
至于解决方法,一个临时的方法是,通过copy()方法使得new.route和s.route指向不同的内存地址,如下图:
但是这种方法并没有从根本上解决表示泄露的问题,之后稍不注意还是可能出现同样的问题。想要解决表示泄露,我们最好还是在构造数据结构的时候不要嫌麻烦,将内部的各个变量都标记为private,使用observer方法实现对内部数据的访问,并通过防御式拷贝来避免将内部变量的地址泄露出去。
在python中,没有Java中的private关键字,取而代之的是我们可以通过在变量或者方法名前加两个下划线来将其标记为private: