如何在此 PyQt5 代码中设置实例方法 Monkeypatch?

问题描述 投票:0回答:1

是的,我看到了:

...但这些方法似乎都不适用于我的示例:

基本上,这是一个尝试使用 QtAwesome 为 QMessageBox 提供旋转图标的示例; QtAwesome 中有一个类

Spin
可以做动画,它有一个方法
_update
:我想对这个方法进行猴子补丁,所以首先调用原始方法,然后打印一条消息 - 所以我有这个:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
import qtawesome as qta

class Example(QWidget):
  def __init__(self):
    super().__init__()
    self.initUI()

  def initUI(self):
    self.setGeometry(300, 300, 300, 220)
    self.setWindowTitle('Hello World')
    self.show()
    msgBox = QMessageBox( QMessageBox.Information, "Title", "Content ...", QMessageBox.Cancel )
    orig_ipmsize = msgBox.iconPixmap().size()
    print(orig_ipmsize.width(), orig_ipmsize.height()) # 0 0 for QMessageBox.NoIcon; 32 32 for QMessageBox.Information
    animation = qta.Spin(msgBox, autostart=True)

    DO_MONKEYPATCH = 2 # 1 or 2

    if DO_MONKEYPATCH == 1:
      old_anim_update = animation._update
      def new_anim_update(self):
        old_anim_update(self) # TypeError: Spin._update() takes 1 positional argument but 2 were given
        print("new_anim_update")
      animation._update = new_anim_update.__get__(animation, qta.Spin) # https://stackoverflow.com/a/28127947

    elif DO_MONKEYPATCH == 2:
      def update_decorator(method):
        def decorate_update(self=None):
          method(self) # TypeError: Spin._update() takes 1 positional argument but 2 were given
          print("decorate_update")
        return decorate_update
      animation._update = update_decorator(animation._update) # https://stackoverflow.com/a/8726680

    #print(animation._update)
    spin_icon = qta.icon('fa5s.spinner', color='red', animation=animation)
    msgBox.setIconPixmap(spin_icon.pixmap(orig_ipmsize)) #msgBox.setIcon(spin_icon)
    #animation.start()
    returnValue = msgBox.exec()

if __name__ == '__main__':
  app = QApplication(sys.argv)
  ex = Example()
  sys.exit(app.exec_())

但是,无论我选择哪种

DO_MONKEYPATCH
方法,我都会得到
TypeError: Spin._update() takes 1 positional argument but 2 were given


好吧,只是注意到在编写问题时这两个错误是如何应用的,并发现如果我将“旧方法”调用更改为不使用

(self)
参数 - 即我将
old_anim_update(self)
/
method(self)
更改为
old_anim_update()
/
method()
- 那么两个
DO_MONKEYPATCH
方法都允许在没有位置参数错误的情况下运行 - 但是只有
DO_MONKEYPATCH
方法 1 似乎保留
self
:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox
import qtawesome as qta

class Example(QWidget):
  def __init__(self):
    super().__init__()
    self.initUI()

  def initUI(self):
    self.setGeometry(300, 300, 300, 220)
    self.setWindowTitle('Hello World')
    self.show()
    msgBox = QMessageBox( QMessageBox.Information, "Title", "Content ...", QMessageBox.Cancel )
    orig_ipmsize = msgBox.iconPixmap().size()
    print(orig_ipmsize.width(), orig_ipmsize.height()) # 0 0 for QMessageBox.NoIcon; 32 32 for QMessageBox.Information
    animation = qta.Spin(msgBox, autostart=True)

    DO_MONKEYPATCH = 1 # 1 or 2

    if DO_MONKEYPATCH == 1:
      old_anim_update = animation._update
      def new_anim_update(self):
        old_anim_update() # no error
        print("new_anim_update {}".format(self)) # self is <qtawesome.animation.Spin object at 0x00000238f1d45f10>
      animation._update = new_anim_update.__get__(animation, qta.Spin) # https://stackoverflow.com/a/28127947

    elif DO_MONKEYPATCH == 2:
      def update_decorator(method):
        def decorate_update(self=None):
          method() # TypeError: Spin._update() takes 1 positional argument but 2 were given
          print("decorate_update {}".format(self)) # self is None
        return decorate_update
      animation._update = update_decorator(animation._update) # https://stackoverflow.com/a/8726680

    #print(animation._update)
    spin_icon = qta.icon('fa5s.spinner', color='red', animation=animation)
    msgBox.setIconPixmap(spin_icon.pixmap(orig_ipmsize)) #msgBox.setIcon(spin_icon)
    #animation.start()
    returnValue = msgBox.exec()

if __name__ == '__main__':
  app = QApplication(sys.argv)
  ex = Example()
  sys.exit(app.exec_())

所以,似乎

DO_MONKEYPATCH == 1
方法是最初提出的这个问题的答案 - 但我仍然担心,我对
old_anim_update()
的调用,在没有任何对
self
的原始引用的情况下,如何正确调用旧方法?或者有更正确的方法来做这种monkeypatch吗?

python python-3.x pyqt5 monkeypatching
1个回答
0
投票

仅执行

old_anim_update()
而不使用
self
有效的原因是因为
old_anim_update
已经是对实例方法的引用,并且您不需要使用
self
来调用它。

当您执行

update_decorator(animation._update)
时,您将传递对已经 bound 到实例的函数的引用,从而成为实例方法。

澄清上述内容的简化版本可能如下:

something = animation._update
something()

上面将自动调用

qta.Spin._update()
,并将
animation
作为其
self

self
是必要参数的情况是,如果您使用对 class 的命名空间中的方法的引用:

something = qta.Spin._update
something(animation)

在上面的例子中,

animation
参数变成了
self
中的
def _update(self)
。这就是在
super()
出现之前,Python 中通常调用默认实例方法的方式:上面的内容与
qta.Spin._update(animation)
相同。

请注意,虽然猴子修补通常可以很好地处理简单的方面,但通常最好创建一个子类,特别是因为这使其可重用。

一个可能的解决方案是只使用

parent_widget
(这是消息框),并使用样式的度量来获取标准像素图大小,这就是 QMessageBox 内部实际执行的操作:

class MsgBoxSpin(qta.Spin):
    def _update(self):
        super()._update()
        iconSize = self.parent_widget.style().pixelMetric(
            QStyle.PM_MessageBoxIconSize, None, self.parent_widget)
        self.parent_widget.setIconPixmap(self.icon.pixmap(
            iconSize, iconSize))

    def setIcon(self, icon):
        self.icon = icon
...

animation = qta.Spin(msgBox, autostart=True)
spin_icon = qta.icon('fa5s.spinner', color='red', animation=animation)
animation.setIcon(spin_icon)

上面的内容是必要的,因为给定的父级实际上是消息框,并且

_update()
将调用 its
QWidget.update()
,这可能在显示像素图的小部件上没有结果;此外,
qta.Spin
对象实际上对所显示的实际图标一无所知,这意味着:

  • 我们必须明确“告诉”它要在覆盖中使用的图标;
  • 我们只能使用带有特定图标和特定目标消息框的动画;

它可以工作,但它也有点笨重。

不过,还有另一种选择。当我只想显示 QIcon 时,我有时会使用没有设置边框或背景且禁用任何交互的虚拟按钮。

QMessageBox 在 QLabel 中显示图标,该图标具有硬编码的对象名称 (

qt_msgboxex_icon_label
),因此我们可以使用
QObject.findChild()
检索它,为其创建布局,并向其添加虚拟按钮。最后,我们设置一个具有适当大小的透明 QPixmap(如上所述),确保它始终需要必要的空间,即使它是空的。
所有这些使得在 QMessageBox 中使用实际的 QIcon 对象成为可能。

class MessageBox(QMessageBox):
    _iconWidget = None
    def setIcon(self, icon):
        if isinstance(icon, QMessageBox.Icon):
            super().setIcon(icon)
            if self._iconWidget:
                self._iconWidget.deleteLater()
                self._iconWidget = None
        elif isinstance(icon, QIcon):
            self.iconWidget().setIcon(icon)

    def iconWidget(self):
        if not self._iconWidget:
            label = self.findChild(QLabel, 'qt_msgboxex_icon_label')
            iconSize = self.style().pixelMetric(
                QStyle.PM_MessageBoxIconSize, None, self)
            pm = QPixmap(iconSize, iconSize)
            pm.fill(Qt.transparent)
            self.setIconPixmap(pm)

            layout = label.layout()
            if layout is None:
                layout = QVBoxLayout(label)
                layout.setContentsMargins(0, 0, 0, 0)

            self._iconWidget = QPushButton()
            layout.addWidget(self._iconWidget)
            self._iconWidget.setStyleSheet('''
                QPushButton {
                    border: none;
                    background: none;
                }
            ''')
            self._iconWidget.setIconSize(QSize(iconSize, iconSize))
            self._iconWidget.setFocusPolicy(Qt.NoFocus)
            self._iconWidget.setAttribute(Qt.WA_TransparentForMouseEvents)
        return self._iconWidget

然后您可以使用自定义消息框功能创建图标

iconWidget()
(因为它需要父级进行更新),并且您不再需要任何猴子修补或图标子类化。

© www.soinside.com 2019 - 2024. All rights reserved.