【问题标题】:Cannot wrap QWebView.load method无法包装 QWebView.load 方法
【发布时间】:2026-01-19 02:35:02
【问题描述】:

我相信能够包装函数调用是一种常见的做法(至少对我来说是这样)。

例如,一个极简包装函数如下所示:

def wrap(fn, *args, **kwargs):
    return fn(*args, **kwargs)

你可以通过

调用任意方法
wrap(qt_method, 1, 2, foo='bar')

相当于直接调用

qt_method(1,2, foo='bar')

这通常对我有用。但是,我遇到了一个没有的情况。

QWebView.load() 似乎不喜欢将空字典扩展到其调用签名中。例如。 wrap(my_webview.load, QUrl('http://istonyabbottstillprimeminister.com')) 失败,但有以下例外: TypeError: QWebView.load(QUrl): argument 1 has unexpected type 'QUrl'.

下面是一个简单的工作示例,它展示了一些可行和不可行的东西。我还没有找到另一种无法像这样包装的 Qt 方法。

import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtWebKit import QWebView

qapplication = QtGui.QApplication(sys.argv)

webview = QWebView()

url = QtCore.QUrl('http://istonyabbottstillprimeminister.com')

# basic wrapping function
def wraps(fn, *args, **kwargs):
    return fn(*args, **kwargs)

args = ()
kwargs = {}

wraps(webview.load, url)     # Doesn't work
webview.load(url, **kwargs)  # Doesn't work
webview.load(url)            # works
webview.url(*args, **kwargs) # works

webview.show()
qapplication.exec_()

当您继承 QWebView 并像这样覆盖 load 方法时,此问题也适用:

def load(self *args, **kwargs):
    return QWebView.load(self, *args, **kwargs)

当然,如果您改为调用 QWebView.load(self, *args) 或不在方法签名中使用 *args, **kwargs,那么您不会得到异常(这遵循上述最小化工作示例中所见)

任何对此的见解将不胜感激。

【问题讨论】:

  • 我认为您应该编辑您的问题以关注核心问题。 webview.load(url, **{}) 不起作用的事实表明这与“包装”没有任何关系。只是QWebView.load 不适用于关键字参数。
  • 我只是部分同意你的观点。在直接调用函数时,没有人愿意将 empty 字典解压缩到关键字参数中。发生这种情况的唯一合法情况是当您创建通用函数包装器或在子类化时覆盖方法时。因此,我觉得任何从用例中删除问题的尝试都会导致人们只是告诉我永远不要将webview.load 称为webview.load(url, **{})

标签: python pyqt pyqt4 python-sip


【解决方案1】:

我无法重现这一点,除非使用具有特定调用签名组合的重载 PyQt 方法。只有当一个签名只有一个参数,而另一个签名只有一个参数,而其余的关键字参数都具有默认值时,才会出现此错误。

PyQt4 中正好有九个这样的方法:

QtNetwork
  QSslSocket
    addDefaultCaCertificates(QString, QSsl.EncodingFormat format=QSsl.Pem, QRegExp.PatternSyntax syntax=QRegExp.FixedString)
    addDefaultCaCertificates(list-of-QSslCertificate)

    addCaCertificates(QString, QSsl.EncodingFormat format=QSsl.Pem, QRegExp.PatternSyntax syntax=QRegExp.FixedString)
    addCaCertificates(list-of-QSslCertificate)

    setPrivateKey(QSslKey)
    setPrivateKey(QString, QSsl.KeyAlgorithm algorithm=QSsl.Rsa, QSsl.EncodingFormat format=QSsl.Pem, QByteArray passPhrase=QByteArray())

    setLocalCertificate(QSslCertificate)
    setLocalCertificate(QString, QSsl.EncodingFormat format=QSsl.Pem)

QtWebKit
  QWebFrame
    load(QUrl)
    load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())

  QGraphicsWebView
    load(QUrl)
    load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())

  QWebView
    load(QUrl)
    load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())

QtGui
  QGraphicsScene
    items(Qt.SortOrder)
    QGraphicsScene.items(QPointF)
    QGraphicsScene.items(QRectF, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
    QGraphicsScene.items(QPolygonF, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
    QGraphicsScene.items(QPainterPath, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)

  QGraphicsView
    items(QPoint)
    QGraphicsView.items(QRect, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
    QGraphicsView.items(QPolygon, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)
    QGraphicsView.items(QPainterPath, Qt.ItemSelectionMode mode=Qt.IntersectsItemShape)

你碰巧碰上了一个。我会将此报告为针对 SIP 的错误。它用于确定调用哪个方法的启发式方法通常看起来非常好,并且恰好对于这种特定的组合或调用签名它失败了。如果您想最大程度地确保在输入可能无法很好地解析其参数的任意函数时事情不会中断,我会在您的代码中使用类似这样的东西:

def call_method_considerately(method, *args, **kwargs):
    if args and kwargs:
        return method(*args, **kwargs)
    elif args:
        return method(*args)
    elif kwargs:
        return method(**kwargs)
    else:
        return method()

【讨论】:

    【解决方案2】:

    我认为这是 PyQt 作为 C++ 库的 Python 包装器的产物。请注意,QWebView.load 不是用 Python 编写的函数,而是编译扩展提供的“内置方法”。 The documentation for QWebView.load 显示了具有不同参数签名的两个版本。这种方法重载在 Python 中是不可能的,所以大概QWebView.load 正在使用一些启发式方法来确定您要调用哪个版本,而当您使用 kwargs 时,这些启发式方法会失败。 (Python 函数无法区分根本不传递 kwargs 和传递空 kwargs,但 C 扩展模块可以。)换句话说,当您调用 load(url, **kwargs) 时,它认为您正在尝试使用该版本load 接受 QNetworkRequest 而不是 QUrl。

    不幸的是,在为 C++ 库使用 Python 包装器时,有时会出现此类问题,因为 C++ API 可能会使用重载的函数签名,这些函数签名必须以某种方式转换到无法重载的 Python 世界中。在 PyQt4 错误跟踪器上提出问题可能值得。据推测,这可以通过使底层启发式更智能来解决(例如,它应该将空 kwargs 视为与省略 kwargs 相同)。与此同时,您必须通过明确检查您是否要调用 QWebView.load 来解决它,如果是,则不要传递 kwargs。

    请注意,正如您在程序中描述的那样,该问题与函数包装无关。 webview.load(url, **{}) 自行失败,根本没有编写任何包装器。这意味着问题在于QWebView.load 处理关键字参数的方式。你发现这个是因为你在它周围写了一个包装器,但是无论有没有包装器,问题都存在。

    【讨论】:

    • 除非我遗漏了什么,否则异常显示TypeError: QWebView.load(QUrl) 的事实是否表明它已找到load 的正确版本?
    • @three_pineapples:我不这么认为。 QWebView.load 的文档字符串显示了两个参数签名,所以我认为只是 QUrl 版本是“默认”版本(或至少是默认名称/文档字符串),并且在尝试从那里分派时失败。
    • @BrenBarn。我认为三个菠萝是有道理的,因为当由于无效/不明确的参数而出现 TypeError 时,PyQt 通常会显示 all 可用的重载。不过,这个问题确实需要在PyQt mailing list 上报告 - 在这里进一步讨论并没有什么收获。
    【解决方案3】:

    由于python不支持函数重载,所以python函数的签名通常包含*args或**kwargs来模拟“重载”。 C++ Qt 实现了很多重载函数,比如这里: http://qt-project.org/doc/qt-4.8/qwebview.html 他们有:

    void    load ( const QUrl & url )
    void    load ( const QNetworkRequest & request, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, const QByteArray & body = QByteArray() )
    implements the overloaded functions.
    

    但是,如果你查看 PyQt4 函数签名(函数原型,它们并不总是有 **kwargs),以负载为例,PyQt4 中定义的负载实际上是:

     def load(self, *__args): # real signature unknown; restored from __doc__ with multiple overloads
        """
        QWebView.load(QUrl)
        QWebView.load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())
        """
        pass
    

    所以你可以试试这个:

    qapplication = QtGui.QApplication(sys.argv)
    
    webview = QWebView()
    
    url = QtCore.QUrl('http://istonyabbottstillprimeminister.com')
    
    
    # basic wrapping function
    def wraps(fn, *args, **kwargs):
        if not len(kwargs):
            return fn(*args)
        else:
            return fn(*args, **kwargs)
    
    args = ()
    kwargs = {}
    
    wraps(webview.load, url)
    webview.load(url)
    
    webview.show()
    qapplication.exec_()
    

    因此,通过这种方式,您可以使用 doc 来检测可能的函数签名。

    print webview.load.__doc__
    

    输出是 QWebView.load(QUrl) QWebView.load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())

    【讨论】: