为什么 PyQt 类 的反弹方法会引发 TypeError
Why do rebound methods of PyQt classes raise a TypeError
使我的代码与 PyQt5/6 和 PySide2/6 兼容,我写了
if not hasattr(QtCore.QDateTime, 'toPython'): # fix for PyQt5/6
QtCore.QDateTime.toPython = QtCore.QDateTime.toPyDateTime
运行 使用 PyQt5 或 PyQt6,这导致
TypeError: toPyDateTime(self): first argument of unbound method must have type 'QDateTime'
调用函数时:
QtCore.QDateTime.currentDateTime().toPython()
但是如果我把调用改成
QtCore.QDateTime.toPython(QtCore.QDateTime.currentDateTime())
没有错误。
但是,当我把第一段代码改成
if not hasattr(QtCore.QDateTime, 'toPython'): # fix for PyQt5/6
QtCore.QDateTime.toPython = lambda self: QtCore.QDateTime.toPyDateTime(self)
无论我如何调用 toPython
函数,一切正常。为什么我在这里需要 lambda 表达式?
已添加。 为了解释我期望的行为,有一段简单的代码:
class A:
def __init__(self) -> None:
print(f'make A ({hex(id(self))})')
def foo(self) -> None:
print(f'A ({hex(id(self))}) foo')
class B:
def __init__(self) -> None:
print(f'make B ({hex(id(self))})')
B.bar = A.foo
b: B = B() # prints “make B (0x7efc04c67f10)” (or another id)
B.bar(b) # prints “A (0x7efc04c67f10) foo” (same id, no error)
b.bar() # same result as before, no error
相反,下面的代码不起作用:
from PyQt6.QtCore import QDateTime
QDateTime.toPython = QDateTime.toPyDateTime
t: QDateTime = QDateTime.currentDateTime()
QDateTime.toPython(t) # no error
t.toPython() # raises TypeError
这是由于 PyQt 和 PySide 之间的实现差异。在前者中,类 的大多数方法都是围绕 C-functions 的薄包装器,它们没有实现 descriptor protocol(即它们没有 __get__
方法)。因此,在这方面,它们等同于 built-in 函数,例如 len
:
>>> type(len)
<class 'builtin_function_or_method'>
>>> type(QtCore.QDateTime.toPyDateTime) is type(len)
True
>>> hasattr(QtCore.QDateTime.toPyDateTime, '__get__')
False
相比之下,大多数 PySide 方法 do 实现描述符协议:
>>> type(QtCore.QDateTime.toPython)
<class 'method_descriptor'>
>>> hasattr(QtCore.QDateTime.toPython, '__get__')
True
这意味着如果您取消兼容性修复,它会按预期工作:
>>> from PySide2 import QtCore
QtCore.QDateTime.toPyDateTime = QtCore.QDateTime.toPython
>>> QtCore.QDateTime.currentDateTime().toPyDateTime()
datetime.datetime(2022, 4, 29, 11, 52, 51, 67000)
但是,如果您想保留当前的命名方案,使用包装函数(例如 lambda
)本质上是最好的选择。所有 user-defined Python 函数都支持描述符协议,这就是为什么您的示例使用简单 user-defined 类 的行为符合预期。可能建议的唯一改进是改用 partialmethod。这将节省编写一些 boiler-plate 代码,并具有提供更多信息的额外好处 error-messages:
>>> QtCore.QDateTime.toPython = partialmethod(QtCore.QDateTime.toPyDateTime)
>>> d = QtCore.QDateTime.currentDateTime()
>>> d.toPython()
datetime.datetime(2022, 4, 29, 12, 13, 15, 434000)
>>> d.toPython(42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.10/functools.py", line 388, in _method
return self.func(cls_or_self, *self.args, *args, **keywords)
TypeError: toPyDateTime(self): too many arguments
我想这里唯一剩下的一点就是为什么 PyQt 和 PySide 在实现上完全不同的问题。您可能需要 ask the author of PyQt 才能得到关于此的明确解释。我的猜测是,这至少部分是出于历史原因,因为 PyQt 的存在时间比 PySide 长得多——但毫无疑问还有其他一些技术考虑因素。
使我的代码与 PyQt5/6 和 PySide2/6 兼容,我写了
if not hasattr(QtCore.QDateTime, 'toPython'): # fix for PyQt5/6
QtCore.QDateTime.toPython = QtCore.QDateTime.toPyDateTime
运行 使用 PyQt5 或 PyQt6,这导致
TypeError: toPyDateTime(self): first argument of unbound method must have type 'QDateTime'
调用函数时:
QtCore.QDateTime.currentDateTime().toPython()
但是如果我把调用改成
QtCore.QDateTime.toPython(QtCore.QDateTime.currentDateTime())
没有错误。
但是,当我把第一段代码改成
if not hasattr(QtCore.QDateTime, 'toPython'): # fix for PyQt5/6
QtCore.QDateTime.toPython = lambda self: QtCore.QDateTime.toPyDateTime(self)
无论我如何调用 toPython
函数,一切正常。为什么我在这里需要 lambda 表达式?
已添加。 为了解释我期望的行为,有一段简单的代码:
class A:
def __init__(self) -> None:
print(f'make A ({hex(id(self))})')
def foo(self) -> None:
print(f'A ({hex(id(self))}) foo')
class B:
def __init__(self) -> None:
print(f'make B ({hex(id(self))})')
B.bar = A.foo
b: B = B() # prints “make B (0x7efc04c67f10)” (or another id)
B.bar(b) # prints “A (0x7efc04c67f10) foo” (same id, no error)
b.bar() # same result as before, no error
相反,下面的代码不起作用:
from PyQt6.QtCore import QDateTime
QDateTime.toPython = QDateTime.toPyDateTime
t: QDateTime = QDateTime.currentDateTime()
QDateTime.toPython(t) # no error
t.toPython() # raises TypeError
这是由于 PyQt 和 PySide 之间的实现差异。在前者中,类 的大多数方法都是围绕 C-functions 的薄包装器,它们没有实现 descriptor protocol(即它们没有 __get__
方法)。因此,在这方面,它们等同于 built-in 函数,例如 len
:
>>> type(len)
<class 'builtin_function_or_method'>
>>> type(QtCore.QDateTime.toPyDateTime) is type(len)
True
>>> hasattr(QtCore.QDateTime.toPyDateTime, '__get__')
False
相比之下,大多数 PySide 方法 do 实现描述符协议:
>>> type(QtCore.QDateTime.toPython)
<class 'method_descriptor'>
>>> hasattr(QtCore.QDateTime.toPython, '__get__')
True
这意味着如果您取消兼容性修复,它会按预期工作:
>>> from PySide2 import QtCore
QtCore.QDateTime.toPyDateTime = QtCore.QDateTime.toPython
>>> QtCore.QDateTime.currentDateTime().toPyDateTime()
datetime.datetime(2022, 4, 29, 11, 52, 51, 67000)
但是,如果您想保留当前的命名方案,使用包装函数(例如 lambda
)本质上是最好的选择。所有 user-defined Python 函数都支持描述符协议,这就是为什么您的示例使用简单 user-defined 类 的行为符合预期。可能建议的唯一改进是改用 partialmethod。这将节省编写一些 boiler-plate 代码,并具有提供更多信息的额外好处 error-messages:
>>> QtCore.QDateTime.toPython = partialmethod(QtCore.QDateTime.toPyDateTime)
>>> d = QtCore.QDateTime.currentDateTime()
>>> d.toPython()
datetime.datetime(2022, 4, 29, 12, 13, 15, 434000)
>>> d.toPython(42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.10/functools.py", line 388, in _method
return self.func(cls_or_self, *self.args, *args, **keywords)
TypeError: toPyDateTime(self): too many arguments
我想这里唯一剩下的一点就是为什么 PyQt 和 PySide 在实现上完全不同的问题。您可能需要 ask the author of PyQt 才能得到关于此的明确解释。我的猜测是,这至少部分是出于历史原因,因为 PyQt 的存在时间比 PySide 长得多——但毫无疑问还有其他一些技术考虑因素。