From 0664231cc0b393740a37b0dcbc92fb2b631628d5 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:51:20 +0500 Subject: [PATCH 1/8] Add Py_UNREACHABLE --- Modules/_testexternalinspection.c | 2 +- Python/remote_debug.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index b43e8b2155730f..0853259256685c 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -65,7 +65,7 @@ _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle) address = search_map_for_section(handle, "AsyncioDebug", "_asyncio.cpython"); } #else - address = 0; + Py_UNREACHABLE(); #endif return address; diff --git a/Python/remote_debug.h b/Python/remote_debug.h index cb1baf799052d5..3af0f7c9a93f25 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -699,7 +699,7 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_map_for_section(handle, "PyRuntime", "python"); } #else - address = 0; + Py_UNREACHABLE(); #endif return address; From cd9a499104a039b54e37de76cd5fce0715b4b27e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:54:10 +0500 Subject: [PATCH 2/8] Remove unused refcnt --- Modules/_testexternalinspection.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index 0853259256685c..7b16de27a9070e 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -482,9 +482,6 @@ parse_task( return -1; } - uintptr_t refcnt; - read_ptr(handle, task_address + sizeof(Py_ssize_t), &refcnt); - PyObject* result = PyList_New(0); if (result == NULL) { return -1; From 3ed2837897f02e23cb0fb8f1ed34d2d61bc501ba Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:55:24 +0500 Subject: [PATCH 3/8] Reset result if can not close file descriptor --- Python/remote_debug.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 3af0f7c9a93f25..321d4060a63587 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -342,6 +342,7 @@ search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_ munmap(map, fs.st_size); if (close(fd) != 0) { PyErr_SetFromErrno(PyExc_OSError); + result = 0; } return result; } @@ -495,6 +496,7 @@ search_elf_file_for_section( } if (fd >= 0 && close(fd) != 0) { PyErr_SetFromErrno(PyExc_OSError); + result = 0; } return result; } @@ -570,7 +572,10 @@ search_linux_map_for_section(proc_handle_t *handle, const char* secname, const c } PyMem_Free(line); - fclose(maps_file); + if (fclose(maps_file) != 0) { + PyErr_SetFromErrno(PyExc_OSError); + retval = 0; + } return retval; } From cc46cc28d9d20ae5180e3d8e69583a1afe5e6dc0 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:55:36 +0500 Subject: [PATCH 4/8] Set exception only if not set --- Python/remote_debug.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 321d4060a63587..12926a94757548 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -372,7 +372,9 @@ search_map_for_section(proc_handle_t *handle, const char* secname, const char* s mach_port_t proc_ref = pid_to_task(handle->pid); if (proc_ref == 0) { - PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID"); + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID"); + } return 0; } From 6a72bf9c24eca95ec44f41b816fe756734af7cf2 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 00:56:10 +0500 Subject: [PATCH 5/8] Chain exception to get more info --- Modules/_testexternalinspection.c | 66 +++++++++++++++++++------------ Python/remote_debug.h | 9 +++++ 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index 7b16de27a9070e..e8350d6473bcb3 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -23,6 +23,18 @@ # define HAVE_PROCESS_VM_READV 0 #endif +#ifdef CHAIN_EXCEPTIONS +#error "CHAIN_EXCEPTIONS should not be defined" +#endif + +#define CHAIN_EXCEPTIONS(Type, Msg) \ + do { \ + PyObject *exc = PyErr_GetRaisedException(); \ + PyErr_SetString((Type), (Msg)); \ + _PyErr_ChainExceptions1(exc); \ + } while(0) + + struct _Py_AsyncioModuleDebugOffsets { struct _asyncio_task_object { uint64_t size; @@ -304,7 +316,7 @@ parse_task_name( if ((flags & Py_TPFLAGS_LONG_SUBCLASS)) { long res = read_py_long(handle, offsets, task_name_addr); if (res == -1) { - PyErr_SetString(PyExc_RuntimeError, "Failed to get task name"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to get task name"); return NULL; } return PyUnicode_FromFormat("Task-%d", res); @@ -1156,30 +1168,32 @@ get_all_awaited_by(PyObject* self, PyObject* args) return 0; } + PyObject *result = NULL; + uintptr_t runtime_start_addr = _Py_RemoteDebug_GetPyRuntimeAddress(handle); if (runtime_start_addr == 0) { if (!PyErr_Occurred()) { PyErr_SetString( PyExc_RuntimeError, "Failed to get .PyRuntime address"); } - return NULL; + goto result_err; } struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_addr, &local_debug_offsets)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + goto result_err; } - PyObject *result = PyList_New(0); + result = PyList_New(0); if (result == NULL) { - return NULL; + goto result_err; } uint64_t interpreter_state_list_head = @@ -1256,7 +1270,7 @@ get_all_awaited_by(PyObject* self, PyObject* args) return result; result_err: - Py_DECREF(result); + Py_XDECREF(result); _Py_RemoteDebug_CleanupProcHandle(handle); return NULL; } @@ -1296,7 +1310,7 @@ get_stack_trace(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read debug offsets"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } @@ -1354,40 +1368,40 @@ get_async_stack_trace(PyObject* self, PyObject* args) return 0; } + PyObject *result = NULL; + uintptr_t runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle); if (runtime_start_address == 0) { if (!PyErr_Occurred()) { PyErr_SetString( PyExc_RuntimeError, "Failed to get .PyRuntime address"); } - return NULL; + goto result_err; } struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); - return NULL; + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + goto result_err; } - PyObject* result = PyList_New(1); + result = PyList_New(1); if (result == NULL) { - return NULL; + goto result_err; } PyObject* calls = PyList_New(0); if (calls == NULL) { - Py_DECREF(result); - return NULL; + goto result_err; } if (PyList_SetItem(result, 0, calls)) { /* steals ref to 'calls' */ - Py_DECREF(result); Py_DECREF(calls); - return NULL; + goto result_err; } uintptr_t running_task_addr = (uintptr_t)NULL; @@ -1395,7 +1409,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &local_async_debug, &running_task_addr) ) { - PyErr_SetString(PyExc_RuntimeError, "Failed to find running task"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running task"); goto result_err; } @@ -1410,7 +1424,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) running_task_addr + local_async_debug.asyncio_task_object.task_coro, &running_coro_addr )) { - PyErr_SetString(PyExc_RuntimeError, "Failed to read running task coro"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read running task coro"); goto result_err; } @@ -1440,7 +1454,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &address_of_current_frame) ) { - PyErr_SetString(PyExc_RuntimeError, "Failed to find running frame"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running frame"); goto result_err; } @@ -1456,7 +1470,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) ); if (res < 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to parse async frame object"); + CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to parse async frame object"); goto result_err; } @@ -1498,7 +1512,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) result_err: _Py_RemoteDebug_CleanupProcHandle(handle); - Py_DECREF(result); + Py_XDECREF(result); return NULL; } diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 12926a94757548..7627d65cea59c6 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -688,14 +688,18 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_windows_map_for_section(handle, "PyRuntime", L"python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL + PyObject *exc = PyErr_GetRaisedException(); PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__linux__) // On Linux, search for 'python' in executable or DLL address = search_linux_map_for_section(handle, "PyRuntime", "python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL + PyObject *exc = PyErr_GetRaisedException(); PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__APPLE__) && TARGET_OS_OSX // On macOS, try libpython first, then fall back to python @@ -704,6 +708,11 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) // TODO: Differentiate between not found and error PyErr_Clear(); address = search_map_for_section(handle, "PyRuntime", "python"); + if (address == 0) { + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); + } } #else Py_UNREACHABLE(); From 6466cf9f61fff26ed2af6b01b741a53e5d40ce6e Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 01:11:34 +0500 Subject: [PATCH 6/8] Remove error string from macos impl to keep PermissionError --- Python/remote_debug.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 7627d65cea59c6..edc77c302916ca 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -708,11 +708,6 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) // TODO: Differentiate between not found and error PyErr_Clear(); address = search_map_for_section(handle, "PyRuntime", "python"); - if (address == 0) { - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); - _PyErr_ChainExceptions1(exc); - } } #else Py_UNREACHABLE(); From e7183302c4b95f4221bd08c9d91b31a0d24eea12 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 01:16:34 +0500 Subject: [PATCH 7/8] Replace macro with static function --- Modules/_testexternalinspection.c | 32 ++++++++++--------------------- Python/remote_debug.h | 17 ++++++++++------ 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index e8350d6473bcb3..1ecbad56594336 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -23,18 +23,6 @@ # define HAVE_PROCESS_VM_READV 0 #endif -#ifdef CHAIN_EXCEPTIONS -#error "CHAIN_EXCEPTIONS should not be defined" -#endif - -#define CHAIN_EXCEPTIONS(Type, Msg) \ - do { \ - PyObject *exc = PyErr_GetRaisedException(); \ - PyErr_SetString((Type), (Msg)); \ - _PyErr_ChainExceptions1(exc); \ - } while(0) - - struct _Py_AsyncioModuleDebugOffsets { struct _asyncio_task_object { uint64_t size; @@ -316,7 +304,7 @@ parse_task_name( if ((flags & Py_TPFLAGS_LONG_SUBCLASS)) { long res = read_py_long(handle, offsets, task_name_addr); if (res == -1) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to get task name"); + chain_exceptions(PyExc_RuntimeError, "Failed to get task name"); return NULL; } return PyUnicode_FromFormat("Task-%d", res); @@ -1181,13 +1169,13 @@ get_all_awaited_by(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_addr, &local_debug_offsets)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); goto result_err; } @@ -1310,7 +1298,7 @@ get_stack_trace(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } @@ -1381,13 +1369,13 @@ get_async_stack_trace(PyObject* self, PyObject* args) struct _Py_DebugOffsets local_debug_offsets; if (_Py_RemoteDebug_ReadDebugOffsets(handle, &runtime_start_address, &local_debug_offsets)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read debug offsets"); goto result_err; } struct _Py_AsyncioModuleDebugOffsets local_async_debug; if (read_async_debug(handle, &local_async_debug)) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); + chain_exceptions(PyExc_RuntimeError, "Failed to read asyncio debug offsets"); goto result_err; } @@ -1409,7 +1397,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &local_async_debug, &running_task_addr) ) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running task"); + chain_exceptions(PyExc_RuntimeError, "Failed to find running task"); goto result_err; } @@ -1424,7 +1412,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) running_task_addr + local_async_debug.asyncio_task_object.task_coro, &running_coro_addr )) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to read running task coro"); + chain_exceptions(PyExc_RuntimeError, "Failed to read running task coro"); goto result_err; } @@ -1454,7 +1442,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) handle, runtime_start_address, &local_debug_offsets, &address_of_current_frame) ) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to find running frame"); + chain_exceptions(PyExc_RuntimeError, "Failed to find running frame"); goto result_err; } @@ -1470,7 +1458,7 @@ get_async_stack_trace(PyObject* self, PyObject* args) ); if (res < 0) { - CHAIN_EXCEPTIONS(PyExc_RuntimeError, "Failed to parse async frame object"); + chain_exceptions(PyExc_RuntimeError, "Failed to parse async frame object"); goto result_err; } diff --git a/Python/remote_debug.h b/Python/remote_debug.h index edc77c302916ca..2072f6e03102a8 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -109,6 +109,15 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) { handle->pid = 0; } +// Helper to chain exceptions and avoid repetitions +static void +chain_exceptions(PyObject *exception, const char *string) +{ + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(exception, string); + _PyErr_ChainExceptions1(exc); +} + #if defined(__APPLE__) && TARGET_OS_OSX static uintptr_t @@ -688,18 +697,14 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_windows_map_for_section(handle, "PyRuntime", L"python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); - _PyErr_ChainExceptions1(exc); + chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); } #elif defined(__linux__) // On Linux, search for 'python' in executable or DLL address = search_linux_map_for_section(handle, "PyRuntime", "python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); - _PyErr_ChainExceptions1(exc); + chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); } #elif defined(__APPLE__) && TARGET_OS_OSX // On macOS, try libpython first, then fall back to python From 40e099b080700eb049349e5fb64634055177877a Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Sat, 26 Apr 2025 01:33:58 +0500 Subject: [PATCH 8/8] Remove chain_exceptions from remote_debug to make clang on macos happy - I believe it is not worth to add conditional compilation of such small function like chain_exceptions --- Modules/_testexternalinspection.c | 9 +++++++++ Python/remote_debug.h | 17 ++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Modules/_testexternalinspection.c b/Modules/_testexternalinspection.c index 1ecbad56594336..b65c5821443ebf 100644 --- a/Modules/_testexternalinspection.c +++ b/Modules/_testexternalinspection.c @@ -45,6 +45,15 @@ struct _Py_AsyncioModuleDebugOffsets { } asyncio_thread_state; }; +// Helper to chain exceptions and avoid repetitions +static void +chain_exceptions(PyObject *exception, const char *string) +{ + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(exception, string); + _PyErr_ChainExceptions1(exc); +} + // Get the PyAsyncioDebug section address for any platform static uintptr_t _Py_RemoteDebug_GetAsyncioDebugAddress(proc_handle_t* handle) diff --git a/Python/remote_debug.h b/Python/remote_debug.h index 2072f6e03102a8..edc77c302916ca 100644 --- a/Python/remote_debug.h +++ b/Python/remote_debug.h @@ -109,15 +109,6 @@ _Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) { handle->pid = 0; } -// Helper to chain exceptions and avoid repetitions -static void -chain_exceptions(PyObject *exception, const char *string) -{ - PyObject *exc = PyErr_GetRaisedException(); - PyErr_SetString(exception, string); - _PyErr_ChainExceptions1(exc); -} - #if defined(__APPLE__) && TARGET_OS_OSX static uintptr_t @@ -697,14 +688,18 @@ _Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle) address = search_windows_map_for_section(handle, "PyRuntime", L"python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__linux__) // On Linux, search for 'python' in executable or DLL address = search_linux_map_for_section(handle, "PyRuntime", "python"); if (address == 0) { // Error out: 'python' substring covers both executable and DLL - chain_exceptions(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + PyObject *exc = PyErr_GetRaisedException(); + PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process."); + _PyErr_ChainExceptions1(exc); } #elif defined(__APPLE__) && TARGET_OS_OSX // On macOS, try libpython first, then fall back to python