PySide2兼容PySide1的补丁代码

Maya2017以及Nuke10的PySide都升级到PySide2了,之前PySide1的大量代码都无法在新软件上使用,这主要是由于PySide2不仅调整了模块位置,还增删了一系列模块,下面我分享一段Shotgun的兼容代码,把模块位置调整回来:

from __future__ import with_statement

import os
import functools
import imp
import subprocess
import sys
import webbrowser


class PySide2Patcher(object):
    _core_to_qtgui = set([
        "QAbstractProxyModel",
        "QItemSelection",
        "QItemSelectionModel",
        "QItemSelectionRange",
        "QSortFilterProxyModel",
        "QStringListModel"
    ])


    @classmethod
    def _move_attributes(cls, dst, src, names):
        """
        Moves a list of attributes from one package to another.

        :param names: Names of the attributes to move.
        """
        for name in names:
            if not hasattr(dst, name):
                setattr(dst, name, getattr(src, name))

    @classmethod
    def _patch_QTextCodec(cls, QtCore):
        """
        Patches in QTextCodec.

        :param QTextCodec: The QTextCodec class.
        """
        original_QTextCodec = QtCore.QTextCodec

        class QTextCodec(original_QTextCodec):
            @staticmethod
            def setCodecForCStrings(codec):
                pass

        QtCore.QTextCodec = QTextCodec

    @classmethod
    def _fix_QCoreApplication_api(cls, wrapper_class, original_class):

        wrapper_class.CodecForTr = 0
        wrapper_class.UnicodeUTF8 = 1
        wrapper_class.DefaultCodec = wrapper_class.CodecForTr

        @staticmethod
        def translate(context, source_text, disambiguation=None, encoding=None, n=None):

            if n is not None:
                return original_class.translate(context, source_text, disambiguation, n)
            else:
                return original_class.translate(context, source_text, disambiguation)

        wrapper_class.translate = translate

    @classmethod
    def _patch_QCoreApplication(cls, QtCore):

        original_QCoreApplication = QtCore.QCoreApplication

        class QCoreApplication(original_QCoreApplication):
            pass
        cls._fix_QCoreApplication_api(QCoreApplication, original_QCoreApplication)
        QtCore.QCoreApplication = QCoreApplication

    @classmethod
    def _patch_QApplication(cls, QtGui):

        original_QApplication = QtGui.QApplication

        class QApplication(original_QApplication):
            def __init__(self, *args):
                original_QApplication.__init__(self, *args)
                QtGui.qApp = self

            @staticmethod
            def palette(widget=None):

                return original_QApplication.palette(widget)

        cls._fix_QCoreApplication_api(QApplication, original_QApplication)

        QtGui.QApplication = QApplication

    @classmethod
    def _patch_QAbstractItemView(cls, QtGui):

        original_QAbstractItemView = QtGui.QAbstractItemView

        class QAbstractItemView(original_QAbstractItemView):
            def __init__(self, *args):
                original_QAbstractItemView.__init__(self, *args)

                if hasattr(self, "dataChanged"):
                    original_dataChanged = self.dataChanged

                    def dataChanged(tl, br, roles=None):
                        original_dataChanged(tl, br)
                    self.dataChanged = lambda tl, br, roles: dataChanged(tl, br)

        QtGui.QAbstractItemView = QAbstractItemView

    @classmethod
    def _patch_QStandardItemModel(cls, QtGui):

        original_QStandardItemModel = QtGui.QStandardItemModel

        class SignalWrapper(object):
            def __init__(self, signal):
                self._signal = signal

            def emit(self, tl, br):
                self._signal.emit(tl, br, [])

            def __getattr__(self, name):
                return getattr(self._signal, name)

        class QStandardItemModel(original_QStandardItemModel):
            def __init__(self, *args):
                original_QStandardItemModel.__init__(self, *args)
                self.dataChanged = SignalWrapper(self.dataChanged)

        QtGui.QStandardItemModel = QStandardItemModel

    @classmethod
    def _patch_QMessageBox(cls, QtGui):

        button_list = [
            QtGui.QMessageBox.Ok,
            QtGui.QMessageBox.Open,
            QtGui.QMessageBox.Save,
            QtGui.QMessageBox.Cancel,
            QtGui.QMessageBox.Close,
            QtGui.QMessageBox.Discard,
            QtGui.QMessageBox.Apply,
            QtGui.QMessageBox.Reset,
            QtGui.QMessageBox.RestoreDefaults,
            QtGui.QMessageBox.Help,
            QtGui.QMessageBox.SaveAll,
            QtGui.QMessageBox.Yes,
            QtGui.QMessageBox.YesAll,
            QtGui.QMessageBox.YesToAll,
            QtGui.QMessageBox.No,
            QtGui.QMessageBox.NoAll,
            QtGui.QMessageBox.NoToAll,
            QtGui.QMessageBox.Abort,
            QtGui.QMessageBox.Retry,
            QtGui.QMessageBox.Ignore
        ]


        def _method_factory(icon, original_method):

            def patch(parent, title, text, buttons=QtGui.QMessageBox.Ok, defaultButton=QtGui.QMessageBox.NoButton):

                msg_box = QtGui.QMessageBox(parent)
                msg_box.setWindowTitle(title)
                msg_box.setText(text)
                msg_box.setIcon(icon)
                for button in button_list:
                    if button & buttons:
                        msg_box.addButton(button)
                msg_box.setDefaultButton(defaultButton)
                msg_box.exec_()
                return msg_box.standardButton(msg_box.clickedButton())

            functools.update_wrapper(patch, original_method)

            return staticmethod(patch)

        original_QMessageBox = QtGui.QMessageBox

        class QMessageBox(original_QMessageBox):

            critical = _method_factory(QtGui.QMessageBox.Critical, QtGui.QMessageBox.critical)
            information = _method_factory(QtGui.QMessageBox.Information, QtGui.QMessageBox.information)
            question = _method_factory(QtGui.QMessageBox.Question, QtGui.QMessageBox.question)
            warning = _method_factory(QtGui.QMessageBox.Warning, QtGui.QMessageBox.warning)

        QtGui.QMessageBox = QMessageBox

    @classmethod
    def _patch_QDesktopServices(cls, QtGui, QtCore):

        if hasattr(QtGui, "QDesktopServices"):
            return

        class QDesktopServices(object):

            @classmethod
            def openUrl(cls, url):
                if not isinstance(url, QtCore.QUrl):
                    url = QtCore.QUrl(url)

                if url.isLocalFile():
                    url = url.toLocalFile().encode("utf-8")

                    if sys.platform == "darwin":
                        return subprocess.call(["open", url]) == 0
                    elif sys.platform == "win32":
                        os.startfile(url)
                        return os.path.exists(url)
                    elif sys.platform.startswith("linux"):
                        return subprocess.call(["xdg-open", url]) == 0
                    else:
                        raise ValueError("Unknown platform: %s" % sys.platform)
                else:
                    try:
                        return webbrowser.open_new_tab(url.toString().encode("utf-8"))
                    except:
                        return False

            @classmethod
            def displayName(cls, type):
                cls.__not_implemented_error(cls.displayName)

            @classmethod
            def storageLocation(cls, type):
                cls.__not_implemented_error(cls.storageLocation)

            @classmethod
            def setUrlHandler(cls, scheme, receiver, method_name=None):
                cls.__not_implemented_error(cls.setUrlHandler)

            @classmethod
            def unsetUrlHandler(cls, scheme):
                cls.__not_implemented_error(cls.unsetUrlHandler)

            @classmethod
            def __not_implemented_error(cls, method):
                raise NotImplementedError(
                    "PySide2 and Toolkit don't support 'QDesktopServices.%s' yet. Please contact %s" %
                    (method.__func__, 'asdf@qq.com')
                )

        QtGui.QDesktopServices = QDesktopServices

    @classmethod
    def patch(cls, QtCore, QtGui, QtWidgets, PySide2):

        qt_core_shim = imp.new_module("PySide.QtCore")
        qt_gui_shim = imp.new_module("PySide.QtGui")


        cls._move_attributes(qt_gui_shim, QtWidgets, dir(QtWidgets))
        cls._move_attributes(qt_gui_shim, QtGui, dir(QtGui))


        cls._move_attributes(qt_gui_shim, QtCore, cls._core_to_qtgui)
        cls._move_attributes(qt_core_shim, QtCore, set(dir(QtCore)) - cls._core_to_qtgui)

        cls._patch_QTextCodec(qt_core_shim)
        cls._patch_QCoreApplication(qt_core_shim)
        cls._patch_QApplication(qt_gui_shim)
        cls._patch_QAbstractItemView(qt_gui_shim)
        cls._patch_QStandardItemModel(qt_gui_shim)
        cls._patch_QMessageBox(qt_gui_shim)
        cls._patch_QDesktopServices(qt_gui_shim, qt_core_shim)

        return qt_core_shim, qt_gui_shim




import PySide2
from PySide2 import QtCore, QtGui, QtWidgets

def _import_module_by_name(parent_module_name, module_name):

    module = None
    try:
        module = __import__(parent_module_name, globals(), locals(), [module_name])
        module = getattr(module, module_name)
    except Exception as e:
        pass
    return module


QtCore, QtGui = PySide2Patcher.patch(QtCore, QtGui, QtWidgets, PySide2)
QtNetwork = _import_module_by_name("PySide2", "QtNetwork")
QtWebKit = _import_module_by_name("PySide2.QtWebKitWidgets", "QtWebKit")


###########################################################################################
# Test In Maya2017 widonw = QtGui.QWidget() widonw.show()

我们把这段代码复制到Maya2017中的Scirpt Editor中执行,可以看到,QtGui.QWidget()成功运行。说明当前PySide2中的模块已经按照PySide1的方式整理好了,兼容问题搞定,你可以沿用PySide1的调用方式来使用PySide2。

当然你也可以采用Autodesk官方提供的解决方案:

try:
    from PySide2.QtCore import * 
    from PySide2.QtGui import * 
    from PySide2.QtWidgets import *
    from PySide2 import __version__
    from shiboken2 import wrapInstance 
except ImportError:
    from PySide.QtCore import * 
    from PySide.QtGui import * 
    from PySide import __version__
    from shiboken import wrapInstance 

但这种方案有个弊端,所有控件都没有缺少引入模块的标识,代码并不适合阅读。读者各取所长吧。

原文地址:https://www.cnblogs.com/hksac/p/9502236.html