如果同时安装了PyQt4和PyQt5(自己编译或者安装预编译版本),在运行某个PyQt4或者PyQt5的Python脚本时,很有可能出现以下的错误提示:
RuntimeError: the sip module implements API vX.X but the PyQt5.QtCore module requires API vY.Y
本文帮助大家解决这个问题。
==========================================================
1. 什么是sip?
sip是RiverBank(也就是PyQt的开发商)开发的用于PyQt的Python/C++混合编程解决方案。由于Qt框架的复杂性,PyQt并没有使用Cython、SWIG的混合编程方案,而是自己单独做了一套框架。sip包括一个sip工具、SDK和Python Module。
与SWIG类似,使用sip也需要先编写一个『配置文件』,然后使用sip工具『编译』为C++源文件,最后,和Qt库一起编译形成适用于Python的PyQt。
与SWIG不同的是,sip同时以Python Module的形式存在,也就是说,作为Python Module的PyQt,依赖于作为Python Module的sip。而对于SWIG,一旦自动生成的C++生成完毕,整个流程就不再依赖SWIG了。
2. sip和PyQt的版本依赖
本文写作之时,PyQt4的最高版本为4.8.7,与Qt4的最高版本相同;PyQt5的最高版本为5.5.0,略低于Qt5的最高版本5.5.1;而sip的版本为4.16.9,和PyQt的版本在字面上无关联。
在sip内部还有sip的API版本,作为C API的宏SIP_API_MAJOR_NR和SIP_API_MINOR_NR定义于sip.h。每个sip版本的API的版本是固定的,每个PyQt版本所需要的sip API版本是一个范围。如果PyQt想正常运行,sip的API版本必须落在该PyQt版本所需要的sip API版本的范围内。这个范围是这样规定的:
- API的主版本号必须相同。(主版本号不互相兼容原则)
- API的副版本号,PyQt版本所需的值不可大于sip提供的值。(副版本号仅向下兼容原则)
例如,
- 某PyQt在编译时选取的sip的API版本号为8.0,那么它可以在API的版本号为8.1的sip的支持下运行。
- 某PyQt在编译时选取的sip的API版本号为8.1,那么它不可以在API的版本号为8.0的sip的支持下运行。
- 某PyQt在编译时选取的sip的API版本号为9.0,那么它不可以在API的版本号为10.0的sip的支持下运行。
- 某PyQt在编译时选取的sip的API版本号为10.0,那么它不可以在API的版本号为9.0的sip的支持下运行。
那么sip的API版本和sip自身的版本有何关系?RiverBank的文档是这么说的:
SIP_API_MAJOR_NR
This is a C preprocessor symbol that defines the major number of the SIP API. Its value is a number. There is no direct relationship between this and the SIP version number.
SIP_API_MINOR_NR
This is a C preprocessor symbol that defines the minor number of the SIP API. Its value is a number. There is no direct relationship between this and the SIP version number.
……
某系统已经安装了sip,想知道可以支持哪些版本的PyQt?
最好的办法似乎是:
- 启动python, import sip 并 print sip.SIP_VERSION_STR,得到sip的版本
- 去PyQt download 观看与这个版本的sip发布年代相近的PyQt
(如果有读者能提供更好的经验欢迎指点)
3. PyQt4和PyQt5共享sip之不可能
我们假设这样一种情况:
某系统自带了版本为4.7.1的PyQt4,以及对应的sip(版本4.14,API版本9.0)。今欲安装最新的PyQt 5.5.0。发现编译过程需要sip 4.16,API版本11.2,于是下载和重新编译sip 4.16,勉强编译通过,但安装时犯了难,因为不存在同时能够支持PyQt4.7和PyQt5.5的sip!
当然,可能的解决方法有
- 换个系统
- virtualenv
- 升级PyQt4到最新4.8.7,feature是能够向下兼容4.7.1的
有没有不那么兴师动众的方法?
4. 问题之解决
事实上,只需要让PyQt4和PyQt5各用自己的sip就行了。
第一步,找到sip的安装位置。通常的位置是Python的site-packages,其文件名为sip.pyd(Windows)或者 sip.so (*nix)。例如,在Windows下,其位置为C:\Python27\Lib\site-packages\sip.pyd,在Linux(Ubuntu)下,其位置为/usr/lib/python2.7/dist-packages/sip.****.so;在macOS下,其位置为/Library/Python/2.7/site-packages/sip.so(对于系统自带的Python)
第二步,把sip移动到对应版本的PyQt目录内。例如,若sip和PyQt4匹配而与PyQt5不匹配,就把sip放到PyQt4的安装目录内。
第三步,编译不匹配的PyQt版本并安装,但不要安装sip,而是把sip的python module直接放入对应的PyQt的安装目录内。
到了这一步,PyQt4和PyQt5的安装目录有了各自对应的sip。此时若import PyQt4或者import PyQt5,会遇到sip无法找到的问题。没有关系。我们利用__init__.py这个文件,在import PyQtX之时自动找到对应版本的sip。方法是这样的:
打开PyQtX/__init__.py(X是4和5,每个PyQt都需要做),默认情况下这个文件只有PyQt的版权声明。在下面填上几句:
import sys
import os
sys.path.append(os.path.realpath(os.path.dirname(__file__)))
我们知道,PyQt的核心module都是以动态链接库形式存在。你import PyQt4的时候,在加载动态链接库之前,Python会发现PyQt4/__init__.py并加以执行。这样,当动态链接库(比如QtCore)寻找sip的时候,它总会在自己所在的目录找到正确版本的sip。