迁移到 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 中所有已弃用的短格式名称是什么? 的相关文章

  • PyQt5安装错误(QtCore模块错误:无法创建C++代码)

    我正在尝试在我的 PC 上安装 PyQt5 以访问 QtDesigner 我在安装过程中遇到以下错误 python3 configure py qmake usr local Cellar qt 5 10 1 bin qmake sip u
  • 如何使 Python、QT 和 Webkit 在无头服务器上工作?

    我有 Debian Linux 服务器 我用它来做各种事情 我希望它能够完成一些我需要定期完成的网络抓取工作 这段代码可以是在这里找到 http bit ly QeqvzX import sys from PyQt4 QtGui impor
  • 如何在pyqt5中创建小部件来显示谷歌地图

    如何从用户那里获取纬度和经度并在 pyqt5 小部件的地图上显示该位置 我的操作系统是Windows 我找到了这个链接 https github com eyllanesc qMap https github com eyllanesc q
  • 如何在 OS X 上的 Qt 应用程序中设置应用程序图标,足以进行分发?

    跟进这个答案 https stackoverflow com a 20918932 368896 to 这个问题 https stackoverflow com questions 20909341 what is the fastest
  • 如何让小部件在上下文菜单出现时接收鼠标释放事件

    在Ubuntu20 04上 当上下文菜单出现时 我无法让小部件接收鼠标释放事件 而Windows可以接收 我的pyqt版本是5 15 2 我考虑过手动发送鼠标释放事件 但我不知道当上下文菜单出现时哪些系统会收到鼠标释放事件 这样做可能会导致
  • 获取 QListView 中所有可见项目的简单方法

    我正在尝试使用 Qt Framework 开发一个图像库应用程序 应用程序从所选文件夹加载所有图像 并使用 QListView 控件显示这些图像 但现在我想通过仅加载用户可见的图像来减少内存消耗 由于没有直接函数来获取视图中的所有可见项目
  • 在标准 python 线程中发出信号

    我有一个线程应用程序 其中有一个网络线程 UI 部分通过callback到这个线程 线程是一个normalpython 线程 它是NO QThread 是否可以在该线程内发出 PyQT Slot 不 不可能像这样从 python 线程发出
  • 完全彻底卸载QT Creator

    问题 如何从 Linux 机器上卸载 QT Creator 我的 Debian Jessie 机器上的安装已损坏 我尝试过重新安装 修复等 但没有成功 建议我完全卸载 获取最新版本并重新安装 问题是我不确定如何执行此操作 每次我尝试时 QT
  • QMainWindow 上的 Qt 布局

    我设计了一个QMainWindow with QtCreator s设计师 它由默认的中央小部件 aQWidget 其中包含一个QVBoxLayout以及其中的所有其他小部件 现在我想要的一切就是QVBoxLayout自动占据整个中央小部件
  • 使用 OpenGL 渲染 QImage

    与我相关的其他问题 https stackoverflow com questions 20126354 render qimage from sooffscreenrenderer in qglwidget 我认为更核心的问题是 如何渲染
  • 如何去除QWizard中的水平线?

    我正在研究一个样式表QWizard我想删除按钮上方的水平线 我尝试递归浏览所有小部件并将其边框设置为无 但似乎没有任何小部件具有此边框 这是我的代码 可以找到完整的可构建示例here https gist github com ardeid
  • Qt 嵌入式触摸屏 QMouseEvents 在收到 MouseButtonRelease 之前未收到

    我在带有触摸屏的小型 ARM 嵌入式 Linux 设备上使用 Qt 4 8 3 我的触摸屏配置了 tslib 并对其进行了校准 因此 etc 中有一个 pointcal 文件 我的触摸事件的位置工作得很好 但无论如何我都会在鼠标按下或鼠标释
  • PyQt4 QPalette 不工作

    btn QtGui QPushButton Button self palettes btn palette palettes setColor btn backgroundRole QtCore Qt green btn setPalet
  • 如何在 Qt 应用程序中嵌入 Python 解释器?

    有没有一种简单的方法可以将 Python 解释器嵌入到 Qt 应用程序中 如果可能的话 我希望有一个跨平台的解决方案 这就是目的PythonQt http pythonqt sourceforge net 它支持 Windows Linux
  • Qt中用于线程间通信的类设计

    问题陈述 用相机跟踪物体并相应地移动相机的方位角和仰角 Process 相机获取物体的图像 处理相机的每一帧以查找物体 应该被跟踪 并将每帧中生成的信息传递给机械设备 万向节 以平移和倾斜方式移动摄像机 Design 主 Gui 在一个线程
  • 了解 Qt3D 创建的网格

    我创建了一个 Qt3D 网格 如下所示 Qt3DCore QEntity newEntity new Qt3DCore QEntity Qt3DExtras QConeMesh mesh new Qt3DExtras QConeMesh m
  • Qt QML 数据模型似乎不适用于 C++

    我一直在使用中的示例http doc qt digia com 4 7 qdeclarativemodels html http doc qt digia com 4 7 qdeclarativemodels html这是 QML 声明性数
  • QML 列表视图拖放

    我想创建两个 qml 列表视图 可以执行两个功能 拖放一个列表中的项目以更改项目的顺序 跨列表拖放项目 项目将从一个列表中删除并添加到另一个列表中 根据 Qt 文档中的拖放示例 我决定创建两个访问同一列表模型的列表视图 列表模型中的每个项目
  • Q风格所有权

    在 Qt 应用程序中使用样式时 我遇到了一个有趣的问题QStyle所有权 QStyle继承自QObject 通常接受QObject parent作为构造函数参数来管理其子级的生命周期 但QStyle的构造函数没有此构造函数参数 第一个问题
  • 为什么 QT 设计器重新调整大小或不允许我缩小或展开小部件或按钮?

    很多时候 在使用 QT 设计器时 我发现自己需要通过缩小或扩展来调整事物的大小 每当我尝试这样做时 程序都不允许我这样做 而只是恢复到将对象放置在窗口中时给我的原始默认大小 无论我的布局如何 为什么要这样做 是否有可能改变这一点 以便我可以

随机推荐