Skip to content

Commit 2a46a45

Browse files
usiemsjcfr
authored andcommitted
[Backport] Make __enter__ and __exit__ slots work with "with" statements
The problem was that the "with" statement doesn't use the conventional __getattr__ access, but directly accesses the tp_dict of the type, which usually is empty in PythonQt, so we had to put the __enter__ and __exit__ methods there, too. And for binding the methods to the instance, tp_descr_get was used in this case, so we had to implement that for slots, too. Discussed with and reviewed by Florian Link (cherry picked from commit MeVisLab/pythonqt@d13b66c)
1 parent 0b25ba9 commit 2a46a45

File tree

3 files changed

+43
-0
lines changed

3 files changed

+43
-0
lines changed

src/PythonQt.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,32 @@ void PythonQtPrivate::registerCPPClass(const char* typeName, const char* parentT
19741974
if (shell) {
19751975
info->setShellSetInstanceWrapperCB(shell);
19761976
}
1977+
if (info->typeSlots() & PythonQt::Type_EnterExit) {
1978+
// context handlers must be handled specially, because optimized accesses
1979+
// cause that the __enter__ and __exit__ methods will not be found by
1980+
// conventional means:
1981+
PyObject* classObj = (PyObject*)info->pythonQtClassWrapper();
1982+
PyTypeObject* typeObj = (PyTypeObject*)classObj;
1983+
// __enter__ and _exit__ must be inserted directly into the tp_dict,
1984+
// otherwise they are not found by "with":
1985+
PyObject* tp_dict = typeObj->tp_dict;
1986+
PyObject* enterMethod = PyObject_GetAttrString(classObj, "__enter__");
1987+
if (enterMethod) {
1988+
PyDict_SetItemString(tp_dict, "__enter__", enterMethod);
1989+
Py_DECREF(enterMethod);
1990+
}
1991+
PyErr_Clear();
1992+
PyObject* exitMethod = PyObject_GetAttrString(classObj, "__exit__");
1993+
if (exitMethod) {
1994+
PyDict_SetItemString(tp_dict, "__exit__", exitMethod);
1995+
Py_DECREF(exitMethod);
1996+
}
1997+
PyErr_Clear();
1998+
// invalidate attribute cache for this type:
1999+
// (as the search for the class methods otherwise cached methods as "not existing", and
2000+
// we don't want to rely on the wrong empty entries being evicted from the cache by chance):
2001+
typeObj->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
2002+
}
19772003
}
19782004

19792005
PyObject* PythonQtPrivate::packageByName(const char* name)

src/PythonQt.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ class PYTHONQT_EXPORT PythonQt : public QObject {
208208
Type_MappingSetItem = 1 << 21,
209209
Type_MappingGetItem = 1 << 22,
210210

211+
Type_EnterExit = 1 << 23,
212+
211213
Type_Invert = 1 << 29,
212214
Type_RichCompare = 1 << 30,
213215
Type_NonZero = 1 << 31,

src/PythonQtSlot.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,20 @@ meth_richcompare(PythonQtSlotFunctionObject *a, PythonQtSlotFunctionObject *b, i
788788
Py_RETURN_FALSE;
789789
}
790790

791+
static PyObject*
792+
meth_descr_get(PyObject *descr, PyObject *obj, PyObject* type)
793+
{
794+
if (PythonQtSlotFunction_Check(descr)) {
795+
PythonQtSlotFunctionObject *slotObj = (PythonQtSlotFunctionObject*)descr;
796+
return PythonQtSlotFunction_New(slotObj->m_ml, obj, NULL);
797+
}
798+
else {
799+
// wrong type
800+
Py_IncRef(descr);
801+
return descr;
802+
}
803+
}
804+
791805
PyTypeObject PythonQtSlotFunction_Type = {
792806
PyVarObject_HEAD_INIT(&PyType_Type, 0)
793807
"builtin_qt_slot",
@@ -825,6 +839,7 @@ PyTypeObject PythonQtSlotFunction_Type = {
825839
meth_getsets, /* tp_getset */
826840
0, /* tp_base */
827841
0, /* tp_dict */
842+
meth_descr_get, /* tp_descr_get */
828843
};
829844

830845
/* Clear out the free list */

0 commit comments

Comments
 (0)