为什么我们有一个__feature__?¶
历史¶
在PySide用户故事PYSIDE-1019中,我们测试了某些方法以使PySide更加符合Python风格。第一个想法是支持某种方式允许snake_case函数名称。
此功能在兼容性问题上相对较低,因为拥有相同功能但不同名称的函数并不理想,但这可能是一个低成本的解决方案。
当涉及到true_property时,情况就变得不同了。当我们支持将属性作为一等对象而不是getter和setter函数时,就会出现冲突,因为一个函数不能同时作为属性(没有大括号)和函数。
这一考虑引导我们产生了以下想法: 功能必须按模块可选。
为什么每个模块都可以选择功能?¶
假设你有一些预先存在的代码。也许你使用了一些下载的代码或者你生成了一个接口文件。当你现在决定使用一个功能时,你不希望所有这些现有的东西变得不正确。通过使用语句
from __feature__ import ...
你声明这个模块使用了一些特性。其他模块不会受到这个决定的影响,可以保持不变。
为什么是dunder,而不是__future__?¶
特别是在Python 2中,但在某些情况下也在Python 3中,有future语句
from __future__ import ...
这是一个只能在模块开头出现的语句,它会改变Python解析器的工作方式。
我们的第一个想法是为PySide模仿这种行为,尽管我们有点作弊:特性声明不是一个语法结构,我们不能轻易禁止它出现在模块的中间。
然后我们意识到Python的__future__导入和PySide的__feature__导入的意图是不同的:虽然Python通过__future__暗示了一些改进,但我们不想与__feature__相关联。我们只是认为一些来自Python的用户可能会喜欢我们的功能,而其他人则习惯于C++的惯例,并认为偏离Qt文档的内容是一个缺点。
使用from __feature__ import ...符号的意图是希望人们看到它与Python的__future__语句的相似性,并将该导入放在模块的开头,以便非常明显地表明该模块具有一些特殊的全局差异。
snake_case 功能¶
通过使用语句
from __feature__ import snake_case
此模块中使用的所有类的所有方法都在更改它们的名称。
更改名称的算法如下:
如果名称少于3个字符,或者
如果两个大写字符相邻,或者
如果名称以
gl开头(表示 OpenGL),名称保持不变。否则
单个大写字母
C被替换为_c
true_property 特性¶
通过使用语句
from __feature__ import true_property
在此模块中使用的所有类的方法,如果在Qt文档中声明为属性,则在Python中成为真正的属性。
此功能与过去不兼容,无法共存;这就是为什么开发这个功能想法的原因。
常规属性¶
普通属性的名称与之前相同:
QtWidgets.QLabel().color()
成为属性
QtWidgets.QLabel().color
当还有一个setter方法时,
QtWidgets.QLabel().setColor(value)
成为属性
QtWidgets.QLabel().color = value
普通属性会吞没getter和setter函数,并用属性对象替换它们。
特殊属性¶
特殊属性是指那些具有非标准名称的属性。
QtWidgets.QLabel().size()
成为属性
QtWidgets.QLabel().size
但这里我们没有setSize函数,而是
QtWidgets.QLabel().resize(value)
这成为属性
QtWidgets.QLabel().size = value
在这种情况下,setter 不会被忽略,因为很多人已经习惯了 resize 函数。
类属性¶
应该提到的是,我们不仅支持Python中已知的常规属性。还有类属性的概念,它们总是调用它们的getter和setter:
像前面提到的QtWidgets.QLabel这样的常规属性具有以下可见性:
>>> QtWidgets.QLabel.size
<property object at 0x113a23540>
>>> QtWidgets.QLabel().size
PySide6.QtCore.QSize(640, 480)
类属性也不需要实例即可评估:
>>> QtWidgets.QApplication.windowIcon
<PySide6.QtGui.QIcon(null) at 0x113a211c0>
只有直接访问正确的类字典才能检查它:
>>> QtGui.QGuiApplication.__dict__["windowIcon"]
<PySide6.PyClassProperty object at 0x114fc5270>
关于属性完整性¶
有许多属性,Python程序员一致认为这些函数应该是属性,但有一些不是属性,比如
>>> QtWidgets.QMainWindow.centralWidget
<method 'centralWidget' of 'PySide6.QtWidgets.QMainWindow' objects>
我们目前正在讨论是否应该纠正这些罕见的情况,因为它们可能只是遗漏。记住缺失的属性似乎相当麻烦,与其在Qt文档中查找所有属性,不如添加所有应该是属性且明显缺失的属性会更容易。
名称冲突及解决方案¶
在某些罕见的情况下,属性已经作为一个函数存在,要么有多个签名,要么有参数。这在C++中也不是很好,但在Python中这是被禁止的。例如:
>>> from PySide6 import *
>>> from PySide6.support.signature import get_signature
>>> import pprint
>>> pprint.pprint(get_signature(QtCore.QTimer.singleShot))
[<Signature (arg__1: int, arg__2: Callable) -> None>,
<Signature (msec: int, receiver: PySide6.QtCore.QObject, member: bytes) -> None>,
<Signature (msec: int, timerType: PySide6.QtCore.Qt.TimerType,
receiver: PySide6.QtCore.QObject, member: bytes) -> None>]
在创建此属性时,我们尊重现有函数,并通过附加下划线为该属性使用一个稍有不同的名称。
>>> from __feature__ import true_property
>>> QtCore.QTimer.singleShot_
<property object at 0x118e5f8b0>
我们希望这些冲突能在未来的Qt版本中得到解决。
__feature__ 导入¶
from __feature__ import ... 的实现是通过对内置的 __import__ 进行轻微修改来构建的。我们通过在内置模块中分配变量来明确这一点。此修改在 Qt for Python 导入时发生:
原始函数在
__import__中保留在__orig_import__中。新功能在
__feature_import__中,并分配给__import__。
此函数首先调用Python函数PySide6.support.__feature__.feature_import,如果功能导入不适用,则回退到__orig_import__。
覆盖 __import__¶
不建议这样做。导入修改应使用导入钩子完成,请参阅Python文档中的Import-Hooks。
如果您希望在不破坏功能的情况下修改__import__,请仅覆盖__orig_import__函数。
集成开发环境和修改Python存根文件¶
Qt for Python 提供了预生成的 .pyi 存根文件,这些文件与二进制模块位于同一位置。例如,在 site-packages 目录中,你可以在 QtCore.abi3.so 或 Windows 上的 QtCore.pyd 旁边找到一个 QtCore.pyi 文件。
在使用__feature__时,通常与常见的IDE一起使用,您可能希望提供一个功能感知版本的.pyi文件以获得正确的显示。最简单的方法是使用以下命令一次性更改它们:
pyside6-genpyi all --feature snake_case true_property
使用 __feature__ 与 UIC 文件¶
功能可以与生成的UIC文件自由结合使用。UIC文件故意不被转换。将它们与其他Python模块中的功能选择混合使用应该总是有效的,因为切换将根据需要由当前活动的模块选择。(如果某个示例失败,请向我们报告)