Не удается обернуть метод 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())

Другие вопросы по тегам