精华内容
下载资源
问答
  • pyqt5-21.多线程数据库连接错误
    2020-09-20 22:06:44

    QSqlDatabasePrivate::database: requested database does not belong to the calling thread.

    连接数据库的名字不可以使用一个,创建一个随机数,保证不重复

    https://forum.qt.io/topic/113422/requested-database-does-not-belong-to-the-calling-thread

    https://blog.csdn.net/shawzg/article/details/96484966

    '''
    
    db_mutex = QMutex()
    
    def connectdb():
        
        db_mutex.lock() 
        
        db =  QSqlDatabase.addDatabase('QSQLITE',str(random.random()))
        
        db.setDatabaseName("db/database.db")
        db_mutex.unlock()
        
        if not db.open():
            mess = "打开数据库 database.db 错误:\n" + db.lastError().text()
            mbox = Messbox(mess)
            globalv.gl_log_db.info(mess)
            return None
        
        return db
    
    '''
    
    更多相关内容
  • PyQt5多线程及布局

    2021-11-18 10:45:02
    PyQt5多线程多线程QTimerQThreadWeb交互一个简单的交互布局绝对布局水平盒布局垂直盒布局设置控件对齐方式让按钮永远在窗口某位置栅格布局实现计算器栅格单元格跨列             &...

     
     
     
     
     
     
     
     

    多线程


     

    PyQt5 中常用的多线程方法主要是:QTimer、QThread 两种

     
     
     
     

    QTimer

    方法含义
    timer.start(n)每 n 毫秒调用一次
    QTimer.singleShot(n, app.quit)过 n 毫秒调用一次,且仅调用一次。

    app.quit 可以替换成其他需要执行的事件

     

    定时刷新时间

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    
    
    class ShowTime(QWidget):
        def __init__(self):
            super(ShowTime, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("动态显示当前时间")
    
            self.label = QLabel("显示当前时间")
            self.startBtn = QPushButton("开始")
            self.endBtn = QPushButton("结束")
            layout = QGridLayout()
    
            self.timer = QTimer()
            self.timer.timeout.connect(self.showTimer)
    
            layout.addWidget(self.label, 0, 0, 1, 2)
            layout.addWidget(self.startBtn, 1, 0)
            layout.addWidget(self.endBtn, 1, 1)
    
            self.startBtn.clicked.connect(self.startTimer)
            self.endBtn.clicked.connect(self.endTimer)
    
            self.setLayout(layout)
    
        def showTimer(self):
            print("show")
            time = QDateTime.currentDateTime()
    
            timeDisplay = time.toString("yyyy-MM-dd hh:mm:ss dddd")
            self.label.setText(timeDisplay)
    
        def startTimer(self):
            # 让定时器以每 1 秒( 1000 毫秒)为周期进行循环调用其身上绑定的槽
            self.timer.start(1000)
            self.startBtn.setEnabled(False)
            self.endBtn.setEnabled(True)
    
        def endTimer(self):
            self.timer.stop()
            self.startBtn.setEnabled(True)
            self.endBtn.setEnabled(False)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = ShowTime()
        main.show()
        sys.exit(app.exec_())
    

     

    定时关闭窗口

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    from PyQt5.QtGui import *
    
    
    class ShowTime(QWidget):
        def __init__(self):
            super(ShowTime, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("动态显示当前时间")
    
            QTimer.singleShot(5000, app.quit)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = ShowTime()
        main.show()
        sys.exit(app.exec_())
    

     
     
     
     

    QThread

    如果使用单线程进行计算、刷新页面上某些值时(如更新 lcdNumber)。当事件仅执行一次时,没有问题,但是当事件是持续运行的循环事件,即使内部有 time.sleep() 之类的代码,其GUI也不会更新,而是期望等退出循环后再进行渲染,此期间程序的GUI将会未响应,但是程序依旧在后台运行

     

    计数器及自动关闭

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    # 全局计数器
    sec = 0
    
    
    # 继承 QThread 并在其基础上实现新功能
    class WorkThread(QThread):
        # 创建自定义信号
        # 每隔一秒发送一次信号
        timer = pyqtSignal()
        # 技术结束后发送一次信号
        end = pyqtSignal()
    
        def run(self):
            while True:
                # 睡眠一秒
                self.sleep(1)
                # 计数器到 5 时,关闭窗口
                if sec == 5:
                    # 发送 end 信号,触发槽函数
                    self.end.emit()
    
                    # 结束该信号的持续监听
                    break
    
                # 每隔一秒发送一次 timer 信号
                self.timer.emit()
    
    
    class Counter(QWidget):
        def __init__(self):
            super(Counter, self).__init__()
            self.setWindowTitle("使用 QThread 编写计数器")
            self.resize(300, 120)
    
            layout = QVBoxLayout()
            # 类似 led 的显示屏
            self.lcdNumber = QLCDNumber()
            layout.addWidget(self.lcdNumber)
    
            button = QPushButton("开始计数")
            layout.addWidget(button)
    
            # 实例化继承了 Thread 的自定义子类,以开辟新线程进行某些工作
            self.workThread = WorkThread()
            # 持续发送 timer 信号
            self.workThread.timer.connect(self.countTime)
            # 到达 sec==5 触发 end 信号
            self.workThread.end.connect(self.end)
    
            button.clicked.connect(self.work)
    
            self.setLayout(layout)
    
        def countTime(self):
            global sec
            sec += 1
            self.lcdNumber.display(sec)
    
        def end(self):
            QMessageBox.information(self, "消息", "计数结束", QMessageBox.Ok)
    
        def work(self):
            # 开启新线程,并发送自定义信号
            self.workThread.start()
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = Counter()
        main.show()
        sys.exit(app.exec_())
    

     
     
     
     
     
     
     
     

    布局


     

    绝对布局

    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    
    class AbsoluteLayout(QWidget):
        def __init__(self):
            super(AbsoluteLayout, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("绝对布局")
    
            self.label1 = QLabel("欢迎", self)
            self.label1.move(15, 20)
    
            self.label2 = QLabel("学习", self)
            self.label2.move(35, 40)
    
            self.label3 = QLabel("PyQT5", self)
            self.label3.move(55, 80)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = AbsoluteLayout()
        main.show()
        sys.exit(app.exec_())
    
    

     
     
     
     

    水平盒布局

    在这里插入图片描述

    import sys, math
    from PyQt5.QtWidgets import *
    
    
    class HBoxLayout(QWidget):
        def __init__(self):
            super(HBoxLayout, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("水平盒布局")
    
            layout = QHBoxLayout()
            layout.addWidget(QPushButton("按钮1"))
            layout.addWidget(QPushButton("按钮2"))
            layout.addWidget(QPushButton("按钮3"))
            layout.addWidget(QPushButton("按钮4"))
            layout.addWidget(QPushButton("按钮5"))
            layout.addWidget(QPushButton("按钮6"))
    
            self.setLayout(layout)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = HBoxLayout()
        main.show()
        sys.exit(app.exec_())
    

     
     
     
     

    垂直盒布局

    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    
    class HBoxLayout(QWidget):
        def __init__(self):
            super(HBoxLayout, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("水平盒布局")
    
            layout = QVBoxLayout()
            layout.addWidget(QPushButton("按钮1"))
            layout.addWidget(QPushButton("按钮2"))
            layout.addWidget(QPushButton("按钮3"))
            layout.addWidget(QPushButton("按钮4"))
            layout.addWidget(QPushButton("按钮5"))
            layout.addWidget(QPushButton("按钮6"))
            layout.setSpacing(40)
            self.setLayout(layout)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = HBoxLayout()
        main.show()
        sys.exit(app.exec_())
    

     
     
     
     

    设置控件对齐方式

    通过调整 addWidget() 的参数来控制控件的位置

    • stretch —— 设置伸缩量,即控件占用空间的比例
    • alignment —— 对齐方式

    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    
    class HBoxLayout(QWidget):
        def __init__(self):
            super(HBoxLayout, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("水平盒布局")
    
            layout = QHBoxLayout()
            layout.addWidget(QPushButton("按钮1"), 1, Qt.AlignLeft | Qt.AlignTop)
            layout.addWidget(QPushButton("按钮2"), 1, Qt.AlignLeft | Qt.AlignTop)
            layout.addWidget(QPushButton("按钮3"), 1, Qt.AlignLeft | Qt.AlignTop)
            layout.addWidget(QPushButton("按钮4"), 1, Qt.AlignLeft | Qt.AlignBottom)
            layout.addWidget(QPushButton("按钮5"), 1, Qt.AlignLeft | Qt.AlignBottom)
            layout.addWidget(QPushButton("按钮6"), 1, Qt.AlignLeft | Qt.AlignBottom)
            layout.setSpacing(40)
            self.setLayout(layout)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = HBoxLayout()
        main.show()
        sys.exit(app.exec_())
    

     
     
     
     

    让按钮永远在窗口某位置

    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    
    
    class FixBtn(QWidget):
        def __init__(self):
            super(FixBtn, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("垂直盒布局")
    
            okBtn = QPushButton("确定")
            canBtn = QPushButton("取消")
    
            layout = QHBoxLayout()
            # 关键在这里,添加一个伸缩量,该伸缩量后方的控件都被挤到右侧了
            layout.addStretch(1)
            layout.addWidget(okBtn)
            layout.addWidget(canBtn)
    
            layout1 = QVBoxLayout()
            btn1 = QPushButton("按钮1")
            btn2 = QPushButton("按钮2")
            btn3 = QPushButton("按钮3")
    
            # 添加一个为 0 的伸缩量,在遇到第二个伸缩量前的控件,全部向顶部(如果是水平就是左侧)排列
            layout1.addStretch(0)
            layout1.addWidget(btn1)
            layout1.addWidget(btn2)
            layout1.addWidget(btn3)
            # 添加一个伸缩量,让其下方的值全部靠底排列
            layout1.addStretch(1)
            layout1.addLayout(layout)
    
            self.setLayout(layout1)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = FixBtn()
        main.show()
        sys.exit(app.exec_())
    
    

     
     
     
     

    栅格布局实现计算器

    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    
    
    class Cacl(QWidget):
        def __init__(self):
            super(Cacl, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("计算器")
    
            layout = QGridLayout()
            self.setLayout(layout)
    
            names = [
                "Cls", "Back", "", "Close",
                "7", "8", "9", "/",
                "6", "5", "4", "*",
                "3", "2", "1", "-",
                "0", "=", "+", "+",
            ]
    
            # 生成网格坐标
            position = [(i, j) for i in range(5) for j in range(4)]
    
            # 绑定网格坐标和文本
            for posi, name in zip(position, names):
                # 不生成按钮
                if name == "":
                    continue
    
                btn = QPushButton(name)
                layout.addWidget(btn, *posi)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = Cacl()
        main.show()
        sys.exit(app.exec_())
    

     
     
     
     

    栅格单元格跨列

    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    
    
    class GridForm(QWidget):
        def __init__(self):
            super(GridForm, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("计算器")
    
            layout = QGridLayout()
            self.setLayout(layout)
    
            tLabel = QLabel("标题")
            aLabel = QLabel("作者")
            cLabel = QLabel("内容")
    
            tLineEdit = QLineEdit()
            aLineEdit = QLineEdit()
            cLineEdit = QTextEdit()
    
            layout.addWidget(tLabel, 1, 0)
            layout.addWidget(aLabel, 2, 0)
            layout.addWidget(cLabel, 3, 0)
            layout.addWidget(tLineEdit, 1, 1)
            layout.addWidget(aLineEdit, 2, 1)
            layout.addWidget(cLineEdit, 3, 1, 5, 1)
    
            layout.setSpacing(10)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = GridForm()
        main.show()
        sys.exit(app.exec_())
    
    

     
     
     
     
     
     
     
     

    表单布局

    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    
    
    class FormForm(QWidget):
        def __init__(self):
            super(FormForm, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("计算器")
    
            layout = QFormLayout()
            self.setLayout(layout)
    
            tLabel = QLabel("标题")
            aLabel = QLabel("作者")
            cLabel = QLabel("内容")
    
            tLineEdit = QLineEdit()
            aLineEdit = QLineEdit()
            cLineEdit = QTextEdit()
    
            layout.addRow(tLabel, tLineEdit)
            layout.addRow(aLabel, aLineEdit)
            layout.addRow(cLabel, cLineEdit)
    
            layout.setSpacing(10)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = FormForm()
        main.show()
        sys.exit(app.exec_())
    

     
     
     
     
     
     
     
     

    拖动控件边界

    在这里插入图片描述
    在这里插入图片描述

    import sys
    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    
    class Splitter(QWidget):
        def __init__(self):
            super(Splitter, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setWindowTitle("拖动控件边界")
    
            layout_hbox = QHBoxLayout()
    
            topleft = QFrame()
            topleft.setFrameShape(QFrame.StyledPanel)
    
            bottom = QFrame()
            bottom.setFrameShape(QFrame.StyledPanel)
    
            textedit = QTextEdit()
            
            # 拖动控件,将需要被拖动边界的两个控件放入到 splitter 内
            splitter1 = QSplitter(Qt.Horizontal)
            splitter1.addWidget(topleft)
            splitter1.addWidget(textedit)
            # 设置左右两侧的尺寸
            splitter1.setSizes([100, 200])
            
            # 形成上下分割的布局
            splitter2 = QSplitter(Qt.Vertical)
            splitter2.addWidget(splitter1)
            splitter2.addWidget(bottom)
    
            layout_hbox.addWidget(splitter2)
            self.setLayout(layout_hbox)
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = Splitter()
        main.show()
        sys.exit(app.exec_())
    
    
    展开全文
  • 文章目录Multithreading PyQt5 application with QThreadPoolBackgroundPreparationThe dumb approach(愚蠢的方法)永远都不要这样做!!Threads and Processes Multithreading PyQt5 application with QThreadPool...

    Multithreading PyQt5 application with QThreadPool

    • 当你构造GUI应用的时候一个常见的问题就是当你尝试运行长时间运行的后台程序时,界面会卡住。在这个教程中,我将列出在PyQt5中实现并行运行的最简单的方式。

    Background

    • 使用Qt开发的应用是基于事件的,这意味着需要根据用户的反应、信号和计时器来进行驱动执行。在一个以时间为驱动的应用中,点击一个按钮的创造一个事件,然后你的应用处理这个事件去产生需要的结果。事件被放入并且提出队列,然后按照顺序进行依次处理。
    app = QApplication([])
    window = MainWindow()
    app.exec_()
    
    • 事件循环是通过在QApplication上调用.exec_()来激发并且和你的python程序是在同一线程上运行。运行事件循环的线程,通常指的是GUI线程,也要处理所有窗口和主要操作系统的交互。
    • 默认的是,任何一个被事件循环激发的操作行为也将同步使用这个线程。在实际操作中,着意味着当你的PyQt应用在运行时,代码中的窗口交互和GUI交互也都将被冻结。
    • 如果你做的很简单,将控制返回给GUI循环就会很快,这个延迟对用户而言将会变得不可感知。然而,如果你需要运行长时间运行的任务,比如说打开或者写大型文件夹,下载一些数据,或者处理一些复杂的图片,这就会成为一个问题。对你的用户而言,应用将会表现得反馈性很差。因为你的应用不在能够和操作系统进行交互。
    • 解决办法也很简单,让你的工作在GUI线程之外进行工作,PyQt提供了简单的接口来帮你准确的实现。

    Preparation

    • 为了阐释多线程的运作,我们需要一个应用来大样子。下述是一个使用PyQt的小型计数的应用程序,我们会借此来阐述多线程的使用,并且看看实际行动的输出结果。只需要将这个程序复制粘贴到你的ide中运行即可。
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    import time
    
    class MainWindow(QMainWindow):
    
    
        def __init__(self, *args, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
    
            self.counter = 0
    
            layout = QVBoxLayout()
    
            self.l = QLabel("Start")
            b = QPushButton("DANGER!")
            b.pressed.connect(self.oh_no)
    
            layout.addWidget(self.l)
            layout.addWidget(b)
    
            w = QWidget()
            w.setLayout(layout)
    
            self.setCentralWidget(w)
    
            self.show()
    
            self.timer = QTimer()
            self.timer.setInterval(1000)
            self.timer.timeout.connect(self.recurring_timer)
            self.timer.start()
    
        def oh_no(self):
            time.sleep(5)
    
        def recurring_timer(self):
            self.counter +=1
            self.l.setText("Counter: %d" % self.counter)
    
    
    app = QApplication([])
    window = MainWindow()
    app.exec_()
    
    • 你应该看见一个上面有数秒的示范窗口。这是由一个反复生成的时间生成,每一秒都会加一。将这个看作是事件循环显示器,是一个简单的方式让我们知道我们的应用正在缓慢工作。也有一个按钮上面写了“Danger”,按下!
      *
    • 你会注意到,没按下一次,计数器都会停一会你的应用就会完全陷入停止。甚至还可能看到这个窗口变白了。

    在这里插入图片描述

    The dumb approach(愚蠢的方法)永远都不要这样做!!

    • 窗口阻塞现象其实就是主Qt事件循环被正在被处理的窗口事件阻塞。你对窗口的点击仍旧被主要的操作系统注册并被发送到应用上,但是这个应用此时仍在执行别的代码,它并不能接收并对这个注册做出回应。他们不得不等到你的代码将控制权传给Qt。
    • 最简单的,也许是最有逻辑性的方式就是去接受代码内部发生的事件。这就使得Qt能够继续对主要的操作系统做出回应,你的应用仍旧能够处在有回应的状态。这很容易实现,你可以在QApplication类中使用静态的.processEvents()方法实现。只需要在你运行事件较长的代码中增加如下语句即可
    QApplication.processEvents()
    
    • 比如说在长时间执行代码中time.sleep(),我们在循环中增加一个语句,代码如下
    def oh_no(self):
        for n in range(5):
            QApplication.processEvents()
            time.sleep(1)
    
    • 现在当你按下你编写的代码,你仍旧会像之前一样进入到处理时间的循环中,也就是计时循环。但是,现在QApplication.processEvents()立即将控制权传给Qt,并且允许它能够照常对事件进行反应。当Qt程序接受了这个事件之后并处理了之后,就会返回继续处理原来干到一半退出的事情。
    • 这是有用,但是很糟糕,主要有以下两个月原因
    • 第一,当你将控制权传给Qt的时候,你的代码就不再运行了。这意味着无论你正在运行的是啥耗时的代码,都将会变得更长。这并不是我们希望看到的。
    • 第二,当你的程序正在处理事件循环外部的代码时,你中断并处理事件响应,如果这个响应要求当前事件的结果,但是你并没有运行完毕,将会使程序处于不可预测地方。
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    import time
    
    class MainWindow(QMainWindow):
    
    
        def __init__(self, *args, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
    
            self.counter = 0
    
            layout = QVBoxLayout()
    
            self.q = QLabel("time")
            self.l = QLabel("Start")
            b = QPushButton("DANGER!")
            b.pressed.connect(self.oh_no)
    
            c = QPushButton("?")
            c.pressed.connect(self.change_message)
            layout.addWidget(self.q)
            layout.addWidget(self.l)
            layout.addWidget(b)
    
            layout.addWidget(c)
    
            w = QWidget()
            w.setLayout(layout)
    
            self.setCentralWidget(w)
    
            self.show()
    
            self.timer = QTimer()
            self.timer.setInterval(1000)
            self.timer.timeout.connect(self.recurring_timer)
            self.timer.start()
    
        def change_message(self):
            self.message = "OH NO"
            self.l.setText(self.message)
    
        def oh_no(self):
            self.message = "pressed"
    
            for i in range(100):
                time.sleep(1)
                self.l.setText(str(i))
                QApplication.processEvents()
            # time.sleep(5)
    
        def recurring_timer(self):
            self.counter +=1
            self.q.setText("Counter: %d" % self.counter)
    
    
    app = QApplication([])
    window = MainWindow()
    app.exec_()
    

    Threads and Processes

    • 如果你往回走一步,想想看你到底想要你的应用如何运作,简而言之,就是让两者同时发生,在反馈用户操作的时候,也能够处理事件
    • 在PyQt应用中,有两个主要的方法去运行独立的任务:线程和进程
    • 线程共享相同的内存空间,所以能够很快启动和消耗最少的资源。共享的内存使得线程之间传递信息变得很容易,但是不同线程读写内存会导致竞争现象和阻塞。在一个pythonGUI中,
    • 进程使用分立的内存空间(并且是一个完全分开的python编译器)。这就回避了任何GIL导致的潜在问题,但是代价就是增加了启动所花费时间,增加的内存开销,以及发送/收取数据的时间复杂度。
    • 如果不是特别的原因,为了简单起见,一般都是使用线程进行解决。在Qt中,子程序更适合于运行以及与外部程序进行交互。

    QRunnable and the QThreadPool

    • 对于在别的线程中运行工作,Qt提供了很简单接口,这在PyQt中也有很好的体现。这主要是依靠两个类实现:QRunnable 和QThreadPool。QRunnable是你想运行的工作的容器,QThreadPool是线程池,通过这个能够选择额外启动的线程。
    • 使用QThreadPool的好处就是能够替你执行任务的排队和执行。除了给工作排队和检索结果,并不需要做任何事情。
    • 为了定义一个自定义的QRunnable,你可以继承QRunnable类,并将你要运行的代码放到run代码中。下面的代码是我们运行时间比较长的代码time.sleep作为QRunnable的具体实施。将下述代码加入到之前的python代码中。
    class Worker(QRunnable):
        '''
        Worker thread
        '''
    
        @pyqtSlot()
        def run(self):
            '''
            Your code goes in this function
            '''
            print("Thread start")
            time.sleep(5)
            print("Thread complete")
    
    • 在另外一个线程运行我们的函数仅仅是创建一个worker实例,并且将之传入到QThreadPool实例中即可,然后就会自动运行。
    • 下一步,将下属模块添加到__init__模块中即可,然后设置线程池。
    self.threadpool = QThreadPool()
    print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
    
    • 最后,将下述几行增加到我们oh_no函数中
    def oh_no(self):
        worker = Worker()
        self.threadpool.start(worker)
    
    • 现在点击按钮,将会创建一个worker去处理运行时间比较长的进程,并通过线程池将之转换到另外一个线程中运作。如果没有足够的线程去应对输入的工作任务,将会将工作任务进行排队,然后按照顺序进行执行。
    • 尝试一下,然后你就能看见你的应用现在正在处理之前出问题的那个按钮,并且没有任何问题。
    • 你尝试多次按一下按钮,检查一下会发生什么。你应该观察一下最大线程的运行数量是不是已经到了.maxThreadCount。如果你点击按钮之后,发现已经运行的足够多的worker,你就会发现的提交的worker就会被放入队列中等待,直到有线程能够给你用。

    Improved QRunnables

    • 如果你想在执行函数中传入自定义的数据,你能通过init函数实现,然后从run的信息槽中能够通过self属性获得数据。
    class Worker(QRunnable):
        '''
        work thread
        '''
        
        def __init__(self,*args,**kwargs):
            super(Worker, self).__init__()
            self.args = args
            self.kwargs = kwargs
        
        @pyqtSlot()
        def run(self):
            '''
            your code goes in this function
            :return:
            '''
            print(self.args,self.kwargs)
            
    
    • 事实上,我们能够利用这个事实,那就是在python中,函数是对象,是可以传递进函数中去执行的,不是每一次都是继承子类。在下面的构造中,我们仅仅需要一个单独的Worker类去处理所有任务的执行。
    class Worker(QRunnable):
        '''
        work thread
    
        继承QRunnable去处理工作进程的设置、信号处理和包装,函数本身对象,并不是子类,是可以进行传递的
    
        :param callback:函数回调是运行在工作进程中,提供的参数args和kwargs是传递进需要执行的进程的
        :param args and kwargs:传递给函数回调进行执行的
        '''
    
        def __init__(self,fn,*args,**kwargs):
            super(Worker, self).__init__()
            self.fn = fn
            self.args = args
            self.kwargs = kwargs
    
        @pyqtSlot()
        def run(self):
            '''
            初始化执行函数,并传入参数
            :return:
            '''
            self.fn(self.args,self.kwargs)
    
    • 你现在可以传入任何python函数,并使用分立的进程去执行。
    def execute_this_fn(self):
        print('hello')
    
    def oh_no(self):
    
        # pass the function to execute
        # pass the function and args
        worker = Worker(self.execute_this_fn)
        self.threadpool.start(worker)
    
    • 实现多线程主要是两个步骤,继承并实现QRunnable的子类,同时在其中的run函数中指定自己要实现函数。然后在原先的类中重建一个threadpool,然后通过run方法进行实现即可。

    Thread IO

    • 有时,我们需要从运行的线程中传回状态和数据。这其中包含计算的结果,产生的异常或者正在运行的程序。Qt提signals和slots框架,让你能够在线程安全的前提下完成这个任务,从而让你GUI前端和运行的线程能够顺利交互。signals使你能够发出值values,然后在你代码中的某一个slot函数接收产生的值。
    • 如下就是一个简单的WorkerSignals类,包含了几个样例signals
    • 注意,自定义的信号只能在QObject的对象上进行定义,因为QRunnable并没有从QObject中产生的,所以我们不能直接定义signals。一个自定义的保存signals的QObject对象才是最简单的解决办法
    import traceback, sys
    
    class WorkerSignals(QObject):
        '''
        Defines the signals available from a running worker thread.
    
        Supported signals are:
    
        finished
            No data
    
        error
            tuple (exctype, value, traceback.format_exc() )
    
        result
            object data returned from processing, anything
    
        '''
        finished = pyqtSignal()
        error = pyqtSignal(tuple)
        result = pyqtSignal(object)
    
    • 在这个样例中,我们定义了三种信号。
      • finished 信号,当任务完成的时候,并没有任何数据显示
      • error 信号,接收Exception类型元组,Exception值以及格式化的回溯信息
      • result 信号,接收执行函数的任何对象类型的结果
    • 你可能不需要这些信号,但是他们包含了程序运行的任何可能的情况。在如下的代码中,我们准备运行一个持久性的任务,这个任务能够利用这些信号给用户产生有用的信息
    class Worker(QRunnable):
        '''
        work thread
    
        继承QRunnable去处理工作进程的设置、信号处理和包装,函数本身对象,并不是子类,是可以进行传递的
    
        :param callback:函数回调是运行在工作进程中,提供的参数args和kwargs是传递进需要执行的进程的
        :param args and kwargs:传递给函数回调进行执行的
        '''
    
        def __init__(self,fn,*args,**kwargs):
            super(Worker, self).__init__()
            self.fn = fn
            self.args = args
            self.kwargs = kwargs
            self.signals = WorkerSignals()
    
        @pyqtSlot()
        def run(self):
            '''
            初始化执行函数,并传入参数
            :return:
            '''
            try:
                result = self.fn(
                    *self.args,**self.kwargs
                )
            except:
                # 异常打印出异常信息
                traceback.print_exc()
                exctype,value = sys.exc_info()[:2]
                self.signals.error.emit((exctype, value, traceback.format_exc()))
            else:
                self.signals.result.emit(result)  # Return the result of the processing
            finally:
                self.signals.finished.emit()  # Done
    
    • 你能将你自己handler函数连接到这些signals上,接受线程完成提醒
    def execute_this_fn(self):
        for n in range(0, 5):
            time.sleep(1)
        return "Done."
    
    def print_output(self, s):
        print(s)
    
    def thread_complete(self):
        print("THREAD COMPLETE!")
    
    def oh_no(self):
        # Pass the function to execute
        worker = Worker(self.execute_this_fn) # Any other args, kwargs are passed to the run function
        worker.signals.result.connect(self.print_output)
        worker.signals.finished.connect(self.thread_complete)
    
        # Execute
        self.threadpool.start(worker)
    
    • 你也想获取当前持久性运行程序的运行状态,这可以通过传入回调函数实现,你正在运行的代码将通过这个传回你的运行结果。你在这里有两个选择:定义新的信号或者使用标准的python函数
    • 无论是哪个方法,你都需要将函数传入目标函数才能使用他们,下面是完整的代码,使用了基于信号的方法,我们传回了一个int数据作为线程进度%的指示器。

    The Complete code

    • 一个完整的代码在下面给出了,展示了自定义QRunnable工作任务和worker和进程信号协调工作。借此你就能够很容易的将你的代码转换到你要调用的平台。
    from PyQt5.QtGui import *
    from PyQt5.QtWidgets import *
    from PyQt5.QtCore import *
    
    import time
    import traceback, sys
    
    
    class WorkerSignals(QObject):
        '''
        Defines the signals available from a running worker thread.
    
        Supported signals are:
    
        finished
            No data
    
        error
            tuple (exctype, value, traceback.format_exc() )
    
        result
            object data returned from processing, anything
    
        progress
            int indicating % progress
    
        '''
        finished = pyqtSignal()
        error = pyqtSignal(tuple)
        result = pyqtSignal(object)
        progress = pyqtSignal(int)
    
    
    class Worker(QRunnable):
        '''
        Worker thread
    
        Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
    
        :param callback: The function callback to run on this worker thread. Supplied args and
                         kwargs will be passed through to the runner.
        :type callback: function
        :param args: Arguments to pass to the callback function
        :param kwargs: Keywords to pass to the callback function
    
        '''
    
        def __init__(self, fn, *args, **kwargs):
            super(Worker, self).__init__()
    
            # Store constructor arguments (re-used for processing)
            self.fn = fn
            self.args = args
            self.kwargs = kwargs
            self.signals = WorkerSignals()
    
            # Add the callback to our kwargs
            self.kwargs['progress_callback'] = self.signals.progress
    
        @pyqtSlot()
        def run(self):
            '''
            Initialise the runner function with passed args, kwargs.
            '''
    
            # Retrieve args/kwargs here; and fire processing using them
            try:
                result = self.fn(*self.args, **self.kwargs)
            except:
                traceback.print_exc()
                exctype, value = sys.exc_info()[:2]
                self.signals.error.emit((exctype, value, traceback.format_exc()))
            else:
                self.signals.result.emit(result)  # Return the result of the processing
            finally:
                self.signals.finished.emit()  # Done
    
    
    
    class MainWindow(QMainWindow):
    
    
        def __init__(self, *args, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
    
            self.counter = 0
    
            layout = QVBoxLayout()
    
            self.l = QLabel("Start")
            b = QPushButton("DANGER!")
            b.pressed.connect(self.oh_no)
    
            layout.addWidget(self.l)
            layout.addWidget(b)
    
            w = QWidget()
            w.setLayout(layout)
    
            self.setCentralWidget(w)
    
            self.show()
    
            self.threadpool = QThreadPool()
            print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
    
            self.timer = QTimer()
            self.timer.setInterval(1000)
            self.timer.timeout.connect(self.recurring_timer)
            self.timer.start()
    
        def progress_fn(self, n):
            print("%d%% done" % n)
    
        def execute_this_fn(self, progress_callback):
            for n in range(0, 5):
                time.sleep(1)
                progress_callback.emit(n*100/4)
    
            return "Done."
    
        def print_output(self, s):
            print(s)
    
        def thread_complete(self):
            print("THREAD COMPLETE!")
    
        def oh_no(self):
            # Pass the function to execute
            worker = Worker(self.execute_this_fn) # Any other args, kwargs are passed to the run function
            worker.signals.result.connect(self.print_output)
            worker.signals.finished.connect(self.thread_complete)
            worker.signals.progress.connect(self.progress_fn)
    
            # Execute
            self.threadpool.start(worker)
    
    
        def recurring_timer(self):
            self.counter +=1
            self.l.setText("Counter: %d" % self.counter)
    
    
    app = QApplication([])
    window = MainWindow()
    app.exec_()
    

    在这里插入图片描述

    Caveats

    • 你可能已经意识到了主程序的一些小问题——我们仍旧使用事件循环来处理工作任务的输出。
    • 当我们仅仅追踪进程时,这并不是一个问题。但是,如果你有工作任务需要返回大量的数据,比如说加载大型文件,运行负载的数据分析,或者查询数据库,——而通过GUI传递这些数据可能会导致性能上的问题。
    • 相似的,如果你的应用需要利用大量的线程和Python结果处理器,你可能会遇到GIL的限制。正如之前提到的,使用线程运行python程序一瞬间只能执行一个线程。处理你线程信号的python代码,能够阻止你的任务顺利执行,反之亦然。
    • 在这些样例中,一般来说使用单纯的python线程池保证你的处理和线程事件的处理和GUI独立是比较好,但是注意,任何pythonGUI都会阻碍python程序的运行,除非他是分立的进程。

    总结

    • 使用线程的方式,还是有很多问题,仍旧是单个python程序运作,著不过是不停地切换,同时这也方便了线程之间的通信。
    • 刚好可以用来作关键帧分析的程序,每一次返回的就是关键帧评价指数,然后在进行比对,确定关键帧对应的图片。

    引用和借鉴

    Multithreading PyQt5 applications with QThreadPool

    展开全文
  • 主要是抓取数据时, cpu占用率太高(进程\线程切换太费系统资源), 内存占用太大变量用完没有删除, 没时间就不改了, 先储存着, 以后有时间再改, 一个系统的功能还未完全, 比如: 数据库查找, 本地txt保存, 多线程多进程...

    有个问题没解决: 将运行过程显示在右边的界面框里的光标问题(光标问题容易解决), 及异常退出的问题.
    错误代码:
    QObject::connect: Cannot queue arguments of type ‘QTextCursor’
    (Make sure ‘QTextCursor’ is registered using qRegisterMetaType().)

    Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
    原因: 意外退出的原因结果调试得出的结果应该是,使用QTextBrowser时,由于使用了多线程,因为使用的方式是将其当做参数传入函数由线程调用, 当一个线程运行完销毁时,销毁了传入的QTextBrowser, 由于线程数据共享所以也导致界面意外崩溃.
    解决: 采用进程间的队列通信,使得前面的问题得到了解决.
    在这里插入图片描述
    在这里插入图片描述
    这幅图存在上述问题.
    代码:(测试过了, 有问题, 主要是抓取数据时, cpu占用率太高(进程\线程切换太费系统资源), 内存占用太大变量用完没有删除, 没时间就不改了, 先储存着, 以后有时间再改, 一个系统的功能还未完全, 比如: 数据库查找, 本地txt保存, 多线程多进程启动时QTextBrowser的数据显示问题; 代码存在的问题: 代码健壮性不足, 代码冗余, 链接获取取巧了)
    主运行代码(main.py):

    from uI import UserInterface
    from moduleFunc import *
    from PyQt5.QtWidgets import QApplication
    import sys
    # 逻辑运行区
    def logicalArea(UI):
        dbName = pymongoInit(UI)
        UI.editA.clear()
        UI.editA.append('欢迎来到逻辑运行区!')
        # 保存+显示
        if UI.uIControl[5][1].isChecked():
            UI.editA.append('逻辑保存')
            if UI.uIControl[7][1].isChecked():
                UI.editA.append('逻辑开启多线程')
                # 显示所有小说的标题及保存所有小说的内容
                if UI.uIControl[11][1].isChecked():
                    UI.editA.append('逻辑开启多进程')
                    # 获取整个站点的小说(待完成)
                    getAllNovelChapterInfo(UI, saveFlag=True, dbName=dbName)
                # 获取一本小说的总info, 及保存小说(已完成)
                else:
                    UI.editA.append('逻辑未开多进程')
                    saveNovelChapterInfo(UI, dbName)
            else:
                UI.editA.append('逻辑未开多线程')
                # 显示所有小说的标题和url及保存相应信息
                if UI.uIControl[11][1].isChecked():
                    UI.editA.append('逻辑开启多进程')
                    # 显示并保存一个站点所有的小说信息
                    saveAllNovelInfo(UI, dbName)
                # 显示单篇小说的内容及保存该内容至指定数据库*********************
                else:
                    UI.editA.append('逻辑未开多进程')
                    saveSimgleArt(UI, dbName)
        else:
            UI.editA.append('逻辑只显示')
            # 显示
            if UI.uIControl[7][1].isChecked():
                UI.editA.append('逻辑开启多线程')
                if UI.uIControl[11][1].isChecked():
                    UI.editA.append('逻辑开启多进程')
                    # 显示一个站点所有小说及每部小说的详细信息的信息(待完成----未开多线程深入一部, 多进入一个网页)
                    getAllNovelChapterInfo(UI)
                else:
                    UI.editA.append('逻辑未开多进程')
                    # 显示一部小说的信息
                    getNovelChapterInfo(UI=UI)
            else:
                UI.editA.append('逻辑未开多线程')
                # 显示所有小说的名字及url
                if UI.uIControl[11][1].isChecked():
                    UI.editA.append('逻辑开启多进程')
                    # 显示所有小说信息
                    getAllNovelInfo(UI)
                else:
                    UI.editA.append('逻辑未开多进程')
                    # 显示一篇小说
                    simgleArt(UI)
    def initData(UI):
        UI.uIControl[0][1].setText('http://www.xbiquge.la/15/15409/')
        UI.uIControl[3][1].setText('<div id="content">(.*?)<p>.*?</p>*.?</div>')
        UI.uIControl[6][1].setText('<h1>(.*?)</h1>')
        UI.uIControl[8][1].setText("<dd><a href='(.*?)' >(.*?)</a></dd>")
        UI.uIControl[10][1].setText(str(16))
        UI.uIControl[12][1].setText(str(4))
        UI.uIControl[13][1].setText('<li><a href="(.*?)">(.*?)</a></li>')
        UI.uIControl[2][1].setText('道君')
        UI.uIControl[14][1].setText('biquge')
    def main():
        app = QApplication(sys.argv)
        UI = UserInterface()
        initData(UI)
    
        UI.uIControl[4][1].clicked.connect(lambda: logicalArea(UI))
        sys.exit(app.exec_())
    if __name__ == '__main__':
        main()
    

    自定义函数部分(moduleFunc.py):

    import pymongo, requests, re, os
    from PyQt5.QtWidgets import QMessageBox
    from threading import Thread
    from multiprocessing import Process, Pool
    # 创建数据库连接
    def pymongoInit(UI):
        client = pymongo.MongoClient('localhost', 27017, connect=False)
        dbName = UI.uIControl[14][1].text()
        biquge = client[dbName]
        return biquge
    # 创建数据表(已完成)
    def pymongeUser(UI=None, biquge=None, name=None, data=None, mulSaveFlag=False):
        try:
            table = biquge[name]
        except:
            if not mulSaveFlag:
                UI.queue.put('<font color=red>创建数据库{}失败</font>'.format(name))
            else:
                print('\033[31;0m创建数据库{}失败\033[0m'.format(name))
            return 0
        # 判断重复
        if data['title'] not in [article['title'] for article in table.find()]:
            table.insert_one(data)
            if not mulSaveFlag:
                UI.queue.put('<font color=green>{0} {1} {2}</font>'.format(name, data['title'], '数据存入成功'))
            else:
                print(('\033[32;0m{0} {1} {2}\033[0m'.format(name, data['title'], '数据存入成功')))
        else:
            # 存入的数据content是列表时,执行本操作
            if isinstance(data['content'], list):
                # 遍历所有数据库
                for obj in table.find():
                    # 查找数据库中content实例为list类型的值
                    if isinstance(obj['content'], list):
                        try:
                            newlyAdd = [item for item in data['content'] if list(item) not in obj['content']]
                        except:
                            print('作者修改了一部分文章')
                        else:
                            if newlyAdd != []:
                                data = {'title': data['title'], 'content': newlyAdd}
                                table.insert_one(data)
                            else:
                                if not mulSaveFlag:
                                    UI.queue.put('<font color=#CCCC33>{}</font>'.format(name) + '<font color=#CCCC33>数据库链接更新已完成</font>')
                                else:
                                    print(('\033[33;0m{}\033[0m'.format(name) + '\033[32;0m数据库链接更新已完成\033[0m'))
            else:
                UI.queue.put('\033[33;0m{}\033[0m'.format(name) + '\033[33;0m{}\033[0m'.format(data['title']) + '\033[33;0m数据已在数据库\033[0m')
    # 获取一章文章, 只返回,不显示(已完成)
    def getPage(UI=None, artUrl=None):
        headers = {'User-Agent': 'Opera/8.0 (Windows NT 5.1; U; en)', }
        try:
            res = requests.get(artUrl, headers=headers)
        except:
            UI.queue.put('<font color=red size=3>获取文章错误</font>' + '<font color=red size=3>{}</font>'.format(artUrl))
            return 0
        else:
            res.encoding = res.apparent_encoding
            try:
                title = re.findall(UI.uIControl[6][1].text(), res.text, re.S)[0]
            except:
                QMessageBox.warning(UI, '警告', 'simglePage正则输入或网址错误, 请检查!')
                return 0
            else:
                try:
                    content = re.findall(UI.uIControl[3][1].text(), res.text, re.M)[0].replace('&nbsp;', '  ').replace(
                        '<br />', '\n')
                except:
                    QMessageBox.warning(UI, '警告', 'pageName正则或网址错误, 请检查!')
                    return 0
                else:
                    return {'title': title, 'content': content, 'id': int(artUrl.split('/')[-1].split('.')[0]), 'link': artUrl}
    
    # 保存一个线\进程的文章
    def saveMulArt(UI, dbName, artUrls, name, mulSaveFlag=False):
        for link in artUrls:
            saveSimgleArt(UI, dbName, link, name, mulSaveFlag)
    
    
    # 获取一本小说的总info, 及保存小说(已完成)
    def saveNovelChapterInfo(UI, dbName, novUrl=None, mulSaveFlag=False):
        info = getNovelChapterInfo(UI=UI, novUrl=novUrl)
        if info == 0:
            return 0
        pymongeUser(UI, dbName, info['title'], info, mulSaveFlag)
        threadNum = UI.uIControl[12][1].text()
        if threadNum.replace(' ', '') == '':
            QMessageBox.warning(UI, '警告', '进程数未填入')
            return 0
        threadNum = int(threadNum)
        if threadNum <= 0:
            QMessageBox.warning(UI, '警告', '进程数填写错误')
            return 0
        links = [link for link, title in info['content']]
        linksPart = len(links) // threadNum
        for i in range(threadNum):
            if i < threadNum - 1:
                th = Thread(target=saveMulArt,
                               args=(UI, dbName, links[i * linksPart: (i + 1) * linksPart], info['title'], mulSaveFlag))
            else:
                th = Thread(target=saveMulArt, args=(UI, dbName, links[i * linksPart:], info['title'], mulSaveFlag))
            th.start()
            UI.queueExist.append(th)
    # 获取所有小说的标题及url并保存相关信息(已完成)
    def saveAllNovelInfo(UI, dbName, rootUrl=None):
        info = getAllNovelInfo(UI, rootUrl)
        if info == 0:
            return 0
        pymongeUser(UI, dbName, info['title'], info)
    # 获取一章文章并保存(已完成)
    def saveSimgleArt(UI, dbName, artUrl=None, name=None, mulSaveFlag=False):
        if artUrl == None:
            artUrl = UI.uIControl[0][1].text()
        if artUrl.replace(' ', '') == '':
            QMessageBox.warning(UI, '警告', '网址未输入')
            return 0
        info = simgleArt(UI, artUrl)
        if info == 0:
            return 0
        if name == None:
            name = UI.uIControl[2][1].text()
            if name.replace(' ', '') == '':
                return 0
        pymongeUser(UI, dbName, name, info, mulSaveFlag)
    # 完成函数,显示一个站点所有小说及每部小说的详细信息的信息(待完成----未开多线程深入一部, 多进入一个网页, 统计每部小说的信息)
    # saveFlag为True时变为保存整个站点数据的函数
    def getAllNovelChapterInfo(UI, rootUrl=None, mulSaveFlag=False, dbName=None):
        info = getAllNovelInfo(UI, rootUrl)
        if info == 0:
            return 0
        mulName = UI.uIControl[12][1].text()
        if mulName.replace(' ', '') == '':
            QMessageBox.warning(UI, '警告', '进程数未填入')
            return 0
        mulName = int(mulName)
        if mulName <= 0:
            QMessageBox.warning(UI, '警告', '进程数填写错误')
            return 0
        regName = UI.uIControl[6][1].text()
        regCont = UI.uIControl[8][1].text()
        if regName.replace(' ', '') == '' or regCont.replace(' ', '') == '':
            QMessageBox.warning(UI, '警告', '多进程名称和内容填入错误')
            return 0
        links = [link for link, title in info['content']]
        linksPart = len(links) // mulName
        for i in range(mulName):
            if i < mulName - 1:
                pro = Process(target=getNovelChapterInfo_raw, args=(UI, links[i * linksPart: (i + 1) * linksPart], mulSaveFlag, dbName))
                pro.start()
            else:
                pro = Process(target=getNovelChapterInfo_raw, args=(UI, links[i * linksPart:], mulSaveFlag, dbName))
                pro.start()
            UI.queueProce.append(pro)
    # saveFlag = False多进程获取小说信息(已完成)
    # saveFlag = True获取整个站点的小说(待完成)
    def getNovelChapterInfo_raw(UI, linksList, mulSaveFlag=False, dbName=None):
        for link in linksList:
            if not mulSaveFlag:
                data = getNovelChapterInfo(UI, link, mulProcess=True)
                UI.queue.put('<font color=green>小说信息: {0} 共{1}章</b></font>'.format(data['title'], len(data['content'])))
            else:
                saveNovelChapterInfo(UI, dbName, novUrl=link, mulSaveFlag=mulSaveFlag)
    # 获取一本小说的总info(已完成)
    def getNovelChapterInfo(UI=None, novUrl=None, mulProcess=False):
        headers = {'User-Agent': 'Opera/8.0 (Windows NT 5.1; U; en)', }
        if novUrl == None:
            novUrl = UI.uIControl[0][1].text()
        if novUrl.replace(' ', '') == '':
            QMessageBox.warning(UI, '警告', '请填写一部小说目录url')
            return 0
        try:
            res = requests.get(novUrl, headers=headers)
        except:
            QMessageBox.warning(UI, '警告', '获取整部小说的url失败')
            return 0
        else:
            res.encoding = res.apparent_encoding
            try:
                title = re.findall(UI.uIControl[6][1].text(), res.text, re.S)[0]
            except:
                QMessageBox.warning(UI, '警告', '获取小说的名称失败')
                return 0
            try:
                content = re.findall(UI.uIControl[8][1].text(), res.text, re.S)
            except:
                QMessageBox.warning(UI, '警告', '获取小说的章节目录失败')
                return 0
            if UI.uIControl[9][1].isChecked():
                urlSplit = novUrl.split('/')
                linksSplit = [link.split('/') for link, title in content]
                links = [link for link, title in content]
                linksPart = ['/'.join([value for value in item if value in urlSplit]) for item in linksSplit]
                links = [novUrl + link.replace(part + '/', '') for link, part in zip(links, linksPart)]
            else:
                links = [novUrl + link for link, title in content]
                links = ['http://' + link.replace('http://', '').replace('//', '/') for link in links]
            tempTitles = [title for link, title in content]
            content = list(zip(links, tempTitles))
            if not mulProcess:
                UI.queue.put('{0}'.format('\n'.join([title + '\n' + link for link, title in content])))
                UI.queue.put('<font color=green><b>整篇小说信息获取成功! {0} 共{1}章</b></font>'.format(title, len(content)))
            return {'title': title, 'content': content, 'link': novUrl}
    # 显示所有小说的标题及url(已完成)
    def getAllNovelInfo(UI, rootUrl=None):
        headers = {'User-Agent': 'Opera/8.0 (Windows NT 5.1; U; en)', }
        if rootUrl == None:
            rootUrl = UI.uIControl[0][1].text()
        if rootUrl.replace(' ', '') == '':
            QMessageBox.warning(UI, '警告', '请填写网站的总目录url')
            return 0
        try:
            res = requests.get(rootUrl, headers=headers)
        except:
            QMessageBox.warning(UI, '警告', '提取网站小说总目录错误' + rootUrl)
            return 0
        else:
            res.encoding = res.apparent_encoding
            try:
                info = re.findall(UI.uIControl[13][1].text(), res.text, re.S)
                info = [(link, title) for link, title in info if len(link.split('/')) > 5]
            except:
                QMessageBox.warning(UI, '警告', '提取网站所有的小说链接错误')
                return 0
            saveName = UI.uIControl[14][1].text()
            if saveName.replace(' ', '') == '':
                QMessageBox.warning(UI, '警告', '网站名称未填入')
                return 0
            showInfo = [' '.join(info) for info in info]
            UI.queue.put('\n'.join(showInfo))
            try:
                links, novelNames = zip(*info)
            except:
                QMessageBox.warning(UI, '警告', '您确认该网页的符合要求?')
                return 0
            UI.queue.put('<font color=green><b>成功找到小说{}部</b></font>'.format(str(len(links))))
            return {'title': saveName, 'content': info}
    # 获取一章文章(已完成)
    def simgleArt(UI=None, artUrl=None):
        if artUrl == None:
            artUrl = UI.uIControl[0][1].text()
            if artUrl.replace(' ', '') == '':
                QMessageBox.warning(UI, '警告', '网址未输入')
                return 0
            article = getPage(UI, artUrl)
            if article == 0:
                return 0
            UI.editA.clear()
            UI.queue.put('<font color=green>{}</font>'.format(article['title']))
            article['content'] = article['content'].replace('小哥哥小姐姐们,推荐,收藏有木有', '').replace(' (三七中文  et)', '')
            UI.queue.put(article['content'])
        else:
            article = getPage(UI, artUrl)
            if article == 0:
                return 0
            article['content'] = article['content'].replace('小哥哥小姐姐们,推荐,收藏有木有', '').replace(' (三七中文  et)', '')
        return {'title': article['title'], 'content': article['content'], 'id': article['id'], 'link': article['link']}
    

    UI部分代码(uI.py):

    import os, sys, functools
    #from queue import Queue
    from PyQt5.QtWidgets import (QWidget, QSplitter, QFrame, QFormLayout,
                                 QLabel, QLineEdit, QCheckBox, QPushButton,
                                 QVBoxLayout, QTextBrowser, QDesktopWidget,
                                 QApplication, QFileDialog, QMessageBox)
    from PyQt5.QtCore import Qt, QTimer
    from multiprocessing import Pipe, Queue
    class UserInterface(QWidget):
        def __init__(self, parent=None):
            super(UserInterface, self).__init__(parent)
            self.setWindowTitle('爬虫UI主窗口')
            self.centralScreen()
            self.uIDesign()
            self.operationInit()
            self.initTimer()
            self.show()
        def uIDesign(self):
            spliter = QSplitter(Qt.Horizontal, self)
            spliter.resize(self.size())
            self.framea  = QFrame()
            self.uIDesignL()
            self.frameb  = QFrame()
            self.uIDesignR()
            spliter.addWidget(self.framea)
            spliter.addWidget(self.frameb)
        # 左边ui
        def uIDesignL(self):
            self.framea.setStyleSheet('background-color: #D1EEEE')
            self.framea.resize(self.size().width()/2, self.size().height()/2)
            # 设置
            form = QFormLayout(self.framea)
            self.uIControl = ( (QLabel('网址:'),           QLineEdit()),
                               (QLabel('保存'),            QPushButton('本地保存路径')),
                               (QLabel('小说名称:'),        QLineEdit()),
                               (QLabel('simglePage正则:'), QLineEdit()),
                               (QLabel(),                 QPushButton('开始执行')),
                               (QLabel('本地保存?'),        QCheckBox('是')),
                               (QLabel('Name正则:'),       QLineEdit()),
                               (QLabel('开启多线程?'),      QCheckBox('是')),
                               (QLabel('mulPage正则:'),    QLineEdit()),
                               (QLabel('链接重复:'),        QCheckBox('是')),
                               (QLabel('多线程数:'),        QLineEdit()),
                               (QLabel('开启多进程?'),      QCheckBox('是')),
                               (QLabel('多进程数:'),        QLineEdit()),
                               (QLabel('root正则'),        QLineEdit()),
                               (QLabel('网站名:'),         QLineEdit()),)
            for row in self.uIControl:
                form.addRow(*row)
        # 控件事件
            self.uIControl[1][1].clicked.connect(self.savePath)
            self.uIControl[5][1].stateChanged.connect(self.checkSaveFile)
            self.uIControl[7][1].stateChanged.connect(self.checkMulThread)
            self.uIControl[11][1].stateChanged.connect(self.checkMulProcss)
        # 保存
        def savePath(self):
            dir, ok = QFileDialog.getSaveFileName(self, '保存', '', 'FilterFile (*)')
            if ok:
                os.mkdir(dir)
                self.uIControl[1][1].setText(dir)
        # 右边ui
        def uIDesignR(self):
            self.frameb.setStyleSheet('background-color: #F0F8FF')
            self.frameb.resize(self.size().width() / 2, self.size().height() / 2)
            layout = QVBoxLayout(self.frameb)
            layout.setContentsMargins(0, 0, 5, 0)
            label = QLabel('<font size=4><b>运行过程</b></font>')
            self.editA = QTextBrowser()
            self.cursor = self.editA.textCursor()
            self.editA.setStyleSheet('background-color: white')
            layout.addWidget(label)
            layout.addWidget(self.editA)
    
    
        # 通知消息
        def noticeMes(self, message):
            QMessageBox.information(self, '通知', message)
        # 初始化操作
        def operationInit(self):
            self.uIControl[0][1].setStyleSheet('background-color: white')
            self.uIControl[3][1].setStyleSheet('background-color: white')
            self.uIControl[6][1].setStyleSheet('background-color: white')
            self.uIControl[14][1].setStyleSheet('background-color: white')
            for lock in [1, 2, 8, 10, 12, 13]:
                self.uIControl[lock][1].setEnabled(False)
        # 开启本地保存
        def checkSaveFile(self):
            if self.uIControl[5][1].isChecked():
                if not self.uIControl[7][1].isChecked() and not self.uIControl[11][1].isChecked():
                    self.uIControl[2][1].setStyleSheet('background-color: white')
                    self.uIControl[2][1].setEnabled(True)
                    self.uIControl[1][1].setEnabled(True)
                    self.editA.append('开启本地保存')
            else:
                for lock in [1, 2]:
                    self.uIControl[lock][1].setEnabled(False)
                self.editA.append('关闭本地保存')
                self.uIControl[1][1].setText('保存')
                self.uIControl[2][1].setStyleSheet('background-color: #D1EEEE')
        # 开启多线程
        def checkMulThread(self):
            if self.uIControl[7][1].isChecked():
                self.uIControl[2][1].setEnabled(False)
                self.uIControl[2][1].setStyleSheet('background-color: #D1EEEE')
                for unlock in [8, 10]:
                    self.uIControl[unlock][1].setEnabled(True)
                    self.uIControl[unlock][1].setStyleSheet('background-color: white')
                self.editA.append('开启多线程')
            else:
                if not self.uIControl[11][1].isChecked() and self.uIControl[5][1].isChecked():
                    self.uIControl[2][1].setEnabled(True)
                    self.uIControl[2][1].setStyleSheet('background-color: white')
                for lock in [8, 10]:
                    self.uIControl[lock][1].setEnabled(False)
                    self.uIControl[lock][1].setStyleSheet('background-color: #D1EEEE')
                self.editA.append('关闭多线程')
        # 开启多进程
        def checkMulProcss(self):
            if self.uIControl[11][1].isChecked():
                self.uIControl[2][1].setEnabled(False)
                self.uIControl[2][1].setStyleSheet('background-color: #D1EEEE')
                for unlock in [12, 13]:
                    self.uIControl[unlock][1].setEnabled(True)
                    self.uIControl[unlock][1].setStyleSheet('background-color: white')
                self.editA.append('开启多进程')
            else:
                if self.uIControl[5][1].isChecked() and not self.uIControl[7][1].isChecked():
                    self.uIControl[2][1].setEnabled(True)
                    self.uIControl[2][1].setStyleSheet('background-color: white')
                for lock in [12, 13]:
                    self.uIControl[lock][1].setEnabled(False)
                    self.uIControl[lock][1].setStyleSheet('background-color: #D1EEEE')
                self.editA.append('关闭多进程')
        # 初始化定时刷新QTextBrowser
        def initTimer(self):
            # 处理线程的队列
            self.queue = Queue()
            # 处理进程的队列
            self.queuep = Queue()
            # 处理线程存在与否的寄存列表初始化, 在线程下载小说的功能中使用
            self.queueExist = []
            # 处理进程存在与否的寄存列表初始化, 在进程下载小说的功能中使用
            self.queueProce = []
            self.time = QTimer(self)
            self.time.start(1000)
            self.time.timeout.connect(self.timeCustomEvent)
        # 初始化定时刷新QTextBrowser的槽函数
        def timeCustomEvent(self):
            if not self.queue.empty() and not self.uIControl[11][1].isChecked():
                outPut = self.queue.get()
                self.editA.append(outPut)
                # 如果列表中的所有线程都已销毁则执行该函数
                if not any([th.is_alive() for th in self.queueExist]) and self.uIControl[5][1].isChecked() and \
                        self.uIControl[7][1].isChecked():
                    #self.editA.append('<font color=green><b>{} 小说下载完毕</b></font>'.format(outPut.split(' ')[0]))
                    novelName = outPut.split('>')[1].split(' ')[0]
                    self.editA.append('<font color=green><b>{} 小说下载完毕</b></font>'.format(novelName))
            #elif not self.queuep.empty():
            '''
            else:
                outPutp = self.queuep.get()
                self.editA.append(outPutp)
                if not any(pro.is_alive() for pro in self.queueProce) and self.uIControl[5][1].isChecked() and \
                        self.uIControl[7][1].isChecked() and self.uIControl[11][1].isChecked():
                    self.editA.append('<font color=green><b>全站小说下载完毕</b></font>')
            '''
        # 初始化界面对中
        def centralScreen(self):
            screen = QDesktopWidget().geometry()
            size = self.size()
            self.move((screen.width() - size.width()) / 2,
                      (screen.height() - size.height()) / 2)
        # 关闭事件确认
        def closeEvent(self, e):
            reply = QMessageBox.question(self, '确认', '您确认要关闭吗?', QMessageBox.Yes|QMessageBox.No, QMessageBox.No)
            if reply == QMessageBox.Yes:
                e.accept()
            else:
                e.ignore()
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        self  = UserInterface()
        sys.exit(app.exec_())
    

    图片:
    , 在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    最初思路的图片:
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • PYQT5实现多线程的方法

    千次阅读 2021-11-16 12:25:24
    pyqt5处理多线程主要有三种方法: a.使用计时器模块QTimer b.使用多线程模块QThread c.使用事件处理功能 QTimer相当于一个定时器,每当定时器时间溢出后,会执行相关的函数。这个时候程序会从主线程界面跳到QTimer...
  • pyqt5 tablewidget 利用线程动态刷新数据

    万次阅读 多人点赞 2017-12-31 21:15:08
    表格刷新数据的方法大概知道要用线程,所以就先尝试写了一个线程,然后每次都获取数据,然后直接通过这种方法来朝table里面更新数据。但是要点击鼠标才会出现更新的数据。
  • Qt平台对SQL编程有着良好的支持,QtSql子模块提供对SQL数据库的支持。 Qt中SQL数据库模块简介 ...通常来说,我们会使用QSqlDatabase建立数据库连接,使用QSqlQuery等类实现数据库的交互(执行SQL语句)。此外还有Q
  • pyqt5+oracle界面展示

    2021-05-08 20:31:52
    最近在研究,pyqt5界面操作,保存到后台Oracle的一个操作,虽然比较简单,对于初学者来说还算是走了不少弯路。下面我来讲讲我自己的一段学习经历,看看是否对广大初学者有所帮助。目前想做一个检查文件状态是否存在...
  • 本章向读者介绍基于PyQt5的图形应用程序的框架及开发过程,以及分时操作系统的消息循环机制,还有多线程程序设计的基本概念和方法。冒泡排序不是重点。 A.3.1 开发环境准备 A.3.1.1 Qt Qt - https://www.qt.io/是...
  • de1.问题起因: PyQt5通过QSqlDatabase连接mysql出现“QSqlDatabase: QMYSQL driver not ...2)写个小测试程序看看当前pyqt5支持哪些数据库连接驱动 #!/usr/bin/env python3 # -*- coding: utf-8 -*- ' Test Data...
  • python+PyQt5实现文件安全传输

    千次阅读 2020-11-13 23:27:43
    总结如下: 通信、传输:socket库 DES加密解密:pyDes库或Crypto库 RSA加密解密:rsa库 MD5哈希:md5库(可选,因为rsa库本身也有签名+摘要+验签的功能) 软件界面:PyQt5库(包含界面设计、多线程等) 常用库:...
  • 在编写界面程序的时候经常会执行一些耗时的操作,所以我们要使用多线程来做耗时任务,主线程用来重绘界面。而子线程里边的实时处理结果需要反馈到界面。而总所周知,子线程里边不能执行界面更新操作。下面是我写微信...
  • 一、PyQt5简介 1、PyQt5简介 PyQt是Qt框架的Python语言实现,由RiverbankComputing开发,是最强大的GUI库之一。PyQt提供了一个设计良好的窗口控件集合,每一个PyQt控件都对应一个Qt控件,因此PyQt的API接口与Qt的...
  • 详解QT数据库类,详细讲解了QT连接Access的具体操作,以及个人的一些理解
  • 前言:想写一个应用好久了,独立开发大概是每个程序员的梦想。自从之前写了一个数据挖掘的小模型,一直想封装...这节之后,我们还剩下多线程进行模型更新,数据库可视化操作模块以及用户操作记录模块,离胜利不远了啊
  • pyside2与pyqt5

    2021-04-26 16:42:23
    这两个模块用到现在,在...在安装时碰到的坑,先用的pyqt5,后装的pyside2qt5并没有碰到过什么问题,但在安装后者是无法运行,会报错this application failed to start because no qt platform plugin could....大概...
  • PyQt5介绍

    万次阅读 2017-09-13 01:42:53
    PyQt5的介绍这个是翻译的英文版的PyQt5的中文教程。这篇教程的目的是让你开始使用PyQt5组件。这个教程中的例程都已经在Linux中测试过了。关于PyQt5 PyQt5是一套绑定Qt5的应用程序框架。他在Python 2.x和3.x中都是...
  • 笔者在使用PyQt5写完一个界面,出现以下错误: 我们这里先看源代码: import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox, QLineEdit from PyQt5.QtGui import QIcon from ...
  • PyQt5中文手册

    千次阅读 2020-12-03 10:55:55
    PyQt5 简介本教程的目的是带领你入门PyQt5。教程内所有代码都在Linux上测试通过。PyQt4 教程是PyQt4的教程,PyQt4是一个Python(同时支持2和3)版的Qt库。关于 PyQt5PyQt5 是Digia的一套Qt5应用框架与python的结合,...
  • python 多线程 通信

    2020-12-18 13:32:48
    一篇文章搞定Python多进程(全)公众号:pythonislover前面写了三篇关于python多线程的文章,大概概况了多线程使用中的方法,文章链接如下:一篇文章搞懂Python多线程简单实现和GIL - ...文章南山yrg2019-05-052651浏览量...
  • PyQt5 中文教程

    千次阅读 2020-12-20 04:04:12
    PyQt5中文教程,翻译自zetcode,项目地址:https://github.com/maicss/PyQt5-Chinese-tutoral这个教程比较好的地方是,能讲解每一段代码的含义。虽然PyQt的函数命名已经非常语义化了,但是对于新手来说,有这一步...
  • PyQt5-登录界面跳转主界面方法

    千次阅读 2020-07-24 10:29:30
    from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtCore import * from PyQt5.QtWidgets import QFileDialog, QMessageBox, QDockWidget, QListWidget from PyQt5.QtGui import * from Ui_Login import Ui_...
  • PyQt5 第一章 PyQt5简介和安装

    万次阅读 2021-01-23 21:17:30
    第一章 PyQt5简介和安装 1.1 PyQt5 简介 PyQt5是Digia的一套Qt5应用框架与python的结合,同时支持2.x和3.x。Qt库由Riverbank ...PyQt5类分为很模块,主要模块有: 模块 功能 QtCore 包含了核心的非GU
  • PyQt 5整体结构

    2020-01-02 14:17:39
    A.1 PyQt5整体介绍 PyQt5是基于图形程序框架Qt5的Python语言实现,由一组Python模块构成。 PyQt5的官方网站是:www.riverbankcomputing.co.uk。 PyQt5模块介绍的官网地址是:...
  • PyQT5 之 Qt Designer 介绍

    2021-08-03 17:47:18
    pyqt5做为Python的一个模块,它有620个类和6000个函数和方法。这是一个跨平台的工具包,它可以运行在所有主要的操作系统,包括UNIX,Windows,Mac OS。pyqt5是双重许可。开发者可以在GPL和商业许可之间进行选择。
  • PyQt5整体介绍

    2020-12-18 13:09:38
    1 PyQt5整体介绍PyQt5是基于图形程序框架Qt5的Python语言实现,由一组Python模块构成。PyQt5的官方网站是:www.riverbankcomputing.co.uk。PyQt5模块介绍的官网地址是:...
  • PyQt5 简介

    2020-12-11 13:48:11
    本教程的目的是带领你入门PyQt5。教程内所有代码都在Linux上测试通过。PyQt4 教程是PyQt4的教程,PyQt4是一个Python(同时支持2和3)版的Qt库。关于 PyQt5PyQt5 是Digia的一套Qt5应用框架与python的结合,同时支持2.x...
  • python PyQt5 教程

    万次阅读 多人点赞 2019-01-19 22:39:02
    PyQt5是一套来自Digia的Qt5应用框架和Python的粘合剂。支持Python2.x和Python3.x版本。 PyQt5以一套Python模块的形式来实现功能。它包含了超过620个类,600个方法和函数。它是一个平台的工具套件,它可以运行在...
  • pyqt5自动更新QTableview内容解决方案

    千次阅读 2020-02-22 15:18:01
    首先是自己在网上找了很久,基本上大家给出的解决方案都是开通一个子线程...from PyQt5.Qt import * import time class ThreadTable(QThread): update_date = pyqtSignal() //自定义一个信号 def __init__(self...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,528
精华内容 611
关键字:

pyqt5多线程连接数据库