Skip to content

Commit 635baad

Browse files
author
rhettinger
committed
Make functional.partial() more closely match the spec by emulating
some useful features of regular functions: * Made weak referencable. * Allow attribute access so a user can set __name__, __doc__, etc. git-svn-id: http://svn.python.org/projects/python/trunk@38587 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent 664b633 commit 635baad

2 files changed

Lines changed: 72 additions & 5 deletions

File tree

Lib/test/test_functional.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import functional
22
import unittest
33
from test import test_support
4+
from weakref import proxy
45

56
@staticmethod
67
def PythonPartial(func, *args, **keywords):
@@ -116,6 +117,22 @@ def f(x, y):
116117
self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0)
117118
self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1)
118119

120+
def test_attributes(self):
121+
p = self.thetype(hex)
122+
try:
123+
del p.__dict__
124+
except TypeError:
125+
pass
126+
else:
127+
self.fail('partial object allowed __dict__ to be deleted')
128+
129+
def test_weakref(self):
130+
f = self.thetype(int, base=16)
131+
p = proxy(f)
132+
self.assertEqual(f.func, p.func)
133+
f = None
134+
self.assertRaises(ReferenceError, getattr, p, 'func')
135+
119136

120137
class PartialSubclass(functional.partial):
121138
pass

Modules/functionalmodule.c

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ typedef struct {
1616
PyObject *fn;
1717
PyObject *args;
1818
PyObject *kw;
19+
PyObject *dict;
20+
PyObject *weakreflist; /* List of weak references */
1921
} partialobject;
2022

2123
static PyTypeObject partial_type;
@@ -63,16 +65,22 @@ partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
6365
Py_INCREF(Py_None);
6466
}
6567

68+
pto->weakreflist = NULL;
69+
pto->dict = NULL;
70+
6671
return (PyObject *)pto;
6772
}
6873

6974
static void
7075
partial_dealloc(partialobject *pto)
7176
{
7277
PyObject_GC_UnTrack(pto);
78+
if (pto->weakreflist != NULL)
79+
PyObject_ClearWeakRefs((PyObject *) pto);
7380
Py_XDECREF(pto->fn);
7481
Py_XDECREF(pto->args);
7582
Py_XDECREF(pto->kw);
83+
Py_XDECREF(pto->dict);
7684
pto->ob_type->tp_free(pto);
7785
}
7886

@@ -128,6 +136,7 @@ partial_traverse(partialobject *pto, visitproc visit, void *arg)
128136
Py_VISIT(pto->fn);
129137
Py_VISIT(pto->args);
130138
Py_VISIT(pto->kw);
139+
Py_VISIT(pto->dict);
131140
return 0;
132141
}
133142

@@ -146,6 +155,47 @@ static PyMemberDef partial_memberlist[] = {
146155
{NULL} /* Sentinel */
147156
};
148157

158+
static PyObject *
159+
partial_get_dict(partialobject *pto)
160+
{
161+
if (pto->dict == NULL) {
162+
pto->dict = PyDict_New();
163+
if (pto->dict == NULL)
164+
return NULL;
165+
}
166+
Py_INCREF(pto->dict);
167+
return pto->dict;
168+
}
169+
170+
static int
171+
partial_set_dict(partialobject *pto, PyObject *value)
172+
{
173+
PyObject *tmp;
174+
175+
/* It is illegal to del p.__dict__ */
176+
if (value == NULL) {
177+
PyErr_SetString(PyExc_TypeError,
178+
"a partial object's dictionary may not be deleted");
179+
return -1;
180+
}
181+
/* Can only set __dict__ to a dictionary */
182+
if (!PyDict_Check(value)) {
183+
PyErr_SetString(PyExc_TypeError,
184+
"setting partial object's dictionary to a non-dict");
185+
return -1;
186+
}
187+
tmp = pto->dict;
188+
Py_INCREF(value);
189+
pto->dict = value;
190+
Py_XDECREF(tmp);
191+
return 0;
192+
}
193+
194+
static PyGetSetDef partail_getsetlist[] = {
195+
{"__dict__", (getter)partial_get_dict, (setter)partial_set_dict},
196+
{NULL} /* Sentinel */
197+
};
198+
149199
static PyTypeObject partial_type = {
150200
PyObject_HEAD_INIT(NULL)
151201
0, /* ob_size */
@@ -166,25 +216,25 @@ static PyTypeObject partial_type = {
166216
(ternaryfunc)partial_call, /* tp_call */
167217
0, /* tp_str */
168218
PyObject_GenericGetAttr, /* tp_getattro */
169-
0, /* tp_setattro */
219+
PyObject_GenericSetAttr, /* tp_setattro */
170220
0, /* tp_as_buffer */
171221
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
172-
Py_TPFLAGS_BASETYPE, /* tp_flags */
222+
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_WEAKREFS, /* tp_flags */
173223
partial_doc, /* tp_doc */
174224
(traverseproc)partial_traverse, /* tp_traverse */
175225
0, /* tp_clear */
176226
0, /* tp_richcompare */
177-
0, /* tp_weaklistoffset */
227+
offsetof(partialobject, weakreflist), /* tp_weaklistoffset */
178228
0, /* tp_iter */
179229
0, /* tp_iternext */
180230
0, /* tp_methods */
181231
partial_memberlist, /* tp_members */
182-
0, /* tp_getset */
232+
partail_getsetlist, /* tp_getset */
183233
0, /* tp_base */
184234
0, /* tp_dict */
185235
0, /* tp_descr_get */
186236
0, /* tp_descr_set */
187-
0, /* tp_dictoffset */
237+
offsetof(partialobject, dict), /* tp_dictoffset */
188238
0, /* tp_init */
189239
0, /* tp_alloc */
190240
partial_new, /* tp_new */

0 commit comments

Comments
 (0)