Понимание непоследовательного поведения кода в Python - PyQt5 против PySide2 - PullRequest
4 голосов
/ 18 апреля 2019

При цитонизации некоторого кода PyQt5 я столкнулся с TypeError: method() takes exactly 1 positional argument (2 given).Как ни странно, замена PyQt5 на PySide2, похоже, не вызывает такого поведения.Я надеялся, что кто-нибудь поможет мне понять, почему это происходит.ПРИМЕЧАНИЕ: запуск напрямую из источника не вызывает этой проблемы ни для PyQt5, ни для PySide2.

Я использую Python 3.6.8, cython 0.28.5.

Я создал пример приложения для воспроизведения этогоповедение.Структура папок выглядит следующим образом:

root/
|- main.py
|- setup.py
|- lib/
    |- __init__.py
    |- test.py

setup.py выполняет ту же функцию, что и $ cythonize -i <filename>, позволяя мне изменить compiler_directives.Фактический код можно найти в репозитории Cython здесь .

setup.py

import os
import tempfile
import shutil
from distutils.core import setup
from Cython.Build.Dependencies import cythonize
from multiprocessing import pool

def run_distutils(args):
    base_dir, ext_modules = args
    script_args = ['build_ext', '-i']
    cwd = os.getcwd()
    temp_dir = None
    try:
        if base_dir:
            os.chdir(base_dir)
            temp_dir = tempfile.mkdtemp(dir=base_dir)
            script_args.extend(['--build-temp', temp_dir])
            setup(
                    script_name='setup.py',
                    script_args=script_args,
                    ext_modules=ext_modules,
                )
    finally:
        if base_dir:
            os.chdir(cwd)
            if temp_dir and os.path.isdir(temp_dir):
                shutil.rmtree(temp_dir)

if __name__ == "__main__":
    ext_paths = ['lib\\test.py']
    cython_exts = cythonize(ext_paths,
                            nthreads=1,
                            compiler_directives={
                                "always_allow_keywords": True,
                            })
    try:
        process_pool = pool.Pool()
        process_pool.map_async(run_distutils, [(".", [ext]) for ext in cython_exts])
    except:
        if process_pool is not None:
            process_pool.terminate()
        raise
    finally:
        if process_pool is not None:
            process_pool.close()
            process_pool.join()

main.py используется для вызова основного внутри test.py, которыйинициирует пользовательский интерфейс.

test.py

import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication

def print_arg(arg):
    print(arg)

class Example(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.btn1 = QPushButton("Button 1", self)
        self.btn1.move(30, 50)
        self.btn2 = QPushButton("Button 2", self)
        self.btn2.move(150, 50)
        self.btn1.clicked.connect(self.buttonClicked)
        self.btn2.clicked.connect(self.buttonClicked)
        self.statusBar()
        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Event sender')
        self.show()

    def buttonClicked(self):
        sender = self.sender()
        self.statusBar().showMessage(sender.text() + ' was pressed')
        print_arg(arg=self.sender())

def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

Я создаю .pyd из test.pyd, выполняя $ python setup.py из корневого каталога.После завершения сборки я перемещаю test.py за пределы lib/ для тестирования с использованием $ python main.py.

При сборке и запуске test.py, как показано выше (с использованием PyQt5), нажатие на любую кнопку вызовет:

Traceback (most recent call last):
  File "lib\test.py", line 26, in lib.test.Example.buttonClicked
    def buttonClicked(self):
TypeError: buttonClicked() takes exactly 1 positional argument (2 given)

Замена PyQt5 на PySide2 в test.py, сборка и последующий запуск кода, та же ошибка TypeError не возникает.Это поведение, которое я хочу исследовать.

В setup.py изменение директивы компилятора с always_allow_keywords на False остановит ошибку TypeError, но вызовет возникновение этой ошибки (это происходитдля PyQt5 и PySide):

Traceback (most recent call last):
  File "lib\test.py", line 29, in lib.test.Example.buttonClicked
    print_arg(arg=self.sender())
TypeError: print_arg() takes no keyword arguments

Было бы замечательно, если бы кто-то смог пролить свет на то, почему поведение PyQt5 и PySide2 отличается.

Спасибо.

1 Ответ

5 голосов
/ 18 апреля 2019

Сигнал нажатия перегружен, то есть он имеет 2 подписи: clicked = pyqtSignal([], [bool]), поэтому, не указывая, какая подпись будет использоваться, возникают проблемы такого типа.Поэтому решение состоит в том, чтобы указать сигнатуру с помощью pyqtSlot:

import sys
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication
from PyQt5.QtCore import pyqtSlot # <--- add this line

def print_arg(arg):
    print(arg)

class Example(QMainWindow):
    # ...

    @pyqtSlot() # <--- add this line
    def buttonClicked(self):
        sender = self.sender()
        self.statusBar().showMessage(sender.text() + ' was pressed')
        print_arg(arg=self.sender())

def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

. В случае PySide2 сигнатура вычитается, но PyQt5 ожидает, что вы четко укажете ее, в противном случае она проверит все возможные случаи.

...