Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Minimal Python 2 & 3 shim around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5.

License

NotificationsYou must be signed in to change notification settings

mottosso/Qt.py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DownloadsRun TestsPyPI versionAnaconda-Server BadgeGitterReviewed by Hound

Qt.py enables you to write software that runs on any of the 4 supported bindings - PySide2, PyQt5, PySide and PyQt4.


News
DateVersionEvent
May 20241.4.1Added support for Qt 6
Jan 20241.3.9Run CI on Github Actions, instead of Travis CI.
Sep 20201.3.0Stability improvements and greater ability forQtCompat.wrapInstance to do its job
Jun 20191.2.1Bugfixes andadditional members
Jan 20181.1.0Adds new test suite, new members
Mar 20171.0.0Increased safety,backwards incompatible
Sep 20160.6.9Stable release
Sep 20160.5.0Alpha release of--convert
Jun 20160.2.6First release of Qt.py
Guides
Table of contents



Project goals

Write once, run in any binding.

Qt.py was born in the film and visual effects industry to address the growing need for software capable of running with more than one flavor of the Qt bindings for Python - PySide, PySide2, PyQt4 and PyQt5.

GoalDescription
Support co-existenceQt.py should not affect other bindings running in same interpreter session.
Build for one, run with allCode written with Qt.py should run on any binding.
Explicit is better than implicitDifferences between bindings should be visible to you.

SeeCONTRIBUTING.md for more details.




Install

Qt.py is a single file and can either becopy/pasted into your project,downloaded as-is, cloned as-is or installed viapip orconda.

# From PyPI$ pip install Qt.py
# From Anaconda$ conda config --add channels conda-forge$ conda install qt.py
  • Pro tip:Never use the latest commit for production. Instead, usethe latest release. That way, when you read bug reports or make one for yourself you will be able to match a version with the problem without which you will not know which fixes apply to you nor would we be able to help you. Installing via pip or conda as above ensures you are provided the lateststable release. Unstable releases are suffixed with a.b, e.g.1.1.0.b3.
  • Pro tip: Supportsvendoring



Usage

Use Qt.py as you would use PySide2.

image

importsysfromQtimportQtWidgetsapp=QtWidgets.QApplication(sys.argv)button=QtWidgets.QPushButton("Hello World")button.show()app.exec_()



Documentation

All members ofQt stem directly from those available via PySide2, along with these additional members.

AttributeReturnsDescription
__version__strVersion of this project
__binding__strA string reference to binding currently in use
__qt_version__strReference to version of Qt, such as Qt 5.6.1
__binding_version__strReference to version of binding, such as PySide 1.2.6

Example

>>>fromQtimport__binding__>>>__binding__'PyQt5'

Compatibility

Qt.py also provides compatibility wrappers for critical functionality that differs across bindings, these can be found in the addedQtCompat submodule.

AttributeReturnsDescription
loadUi(uifile=str, baseinstance=QWidget)QObjectMinimal wrapper of PyQt4.loadUi and PySide equivalent
translate(...)functionCompatibility wrapper aroundQCoreApplication.translate
wrapInstance(addr=long, type=QObject)QObjectWrapper aroundshiboken2.wrapInstance and PyQt equivalent
getCppPointer(object=QObject)longWrapper aroundshiboken2.getCppPointer and PyQt equivalent
isValid(object=QObject)boolWrapper aroundshiboken2.isValid and PyQt equivalent
dataChanged(topLeft=QModelIndex, bottomRight=QModelIndex, roles=[])NoneWrapper aroundQtCore.QAbstractItemModel.dataChanged.emit

Example

>>>fromQtimportQtCompat>>>QtCompat.loadUi

Class specific compatibility objects

Between Qt4 and Qt5 there have been many classes and class members that are obsolete. Under Qt.QtCompat there are many classes with names matching the classes they provide compatibility functions. These will match the PySide2 naming convention.

fromQtimportQtCore,QtWidgets,QtCompatheader=QtWidgets.QHeaderView(QtCore.Qt.Horizontal)QtCompat.QHeaderView.setSectionsMovable(header,False)movable=QtCompat.QHeaderView.sectionsMovable(header)

This also covers inconsistencies between bindings. For example PyQt4's QFileDialog matches Qt4's return value of the selected. While all other bindings return the selected filename and the file filter the user used to select the file.Qt.QtCompat.QFileDialog ensures that getOpenFileName(s) and getSaveFileName always return the tuple.


Environment Variables

These are the publicly facing environment variables that in one way or another affect the way Qt.py is run.

VariableTypeDescription
QT_PREFERRED_BINDING_JSONstrOverride order and content of binding to try. This can apply per Qt.py namespace.
QT_PREFERRED_BINDINGstrOverride order and content of binding to try. Used if QT_PREFERRED_BINDING_JSON does not apply.
QT_VERBOSEboolBe a little more chatty about what's going on with Qt.py
QT_SIP_API_HINTintSets the preferred SIP api version that will be attempted to set.

Subset (or "common members")

Members of Qt.py is a subset of PySide2. Which means for a member to be made accessible via Qt.py, it will need to (1) be accessible via PySide2 and (2) each of the other supported bindings. This excludes large portions of the Qt framework, including the newly added QtQml and QtQuick modules but guarantees that anything you develop with Qt.py will work identically on any binding - PySide, PySide2, PyQt4 and PyQt5. If you need to use such excluded modules with Qt.py, please seeQtSiteConfig.py.

We call this subset "common members" and these can be generated by running thebuild_membership.sh script. The script will output all modules and members of each binding into individual JSON files. These JSON files are then compared and acommon_members.json file is generated. The contents of this file is copy-pasted into the_common_members dictionary of Qt.py. Please note that the script will only use the very latest version of ourDocker test suite to generate the common members subset, using the most up-to-date set of VFX Platform-stipulated software versions.

⚠️ The version of PySide2 used as reference is the one specified onVFX Platform, currently version is 2.0.x. But unfortunately, the version string of PySide2 is not yet properly maintained and the VFX Platform does not specifiy a explicit commit SHA for PySide2. Therefore, it could be difficult to know exactly which PySide2 is running on your system (unless you built it from source). In layman's terms; as PySide2 is in development and is continuously adding new support for modules, you may see differences between PySide2 built early in the year vs PySide2 built later in the year. The exact commit SHAs of PySide2 used by the Qt.py test suite can be reviewed inDOCKER.md. QtC implemented an alternative way to identify which version of PySide2 you are running. You can read more about thathere.


Branch binding-specific code

Some bindings offer features not available in others, you can use__binding__ to capture those.

if"PySide"in__binding__:do_pyside_stuff()

Override preferred choice

If your system has multiple choices where one or more is preferred, you can override the preference and order in which they are tried with this environment variable.

$set QT_PREFERRED_BINDING=PyQt5# Windows$export QT_PREFERRED_BINDING=PyQt5# Unix/OSX$ python -c"import Qt;print(Qt.__binding__)"PyQt5

Constrain available choices and order of discovery by supplying multiple values.

# Try PyQt4 first and then PySide, but nothing else.$export QT_PREFERRED_BINDING=PyQt4:PySide

Using the OS path separator (os.pathsep) which is: on Unix systems and; on Windows.

If you need to control the preferred choice of a specific vendored Qt.py you can use theQT_PREFERRED_BINDING_JSON environment variable instead.

{"Qt":["PyQt5"],"myproject.vendor.Qt":["PyQt5"],"default":["PySide2"]}

This json data forces any code that usesimport Qt orimport myproject.vendor.Qt to use PyQt5(from x import Qt etc works too, this is based on__name__ of the Qt.py being imported). Any other imports of a Qt module will use the "default" PySide2 only. If"default" is not provided or a Qt.py being used does not supportQT_PREFERRED_BINDING_JSON,QT_PREFERRED_BINDING will be respected.

# Try PyQt5 first and then PyQt4 for the Qt module name space.$export QT_PREFERRED_BINDING_JSON="{"Qt":["PyQt5","PyQt4"]}"# Use PyQt4 for any other Qt module name spaces.$export QT_PREFERRED_BINDING=PySide2

QtSiteConfig.py

Add or remove members from Qt.py at run-time.


If you need to expose a module that isn't included in Qt.py by default or wish to remove something from being exposed in Qt.py you can do so by creating aQtSiteConfig.py module and making it available to Python.

  1. Create a new fileQtSiteConfig.py
  2. Implementupdate_members
  3. Expose to Python
# QtSiteConfig.pydefupdate_members(members):"""Called by Qt.py at run-time to modify the modules it makes available.    Arguments:        members (dict): The members considered by Qt.py    """members.pop("QtCore")

Finally, expose the module to Python.

$set PYTHONPATH=/path/to$ python -c"import Qt.QtCore"ImportError: No module named Qt.QtCore

Linux and MacOS users, replaceset withexport


Compile Qt Designer files

WARNING - ALPHA FUNCTIONALITY
See#132 for details.

.ui files compiled viapyside2-uic inherently contain traces of PySide2 - e.g. the linefrom PySide2 import QtGui.

In order to use these with Qt.py, or any other binding, one must first erase such traces and replace them with cross-compatible code.

$ pyside2-uic my_ui.ui -o my_ui.py$ python -m Qt --convert my_ui.py# Creating "my_ui_backup.py"..# Successfully converted "my_ui.py"

Now you may use the file as you normally would, with Qt.py


Loading Qt Designer files

Theuic.loadUi function of PyQt4 and PyQt5 as well as theQtUiTools.QUiLoader().load function of PySide/PySide2 are mapped to a convenience functionloadUi.

importsysfromQtimportQtCompatapp=QtWidgets.QApplication(sys.argv)ui=QtCompat.loadUi(uifile="my.ui")ui.show()app.exec_()

ForPyQt bindings it uses their native implementation, whereas forPySide bindings it uses our custom implementation borrowed from theqtpy project.

loadUi has two arguments as opposed to the multiple that PyQt ships with. Seehere for details - in a nutshell, those arguments differ between PyQt and PySide in incompatible ways.The second argument isbaseinstance which allows a ui to be dynamically loaded onto an existing QWidget instance.

QtCompat.loadUi(uifile="my.ui",baseinstance=QtWidgets.QWidget)

uifile is the string path to the ui file to load.

Ifbaseinstance isNone, the a new instance of the top-levelwidget will be created. Otherwise, the user interface is created withinthe givenbaseinstance. In this casebaseinstance must be aninstance of the top-level widget class in the UI file to load, or asubclass thereof. In other words, if you've created aQMainWindowinterface in the designer,baseinstance must be aQMainWindowor a subclass thereof, too. You cannot load aQMainWindow UI filewith a plainQWidget asbaseinstance.

loadUi returnsbaseinstance, ifbaseinstance is provided.Otherwise it will return the newly created instance of the user interface.


sip API v2

If you're using PyQt4,sip attempts to set its API to version 2 for the following:

  • QString
  • QVariant
  • QDate
  • QDateTime
  • QTextStream
  • QTime
  • QUrl



Rules

The PyQt and PySide bindings are similar, but not identical. Where there is ambiguity, there must to be a clear direction on which path to take.

Governing API

The officialQt 5 documentation is always right. Where the documentation lacks answers, PySide2 is right.

For example.

# PyQt5 adheres to PySide2 signals and slotsPyQt5.Signal=PyQt5.pyqtSignalPyQt5.Slot=PyQt5.pyqtSlot# PySide2 adheres to the official documentationPySide2.QtCore.QStringListModel=PySide2.QtGui.QStringListModel

Caveats

There are cases where Qt.py is not handling incompatibility issues. Please seeCAVEATS.md for more information.




Known Problems

Send us a pull-request with known problems here!




Who's using Qt.py?

Send us a pull-request with your studio here.

Presented at Siggraph 2016, BOF!

image




Projects using Qt.py

Send us a pull-request with your project here.




Projects similar to Qt.py

Comparison matrix.

ProjectAudienceReference bindingLicensePEP8StandalonePyPICo-existence
Qt.pyFilmPySide2MITXXXX
jupyterScientificN/AN/AX
QtPyScientificN/AMITXX
pyqode.qtScientificPyQt5MITXX
QtExtFilmN/AN/AX
python_qt_bindingRoboticsN/ABSDXXXX

Also worth mentioning,pyqt4topyqt5; a good starting point for transitioning to Qt.py.

Send us a pull-request with your project here.




Developer Guide

Tests are performed on each aspect of the shim.

Each of these are run under..

  • Python 2.7
  • Python 3.4
  • Python 3.5
  • Python 3.6

..once for each binding or under a specific binding only.

Each test is run within it's own isolated process, so as to allow animport to occur independently from other tests. Process isolation is handled vianosepipe.

Tests that are written at module level are run four times - once per binding - whereas tests written under a specific if-statement are run only for this particular binding.

ifbinding("PyQt4"):deftest_something_related_to_pyqt4():pass

Code convention

Below are some of the conventions that used throughout the Qt.py module and tests.

  • Etiquette: PEP8
    • All code is written in PEP8. It is recommended you use a linter as you work, flake8 and pylinter are both good options. Anaconda if using Sublime is another good option.
  • Etiquette: Double quotes
    • " = yes, ' = no.
  • Etiquette: Napoleon docstrings
    • Any docstrings are made in Google Napoleon format. SeeNapoleon for details.
  • Etiquette: Semantic Versioning
  • Etiquette: Underscore means private
    • Anything prefixed with an underscore means that it is internal to Qt.py and not for public consumption.

Running tests

Due to the nature of multiple bindings and multiple interpreter support, setting up a development environment in which to properly test your contraptions can be challenging. So here is a guide for how to do just that usingDocker.

With Docker setup, here's what you do. Please note this will pull down a ~1 GB image.

cd Qt.py# Run nosetests (Linux/OSX)docker run --rm -v$(pwd):/Qt.py -e PYTHON=2.7 fredrikaverpil/qt.py:2018docker run --rm -v$(pwd):/Qt.py -e PYTHON=3.4 fredrikaverpil/qt.py:2018docker run --rm -v$(pwd):/Qt.py -e PYTHON=3.5 fredrikaverpil/qt.py:2018docker run --rm -v$(pwd):/Qt.py -e PYTHON=3.6 fredrikaverpil/qt.py:2018# Run nosetests (Windows)docker run --rm -v %CD%:/Qt.py -e PYTHON=2.7 fredrikaverpil/qt.py:2018docker run --rm -v %CD%:/Qt.py -e PYTHON=3.4 fredrikaverpil/qt.py:2018docker run --rm -v %CD%:/Qt.py -e PYTHON=3.5 fredrikaverpil/qt.py:2018docker run --rm -v %CD%:/Qt.py -e PYTHON=3.6 fredrikaverpil/qt.py:2018# Doctest: test_caveats.test_1_qtgui_qabstractitemmodel_createindex ... ok# Doctest: test_caveats.test_2_qtgui_qabstractitemmodel_createindex ... ok# Doctest: test_caveats.test_3_qtcore_qitemselection ... ok# ...## ----------------------------------------------------------------------# Ran 21 tests in 7.799s## OK

Now both you and Github Actions are operating on the same assumptions which means that when the tests pass on your machine, they pass on Github Actions. And everybody wins!

For details on the Docker image for testing, seeDOCKER.md.

SeeCONTRIBUTING.md for more of the good stuff.

Upload to PyPI

To make a new release onto PyPI, you'll need to be mottosso and type this.

cd Qt.pypython .\setup.py sdist bdist_wheelpython -m twine upload .\dist\*



Qt 6 Transition Guide

ReplaceWithNotes
QFont().setWeight(...)QtCompat.QFont.setWeight(font, ...)
QFont().setWeight(QFont().Bold)QFont().setWeight(QFont.Bold)Instance of class doesn't have the enums, apparently
QEvent().ResizeQEvent.ResizeInstance of class doesn't have the enums, seems to apply overall
QtCore.Qt.MidButtonQtCompat.Qt.MidButton
QLabel.setPixmap(str)QLabel.setPixmap(QPixmap())Can't take a string anymore (tested in Maya 2025.0)
QModelIndex.childQModel.indexThis one is apparently from Qt 4 and should not have been in Qt.py to begin with
Submit your known issues here!
Removed Members

Many members were removed from Qt.py due to no longer existing in PySide 6.

If you find where they went, or think some were removed in error, please submit a pull-request!

"QtCore": ["QAbstractState","QAbstractTransition","QEventTransition","QFinalState","QSignalTransition","QTextCodec","QTextDecoder","QTextEncoder","QtCriticalMsg","QtDebugMsg","QtFatalMsg","QtSystemMsg","QtWarningMsg","qChecksum","QPictureIO",],"QtMultimedia": ["QAbstractVideoBuffer","QAbstractVideoSurface","QAudio","QAudioDeviceInfo","QAudioFormat","QAudioInput","QAudioOutput","QVideoFrame","QVideoSurfaceFormat"],"QtNetwork": ["QNetworkConfiguration","QNetworkConfigurationManager","QNetworkSession",],"QtOpenGL": ["QGL","QGLContext","QGLFormat","QGLWidget"],"QtSql": ["QSql","QSqlDatabase","QSqlDriver","QSqlDriverCreatorBase","QSqlError","QSqlField","QSqlIndex","QSqlQuery","QSqlQueryModel","QSqlRecord","QSqlRelation","QSqlRelationalDelegate","QSqlRelationalTableModel","QSqlResult","QSqlTableModel"],"QtSvg": ["QSvgGenerator","QSvgRenderer",],"QtWidgets": ["QActionGroup","QDesktopWidget","QDirModel","QKeyEventTransition","QMouseEventTransition","QUndoCommand","QUndoGroup","QUndoStack",],"QtX11Extras": ["QX11Info"],"QtXml": ["QXmlAttributes","QXmlContentHandler","QXmlDTDHandler","QXmlDeclHandler","QXmlDefaultHandler","QXmlEntityResolver","QXmlErrorHandler","QXmlInputSource","QXmlLexicalHandler","QXmlLocator","QXmlNamespaceSupport","QXmlParseException","QXmlReader","QXmlSimpleReader"],"QtXmlPatterns": ["QAbstractMessageHandler","QAbstractUriResolver","QAbstractXmlNodeModel","QAbstractXmlReceiver","QSourceLocation","QXmlFormatter","QXmlItem","QXmlName","QXmlNamePool","QXmlNodeModelIndex","QXmlQuery","QXmlResultItems","QXmlSchema","QXmlSchemaValidator","QXmlSerializer"]
Static Members Missing from Instances

An overall change is that instances of classes, likeQFont() no longer provides access to static members, such asQFont.Bold. So things like:

font=QFont()font.setWeight(font.Bold)

Must be replaced with:

font=QFont()font.setWeight(QFont.Bold)

Or:

font.setWeight(type(font).Bold)

Tedious and seemingly unnecessary.. But there you have it!

Notes

Qt.py 1.4.0, released in May 2024, added support for Qt 6 whilst preserving compatibility with Qt 4 and 5. That means that in most cases, code you've already written for Qt 4 or 5 will now continue to work with Qt 6, such as Maya 2025.

However, some changes between 5 and 6 require up-front work by you the developer to make your codebase run on Qt 6 whilst continuing to run on Qt 4 and 5.

The above is what we know, please do submit issues and pull-request with what else you find!

See also

The official PySide2 to PySide6 transition guide, which is especially helpful since Qt.py is modeled after PySide2.

About

Minimal Python 2 & 3 shim around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp