关于Python中深拷贝与浅拷贝的理解(三)---监视数据是否变化

在上两篇博客中,已经找到了代码出现bug的原因并进行了调试,但调试的效率和结果并不满意,必须要进行一定的改进。

思路

改进的思路无非是让程序能够自动识别原始数据或计算过程中的结果数据是否改变。这样即可得知在哪一步、哪一个函数对数据进行了改动。(其实用pycharm调试即可,更简单)
对于数据是否改动,可以通过数据的id进行判断
初始的思路是这样:
  • 开一个监视线程,不断检测需要监测的数据,一旦数据发生改动,打印相关信息
talk is cheap,开撸
初始做了一个小例子,界面很简单,如下:

点击一下2按钮,会开启一个线程,连接到监视函数,点一下PushButton按钮,原始数据会加1,之后打印相关信息。监视函数如下:
<pre name="code" class="python">def monitor(self):
        while 1:
            time.sleep(1)
            id1 = id(self.original_data)
            if id1 != self.id0:
                print 'id值改变了', '    原:', self.id0, '     现:', id1
                self.id0 = id1

 
 
 后面发现,其实并不需要2按钮,线程在创建对象后立即生成即可,如下图: 
setDaemon是为了让主线程退出的时候生成的监视线程也退出。(网上搜了一圈并没有发现类似kill或stop线程的方法)
基本的监视线程构造好了,结果也不错,点击一下PushButton按钮后输出如下:
2      id= 32407344
id值改变了     原: 32407368      现: 32407344

***Repl Closed***
但是!

后来在我的计算软件中试用发现,此线程会阻塞UI线程,对线程这一块理解不深,没有找到解决方案,故只能采用别的方法。

改进

为了不阻塞UI,放弃了开启监视线程的方法。由于PyQt采用信号与槽的机制,于是我想,是不是可以让每次操作都发射一个信号,之后调用监视函数,比较此时的数据与原来的数据是否发生变动。
开撸
首先,构造一个信号类:

class Communicate(QObject):
    signal1 = pyqtSignal()

之后,去掉开启线程的代码:
def __init__(self, parent=None):

        self.original_data = 1
        self.id0 = id(self.original_data)
        super(main_software, self).__init__()
        self.setupUi(self)  # 十分必要
        self.c = Communicate()
        self.connect(self.pushButton1, SIGNAL("clicked(bool)"), self.plus)
        self.c.signal1.connect(self.monitor)

这样,只要我每次有动作的时候,发射一个信号(signal1),即可调用monitor函数打印出信息。
发射信号:

self.c.signal1.emit()
注意,每次发射信号都要在计算代码之后,确保计算完(对数据改动或不改动)之后再发射信号。
到此,基本的改进已经完成了, 这样做的不足之处在于,需要在每一个调用的函数后面加上发射信号的语句,比较麻烦。网上搜索是否可以用一个信号来表示所有信号,无果。有待后续折腾。


再次改进

但是,这样就完了?
打印这点信息怎么够,这样如果我操作快一点,或者做自动化测试,没有注意控制台的输出,就无法得知是哪个步骤,或哪个函数对数据进行了改动。所以,必须对监视函数做一下改进!

之后改进的监视函数如下:
def monitor(self):
        try:
            raise Exception
        except:
            f = sys.exc_info()[2].tb_frame.f_back
        print '监视器被触发,执行的函数为:', f.f_code.co_name, '在第', f.f_lineno, '行'
        try:
            id1 = id(self.original_data)
            if id1 != self.id01:
                print '变量  self.original_data的id值改变了', '    原:', self.id01, '     现:', id1
                self.id01 = id1

        except:
            pass
        try:
            id2 = id(self.original_data_copy)
            if id2 != self.id02:
                print '变量  self.original_data_copy的id值改变了', '    原:', self.id02, '     现:', id2
                self.id02 = id2
        except:
            pass
        try:
            id3 = id(self.result_data)
            if id3 != self.id03:
                print '变量  self.result_data的id值改变了', '    原:', self.id03, '     现:', id3
                self.id03 = id3

        except:
            pass
用try是因为结果数据和原始数据的copy并不是一开始就存在的。不写在一个try里是因为如果一句代码发生异常的话,后面的代码得不到执行。不过其实用assert更简单一些……
操作一遍软件,得到的输出结果如下:
监视器被触发,执行的函数为: open_file 在第 201 行
监视器被触发,执行的函数为: load_data 在第 247 行
监视器被触发,执行的函数为: ****** 在第 316 行
监视器被触发,执行的函数为: ****** 在第 410 行
变量  self.original_data_copy的id值改变了     原: 164524888      现: 170648744
监视器被触发,执行的函数为: ****** 在第 410 行
变量  self.original_data_copy的id值改变了     原: 170648744      现: 365066056
监视器被触发,执行的函数为: ****** 在第 457 行
监视器被触发,执行的函数为: ****** 在第 552 行
监视器被触发,执行的函数为: ****** 在第 579 行
监视器被触发,执行的函数为: ****** 在第 579 行
监视器被触发,执行的函数为: ****** 在第 598 行
监视器被触发,执行的函数为: ****** 在第 612 行
监视器被触发,执行的函数为: ****** 在第 643 行
变量  self.result_data的id值改变了     原: 164544352      现: 539089496
监视器被触发,执行的函数为: ****** 在第 741 行
监视器被触发,执行的函数为: ****** 在第 820 行
监视器被触发,执行的函数为: ****** 在第 830 行
监视器被触发,执行的函数为: ****** 在第 878 行
监视器被触发,执行的函数为: ****** 在第 980 行
变量  self.result_data的id值改变了     原: 539089496      现: 553165768
监视器被触发,执行的函数为: ****** 在第 1028 行
监视器被触发,执行的函数为: ****** 在第 1076 行
监视器被触发,执行的函数为: ****** 在第 1125 行
变量  self.result_data的id值改变了     原: 553165768      现: 539090840
监视器被触发,执行的函数为: ****** 在第 1233 行
监视器被触发,执行的函数为: ****** 在第 1233 行
监视器被触发,执行的函数为: ****** 在第 1233 行

***Repl Closed***
注:部分函数名用*代替

其中监视了三个变量,self.original_data,self.original_data_copy,self.result_data。
折腾到此告一段落,具体的方法仍有改进的地方,比如是否可以通过线程来监视,同时并不阻塞UI。或者是否可以捕捉所有信号,用一个信号表示,这样就不用在每个函数后面加上发射信号的语句了。(后来想了想,这种方法也有缺点,就是在调用函数的时候如果立刻发射信号,进行检测,在检测的时候函数可能还没有运行到改变数据的那一行代码,这样检测也就失去了意义)
有更好办法,或有疑问的网友,欢迎留言,共同讨论和进步。



更新

在后续偶然的测试中,发现数组数值改变但id不会变,为避免麻烦,把监视函数中的id方法全部去掉,替换为直接比较原始值和现在的值。


更新后的监视函数如下:
# 监视函数
    def monitor(self):
        try:
            raise Exception
        except:
            f = sys.exc_info()[2].tb_frame.f_back
        print '监视器被触发,执行的函数为:', f.f_code.co_name, '在第', f.f_lineno, '行'
        try:
            odc = copy.deepcopy(self.original_data)
            if (odc.shape != self.odc01.shape) or (False in (odc.values == self.odc01.values)):
                print '变量 self.original_data 的值改变了\n', '    原:\n', self.odc01, '     现:\n', odc
                self.odc01 = odc

        except:
            pass
        try:
            ocdc = copy.deepcopy(self.original_data_copy)
            if (ocdc.shape != self.ocdc01.shape) or (False in (ocdc.values == self.ocdc01.values)):
                print '变量 self.original_data_copy 的值改变了\n', '    原:\n', self.ocdc01, '     现:\n', ocdc
                self.ocdc01 = ocdc
        except:
            pass
        try:
            rdc = copy.deepcopy(self.result_data)
            # 以前的判断有问题,如果直接用False in的方法,在形状不同的情况下会出错,不执行后面的语句。
            # 现在先加上判断形状是否相同,如果形状不同,则值肯定不同。如果形状相同,再考虑后面的值一样不一样,这样不会出错
            if (rdc.shape != self.rdc01.shape) or (False in (rdc.values == self.rdc01.values)):
                print '变量 self.result_data 的值改变了\n', '    原:\n', self.rdc01, '     现:\n', rdc
                self.rdc01 = rdc

        except:
            pass



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值