Skip to content

Commit d13b66c

Browse files
committed
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
1 parent 720a3c2 commit d13b66c

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
@@ -1924,6 +1924,32 @@ void PythonQtPrivate::registerCPPClass(const char* typeName, const char* parentT
19241924
if (shell) {
19251925
info->setShellSetInstanceWrapperCB(shell);
19261926
}
1927+
if (info->typeSlots() & PythonQt::Type_EnterExit) {
1928+
// context handlers must be handled specially, because optimized accesses
1929+
// cause that the __enter__ and __exit__ methods will not be found by
1930+
// conventional means:
1931+
PyObject* classObj = (PyObject*)info->pythonQtClassWrapper();
1932+
PyTypeObject* typeObj = (PyTypeObject*)classObj;
1933+
// __enter__ and _exit__ must be inserted directly into the tp_dict,
1934+
// otherwise they are not found by "with":
1935+
PyObject* tp_dict = typeObj->tp_dict;
1936+
PyObject* enterMethod = PyObject_GetAttrString(classObj, "__enter__");
1937+
if (enterMethod) {
1938+
PyDict_SetItemString(tp_dict, "__enter__", enterMethod);
1939+
Py_DECREF(enterMethod);
1940+
}
1941+
PyErr_Clear();
1942+
PyObject* exitMethod = PyObject_GetAttrString(classObj, "__exit__");
1943+
if (exitMethod) {
1944+
PyDict_SetItemString(tp_dict, "__exit__", exitMethod);
1945+
Py_DECREF(exitMethod);
1946+
}
1947+
PyErr_Clear();
1948+
// invalidate attribute cache for this type:
1949+
// (as the search for the class methods otherwise cached methods as "not existing", and
1950+
// we don't want to rely on the wrong empty entries being evicted from the cache by chance):
1951+
typeObj->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
1952+
}
19271953
}
19281954

19291955
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
@@ -790,6 +790,20 @@ meth_richcompare(PythonQtSlotFunctionObject *a, PythonQtSlotFunctionObject *b, i
790790
Py_RETURN_FALSE;
791791
}
792792

793+
static PyObject*
794+
meth_descr_get(PyObject *descr, PyObject *obj, PyObject* type)
795+
{
796+
if (PythonQtSlotFunction_Check(descr)) {
797+
PythonQtSlotFunctionObject *slotObj = (PythonQtSlotFunctionObject*)descr;
798+
return PythonQtSlotFunction_New(slotObj->m_ml, obj, NULL);
799+
}
800+
else {
801+
// wrong type
802+
Py_IncRef(descr);
803+
return descr;
804+
}
805+
}
806+
793807
PyTypeObject PythonQtSlotFunction_Type = {
794808
PyVarObject_HEAD_INIT(&PyType_Type, 0)
795809
"builtin_qt_slot",
@@ -827,6 +841,7 @@ PyTypeObject PythonQtSlotFunction_Type = {
827841
meth_getsets, /* tp_getset */
828842
0, /* tp_base */
829843
0, /* tp_dict */
844+
meth_descr_get, /* tp_descr_get */
830845
};
831846

832847
/* Clear out the free list */

0 commit comments

Comments
 (0)