无法包装QWebView.load方法

时间:2015-02-09 03:11:35

标签: python pyqt pyqt4 python-sip

我认为能够包装函数调用是常见的做法(至少对我而言)。

例如,作为一个例子,一个小型包装函数看起来像:

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,那么您就不会得到异常(从最小化工作示例ebove中可以看到)

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

3 个答案:

答案 0 :(得分:4)

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

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

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

答案 1 :(得分:4)

除非使用具有特定呼叫签名组合的重载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 :(得分:1)

由于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())