summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/lib/libweakref.tex43
-rw-r--r--Include/weakrefobject.h7
-rw-r--r--Lib/test/test_weakref.py66
-rw-r--r--Lib/weakref.py50
-rw-r--r--Misc/NEWS5
-rw-r--r--Modules/_weakref.c25
-rw-r--r--Objects/object.c3
-rw-r--r--Objects/weakrefobject.c248
8 files changed, 338 insertions, 109 deletions
diff --git a/Doc/lib/libweakref.tex b/Doc/lib/libweakref.tex
index b432f61..c76684b 100644
--- a/Doc/lib/libweakref.tex
+++ b/Doc/lib/libweakref.tex
@@ -68,7 +68,7 @@ Extension types can easily be made to support weak references; see section
information.
-\begin{funcdesc}{ref}{object\optional{, callback}}
+\begin{classdesc}{ref}{object\optional{, callback}}
Return a weak reference to \var{object}. The original object can be
retrieved by calling the reference object if the referent is still
alive; if the referent is no longer alive, calling the reference
@@ -100,7 +100,11 @@ information.
\var{callback}). If either referent has been deleted, the
references are equal only if the reference objects are the same
object.
-\end{funcdesc}
+
+ \versionchanged[This is now a subclassable type rather than a
+ factory function; it derives from \class{object}]
+ {2.4}
+\end{classdesc}
\begin{funcdesc}{proxy}{object\optional{, callback}}
Return a proxy to \var{object} which uses a weak reference. This
@@ -236,6 +240,41 @@ become invalidated before the weak reference is called; the
idiom shown above is safe in threaded applications as well as
single-threaded applications.
+Specialized versions of \class{ref} objects can be created through
+subclassing. This is used in the implementation of the
+\class{WeakValueDictionary} to reduce the memory overhead for each
+entry in the mapping. This may be most useful to associate additional
+information with a reference, but could also be used to insert
+additional processing on calls to retrieve the referent.
+
+This example shows how a subclass of \class{ref} can be used to store
+additional information about an object and affect the value that's
+returned when the referent is accessed:
+
+\begin{verbatim}
+import weakref
+
+class ExtendedRef(weakref.ref):
+ def __new__(cls, ob, callback=None, **annotations):
+ weakref.ref.__new__(cls, ob, callback)
+ self.__counter = 0
+
+ def __init__(self, ob, callback=None, **annotations):
+ super(ExtendedRef, self).__init__(ob, callback)
+ for k, v in annotations:
+ setattr(self, k, v)
+
+ def __call__(self):
+ """Return a pair containing the referent and the number of
+ times the reference has been called.
+ """
+ ob = super(ExtendedRef, self)()
+ if ob is not None:
+ self.__counter += 1
+ ob = (ob, self.__counter)
+ return ob
+\end{verbatim}
+
\subsection{Example \label{weakref-example}}
diff --git a/Include/weakrefobject.h b/Include/weakrefobject.h
index effa0ed..3503892 100644
--- a/Include/weakrefobject.h
+++ b/Include/weakrefobject.h
@@ -22,11 +22,16 @@ PyAPI_DATA(PyTypeObject) _PyWeakref_RefType;
PyAPI_DATA(PyTypeObject) _PyWeakref_ProxyType;
PyAPI_DATA(PyTypeObject) _PyWeakref_CallableProxyType;
-#define PyWeakref_CheckRef(op) \
+#define PyWeakref_CheckRef(op) PyObject_TypeCheck(op, &_PyWeakref_RefType)
+#define PyWeakref_CheckRefExact(op) \
((op)->ob_type == &_PyWeakref_RefType)
#define PyWeakref_CheckProxy(op) \
(((op)->ob_type == &_PyWeakref_ProxyType) || \
((op)->ob_type == &_PyWeakref_CallableProxyType))
+
+/* This macro calls PyWeakref_CheckRef() last since that can involve a
+ function call; this makes it more likely that the function call
+ will be avoided. */
#define PyWeakref_Check(op) \
(PyWeakref_CheckRef(op) || PyWeakref_CheckProxy(op))
diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py
index 0a9e97d..31e2c7f 100644
--- a/Lib/test/test_weakref.py
+++ b/Lib/test/test_weakref.py
@@ -623,6 +623,72 @@ class ReferencesTestCase(TestBase):
finally:
gc.set_threshold(*thresholds)
+
+class SubclassableWeakrefTestCase(unittest.TestCase):
+
+ def test_subclass_refs(self):
+ class MyRef(weakref.ref):
+ def __init__(self, ob, callback=None, value=42):
+ self.value = value
+ super(MyRef, self).__init__(ob, callback)
+ def __call__(self):
+ self.called = True
+ return super(MyRef, self).__call__()
+ o = Object("foo")
+ mr = MyRef(o, value=24)
+ self.assert_(mr() is o)
+ self.assert_(mr.called)
+ self.assertEqual(mr.value, 24)
+ del o
+ self.assert_(mr() is None)
+ self.assert_(mr.called)
+
+ def test_subclass_refs_dont_replace_standard_refs(self):
+ class MyRef(weakref.ref):
+ pass
+ o = Object(42)
+ r1 = MyRef(o)
+ r2 = weakref.ref(o)
+ self.assert_(r1 is not r2)
+ self.assertEqual(weakref.getweakrefs(o), [r2, r1])
+ self.assertEqual(weakref.getweakrefcount(o), 2)
+ r3 = MyRef(o)
+ self.assertEqual(weakref.getweakrefcount(o), 3)
+ refs = weakref.getweakrefs(o)
+ self.assertEqual(len(refs), 3)
+ self.assert_(r2 is refs[0])
+ self.assert_(r1 in refs[1:])
+ self.assert_(r3 in refs[1:])
+
+ def test_subclass_refs_dont_conflate_callbacks(self):
+ class MyRef(weakref.ref):
+ pass
+ o = Object(42)
+ r1 = MyRef(o, id)
+ r2 = MyRef(o, str)
+ self.assert_(r1 is not r2)
+ refs = weakref.getweakrefs(o)
+ self.assert_(r1 in refs)
+ self.assert_(r2 in refs)
+
+ def test_subclass_refs_with_slots(self):
+ class MyRef(weakref.ref):
+ __slots__ = "slot1", "slot2"
+ def __new__(type, ob, callback, slot1, slot2):
+ return weakref.ref.__new__(type, ob, callback)
+ def __init__(self, ob, callback, slot1, slot2):
+ self.slot1 = slot1
+ self.slot2 = slot2
+ def meth(self):
+ return self.slot1 + self.slot2
+ o = Object(42)
+ r = MyRef(o, None, "abc", "def")
+ self.assertEqual(r.slot1, "abc")
+ self.assertEqual(r.slot2, "def")
+ self.assertEqual(r.meth(), "abcdef")
+ self.failIf(hasattr(r, "__dict__"))
+
+
class Object:
def __init__(self, arg):
self.arg = arg
diff --git a/Lib/weakref.py b/Lib/weakref.py
index 510cd7c..cfe9456 100644
--- a/Lib/weakref.py
+++ b/Lib/weakref.py
@@ -42,6 +42,14 @@ class WeakValueDictionary(UserDict.UserDict):
# objects are unwrapped on the way out, and we always wrap on the
# way in).
+ def __init__(self, *args, **kw):
+ UserDict.UserDict.__init__(self, *args, **kw)
+ def remove(wr, selfref=ref(self)):
+ self = selfref()
+ if self is not None:
+ del self.data[wr.key]
+ self._remove = remove
+
def __getitem__(self, key):
o = self.data[key]()
if o is None:
@@ -53,7 +61,7 @@ class WeakValueDictionary(UserDict.UserDict):
return "<WeakValueDictionary at %s>" % id(self)
def __setitem__(self, key, value):
- self.data[key] = ref(value, self.__makeremove(key))
+ self.data[key] = KeyedRef(value, self._remove, key)
def copy(self):
new = WeakValueDictionary()
@@ -117,7 +125,7 @@ class WeakValueDictionary(UserDict.UserDict):
try:
wr = self.data[key]
except KeyError:
- self.data[key] = ref(default, self.__makeremove(key))
+ self.data[key] = KeyedRef(default, self._remove, key)
return default
else:
return wr()
@@ -128,7 +136,7 @@ class WeakValueDictionary(UserDict.UserDict):
if not hasattr(dict, "items"):
dict = type({})(dict)
for key, o in dict.items():
- d[key] = ref(o, self.__makeremove(key))
+ d[key] = KeyedRef(o, self._remove, key)
if len(kwargs):
self.update(kwargs)
@@ -140,12 +148,26 @@ class WeakValueDictionary(UserDict.UserDict):
L.append(o)
return L
- def __makeremove(self, key):
- def remove(o, selfref=ref(self), key=key):
- self = selfref()
- if self is not None:
- del self.data[key]
- return remove
+
+class KeyedRef(ref):
+ """Specialized reference that includes a key corresponding to the value.
+
+ This is used in the WeakValueDictionary to avoid having to create
+ a function object for each key stored in the mapping. A shared
+ callback object can use the 'key' attribute of a KeyedRef instead
+ of getting a reference to the key from an enclosing scope.
+
+ """
+
+ __slots__ = "key",
+
+ def __new__(type, ob, callback, key):
+ self = ref.__new__(type, ob, callback)
+ self.key = key
+ return self
+
+ def __init__(self, ob, callback, key):
+ super(KeyedRef, self).__init__(ob, callback)
class WeakKeyDictionary(UserDict.UserDict):
@@ -298,15 +320,11 @@ class WeakValuedValueIterator(BaseIter):
class WeakValuedItemIterator(BaseIter):
def __init__(self, weakdict):
- self._next = weakdict.data.iteritems().next
+ self._next = weakdict.data.itervalues().next
def next(self):
while 1:
- key, wr = self._next()
+ wr = self._next()
value = wr()
if value is not None:
- return key, value
-
-
-# no longer needed
-del UserDict
+ return wr.key, value
diff --git a/Misc/NEWS b/Misc/NEWS
index 42c4e67..c30f2a9 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,11 @@ What's New in Python 2.4 alpha 1?
Core and builtins
-----------------
+- weakref.ref is now the type object also known as
+ weakref.ReferenceType; it can be subclassed like any other new-style
+ class. There's less per-entry overhead in WeakValueDictionary
+ objects now (one object instead of three).
+
- Bug #951851: Python crashed when reading import table of certain
Windows DLLs.
diff --git a/Modules/_weakref.c b/Modules/_weakref.c
index 2152115..2dfdc14 100644
--- a/Modules/_weakref.c
+++ b/Modules/_weakref.c
@@ -57,26 +57,6 @@ weakref_getweakrefs(PyObject *self, PyObject *object)
}
-PyDoc_STRVAR(weakref_ref__doc__,
-"ref(object[, callback]) -- create a weak reference to 'object';\n"
-"when 'object' is finalized, 'callback' will be called and passed\n"
-"a reference to the weak reference object when 'object' is about\n"
-"to be finalized.");
-
-static PyObject *
-weakref_ref(PyObject *self, PyObject *args)
-{
- PyObject *object;
- PyObject *callback = NULL;
- PyObject *result = NULL;
-
- if (PyArg_UnpackTuple(args, "ref", 1, 2, &object, &callback)) {
- result = PyWeakref_NewRef(object, callback);
- }
- return result;
-}
-
-
PyDoc_STRVAR(weakref_proxy__doc__,
"proxy(object[, callback]) -- create a proxy object that weakly\n"
"references 'object'. 'callback', if given, is called with a\n"
@@ -104,8 +84,6 @@ weakref_functions[] = {
weakref_getweakrefs__doc__},
{"proxy", weakref_proxy, METH_VARARGS,
weakref_proxy__doc__},
- {"ref", weakref_ref, METH_VARARGS,
- weakref_ref__doc__},
{NULL, NULL, 0, NULL}
};
@@ -119,6 +97,9 @@ init_weakref(void)
"Weak-reference support module.");
if (m != NULL) {
Py_INCREF(&_PyWeakref_RefType);
+ PyModule_AddObject(m, "ref",
+ (PyObject *) &_PyWeakref_RefType);
+ Py_INCREF(&_PyWeakref_RefType);
PyModule_AddObject(m, "ReferenceType",
(PyObject *) &_PyWeakref_RefType);
Py_INCREF(&_PyWeakref_ProxyType);
diff --git a/Objects/object.c b/Objects/object.c
index 1c0efdd..26149a7 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -1802,6 +1802,9 @@ _Py_ReadyTypes(void)
if (PyType_Ready(&PyType_Type) < 0)
Py_FatalError("Can't initialize 'type'");
+ if (PyType_Ready(&_PyWeakref_RefType) < 0)
+ Py_FatalError("Can't initialize 'weakref'");
+
if (PyType_Ready(&PyBool_Type) < 0)
Py_FatalError("Can't initialize 'bool'");
diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c
index 575a928..572c224 100644
--- a/Objects/weakrefobject.c
+++ b/Objects/weakrefobject.c
@@ -19,6 +19,15 @@ _PyWeakref_GetWeakrefCount(PyWeakReference *head)
}
+static void
+init_weakref(PyWeakReference *self, PyObject *ob, PyObject *callback)
+{
+ self->hash = -1;
+ self->wr_object = ob;
+ Py_XINCREF(callback);
+ self->wr_callback = callback;
+}
+
static PyWeakReference *
new_weakref(PyObject *ob, PyObject *callback)
{
@@ -26,10 +35,7 @@ new_weakref(PyObject *ob, PyObject *callback)
result = PyObject_GC_New(PyWeakReference, &_PyWeakref_RefType);
if (result) {
- result->hash = -1;
- result->wr_object = ob;
- Py_XINCREF(callback);
- result->wr_callback = callback;
+ init_weakref(result, ob, callback);
PyObject_GC_Track(result);
}
return result;
@@ -92,11 +98,11 @@ _PyWeakref_ClearRef(PyWeakReference *self)
}
static void
-weakref_dealloc(PyWeakReference *self)
+weakref_dealloc(PyObject *self)
{
- PyObject_GC_UnTrack((PyObject *)self);
- clear_weakref(self);
- PyObject_GC_Del(self);
+ PyObject_GC_UnTrack(self);
+ clear_weakref((PyWeakReference *) self);
+ self->ob_type->tp_free(self);
}
@@ -193,6 +199,134 @@ weakref_richcompare(PyWeakReference* self, PyWeakReference* other, int op)
PyWeakref_GET_OBJECT(other), op);
}
+/* Given the head of an object's list of weak references, extract the
+ * two callback-less refs (ref and proxy). Used to determine if the
+ * shared references exist and to determine the back link for newly
+ * inserted references.
+ */
+static void
+get_basic_refs(PyWeakReference *head,
+ PyWeakReference **refp, PyWeakReference **proxyp)
+{
+ *refp = NULL;
+ *proxyp = NULL;
+
+ if (head != NULL && head->wr_callback == NULL) {
+ /* We need to be careful that the "basic refs" aren't
+ subclasses of the main types. That complicates this a
+ little. */
+ if (PyWeakref_CheckRefExact(head)) {
+ *refp = head;
+ head = head->wr_next;
+ }
+ if (head != NULL
+ && head->wr_callback == NULL
+ && PyWeakref_CheckProxy(head)) {
+ *proxyp = head;
+ /* head = head->wr_next; */
+ }
+ }
+}
+
+/* Insert 'newref' in the list after 'prev'. Both must be non-NULL. */
+static void
+insert_after(PyWeakReference *newref, PyWeakReference *prev)
+{
+ newref->wr_prev = prev;
+ newref->wr_next = prev->wr_next;
+ if (prev->wr_next != NULL)
+ prev->wr_next->wr_prev = newref;
+ prev->wr_next = newref;
+}
+
+/* Insert 'newref' at the head of the list; 'list' points to the variable
+ * that stores the head.
+ */
+static void
+insert_head(PyWeakReference *newref, PyWeakReference **list)
+{
+ PyWeakReference *next = *list;
+
+ newref->wr_prev = NULL;
+ newref->wr_next = next;
+ if (next != NULL)
+ next->wr_prev = newref;
+ *list = newref;
+}
+
+static int
+parse_weakref_init_args(char *funcname, PyObject *args, PyObject *kwargs,
+ PyObject **obp, PyObject **callbackp)
+{
+ /* XXX Should check that kwargs == NULL or is empty. */
+ return PyArg_UnpackTuple(args, funcname, 1, 2, obp, callbackp);
+}
+
+static PyObject *
+weakref___new__(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ PyWeakReference *self = NULL;
+ PyObject *ob, *callback = NULL;
+
+ if (parse_weakref_init_args("__new__", args, kwargs, &ob, &callback)) {
+ PyWeakReference *ref, *proxy;
+ PyWeakReference **list;
+
+ if (!PyType_SUPPORTS_WEAKREFS(ob->ob_type)) {
+ PyErr_Format(PyExc_TypeError,
+ "cannot create weak reference to '%s' object",
+ ob->ob_type->tp_name);
+ return NULL;
+ }
+ if (callback == Py_None)
+ callback = NULL;
+ list = GET_WEAKREFS_LISTPTR(ob);
+ get_basic_refs(*list, &ref, &proxy);
+ if (callback == NULL && type == &_PyWeakref_RefType) {
+ if (ref != NULL) {
+ /* We can re-use an existing reference. */
+ Py_INCREF(ref);
+ return (PyObject *)ref;
+ }
+ }
+ /* We have to create a new reference. */
+ /* Note: the tp_alloc() can trigger cyclic GC, so the weakref
+ list on ob can be mutated. This means that the ref and
+ proxy pointers we got back earlier may have been collected,
+ so we need to compute these values again before we use
+ them. */
+ self = (PyWeakReference *) (type->tp_alloc(type, 0));
+ if (self != NULL) {
+ init_weakref(self, ob, callback);
+ if (callback == NULL && type == &_PyWeakref_RefType) {
+ insert_head(self, list);
+ }
+ else {
+ PyWeakReference *prev;
+
+ get_basic_refs(*list, &ref, &proxy);
+ prev = (proxy == NULL) ? ref : proxy;
+ if (prev == NULL)
+ insert_head(self, list);
+ else
+ insert_after(self, prev);
+ }
+ }
+ }
+ return (PyObject *)self;
+}
+
+static int
+weakref___init__(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ PyObject *tmp;
+
+ if (parse_weakref_init_args("__init__", args, kwargs, &tmp, &tmp))
+ return 0;
+ else
+ return 1;
+}
+
PyTypeObject
_PyWeakref_RefType = {
@@ -201,7 +335,7 @@ _PyWeakref_RefType = {
"weakref",
sizeof(PyWeakReference),
0,
- (destructor)weakref_dealloc,/*tp_dealloc*/
+ weakref_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
@@ -210,18 +344,33 @@ _PyWeakref_RefType = {
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
- (hashfunc)weakref_hash, /*tp_hash*/
+ (hashfunc)weakref_hash, /*tp_hash*/
(ternaryfunc)weakref_call, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_RICHCOMPARE,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_RICHCOMPARE
+ | Py_TPFLAGS_BASETYPE, /*tp_flags*/
0, /*tp_doc*/
(traverseproc)gc_traverse, /*tp_traverse*/
(inquiry)gc_clear, /*tp_clear*/
(richcmpfunc)weakref_richcompare, /*tp_richcompare*/
- 0, /*tp_weaklistoffset*/
+ 0, /*tp_weaklistoffset*/
+ 0, /*tp_iter*/
+ 0, /*tp_iternext*/
+ 0, /*tp_methods*/
+ 0, /*tp_members*/
+ 0, /*tp_getset*/
+ 0, /*tp_base*/
+ 0, /*tp_dict*/
+ 0, /*tp_descr_get*/
+ 0, /*tp_descr_set*/
+ 0, /*tp_dictoffset*/
+ (initproc)weakref___init__, /*tp_init*/
+ PyType_GenericAlloc, /*tp_alloc*/
+ weakref___new__, /*tp_new*/
+ PyObject_GC_Del, /*tp_free*/
};
@@ -363,6 +512,15 @@ proxy_nonzero(PyWeakReference *proxy)
return 1;
}
+static void
+proxy_dealloc(PyWeakReference *self)
+{
+ if (self->wr_callback != NULL)
+ PyObject_GC_UnTrack((PyObject *)self);
+ clear_weakref(self);
+ PyObject_GC_Del(self);
+}
+
/* sequence slots */
static PyObject *
@@ -496,7 +654,7 @@ _PyWeakref_ProxyType = {
sizeof(PyWeakReference),
0,
/* methods */
- (destructor)weakref_dealloc, /* tp_dealloc */
+ (destructor)proxy_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
@@ -531,7 +689,7 @@ _PyWeakref_CallableProxyType = {
sizeof(PyWeakReference),
0,
/* methods */
- (destructor)weakref_dealloc, /* tp_dealloc */
+ (destructor)proxy_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
@@ -558,56 +716,6 @@ _PyWeakref_CallableProxyType = {
};
-/* Given the head of an object's list of weak references, extract the
- * two callback-less refs (ref and proxy). Used to determine if the
- * shared references exist and to determine the back link for newly
- * inserted references.
- */
-static void
-get_basic_refs(PyWeakReference *head,
- PyWeakReference **refp, PyWeakReference **proxyp)
-{
- *refp = NULL;
- *proxyp = NULL;
-
- if (head != NULL && head->wr_callback == NULL) {
- if (head->ob_type == &_PyWeakref_RefType) {
- *refp = head;
- head = head->wr_next;
- }
- if (head != NULL && head->wr_callback == NULL) {
- *proxyp = head;
- head = head->wr_next;
- }
- }
-}
-
-/* Insert 'newref' in the list after 'prev'. Both must be non-NULL. */
-static void
-insert_after(PyWeakReference *newref, PyWeakReference *prev)
-{
- newref->wr_prev = prev;
- newref->wr_next = prev->wr_next;
- if (prev->wr_next != NULL)
- prev->wr_next->wr_prev = newref;
- prev->wr_next = newref;
-}
-
-/* Insert 'newref' at the head of the list; 'list' points to the variable
- * that stores the head.
- */
-static void
-insert_head(PyWeakReference *newref, PyWeakReference **list)
-{
- PyWeakReference *next = *list;
-
- newref->wr_prev = NULL;
- newref->wr_next = next;
- if (next != NULL)
- next->wr_prev = newref;
- *list = newref;
-}
-
PyObject *
PyWeakref_NewRef(PyObject *ob, PyObject *callback)
@@ -769,8 +877,10 @@ PyObject_ClearWeakRefs(PyObject *object)
current->wr_callback = NULL;
clear_weakref(current);
- handle_callback(current, callback);
- Py_DECREF(callback);
+ if (callback != NULL) {
+ handle_callback(current, callback);
+ Py_DECREF(callback);
+ }
}
else {
PyObject *tuple = PyTuple_New(count * 2);
@@ -787,10 +897,12 @@ PyObject_ClearWeakRefs(PyObject *object)
current = next;
}
for (i = 0; i < count; ++i) {
- PyObject *current = PyTuple_GET_ITEM(tuple, i * 2);
PyObject *callback = PyTuple_GET_ITEM(tuple, i * 2 + 1);
- handle_callback((PyWeakReference *)current, callback);
+ if (callback != NULL) {
+ PyObject *current = PyTuple_GET_ITEM(tuple, i * 2);
+ handle_callback((PyWeakReference *)current, callback);
+ }
}
Py_DECREF(tuple);
}