8000 Make weak references subclassable: · python/cpython@0a4dd39 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0a4dd39

Browse files
committed
Make weak references subclassable:
- weakref.ref and weakref.ReferenceType will become aliases for each other - weakref.ref will be a modern, new-style class with proper __new__ and __init__ methods - weakref.WeakValueDictionary will have a lighter memory footprint, using a new weakref.ref subclass to associate the key with the value, allowing us to have only a single object of overhead for each dictionary entry (currently, there are 3 objects of overhead per entry: a weakref to the value, a weakref to the dictionary, and a function object used as a weakref callback; the weakref to the dictionary could be avoided without this change) - a new macro, PyWeakref_CheckRefExact(), will be added - PyWeakref_CheckRef() will check for subclasses of weakref.ref This closes SF patch #983019.
1 parent 8139140 commit 0a4dd39

File tree

8 files changed

+338
-109
lines changed

8 files changed

+338
-109
lines changed

Doc/lib/libweakref.tex

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ \section{\module{weakref} ---
6868
information.
6969

7070

71-
\begin{funcdesc}{ref}{object\optional{, callback}}
71+
\begin{classdesc}{ref}{object\optional{, callback}}
7272
Return a weak reference to \var{object}. The original object can be
7373
retrieved by calling the reference object if the referent is still
7474
alive; if the referent is no longer alive, calling the reference
@@ -100,7 +100,11 @@ \section{\module{weakref} ---
100100
\var{callback}). If either referent has been deleted, the
101101
references are equal only if the reference objects are the same
102102
object.
103-
\end{funcdesc}
103+
104+
\versionchanged[This is now a subclassable type rather than a
105+
factory function; it derives from \class{object}]
106+
{2.4}
107+
\end{classdesc}
104108

105109
\begin{funcdesc}{proxy}{object\optional{, callback}}
106110
Return a proxy to \var{object} which uses a weak reference. This
@@ -236,6 +240,41 @@ \subsection{Weak Reference Objects
236240
idiom shown above is safe in threaded applications as well as
237241
single-threaded applications.
238242

243+
Specialized versions of \class{ref} objects can be created through
244+
subclassing. This is used in the implementation of the
245+
\class{WeakValueDictionary} to reduce the memory overhead for each
246+
entry in the mapping. This may be most useful to associate additional
247+
information with a reference, but could also be used to insert
248+
additional processing on calls to retrieve the referent.
249+
250+
This example shows how a subclass of \class{ref} can be used to store
251+
additional information about an object and affect the value that's
252+
returned when the referent is accessed:
253+
254+
\begin{verbatim}
255+
import weakref
256+
257+
class ExtendedRef(weakref.ref):
258+
def __new__(cls, ob, callback=None, **annotations):
259+
weakref.ref.__new__(cls, ob, callback)
260+
self.__counter = 0
261+
262+
def __init__(self, ob, callback=None, **annotations):
263+
super(ExtendedRef, self).__init__(ob, callback)
264+
for k, v in annotations:
265+
setattr(self, k, v)
266+
267+
def __call__(self):
268+
"""Return a pair containing the referent and the number of
269+
times the reference has been called.
270+
"""
271+
ob = super(ExtendedRef, self)()
272+
if ob is not None:
273+
self.__counter += 1
274+
ob = (ob, self.__counter)
275+
return ob
276+
\end{verbatim}
277+
239278

240279
\subsection{Example \label{weakref-example}}
241280

Include/weakrefobject.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@ PyAPI_DATA(PyTypeObject) _PyWeakref_RefType;
2222
PyAPI_DATA(PyTypeObject) _PyWeakref_ProxyType;
2323
PyAPI_DATA(PyTypeObject) _PyWeakref_CallableProxyType;
2424

25-
#define PyWeakref_CheckRef(op) \
25+
#define PyWeakref_CheckRef(op) PyObject_TypeCheck(op, &_PyWeakref_RefType)
26+
#define PyWeakref_CheckRefExact(op) \
2627
((op)->ob_type == &_PyWeakref_RefType)
2728
#define PyWeakref_CheckProxy(op) \
2829
(((op)->ob_type == &_PyWeakref_ProxyType) || \
2930
((op)->ob_type == &_PyWeakref_CallableProxyType))
31+
32+
/* This macro calls PyWeakref_CheckRef() last since that can involve a
33+
function call; this makes it more likely that the function call
34+
will be avoided. */
3035
#define PyWeakref_Check(op) \
3136
(PyWeakref_CheckRef(op) || PyWeakref_CheckProxy(op))
3237

Lib/test/test_weakref.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,72 @@ def callback(*args):
623623
finally:
624624
gc.set_threshold(*thresholds)
625625

626+
627+
class SubclassableWeakrefTestCase(unittest.TestCase):
628+
629+
def test_subclass_refs(self):
630+
class MyRef(weakref.ref):
631+
def __init__(self, ob, callback=None, value=42):
632+
self.value = value
633+
super(MyRef, self).__init__(ob, callback)
634+
def __call__(self):
635+
self.called = True
636+
return super(MyRef, self).__call__()
637+
o = Object("foo")< BEA4 /div>
638+
mr = MyRef(o, value=24)
639+
self.assert_(mr() is o)
640+
self.assert_(mr.called)
641+
self.assertEqual(mr.value, 24)
642+
del o
643+
self.assert_(mr() is None)
644+
self.assert_(mr.called)
645+
646+
def test_subclass_refs_dont_replace_standard_refs(self):
647+
class MyRef(weakref.ref):
648+
pass
649+
o = Object(42)
650+
r1 = MyRef(o)
651+
r2 = weakref.ref(o)
652+
self.assert_(r1 is not r2)
653+
self.assertEqual(weakref.getweakrefs(o), [r2, r1])
654+
self.assertEqual(weakref.getweakrefcount(o), 2)
655+
r3 = MyRef(o)
656+
self.assertEqual(weakref.getweakrefcount(o), 3)
657+
refs = weakref.getweakrefs(o)
658+
self.assertEqual(len(refs), 3)
659+
self.assert_(r2 is refs[0])
660+
self.assert_(r1 in refs[1:])
661+
self.assert_(r3 in refs[1:])
662+
663+
def test_subclass_refs_dont_conflate_callbacks(self):
664+
class MyRef(weakref.ref):
665+
pass
666+
o = Object(42)
667+
r1 = MyRef(o, id)
668+
r2 = MyRef(o, str)
669+
self.assert_(r1 is not r2)
670+
refs = weakref.getweakrefs(o)
671+
self.assert_(r1 in refs)
672+
self.assert_(r2 in refs)
673+
674+
def test_subclass_refs_with_slots(self):
675+
class MyRef(weakref.ref):
676+
__slots__ = "slot1", "slot2"
677+
def __new__(type, ob, callback, slot1, slot2):
678+
return weakref.ref.__new__(type, ob, callback)
679+
def __init__(self, ob, callback, slot1, slot2):
680+
self.slot1 = slot1
681+
self.slot2 = slot2
682+
def meth(self):
683+
return self.slot1 + self.slot2
684+
o = Object(42)
685+
r = MyRef(o, None, "abc", "def")
686+
self.assertEqual(r.slot1, "abc")
687+
self.assertEqual(r.slot2, "def")
688+
self.assertEqual(r.meth(), "abcdef")
689+
self.failIf(hasattr(r, "__dict__"))
690+
691+
626692
class Object:
627693
def __init__(self, arg):
628694
self.arg = arg

Lib/weakref.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ class WeakValueDictionary(UserDict.UserDict):
4242
# objects are unwrapped on the way out, and we always wrap on the
4343
# way in).
4444

45+
def __init__(self, *args, **kw):
46+
UserDict.UserDict.__init__(self, *args, **kw)
47+
def remove(wr, selfref=ref(self)):
48+
self = selfref()
49+
if self is not None:
50+
del self.data[wr.key]
51+
self._remove = remove
52+
4553
def __getitem__(self, key):
4654
o = self.data[key]()
4755
if o is None:
@@ -53,7 +61,7 @@ def __repr__(self):
5361
return "<WeakValueDictionary at %s>" % id(self)
5462

5563
def __setitem__(self, key, value):
56-
self.data[key] = ref(value, self.__makeremove(key))
64+
self.data[key] = KeyedRef(value, self._remove, key)
5765

5866
def copy(self):
5967
new = WeakValueDictionary()
@@ -117,7 +125,7 @@ def setdefault(self, key, default=None):
117125
try:
118126
wr = self.data[key]
119127
except KeyError:
120-
self.data[key] = ref(default, self.__makeremove(key))
128+
self.data[key] = KeyedRef(default, self._remove, key)
121129
return default
122130
else:
123131
return wr()
@@ -128,7 +136,7 @@ def update(self, dict=None, **kwargs):
128136
if not hasattr(dict, "items"):
129137
dict = type({})(dict)
130138
for key, o in dict.items():
131-
d[key] = ref(o, self.__makeremove(key))
139+
d[key] = KeyedRef(o, self._remove, key)
132140
if len(kwargs):
133141
self.update(kwargs)
134142

@@ -140,12 +148,26 @@ def values(self):
140148
L.append(o)
141149
return L
142150

143-
def __makeremove(self, key):
144-
def remove(o, selfref=ref(self), key=key):
145-
self = selfref()
146-
if self is not None:
147-
del self.data[key]
148-
return remove
151+
152+
class KeyedRef(ref):
153+
"""Specialized reference that includes a key corresponding to the value.
154+
155+
This is used in the WeakValueDictionary to avoid having to create
156+
a function object for each key stored in the mapping. A shared
157+
callback object can use the 'key' attribute of a KeyedRef instead
158+
of getting a reference to the key from an enclosing scope.
159+
160+
"""
161+
162+
__slots__ = "key",
163+
164+
def __new__(type, ob, callback, key):
165+
self = ref.__new__(type, ob, callback)
166+
self.key = key
167+
return self
168+
169+
def __init__(self, ob, callback, key):
170+
super(KeyedRef, self).__init__(ob, callback)
149171

150172

151173
class WeakKeyDictionary(UserDict.UserDict):
@@ -298,15 +320,11 @@ def next(self):
298320

299321
class WeakValuedItemIterator(BaseIter):
300322
def __init__(self, weakdict):
301-
self._next = weakdict.data.iteritems().next
323+
self._next = weakdict.data.itervalues().next
302324

303325
def next(self):
304326
while 1:
305-
key, wr = self._next()
327+
wr = self._next()
306328
value = wr()
307329
if value is not None:
308-
return key, value
309-
310-
311-
# no longer needed
312-
del UserDict
330+
return wr.key, value

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ What's New in Python 2.4 alpha 1?
1212
Core and builtins
1313
-----------------
1414

15+
- weakref.ref is now the type object also known as
16+
weakref.ReferenceType; it can be subclassed like any other new-style
17+
class. There's less per-entry overhead in WeakValueDictionary
18+
objects now (one object instead of three).
19+
1520
- Bug #951851: Python crashed when reading import table of certain
1621
Windows DLLs.
1722

Modules/_weakref.c

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,26 +57,6 @@ weakref_getweakrefs(PyObject *self, PyObject *object)
5757
}
5858

5959

60-
PyDoc_STRVAR(weakref_ref__doc__,
61-
"ref(object[, callback]) -- create a weak reference to 'object';\n"
62-
"when 'object' is finalized, 'callback' will be called and passed\n"
63-
"a reference to the weak reference object when 'object' is about\n"
64-
"to be finalized.");
65-
66-
static PyObject *
67-
weakref_ref(PyObject *self, PyObject *args)
68-
{
69-
PyObject *object;
70-
PyObject *callback = NULL;
71-
PyObject *result = NULL;
72-
73-
if (PyArg_UnpackTuple(args, "ref", 1, 2, &object, &callback)) {
74-
result = PyWeakref_NewRef(object, callback);
75-
}
76-
return result;
77-
}
78-
79-
8060
PyDoc_STRVAR(weakref_proxy__doc__,
8161
"proxy(object[, callback]) -- create a proxy object that weakly\n"
8262
"references 'object'. 'callback', if given, is called with a\n"
@@ -104,8 +84,6 @@ weakref_functions[] = {
10484
weakref_getweakrefs__doc__},
10585
{"proxy", weakref_proxy, METH_VARARGS,
10686
weakref_proxy__doc__},
107-
{"ref", weakref_ref, METH_VARARGS,
108-
weakref_ref__doc__},
10987
{NULL, NULL, 0, NULL}
11088
};
11189

@@ -119,6 +97,9 @@ init_weakref(void)
11997
"Weak-reference support module.");
12098
if (m != NULL) {
12199
Py_INCREF(&_PyWeakref_RefType);
100+
PyModule_AddObject(m, "ref",
101+
(PyObject *) &_PyWeakref_RefType);
102+
Py_INCREF(&_PyWeakref_RefType);
122103
PyModule_AddObject(m, "ReferenceType",
123104
(PyObject *) &_PyWeakref_RefType);
124105
Py_INCREF(&_PyWeakref_ProxyType);

Objects/object.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,9 @@ _Py_ReadyTypes(void)
18021802
if (PyType_Ready(&PyType_Type) < 0)
18031803
Py_FatalError("Can't initialize 'type'");
18041804

1805+
if (PyType_Ready(&_PyWeakref_RefType) < 0)
1806+
Py_FatalError("Can't initialize 'weakref'");
1807+
18051808
if (PyType_Ready(&PyBool_Type) < 0)
18061809
Py_FatalError("Can't initialize 'bool'");
18071810

0 commit comments

Comments
 (0)
0