PyQt5 数据在字节类型的信号发射期间丢失

Posted

技术标签:

【中文标题】PyQt5 数据在字节类型的信号发射期间丢失【英文标题】:PyQt5 data lost during signal emit in bytes type 【发布时间】:2017-02-23 13:44:12 【问题描述】:

我正在使用 PyQt5 和 Python 3 显示通过串行端口从嵌入式设备接收到的一些数据。 PySerial 库用于处理串行通信。由于 PySerial 的读取函数的返回类型是 bytes,所以我使用线程来发出 bytes 类型的接收数据。有趣的是,字节b'\x00' 和之后的所有字节在发射-接收周期中都丢失了。但是,如果将发射信号的类型设置为QByteArray,则数据是正确发射和接收的。

下面是重现问题的 MWE:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys

from PyQt5 import QtCore, QtWidgets


class Example(QtWidgets.QWidget):
    new_bytes = QtCore.pyqtSignal(bytes)
    new_qba = QtCore.pyqtSignal('QByteArray')
    bytes_to_emit = b'\x01\x00\x02'

    def __init__(self, parent=None):
        super(Example, self).__init__()
        self.init_ui()
        self.setWindowTitle('Emit Bytes Bug')

    def init_ui(self):
        emit_label = QtWidgets.QLabel('Data to emit')
        data_label = QtWidgets.QLabel(self.bytes_to_emit.hex())

        emit_bytes_button = QtWidgets.QPushButton('Emit bytes')
        emit_qba_button = QtWidgets.QPushButton('Emit QByteArray')
        self.rev_bytes_edit = QtWidgets.QLineEdit()
        self.rev_qba_edit = QtWidgets.QLineEdit()
        clear_button = QtWidgets.QPushButton('Clear receive')

        grid_layout = QtWidgets.QGridLayout()
        grid_layout.addWidget(emit_label, 0, 0)
        grid_layout.addWidget(data_label, 0, 1)
        grid_layout.addWidget(emit_bytes_button, 1, 0)
        grid_layout.addWidget(self.rev_bytes_edit, 1, 1)
        grid_layout.addWidget(emit_qba_button, 2, 0)
        grid_layout.addWidget(self.rev_qba_edit, 2, 1)
        grid_layout.addWidget(clear_button, 3, 0)

        self.setLayout(grid_layout)

        emit_bytes_button.clicked.connect(self.on_bytes_button)
        emit_qba_button.clicked.connect(self.on_qba_button)
        self.new_bytes.connect(self.update_byte_receive)
        self.new_qba.connect(self.update_qba_receive)
        clear_button.clicked.connect(self.on_clear_button)

    def update_byte_receive(self, data):
        data_str = data.hex()
        self.rev_bytes_edit.insert(data_str)

    def update_qba_receive(self, data):
        tmp_bytes = bytes(data)
        data_str = tmp_bytes.hex()
        self.rev_qba_edit.insert(data_str)

    def on_bytes_button(self):
        self.new_bytes.emit(self.bytes_to_emit)

    def on_qba_button(self):
        self.new_qba.emit(self.bytes_to_emit)

    def on_clear_button(self):
        self.rev_bytes_edit.clear()
        self.rev_qba_edit.clear()

def main():
    app = QtWidgets.QApplication(sys.argv)
    form = Example()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()

要发出的数据是b'\x01\x00\x02',点击'Emit bytes'按钮会发出'bytes'类型的数据,并在按钮右侧的lineedit中显示接收到的数据。同样,单击“Emit QByteArray”按钮将发出QByteArray 类型的数据,并在按钮对应的lineedit 上显示接收到的数据。 “清除接收”按钮只是清除行编辑。

虽然我已经知道替代的工作解决方案,即使用 QByteArray 类型发出字节信号,但我想知道在发出信号时首选哪种信号类型。如果需要发出的数据已经在 bytes 类型中,那么使用 Python 3 的原生 bytes 类型是否会更快?还是 PyQt5 的 QByteArray 更接近底层 C++ 代码?任何建议将不胜感激。

Python3 版本:3.5

PyQt5 版本:5.7

提前致谢

【问题讨论】:

我使用的是 Python 3.4 和 PySide 1.2.2,这段代码运行良好。 @HashSplat 这可能是由于PyQtPySidepythonC++ 之间处理类型转换的方式不同 @user3419537。没有。它对我使用 Python-3.6 和 PyQt-5.7.1 也非常有效,而且真的没有理由不这样做。 update_byte_receive收到的类型是<class 'bytes'>,和预期的一样,而且值没有被截断。如果 OP 有什么不同,那一定是由于他们正在使用的特定版本的 SIP 和/或 PyQt5 中的错误。 @user3419537。 PyQt5 仅针对 Python 3 进行了全面测试,因此就 Python 2 而言,所有的赌注都没有了。但是,如果您使用 Python bytearray,则该示例应该可以工作。 @Daniel。在皮下,PyQt4 和 PyQt5 在某些方面有很大的不同。我建议你尝试使用 PyQt-5.7.1。我很确定最近修复了一个与此相关的错误。 PS:我测试了 SIP-4.19 和 SIP-4.19.1。 【参考方案1】:

您可能想查看 Signals and Slots 的 PyQt 文档

当发出信号时,如果可能,所有参数都将转换为 C++ 类型。

和Python Strings, Qt Strings and Unicode

对于 Python v3,默认情况下会完成以下转换。

1234563 或实现buffer 协议的 Python 对象。

如果 Qt 需要 char(或 const 版本),则 PyQt5 将接受与 char * 相同的类型,并且还要求单个 提供了字符。

如果Qt 需要signed char *unsigned char *(或const 版本),那么PyQt5 将接受bytes

如果Qt 需要signed charunsigned char(或const 版本),那么PyQt5 将接受长度为1 的bytes

如果Qt 需要QString,那么PyQt5 将接受str、仅包含ASCII 字符的bytesQByteArrayNone

如果Qt 期望QByteArray,那么PyQt5 也将接受bytes

如果Qt 需要QByteArray,那么PyQt5 也将接受仅包含Latin-1 字符的str

如您所见,PyQt 很乐意将bytes 对象转换为几种C++/Qt 类型。它怎么知道你想要QByteArray?没有。

如果您在调试器中停止代码并查看new_bytes 信号,您将看到该信号已被赋予QString 类型(至少这是我系统上python2.7 下发生的情况)。如果您熟悉C 字符串的工作原理,您就会知道字节x/00 表示字符串的结尾,而与缓冲区大小无关。这就是您的输出在 `x/00 之后终止的原因。

无论哪种方式,python 对象都需要转换为C++ 类型。用QtCore.pyqtSignal('QByteArray') 声明信号会明确告诉PyQt 你想要哪种类型。

【讨论】:

大部分内容并不真正相关,因为在示例中,信号是在 Python 中定义和发出的,而不是在 Qt/C++ 中。这很重要,例如,当发出 Qt/C++ 一无所知的 Python 特定对象(例如tuple)时。对于这些情况,根本不进行任何转换 - 实际上,会发出一个指针。使用 Python 3 而不是 Python 2 进行测试也很重要。对于后者,您需要使用 Python bytearray 而不是 bytes 才能获得正确的行为。 在 python3 的 bytes 和 PyQt5 的 QByteArray 之间是否有偏好发送信号的类型,由发送方和接收方的预期数据类型 bytes 提供? @Daniel。从表面上看,如果您收到bytes,那么同时发送bytes 似乎更高效,因为它避免了与QByteArray 之间的转换。然而,布丁的证据在吃,你知道timeit module在哪里,对吧?我的预感是,除非您发送大量数据或每秒发出数百万个信号或类似情况,否则它不会产生什么影响。

以上是关于PyQt5 数据在字节类型的信号发射期间丢失的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5——信号和槽初探

PyQt 信号和插槽:“新风格”发射?

七PyQT5控件——QSlider,QSpinBox

PyQt5快速入门PyQt5信号槽机制

PyQt5的信号和槽

PyQt5信号与槽详解