Python 无法包装QWebView.load方法
我相信能够包装函数调用是一种常见的做法(至少对我来说是这样) 例如,作为示例,minimilistic包装函数如下所示:Python 无法包装QWebView.load方法,python,pyqt,pyqt4,python-sip,Python,Pyqt,Pyqt4,Python Sip,我相信能够包装函数调用是一种常见的做法(至少对我来说是这样) 例如,作为示例,minimilistic包装函数如下所示: def wrap(fn, *args, **kwargs): return fn(*args, **kwargs) 你可以通过 wrap(qt_method, 1, 2, foo='bar') 这相当于直接打电话 qt_method(1,2, foo='bar') 这通常对我有效。然而,我遇到了一个案例,但事实并非如此 QWebView.load()似乎不喜欢将
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):参数1具有意外的类型“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
,那么您不会得到异常(这是从minimilistic工作示例ebove中看到的)
任何对此的理解都会被理解。
< P>我认为这是PyQt的一个伪像,它是C++库的Python包装器。请注意,QWebView.load
不是用Python编写的函数,而是由编译扩展提供的“内置方法”。对于QWebView.load
显示具有不同参数签名的两个版本。这种方法重载在Python中是不可能的,因此推测QWebView.load
正在使用一些启发式方法来确定要调用哪个版本,而这些启发式方法在使用kwargs时失败。(Python函数无法区分根本不传递KWARG和传递空KWARG之间的区别,但C扩展模块可以。)换句话说,当您调用load(url,**kwargs)
时,它认为您正在尝试使用接受QNetworkRequest而不是QUrl的load
版本
不幸的是,当使用Python包装器来处理C++库时,有时会出现这种问题,因为C++ API可能会使用重载函数签名,这些函数必须以某种方式被翻译成Python世界,而在这种情况下,不可能重载。也许值得在PyQt4 bug追踪器上提出一个问题。大概可以通过使底层启发式更智能来解决这个问题(例如,它应该像对待省略的Kwarg一样对待空Kwarg)。同时,您必须通过显式检查是否要调用QWebView.load
,来解决这个问题,如果要调用,则不要传递kwargs
请注意,这个问题与程序中描述的函数包装无关
webview.load(url,***{})
本身失败,根本不写任何包装。这意味着问题出在QWebView.load
处理关键字参数的方式上。您发现这一点是因为您在其周围编写了一个包装器,但无论是否使用包装器,问题都存在。由于python不支持函数重载,因此python函数的签名通常包含*args或**kwargs以模拟“重载”。C++ QT实现了很多重载功能,如:
它们有:
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=QNetworkAccessManager.GetOperation、QByteArray body=QByteArray())我无法复制此操作,除非使用具有特定调用签名组合的重载PyQt方法。只有当一个签名只有一个参数,而另一个签名只有一个参数,并且rest关键字参数具有默认值时,这个bug才会被击中 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()
我认为你应该修改你的问题,把重点放在核心问题上。
webview.load(url,***{})
不起作用的事实表明,这与“包装”没有任何关系。只是QWebView.load
不适用于关键字参数。我只是部分同意你的观点。在直接调用函数时,没有任何人会希望将空字典解压到关键字参数中。发生这种情况的唯一合法情况是创建泛型函数包装器或在子类化时重写方法。因此,我觉得任何试图从用例中删除问题的尝试都会导致人们简单地告诉我永远不要调用webview.load
Aswebview.load(url,***{})
,除非我遗漏了什么,否则异常不是说TypeError:QWebView.load(qrl)
表明它找到了加载的正确版本?@three_Pinepples:我不这么认为。QWebView.load
的docstring显示了两个参数签名,所以我认为这只是因为QUrl版本是“默认”版本(或者至少是默认名称/docstring),并且在尝试从那里分派时失败。@BrenBarn。我认为三个菠萝是有道理的,因为PyQt会占据一切