Не удается обернуть метод 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): 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
в сигнатуре методов вы не получите исключения (что следует из того, что видно из минимистического рабочего примера выше)
Любое понимание этого будет оценено.
3 ответа
Я не могу воспроизвести это, за исключением перегруженных методов 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()
Я думаю, что это артефакт PyQt, являющегося оболочкой Python для библиотеки C++. Обратите внимание, что QWebView.load
это не функция, написанная на Python, а "встроенный метод", предоставляемый скомпилированным расширением. Документация для QWebView.load
показывает две версии с разными сигнатурами аргументов. Такая перегрузка методов невозможна в Python, поэтому предположительно QWebView.load
использует некоторую эвристику, чтобы определить, какую версию вы хотите вызвать, и эта эвристика не работает, когда вы используете kwargs. (Функция Python не может определить разницу между тем, чтобы вообще не передавать kwargs и не передавать пустые kwargs, а модуль расширения C может.) Другими словами, когда вы вызываете load(url, **kwargs)
думает, что вы пытаетесь использовать версию load
который принимает QNetworkRequest, а не QUrl.
К сожалению, такого рода проблемы иногда возникают при использовании оболочек Python для библиотек C++, поскольку API-интерфейсы C++ могут использовать перегруженные сигнатуры функций, которые необходимо каким-то образом преобразовать в мир Python, где перегрузка невозможна. Возможно, стоит поднять проблему с трекером ошибок PyQt4. Предположительно, это можно исправить, сделав базовую эвристику умнее (например, она должна относиться к пустым kwargs так же, как к пропущенным kwargs). В то же время вам придется обойти это, явно проверив, если вы собираетесь позвонить QWebView.load
и не проходя мимо kwargs, если так.
Обратите внимание, что проблема не имеет ничего общего с переносом функций, как вы описываете это в своей программе. webview.load(url, **{})
терпит неудачу сам по себе, без написания каких-либо оболочек вообще. Это означает, что проблема в пути QWebView.load
обрабатывает аргументы ключевых слов. Вы нашли это, потому что написали обертку вокруг нее, но проблема существует с оберткой или без нее.
Поскольку 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_()
Таким образом, вы можете использовать документ для определения возможной сигнатуры функции.
print webview.load.__doc__
Выходные данные - QWebView.load(QUrl) QWebView.load(QNetworkRequest, QNetworkAccessManager.Operation operation=QNetworkAccessManager.GetOperation, QByteArray body=QByteArray())