了解不一致的 cythonized 代码行为 - PyQt5 与 PySide2
Understanding inconsistent cythonized code behavior - PyQt5 vs. PySide2
在对一些 PyQt5 代码进行 cythonizing 时,我遇到了 TypeError: method() takes exactly 1 positional argument (2 given)
。
奇怪的是,用 PySide2 替换 PyQt5 似乎不会导致此行为。我希望有人能帮助我理解为什么会这样。
注意:运行 直接来自源代码不会导致 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 repo here.
中找到
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
中的主程序,它启动 UI.
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()
我通过从根目录执行 $ python setup.py
从 test.pyd
创建 .pyd
。构建完成后,我将 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)
在 test.py
中用 PySide2 替换 PyQt5,构建然后 运行 代码,没有引发相同的 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 的行为不同,那就太好了。
谢谢。
点击信号过载,也就是说,它有 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希望你明确指出,否则它会检查所有可能的情况。
在对一些 PyQt5 代码进行 cythonizing 时,我遇到了 TypeError: method() takes exactly 1 positional argument (2 given)
。
奇怪的是,用 PySide2 替换 PyQt5 似乎不会导致此行为。我希望有人能帮助我理解为什么会这样。
注意:运行 直接来自源代码不会导致 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 repo here.
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
中的主程序,它启动 UI.
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()
我通过从根目录执行 $ python setup.py
从 test.pyd
创建 .pyd
。构建完成后,我将 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)
在 test.py
中用 PySide2 替换 PyQt5,构建然后 运行 代码,没有引发相同的 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 的行为不同,那就太好了。
谢谢。
点击信号过载,也就是说,它有 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希望你明确指出,否则它会检查所有可能的情况。