迁移到 Qt6/PyQt6:Qt5 中所有已弃用的短格式名称是什么?

2024-04-27

我正在尝试从以下位置迁移代码库PyQt5 to PyQt6。我读过这篇文章(参见https://www.pythonguis.com/faq/pyqt5-vs-pyqt6/ https://www.pythonguis.com/faq/pyqt5-vs-pyqt6/)所有枚举成员都必须使用其完全限定名称来命名。文章给出了这样的例子:

# PyQt5
widget = QCheckBox("This is a checkbox")
widget.setCheckState(Qt.Checked)
# PyQt6
widget = QCheckBox("This is a checkbox")
widget.setCheckState(Qt.CheckState.Checked)

然后文章继续:

“更新的值太多,无法在这里全部提及。但是,如果您要转换代码库,通常只需在线搜索简短形式,较长的形式就会出现在结果中。”

我明白了。这句话基本上说了一些大意:

“如果 Python 解释器遇到错误,并且错误结果是一个简短的枚举,您可能会在网上找到解决方案。”

我明白了。但这不是我想要迁移代码库的方式。我想要所有简短形式枚举的完整列表,然后对每个枚举执行全局搜索和替换。

我在哪里可以找到这样的列表?


我写了一个脚本来提取allPyQt6 安装中的短格式和相应的完全限定枚举名称。然后它会自动进行转换:

# -*- coding: utf-8 -*-
# ================================================================================================ #
#                                       ENUM CONVERTER TOOL                                        #
# ================================================================================================ #

from typing import *
import os, argparse, inspect, re
q = "'"

help_text = '''
Copyright (c) 2022 Kristof Mulier
MIT licensed, see bottom

ENUM CONVERTER TOOL
===================
The script starts from the toplevel directory (assuming that you put this file in that directory)
and crawls through all the files and folders. In each file, it searches for old-style enums to
convert them into fully qualified names.

HOW TO USE
==========
Fill in the path to your PyQt6 installation folder. See line 57:

    pyqt6_folderpath = 'C:/Python39/Lib/site-packages/PyQt6'

Place this script in the toplevel directory of your project. Open a terminal, navigate to the
directory and invoke this script:

    $ python enum_converter_tool.py
    
WARNING
=======
This script modifies the files in your project! Make sure to backup your project before you put this
file inside. Also, you might first want to do a dry run:

    $ python enum_converter_tool.py --dry_run
    
FEATURES
========
You can invoke this script in the following ways:

    $ python enum_converter_tool.py                   No parameters. The script simply goes through
                                                      all the files and makes the replacements.
                                                      
    $ python enum_converter_tool.py --dry_run         Dry run mode. The script won't do any replace-
                                                      ments, but prints out what it could replace.
                                                      
    $ python enum_converter_tool.py --show            Print the dictionary this script creates to
                                                      convert the old-style enums into new-style.
                                                      
    $ python enum_converter_tool.py --help            Show this help info

'''

# IMPORTANT: Point at the folder where PyQt6 stub files are located. This folder will be examined to
# fill the 'enum_dict'.
# pyqt6_folderpath = 'C:/Python39/Lib/site-packages/PyQt6'
# EDIT: @Myridium suggested another way to fill this 'pyqt6_folderpath'
# variable:
import PyQt6
pyqt6_folderpath = PyQt6.__path__[0]

# Figure out where the toplevel directory is located. We assume that this converter tool is located
# in that directory. An os.walk() operation starts from this toplevel directory to find and process
# all files.
toplevel_directory = os.path.realpath(
    os.path.dirname(
        os.path.realpath(
            inspect.getfile(
                inspect.currentframe()
            )
        )
    )
).replace('\\', '/')

# Figure out the name of this script. It will be used later on to exclude oneself from the replace-
# ments.
script_name = os.path.realpath(
    inspect.getfile(inspect.currentframe())
).replace('\\', '/').split('/')[-1]

# Create the dictionary that will be filled with enums
enum_dict:Dict[str, str] = {}

def fill_enum_dict(filepath:str) -> None:
    '''
    Parse the given stub file to extract the enums and flags. Each one is inside a class, possibly a
    nested one. For example:

               ---------------------------------------------------------------------
               | class Qt(PyQt6.sip.simplewrapper):                                |
               |     class HighDpiScaleFactorRoundingPolicy(enum.Enum):            |
               |         Round = ... # type: Qt.HighDpiScaleFactorRoundingPolicy   |
               ---------------------------------------------------------------------

    The enum 'Round' is from class 'HighDpiScaleFactorRoundingPolicy' which is in turn from class
    'Qt'. The old reference style would then be:
        > Qt.Round

    The new style (fully qualified name) would be:
        > Qt.HighDpiScaleFactorRoundingPolicy.Round

    The aim of this function is to fill the 'enum_dict' with an entry like:
    enum_dict = {
        'Qt.Round' : 'Qt.HighDpiScaleFactorRoundingPolicy.Round'
    }
    '''
    content:str = ''
    with open(filepath, 'r', encoding='utf-8', newline='\n', errors='replace') as f:
        content = f.read()

    p = re.compile(r'(\w+)\s+=\s+\.\.\.\s+#\s*type:\s*([\w.]+)')
    for m in p.finditer(content):
        # Observe the enum's name, eg. 'Round'
        enum_name = m.group(1)

        # Figure out in which classes it is
        class_list = m.group(2).split('.')

        # If it belongs to just one class (no nesting), there is no point in continuing
        if len(class_list) == 1:
            continue

        # Extract the old and new enum's name
        old_enum = f'{class_list[0]}.{enum_name}'
        new_enum = ''
        for class_name in class_list:
            new_enum += f'{class_name}.'
            continue
        new_enum += enum_name

        # Add them to the 'enum_dict'
        enum_dict[old_enum] = new_enum
        continue
    return

def show_help() -> None:
    '''
    Print help info and quit.
    '''
    print(help_text)
    return

def convert_enums_in_file(filepath:str, dry_run:bool) -> None:
    '''
    Convert the enums in the given file.
    '''
    filename:str = filepath.split('/')[-1]

    # Ignore the file in some cases
    if any(filename == fname for fname in (script_name, )):
        return

    # Read the content
    content:str = ''
    with open(filepath, 'r', encoding='utf-8', newline='\n', errors='replace') as f:
        content = f.read()

    # Loop over all the keys in the 'enum_dict'. Perform a replacement in the 'content' for each of
    # them.
    for k, v in enum_dict.items():
        if k not in content:
            continue
        # Compile a regex pattern that only looks for the old enum (represented by the key of the
        # 'enum_dict') if it is surrounded by bounds. What we want to avoid is a situation like
        # this:
        #     k = 'Qt.Window'
        #     k found in 'qt.Qt.WindowType.Window'
        # In the situation above, k is found in 'qt.Qt.WindowType.Window' such that a replacement
        # will take place there, messing up the code! By surrounding k with bounds in the regex pat-
        # tern, this won't happen.
        p = re.compile(fr'\b{k}\b')

        # Substitute all occurences of k (key) in 'content' with v (value). The 'subn()' method re-
        # turns a tuple (new_string, number_of_subs_made).
        new_content, n = p.subn(v, content)
        if n == 0:
            assert new_content == content
            continue
        assert new_content != content
        print(f'{q}{filename}{q}: Replace {q}{k}{q} => {q}{v}{q} ({n})')
        content = new_content
        continue

    if dry_run:
        return

    with open(filepath, 'w', encoding='utf-8', newline='\n', errors='replace') as f:
        f.write(content)
    return

def convert_all(dry_run:bool) -> None:
    '''
    Search and replace all enums.
    '''
    for root, dirs, files in os.walk(toplevel_directory):
        for f in files:
            if not f.endswith('.py'):
                continue
            filepath = os.path.join(root, f).replace('\\', '/')
            convert_enums_in_file(filepath, dry_run)
            continue
        continue
    return

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description = 'Convert enums to fully-qualified names',
        add_help    = False,
    )
    parser.add_argument('-h', '--help'    , action='store_true')
    parser.add_argument('-d', '--dry_run' , action='store_true')
    parser.add_argument('-s', '--show'    , action='store_true')
    args = parser.parse_args()
    if args.help:
        show_help()
    else:
        #& Check if 'pyqt6_folderpath' exists
        if not os.path.exists(pyqt6_folderpath):
            print(
                f'\nERROR:\n'
                f'Folder {q}{pyqt6_folderpath}{q} could not be found. Make sure that variable '
                f'{q}pyqt6_folderpath{q} from line 57 points to the PyQt6 installation folder.\n'
            )
        else:
            #& Fill the 'enum_dict'
            type_hint_files = [
                os.path.join(pyqt6_folderpath, _filename)
                for _filename in os.listdir(pyqt6_folderpath)
                if _filename.endswith('.pyi')
            ]
            for _filepath in type_hint_files:
                fill_enum_dict(_filepath)
                continue

            #& Perform requested action
            if args.show:
                import pprint
                pprint.pprint(enum_dict)
            elif args.dry_run:
                print('\nDRY RUN\n')
                convert_all(dry_run=True)
            else:
                convert_all(dry_run=False)
    print('\nQuit enum converter tool\n')


# MIT LICENSE
# ===========
# Copyright (c) 2022 Kristof Mulier
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
# associated documentation files (the "Software"), to deal in the Software without restriction, in-
# cluding without limitation the rights to use, copy, modify, merge, publish, distribute, sublicen-
# se, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to
# do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substan-
# tial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRIN-
# GEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

确保备份您的 Python 项目。然后将此文件放在项目的顶级目录中。修改第 57 行(!),使其指向您的 PyQt6 安装文件夹。

首先运行脚本--dry_run标记以确保您同意替换。然后在没有任何标志的情况下运行它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

迁移到 Qt6/PyQt6:Qt5 中所有已弃用的短格式名称是什么? 的相关文章

  • 如何在Qt WebKit中将网页保存为“另存为完整网页”

    我需要使用 Qt WebKit 保存网页 类似于 另存为完整网页 以下是我的要求 保存索引html文件 维护实体编码 需要下载所有链接的图像和其他资源 需要将html页面中的资源路径更改为本地下载路径 需要维护网页当前状态 我可以使用 Qt
  • Qt - 如何在 QGraphicsPixmapItem 中显示 gif(动画)图像

    我正在尝试在 QGraphicsPixmapItem 中使用一张闪烁的图像 显示的图像没有动画效果 下面是原始图像 下面是在 QGraphicsPixmapItem 中使用此图像的 QGraphicsScene 有人能说一下如何实现这一目标
  • 删除行时 QModelIndex 变得无效

    我正在子类化QAbstractItemModel显示项目QTreeView 并且在这个子类中 projectModel 我有一个功能可以删除树视图中当前选定的索引 Component是用于表示模型所有成员的类 void projectMod
  • 从 Qt 中的半透明小部件中擦除绘制区域

    我面临着必须擦除 Qt 小部件上先前绘制的区域的问题 基本思想是 用户通过单击并拖动鼠标来选择屏幕的一个区域 并在所选区域上绘制一个矩形 标题 class ClearBack public QWidget Q OBJECT public e
  • 使用 Qt 在 xoverlay 之上绘制

    我希望在使用 Xoverlay 渲染的视频流之上绘制一些 UI 我正在使用 gstreamer 播放视频并使用 xoverlay 在 xvimagesink 上渲染它 我的小部件继承自 QGLWidget 我希望使用 QPainter 绘制
  • 如何在C++、Qt、QML、Blackberry 10 Cascades Beta 3 SDK中制作图表/图形(如折线图、条形图、圆形图)等?

    我需要知道如何在 Blackberry 10 Cascades Beta 3 SDK QML Qt C 中制作图表 图表 如果有人可以向我展示一个例子或向我指出一些可以告诉我如何做到这一点的东西 我将不胜感激 您应该查看 QChart js
  • 设置缩放 QGraphicsItem 的变换点

    我这里有个问题 在给定的代码中 动画和转换工作得很好 但是 我想要设置变换点 根据我的说法 我必须设置 setTransformationOriginPoint item boundingRect center 然而 看起来转换是从左上角开
  • 如何为Python程序创建自定义GUI?

    我想为具有自定义设计的 python 程序创建一个 GUI 我在 Photoshop 中有一个模型 我正在寻找一个支持主题的库或任何其他可以完成这项工作的库 我的 GUI 设计包含渐变 边框 边框半径和带有自定义最小化和关闭按钮的自定义标题
  • 来自无符号字符缓冲区的 QImage(jpg 格式)

    我有类型的缓冲区unsigned char 我用JPG图像填充 我想使用此缓冲区将图像绘制到 QLabel 中的应用程序屏幕上 我已经这样做了 但图像不正确 谁能告诉我最好的方法是什么 QPixmap pix QPixmap fromIma
  • 无法初始化静态QList?

    我收到以下错误 Cube cpp 10 error expected initializer before lt lt token 以下是头文件的重要部分 ifndef CUBE H define CUBE H include
  • 如何在点击 Qtablewidget 单元格时获取放置在该单元格中的小部件的行号?

    我正在尝试的是当用户选择项目时获取 QcomboBox 的行号 虽然使用它很容易获得单元格的列和行 cellClicked int int 信号 但仅当单元格上没有小部件时才有效 那么如果单元格中放置了小部件 如何获取行号 Note 所有组
  • 在 Qt C++ 中使用多个键

    我正在构建 坦克 游戏 我使用关键事件在地图上运行我的坦克 实际上我当时只能使用一把钥匙 但我需要有能力去完成任务 同时向上和离开 这是我的单键事件代码 switch event gt key case Qt Key Up if ui gt
  • 如何设置 Xcode 来代替 Qt Creator 工作?

    我不使用 Qt Creator 的 UI 设计功能 对于一个新项目 我想体验一下使用 Xcode 的工作 这将是一个常规的 Qt 项目 使用 C 和 Qt 库开发 就像在 Qt Creator 中一样 我没有使用 OS X 尤其是 Xcod
  • 如何在 QTableWidget 的行和列中自动换行文本?

    I tried QTableWidget j new QTableWidget 10000 5 centralWidget j gt setColumnWidth 0 500 j gt setColumnWidth 1 30 j gt se
  • Qt + win32 + mingw 上的原生 Windows API 链接问题

    我正在尝试使用 mingw 工具集将本机 Windows API 与 Qt 结合使用 部分功能存在链接问题 会发生什么 这是 mingw 名称修改的错误吗 ifdef Q WS WIN HWND hwnd QWidget winId HDC
  • 通过对 XmlHttpRequest (REST) 的响应在 QML 中显示图像

    我需要从 REST API 调用中获取 jpeg 图像 我使用 XMLHttpRequest 因为请求需要身份验证标头 即我不能只创建一个图像并将源设置为带有 user passwd url 的 URL 我认为我可以通过将 REST 数据设
  • 获取小部件的背景颜色 - 真的

    我无法获取小部件的实际背景颜色 在我的特殊情况下 我在使用 QTabWidget 中的小部件时遇到问题 这是在Windows7上 因此 经典的小部件有一些灰色背景 而选项卡内的小部件通常用白色背景绘制 I tried def bgcolor
  • QToolBar 的菜单延迟

    我通过制作 QAction 并向其添加 QMenu 在 QToolBar 上有一个菜单 如何消除单击图标时出现菜单之前的延迟 QToolBar myToolBar new QToolBar this QAction myAction new
  • Qt GUI 编程设计

    我正在尝试创建一个 GUI 应用程序 主窗口 一个QMainWindow 包含 9 个固定大小的标签以及主窗口的大小 我尝试在没有 Qt GUI Designer 的情况下以编程方式制作它 该项目构建时没有错误 但我看不到主窗口上显示的任何
  • 调整 QML 图像显示尺寸

    我有一个带有嵌套的 QML 窗口RowLayout 在内排我有两个图像 来源 png这些图像的文件 故意 相当大 当我尝试设置height这些图像上的属性使它们变小 但它们仍然被画得很大 Desired Appearance Actual

随机推荐