程序生涯:我走过的坑(Python)

前言

从2016年大二开始正式接触编程,大三开始接触python,由于其开源性和多样性,使我遇到过无数大大小小的问题,同一个问题有时候会重复遇到,每次都要重新上论坛寻求解决方案,花费了很多时间,因此今日痛定思痛,决定开贴,记录我的问题以及最终解决方案,一来做工和笔记,二来希望有一天,这几篇帖子能够帮助更多的人
by:liuxin
2019/7/9

1. anaconda配置清华镜像源

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --set show_channel_urls yes

同时,这里介绍下,pip的镜像源修改:
1、在home目录下面创建名为.pip文件夹(mkdir)
2、在创建好的.pip文件夹中创建名为pip.conf的文件
3、在pip.conf文件中输入以下内容

[global]
timeout = 6000
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host = https://pypi.tuna.tsinghua.edu.cn/simple

镜像源

  1. 中国科学技术大学 : https://pypi.mirrors.ustc.edu.cn/simple
  2. 清华:https://pypi.tuna.tsinghua.edu.cn/simple
  3. 豆瓣:http://pypi.douban.com/simple/
  4. 华中理工大学 : http://pypi.hustunique.com/simple
  5. 山东理工大学 : http://pypi.sdutlinux.org/simple

2. 配置Pycharm+PyQt

(1)安装pyqt5,可直接运行conda install pyqt,便可安装相对应的pyqt版本全套包。
(2)将designer以及pyuic添加到pycharm的External Tools方便我们调用
setting>>Tools>>External Tools>>添加,设置参数
designer

参数名
Namedesigner
ProgramD:\ProgramData\Anaconda3\Lib\site-packages\pyqt5_tools\designer.exe
Agrument$FileDir$$FileName$
Working dictory$FileDir$

pyuic

参数名
Namepyuic
ProgramD:\ProgramData\Anaconda3\python.exe
Agrument-m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
Working dictory$FileDir$

3. 关于继承designer设计的UI

我们利用Qt designer设计好了基本的UI框架,会生成一个UI的class,利用pyuic转换为我们的*.py文件,但通常情况下我们需要修改或者增加我们的UI函数。
如果直接在此py文件下进行改动,一旦我们在Qt Designer中更新我们UI,那么整个py文件都会被覆盖。
于是,最好的方式就是在另一个文件中新建一个类继承自动构建的UI类。
具做法是:

class edit_UI(QWidget,Ui_Form):
    '编辑页面的UI'
    def __init__(self):
        super(edit_UI,self).__init__()
        self.setupUi(self)
        
if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    myUI =edit_UI()
    myUI.show()

    sys.exit(app.exec_())

同时继承QWidget,Ui_Form类 ,并利用super函数调用父类的构造函数,引入各个控件。

4.pyinstaller打包问题

4.1 在ui程序中出现could not find or load the Qt platform plugin “windows”

在pyinstaller 的命令中,我们可以选择全打包或不全打包的形式
命令如下:
全包pyinstaller -F -w xxx.py
非全包pyinstaller -w xxx.py
全包的优势就是可以全部将所有的包以及动态链接库都打包在exe中,很少出现环境缺少的情况,但是劣势就是exe文件非常大,并且在执行时需要巨长的时间去解压整个exe。
非全包的优势在于exe文件小,并且执行速度快。但对于有着UI的程序来说,打包完成后会存在could not find or load the Qt platform plugin "windows"这样的问题,解决方法是,将anaconda环境下Library\plugins下的platforms文件夹复制到项目文件目录下(要复制整个文件夹),即可解决该问题。

4.2 打包时出现maximum recursion depth exceeded

利用pyinstaller打包会出现递归层数超过预设数值,在python环境下,此阈值是有一个默认值的,也是可以修改的。
利用pyinstaller -F -w xxx.py打包虽然会失败,但会生成一个xxx.spec文件,利用notepoad++或者其他文本编辑器在第二行插入:

import sys
sys.setrecursionlimit(5000)					

执行pyinstaller -F -w xxx.spec ,sucessful!!

5 在子线程更新QT线程中的控件报错问题

在大部分的编程语言中,例如C#、Python等,都有所谓的线程保护机制,特别是在涉及UI编程的时候,就有非常明显。
例如下面这个例子:

    def run(self):
        """
        the thread of listening
        :return:
        """
        count = 0
        while True:
            try:
                self.args+= self.ser.read().hex()
                count+=1
                if(count%14==0):
                    count=0
                    if self.target is not None:
                        self.target(self.args)
                    self.args=''
            except Exception as e:
                self.args=e

这是一个单独开出的一个串口监听的子线程,当接收完14个16进制字符的时候,就调用我们的回调函数:self.target(),在回调函数里去更新UI,将串口读出来的信息显示到UI控件里。
传入的回调函数如下:

    def read_rfid(self,rfid):
        """

        :param rfid:
        :return:
        """
        self.textBrowser.append(rfid)
        # self.recv_signal.emit(rfid)

我们运行:

QObject::connect: Cannot queue arguments of type 'QTextCursor' (Make sure 'QTextCursor' is registered using qRegisterMetaType().)
显示了这样的错误,虽然有时候也能更新UI,但是,这样做是极其不安全的,指不定什么时候程序就意外停止了。
解决方法:
利用Qt中的信号与槽。信号与槽是Qt提供的一个触发机制,作用类似于事件,但和事件有很大区别。
创建了一个信号之后,可以在某个事件发生的时候,人为或者由Qt组件自动广播,如果有槽函数(类似于回调函数)绑定了这个信号,那么信号一旦广播,槽函数就会被执行。
那为什么它在多线程的时候能够保护线程呢?为什么就用普通的回调函数不行呢?
首先第一个问题,我们在进行信号与槽连接的时候,会调用:
connect(sender, signal, receiver, slot);
其实,connect还有一个Qt::ConnectionType参数,只是它带有默认值,且多数情况下,默认值足够了,所以最少有机会去了解。Qt::ConnectionType的可选值如下:

  • Qt::AutoConnection:
    默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection。

  • Qt::DirectConnection: 槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程。效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

  • Qt::QueuedConnection: 槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

  • Qt::BlockingQueuedConnection: 槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。

  • Qt::UniqueConnection: 这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。

从这里我们就可以看出了,针对于多线程的时候,信号与槽的连接是考虑的线程安全的,利用QueuedConnection,会在UI自身的事件循环里,也就是UI自身的线程中,去调用槽函数,熟悉C#的朋友应该知道C#中的消息循环,C#利用事件和委托一样可以完成这样的操作,以确保线程的安全。

最后我的解决方案:
在类中手动创建信号

class StudentSwipe(QWidget,Ui_Stu_Swipe):
    recv_signal = pyqtSignal(str)
    def __init__(self,log,target=None,args=None):
        super(StudentSwipe,self).__init__()
        self.log=log
        self.setupUi(self)
        self.target=target
        self.args=args
        self.connect_signal_slot()
................................

并且利用connect函数与槽函数相连接,在槽函数中更新UI。

	#信号与槽连接
    def connect_signal_slot(self):
        """
        connect qt signal and slot
        :return:
        """
        self.recv_signal.connect(self.stu_swipe) #连接stu_swipe方法
        self.pushButton_extract.clicked.connect(self.extract_to_excel)

发送信号:

    def read_rfid(self,rfid):
        """

        :param rfid:
        :return:
        """
        # self.textBrowser.append(rfid)   #illegal operation
        self.recv_signal.emit(rfid)#发送信号

最后槽函数更新UI。完美运行!!
最后,我要提一点,这里的信号必须定义为类属性,而不能在__init__()方法中进行定义。否则,信号会没有connect()方法!,具体原因我会在下一节讨论。

6 关于自己定义的Qt Signal(信号)没有槽函数的问题,以及背后python类变量和实例变量的讨论。

在第五节中,我尝试在__init__()函数中定义了这样一个信号:

    def __init__(self,log,target=None,args=None):
        super(StudentSwipe,self).__init__()
        self.log=log
        self.setupUi(self)
        self.target=target
        self.args=args
        self.recv_signal = pyqtSignal(str)
        self.connect_signal_slot()

但是最后在利用connect连接后,运行提示:

AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'

我当时其实特别懵逼,我屡试不爽的信号居然没有connect属性了!!!!在检查了10遍是不是打错字之后,哥们彻底疯了。
后来上网查了查才知道原因。
我贴上那篇帖子的链接:信号定义问题
根据描述,在作为非类属性的实例化对象,实际上是用的Unbound and Bound Signals
官方文档描述:

A signal (specifically an unbound signal) is a class attribute. When a signal is referenced as an attribute of an instance of the class then PyQt5 automatically binds the instance to the signal in order to create a bound signal. This is the same mechanism that Python itself uses to create bound methods from class functions.

意思是:一个信号(特别是一个未绑定的信号)是类中的一个属性当信号作为属性被实例化后,PyQt5会自动绑定到实例信号并创建一个绑定的信号。这和Python自身创建绑定方法的功能类似。
所以一定要放在类的定义了,我查了下,就这就类变量和实例变量的区别:
我尝试自己去描述,但是我觉得还是不够清晰,于是直接用了大佬的帖子:
Python基础-类变量和实例变量
讲得很清楚,所以一定要注意,正确的使用实例变量和类变量,(PS:按照面对对象编程的概念,实际上每个属性都应该是实例变量所特定持有的)

7 tensorflow配置

先说句MMP以致敬一下午配置环境的时间。tensorflow的环境配置一直是让人诟病的,要么老老实实的走前人走过的路,安装一个旧的大家都验证成功的路,要么自己装,然后经历若干努力,最后老老实实走前人走过的路。
我这里记录下我自己的环境,以防未来吧环境搞炸了也恢复不了。

python:3.6.5
conda:4.6.14
CUDA:10.0, V10.0.130
cudnn:v7.5.1.10
tensorflow-gpu:1.14.0
numpy:1.16.4

CUDA历史版本下载地址:https://developer.nvidia.com/cuda-toolkit-archive
cudnn历史版本下载地址:https://developer.nvidia.com/rdp/cudnn-archive
tensorflow直接用pip指定版本就好了。
常用cuda:cudnn:tensorflow对应表:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值