]> granicus.if.org Git - python/commitdiff
#1506171: added operator.methodcaller().
authorGeorg Brandl <georg@python.org>
Sat, 23 Feb 2008 23:04:35 +0000 (23:04 +0000)
committerGeorg Brandl <georg@python.org>
Sat, 23 Feb 2008 23:04:35 +0000 (23:04 +0000)
Doc/library/operator.rst
Lib/test/test_operator.py
Misc/NEWS
Modules/operator.c

index 5527dc2c8fd0cfffd03c261f22980846b16c5fb3..4ab3fb1810f5f98907d07fbac8f91ea1f6d19d9e 100644 (file)
@@ -538,6 +538,17 @@ Examples::
    [('orange', 1), ('banana', 2), ('apple', 3), ('pear', 5)]
 
 
+.. function:: methodcaller(name[, args...])
+
+   Return a callable object that calls the method *name* on its operand.  If
+   additional arguments and/or keyword arguments are given, they will be given
+   to the method as well.  After ``f = methodcaller('name')``, the call ``f(b)``
+   returns ``b.name()``.  After ``f = methodcaller('name', 'foo', bar=1)``, the
+   call ``f(b)`` returns ``b.name('foo', bar=1)``.
+
+   .. versionadded:: 2.6
+
+
 .. _operator-map:
 
 Mapping Operators to Functions
index 3f3ea0033e5582fa5f599fb92ae4a03790863622..1c3fda326fa7a8bbf106e08c1dd0e76e7973803d 100644 (file)
@@ -440,6 +440,24 @@ class OperatorTestCase(unittest.TestCase):
         self.assertEqual(operator.itemgetter(2,10,5)(data), ('2', '10', '5'))
         self.assertRaises(TypeError, operator.itemgetter(2, 'x', 5), data)
 
+    def test_methodcaller(self):
+        self.assertRaises(TypeError, operator.methodcaller)
+        class A:
+            def foo(self, *args, **kwds):
+                return args[0] + args[1]
+            def bar(self, f=42):
+                return f
+        a = A()
+        f = operator.methodcaller('foo')
+        self.assertRaises(IndexError, f, a)
+        f = operator.methodcaller('foo', 1, 2)
+        self.assertEquals(f(a), 3)
+        f = operator.methodcaller('bar')
+        self.assertEquals(f(a), 42)
+        self.assertRaises(TypeError, f, a, a)
+        f = operator.methodcaller('bar', f=5)
+        self.assertEquals(f(a), 5)
+
     def test_inplace(self):
         class C(object):
             def __iadd__     (self, other): return "iadd"
index 9146bcb8e48df8944857c6c77c5556abe5c92f5c..cac693286dc1a861c1ce62f0f14f0e44187cfc64 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -1196,6 +1196,8 @@ Library
 Extension Modules
 -----------------
 
+- Patch #1506171: added operator.methodcaller().
+
 - Patch #1826: operator.attrgetter() now supports dotted attribute paths.
 
 - Patch #1957: syslogmodule: Release GIL when calling syslog(3)
index 4d4a9401b62f5c5056b41e27b192ce53c674efca..fd98efdbe7d64044989f89f8919b4e377cd1822d 100644 (file)
@@ -620,6 +620,139 @@ static PyTypeObject attrgetter_type = {
        attrgetter_new,                 /* tp_new */
        0,                              /* tp_free */
 };
+
+
+/* methodcaller object **********************************************************/
+
+typedef struct {
+       PyObject_HEAD
+       PyObject *name;
+       PyObject *args;
+       PyObject *kwds;
+} methodcallerobject;
+
+static PyTypeObject methodcaller_type;
+
+static PyObject *
+methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+       methodcallerobject *mc;
+       PyObject *name, *newargs;
+
+       if (PyTuple_GET_SIZE(args) < 1) {
+               PyErr_SetString(PyExc_TypeError, "methodcaller needs at least "
+                               "one argument, the method name");
+               return NULL;
+       }
+
+       /* create methodcallerobject structure */
+       mc = PyObject_GC_New(methodcallerobject, &methodcaller_type);
+       if (mc == NULL) 
+               return NULL;    
+
+       newargs = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
+       if (newargs == NULL) {
+               Py_DECREF(mc);
+               return NULL;
+       }
+       mc->args = newargs;
+
+       name = PyTuple_GET_ITEM(args, 0);
+       Py_INCREF(name);
+       mc->name = name;
+
+       Py_XINCREF(kwds);
+       mc->kwds = kwds;
+
+       PyObject_GC_Track(mc);
+       return (PyObject *)mc;
+}
+
+static void
+methodcaller_dealloc(methodcallerobject *mc)
+{
+       PyObject_GC_UnTrack(mc);
+       Py_XDECREF(mc->name);
+       Py_XDECREF(mc->args);
+       Py_XDECREF(mc->kwds);
+       PyObject_GC_Del(mc);
+}
+
+static int
+methodcaller_traverse(methodcallerobject *mc, visitproc visit, void *arg)
+{
+       Py_VISIT(mc->args);
+       Py_VISIT(mc->kwds);
+       return 0;
+}
+
+static PyObject *
+methodcaller_call(methodcallerobject *mc, PyObject *args, PyObject *kw)
+{
+       PyObject *method, *obj, *result;
+
+       if (!PyArg_UnpackTuple(args, "methodcaller", 1, 1, &obj))
+               return NULL;
+       method = PyObject_GetAttr(obj, mc->name);
+       if (method == NULL)
+               return NULL;
+       result = PyObject_Call(method, mc->args, mc->kwds);
+       Py_DECREF(method);
+       return result;
+}
+
+PyDoc_STRVAR(methodcaller_doc,
+"methodcaller(name, ...) --> methodcaller object\n\
+\n\
+Return a callable object that calls the given method on its operand.\n\
+After, f = methodcaller('name'), the call f(r) returns r.name().\n\
+After, g = methodcaller('name', 'date', foo=1), the call g(r) returns\n\
+r.name('date', foo=1).");
+
+static PyTypeObject methodcaller_type = {
+       PyVarObject_HEAD_INIT(NULL, 0)
+       "operator.methodcaller",        /* tp_name */
+       sizeof(methodcallerobject),     /* tp_basicsize */
+       0,                              /* tp_itemsize */
+       /* methods */
+       (destructor)methodcaller_dealloc, /* tp_dealloc */
+       0,                              /* tp_print */
+       0,                              /* tp_getattr */
+       0,                              /* tp_setattr */
+       0,                              /* tp_compare */
+       0,                              /* tp_repr */
+       0,                              /* tp_as_number */
+       0,                              /* tp_as_sequence */
+       0,                              /* tp_as_mapping */
+       0,                              /* tp_hash */
+       (ternaryfunc)methodcaller_call, /* tp_call */
+       0,                              /* tp_str */
+       PyObject_GenericGetAttr,        /* tp_getattro */
+       0,                              /* tp_setattro */
+       0,                              /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */
+       methodcaller_doc,                       /* tp_doc */
+       (traverseproc)methodcaller_traverse,    /* tp_traverse */
+       0,                              /* tp_clear */
+       0,                              /* tp_richcompare */
+       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 */
+       0,                              /* tp_init */
+       0,                              /* tp_alloc */
+       methodcaller_new,               /* tp_new */
+       0,                              /* tp_free */
+};
+
+
 /* Initialization function for the module (*must* be called initoperator) */
 
 PyMODINIT_FUNC
@@ -642,4 +775,9 @@ initoperator(void)
                return;
        Py_INCREF(&attrgetter_type);
        PyModule_AddObject(m, "attrgetter", (PyObject *)&attrgetter_type);
+
+       if (PyType_Ready(&methodcaller_type) < 0)
+               return;
+       Py_INCREF(&methodcaller_type);
+       PyModule_AddObject(m, "methodcaller", (PyObject *)&methodcaller_type);
 }