文章目录
前言
从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
镜像源
- 中国科学技术大学 : https://pypi.mirrors.ustc.edu.cn/simple
- 清华:https://pypi.tuna.tsinghua.edu.cn/simple
- 豆瓣:http://pypi.douban.com/simple/
- 华中理工大学 : http://pypi.hustunique.com/simple
- 山东理工大学 : 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
参数名 | 值 |
---|---|
Name | designer |
Program | D:\ProgramData\Anaconda3\Lib\site-packages\pyqt5_tools\designer.exe |
Agrument | $FileDir$$FileName$ |
Working dictory | $FileDir$ |
pyuic
参数名 | 值 |
---|---|
Name | pyuic |
Program | D:\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对应表: