You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5 pyqtSlot与downloadProgress信号不兼容的TypeError问题解决

PyQt5下载器downloadProgress信号签名不兼容问题解决

环境信息

  • PyQt5版本: 5.15.9
  • Python版本: 3.8
  • 操作系统: Windows 7

问题场景

实现简易网络下载器时,点击start_button触发以下异常:

TypeError('decorated slot has no signature compatible with downloadProgress(qint64,qint64)')

代码实现

from PyQt5.QtWidgets import (
    QWidget,
    QApplication,
    QMessageBox,
    QLineEdit,
    QProgressBar,
    QPushButton,
    QHBoxLayout,
    QVBoxLayout,
    QStyle,
    QFileDialog,
)
from PyQt5.QtCore import QStandardPaths, QUrl, QFile, QSaveFile, QDir, QIODevice, pyqtSlot
from PyQt5.QtWidgets import qApp
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
import sys


class DownloaderWidget(QWidget):
    """A widget to download a http file to a destination file"""

    def __init__(self, parent=None):
        super().__init__(parent)

        self.manager = QNetworkAccessManager(self)
        self.link_box = QLineEdit()
        self.dest_box = QLineEdit()
        self.progress_bar = QProgressBar()

        self.start_button = QPushButton("Start")
        self.abort_button = QPushButton("Abort")

        self.link_box.setPlaceholderText("Download Link ...")

        self._open_folder_action = self.dest_box.addAction(
            qApp.style().standardIcon(QStyle.SP_DirOpenIcon), QLineEdit.TrailingPosition
        )
        self._open_folder_action.triggered.connect(self.on_open_folder)

        # Current QFile
        self.file = None
        # Current QNetworkReply
        self.reply = None

        # Default http url
        self.link_box.setText(
            "http://master.qt.io/archive/qt/6.0/6.0.1/single/qt-everywhere-src-6.0.1.zip"
        )

        # Default destination dir
        self.dest_box.setText(
            QDir.fromNativeSeparators(
                QStandardPaths.writableLocation(QStandardPaths.DownloadLocation)
            )
        )

        # buttons bar layout
        hlayout = QHBoxLayout()
        hlayout.addStretch()
        hlayout.addWidget(self.start_button)
        hlayout.addWidget(self.abort_button)

        # main layout
        vlayout = QVBoxLayout(self)
        vlayout.addWidget(self.link_box)
        vlayout.addWidget(self.dest_box)
        vlayout.addWidget(self.progress_bar)
        vlayout.addStretch()
        vlayout.addLayout(hlayout)

        self.resize(300, 100)

        self.start_button.clicked.connect(self.on_start)
        self.abort_button.clicked.connect(self.on_abort)

    @pyqtSlot()
    def on_start(self):
        """When user press start button"""

        # http file
        url_file = QUrl(self.link_box.text())

        # destination file
        dest_path = QDir.fromNativeSeparators(self.dest_box.text().strip())
        dest_file = QDir(dest_path).filePath(url_file.fileName())

        # Ask a question if file already exists
        if QFile.exists(dest_file):
            ret = QMessageBox.question(
                self,
                "File exists",
                "Do you want to override the file ?",
                QMessageBox.Yes | QMessageBox.No,
            )
            if ret == QMessageBox.No:
                return

            QFile.remove(dest_file)

        self.start_button.setDisabled(True)
        # Create the file in write mode to append bytes
        self.file = QSaveFile(dest_file)

        try:
            if self.file.open(QIODevice.WriteOnly):
                self.reply = self.manager.get(QNetworkRequest(url_file))
                print(f"signal:{self.reply.downloadProgress.signal}")
                self.reply.downloadProgress.connect(self.on_progress)
                self.reply.finished.connect(self.on_finished)
                self.reply.readyRead.connect(self.on_ready_read)
                self.reply.errorOccurred.connect(self.on_error)
            else:
                error = self.file.errorString()
                print(f"Cannot open device: {error}")
        except Exception as e:
            print(repr(e))

    @pyqtSlot()
    def on_abort(self):
        """When user press abort button"""
        if self.reply:
            self.reply.abort()
            self.progress_bar.setValue(0)

        if self.file:
            self.file.cancelWriting()

        self.start_button.setDisabled(False)

    @pyqtSlot()
    def on_ready_read(self):
        """ Get available bytes and store them into the file"""
        if self.reply:
            if self.reply.error() == QNetworkReply.NoError:
                self.file.write(self.reply.readAll())

    @pyqtSlot()
    def on_finished(self):
        """ Delete reply and close the file"""
        if self.reply:
            self.reply.deleteLater()

        if self.file:
            self.file.commit()

        self.start_button.setDisabled(False)

    @pyqtSlot(int, int)
    def on_progress(self, bytesReceived: int, bytesTotal: int):
        """ Update progress bar"""
        self.progress_bar.setRange(0, bytesTotal)
        self.progress_bar.setValue(bytesReceived)

    @pyqtSlot(QNetworkReply.NetworkError)
    def on_error(self, code: QNetworkReply.NetworkError):
        """ Show a message if an error happen """
        if self.reply:
            QMessageBox.warning(self, "Error Occurred", self.reply.errorString())

    @pyqtSlot()
    def on_open_folder(self):

        dir_path = QFileDialog.getExistingDirectory(
            self, "Open Directory", QDir.homePath(), QFileDialog.ShowDirsOnly
        )

        if dir_path:
            dest_dir = QDir(dir_path)
            self.dest_box.setText(QDir.fromNativeSeparators(dest_dir.path()))


if __name__ == "__main__":

    app = QApplication(sys.argv)

    w = DownloaderWidget()
    w.show()
    sys.exit(app.exec())

疑问解答

为什么签名不一致?

Qt的C++底层中,QNetworkReply::downloadProgress信号的参数类型是qint64(64位有符号整数)。PyQt文档里标注为int是因为Python的int类型可以无缝兼容任意长度的整数,包括qint64,但使用@pyqtSlot装饰器时,必须严格匹配Qt底层的类型签名,不能直接用Python的int类型标识,否则就会出现签名不兼容的错误。你打印出的2downloadProgress(qint64,qint64)里的qint64就是底层的真实类型标识。

如何修改代码解决问题?

只需要修改on_progress方法的@pyqtSlot装饰器,将参数类型改为匹配底层的qint64即可,有两种方式:

方式1:使用字符串类型标识

@pyqtSlot('qint64', 'qint64')
def on_progress(self, bytesReceived: int, bytesTotal: int):
    """ Update progress bar"""
    self.progress_bar.setRange(0, bytesTotal)
    self.progress_bar.setValue(bytesReceived)

方式2:导入PyQt的qint64类型后使用

先在导入部分添加:

from PyQt5.QtCore import qint64

然后修改装饰器:

@pyqtSlot(qint64, qint64)
def on_progress(self, bytesReceived: int, bytesTotal: int):
    """ Update progress bar"""
    self.progress_bar.setRange(0, bytesTotal)
    self.progress_bar.setValue(bytesReceived)

两种方式都能让槽函数的签名与信号匹配,解决报错问题。


内容的提问来源于stack exchange,提问作者shinyang May

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 15:30:02