1. 程式人生 > >同時安裝PyQt4和PyQt5之sip版本區分

同時安裝PyQt4和PyQt5之sip版本區分

如果同時安裝了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版本的範圍內。這個範圍是這樣規定的:

  1. API的主版本號必須相同。(主版本號不互相相容原則)
  2. API的副版本號,PyQt版本所需的值不可大於sip提供的值。(副版本號僅向下相容原則)

例如,

  1. 某PyQt在編譯時選取的sip的API版本號為8.0,那麼它可以在API的版本號為8.1的sip的支援下執行
  2. 某PyQt在編譯時選取的sip的API版本號為8.1,那麼它不可以在API的版本號為8.0的sip的支援下執行
  3. 某PyQt在編譯時選取的sip的API版本號為9.0,那麼它不可以在API的版本號為10.0的sip的支援下執行
  4. 某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?

最好的辦法似乎是:

  1. 啟動python, import sip 並 print sip.SIP_VERSION_STR,得到sip的版本
  2. 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!

當然,可能的解決方法有

  1. 換個系統
  2. virtualenv
  3. 升級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。方法是這樣的:

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。