1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
From 9e1f15f0b7e354daff8cb8ce9eba2b4f11d48c71 Mon Sep 17 00:00:00 2001
From: Stephen Macke <stephen.macke@databricks.com>
Date: Sat, 30 Aug 2025 21:35:56 -0700
Subject: [PATCH] deduperreload should patch NULL for empty closure rather than
None
---
.../deduperreload/deduperreload_patching.py | 34 +++++++++----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/IPython/extensions/deduperreload/deduperreload_patching.py b/IPython/extensions/deduperreload/deduperreload_patching.py
index a8b53e68e78..36ee103a8e4 100644
--- a/IPython/extensions/deduperreload/deduperreload_patching.py
+++ b/IPython/extensions/deduperreload/deduperreload_patching.py
@@ -4,6 +4,7 @@
from typing import Any
NOT_FOUND: object = object()
+NULL: object = object()
_MAX_FIELD_SEARCH_OFFSET = 50
if sys.maxsize > 2**32:
@@ -55,12 +56,17 @@ def try_write_readonly_attr(
if offset == -1:
return
obj_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(obj)).value
- new_value_addr = ctypes.c_void_p.from_buffer(ctypes.py_object(new_value)).value
+ if new_value is NULL:
+ new_value_addr: int | None = 0
+ else:
+ new_value_addr = ctypes.c_void_p.from_buffer(
+ ctypes.py_object(new_value)
+ ).value
if obj_addr is None or new_value_addr is None:
return
if prev_value is not None:
ctypes.pythonapi.Py_DecRef(ctypes.py_object(prev_value))
- if new_value is not None:
+ if new_value not in (None, NULL):
ctypes.pythonapi.Py_IncRef(ctypes.py_object(new_value))
ctypes.cast(
obj_addr + WORD_N_BYTES * offset, ctypes.POINTER(WORD_TYPE)
@@ -108,12 +114,10 @@ def try_patch_attr(
def patch_function(
cls, to_patch_to: Any, to_patch_from: Any, is_method: bool
) -> None:
- new_freevars = []
new_closure = []
for freevar, closure_val in zip(
to_patch_from.__code__.co_freevars or [], to_patch_from.__closure__ or []
):
- new_freevars.append(freevar)
if (
callable(closure_val.cell_contents)
and freevar in to_patch_to.__code__.co_freevars
@@ -125,23 +129,19 @@ def patch_function(
)
else:
new_closure.append(closure_val)
- code_with_new_freevars = to_patch_from.__code__.replace(
- co_freevars=tuple(new_freevars)
- )
# lambdas may complain if there is more than one freevar
- cls.try_patch_attr(
- to_patch_to, code_with_new_freevars, "__code__", new_is_value=True
- )
+ cls.try_patch_attr(to_patch_to, to_patch_from, "__code__")
offset = -1
if to_patch_to.__closure__ is None and to_patch_from.__closure__ is not None:
offset = cls.infer_field_offset(to_patch_from, "__closure__")
- cls.try_patch_readonly_attr(
- to_patch_to,
- tuple(new_closure) or None,
- "__closure__",
- new_is_value=True,
- offset=offset,
- )
+ if to_patch_to.__closure__ is not None or to_patch_from.__closure__ is not None:
+ cls.try_patch_readonly_attr(
+ to_patch_to,
+ tuple(new_closure) or NULL,
+ "__closure__",
+ new_is_value=True,
+ offset=offset,
+ )
for attr in ("__defaults__", "__kwdefaults__", "__doc__", "__dict__"):
cls.try_patch_attr(to_patch_to, to_patch_from, attr)
if is_method:
|