Index: Modules/_decorator.c =================================================================== --- Modules/_decorator.c (Revision 0) +++ Modules/_decorator.c (Revision 0) @@ -0,0 +1,249 @@ +/* _decorator module written + * 2006 by Georg Brandl + * Copyright (c) 2006 Python Software Foundation. + * All rights reserved. + */ + +#include "Python.h" +#include "structmember.h" + +typedef struct { + PyObject_HEAD + PyObject *dec; + PyObject *dict; + char *dec_name; +} decoratorobject; + +static PyTypeObject decorator_type; + +static PyObject * +decorator_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *func, *func_name; + decoratorobject *deco; + + if (!PyArg_UnpackTuple(args, "decorator", 1, 1, &func)) + return NULL; + + func = PyTuple_GET_ITEM(args, 0); + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "decorator: the argument must be callable"); + return NULL; + } + + deco = (decoratorobject *)type->tp_alloc(type, 0); + if (deco == NULL) + return NULL; + + deco->dec_name = NULL; + func_name = PyObject_GetAttrString(func, "__name__"); + if (func_name) { + deco->dec_name = PyString_AsString(func_name); + } + /* if we get an AttributeError, the function has no usable name, + * don't complain (deco->dec_name will be NULL) */ + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) + PyErr_Clear(); + else + return NULL; + } + + Py_INCREF(func); + deco->dec = func; + deco->dict = NULL; + + return (PyObject *)deco; +} + + +static void +decorator_dealloc(decoratorobject *deco) +{ + PyObject_GC_UnTrack(deco); + Py_XDECREF(deco->dec); + Py_XDECREF(deco->dict); + deco->ob_type->tp_free(deco); +} + +static PyObject * +decorator_call(decoratorobject *deco, PyObject *args, PyObject *kwds) +{ + PyObject *func, *ret; + PyObject *attr, *dict; + int result = 0; + + assert (PyCallable_Check(deco->dec)); + + if (!PyArg_UnpackTuple(args, (deco->dec_name ? + deco->dec_name : "decorator"), + 1, 1, &func)) + return NULL; + + ret = PyObject_CallFunctionObjArgs(deco->dec, func, NULL); + if (!ret) + return NULL; + + /* Transfer name */ + if (PyObject_HasAttrString(func, "__name__")) { + if ((attr = PyObject_GetAttrString(func, "__name__"))) { + if (PyString_Check(attr)) + result = PyObject_SetAttrString(ret, "__name__", attr); + /* if not a string, do not try to set */ + Py_DECREF(attr); + if (result < 0) { + return NULL; + } + } else { + return NULL; + } + } + /* Transfer docstring */ + if (PyObject_HasAttrString(func, "__doc__")) { + if ((attr = PyObject_GetAttrString(func, "__doc__"))) { + result = PyObject_SetAttrString(ret, "__doc__", attr); + Py_DECREF(attr); + if (result < 0) + return NULL; + } else { + return NULL; + } + } + /* Transfer attributes */ + if (PyObject_HasAttrString(func, "__dict__")) { + attr = PyObject_GetAttrString(func, "__dict__"); + if (!attr) + return NULL; + dict = PyObject_GetAttrString(ret, "__dict__"); + if (!dict) { + Py_DECREF(attr); + return NULL; + } + result = PyDict_Update(dict, attr); + Py_DECREF(attr); + Py_DECREF(dict); + if (result < 0) return NULL; + } + + Py_INCREF(ret); + return ret; +} + +static int +decorator_traverse(decoratorobject *deco, visitproc visit, void *arg) +{ + Py_VISIT(deco->dec); + Py_VISIT(deco->dict); + return 0; +} + +static PyObject * +decorator_repr(decoratorobject *deco) +{ + return PyString_FromFormat("", + (deco->dec_name ? deco->dec_name : "object"), + deco); +} + +static PyObject * +decorator_get_dict(decoratorobject *deco, void *context) +{ + if (!deco->dict) { + deco->dict = PyDict_New(); + if (!deco->dict) + return NULL; + } + Py_INCREF(deco->dict); + return deco->dict; +} + +static PyGetSetDef decorator_getset[] = { + {"__dict__", (getter)decorator_get_dict, NULL, NULL}, + {NULL} /* sentinel */ +}; + + +PyDoc_STRVAR(decorator_doc, +"Makes the decorated function a proper decorator.\n\ +The function decorated with \"decorator\" must be a simple decorator,\n\ +that is, it must take only one argument (the decorated function) and\n\ +return a function. If this simple decorator is afterwards applied to any\n\ +function, the simple decorator will transfer the original functions's\n\ +__name__, __doc__ and __dict__ to the result."); + +static PyTypeObject decorator_type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "_decorator.decorator", /* tp_name */ + sizeof(decoratorobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + (destructor)decorator_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)decorator_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + (ternaryfunc)decorator_call, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + PyObject_GenericSetAttr, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + decorator_doc, /* tp_doc */ + (traverseproc)decorator_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + decorator_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(decoratorobject, dict),/* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + decorator_new, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + + + +PyDoc_STRVAR(_decorator_module_doc, +"Helper module for decorator library.\n\ +\n\ +Contains the \"decorator\" decorator which makes decorated functions\n\ +proper decorators."); + +PyMODINIT_FUNC +init_decorator(void) +{ + PyObject *m, *tup, *obj; + + m = Py_InitModule3("_decorator", NULL, _decorator_module_doc); + if (!m) return; + + /* Initialize decorator type */ + if (PyType_Ready(&decorator_type) < 0) + return; + + /* don't add "decorator" itself, use decorator(decorator) */ + tup = PyTuple_Pack(1, (PyObject *)&decorator_type); + if (!tup) return; + obj = decorator_new(&decorator_type, tup, NULL); + Py_DECREF(tup); + if (!obj) return; + if (PyModule_AddObject(m, "decorator", obj)) + return; +} + Index: Lib/decorator.py =================================================================== --- Lib/decorator.py (Revision 0) +++ Lib/decorator.py (Revision 0) @@ -0,0 +1,16 @@ +"""Commonly used decorators. + +Currently, only @decorator is available which turns the decorated +function into a proper decorator which keeps decorated functions' +__name__, __doc__ and __dict__. +""" + +# $Id$ +# (c) 2006 Georg Brandl +# Licensed to the PSF under a Contributor Agreement. + +__author__ = "Georg Brandl " +__date__ = "08 March 2006" +__version__ = "$Revision$" + +from _decorator import decorator Index: Doc/lib/libdecorator.tex =================================================================== --- Doc/lib/libdecorator.tex (Revision 0) +++ Doc/lib/libdecorator.tex (Revision 0) @@ -0,0 +1,88 @@ +\section{\module{decorator} --- Commonly used decorators.} + +\declaremodule{standard}{decorator} % standard library, in Python + +\moduleauthor{Georg Brandl}{gbrandl@ph.tum.de} +\sectionauthor{Georg Brandl}{gbrandl@ph.tum.de} + +\modulesynopsis{Commonly used decorators.} + +\versionadded{2.5} + +The \module{decorator} module aims to be a collection of decorators +that are widely used, but don't fit elsewhere in the standard library. + + +The \module{decorator} module currently defines only the following decorator: + +\begin{funcdesc}{decorator}{func} + This is a decorator that will turn the function \var{func} into a + proper decorator. A proper decorator is one that copies the decorated + function's \code{__name__}, \code{__doc__} and \code{__dict__} attributes + to the function it returns. + + An example makes this clearer. Suppose you define the following functions: + + \begin{verbatim} + def logged(func): + def new_function(*args, **kwargs): + print "%s called with %s, %s" % (func.__name__, + args, kwargs) + return func(*args, **kwargs) + return new_function + + @logged + def print_indented(indent, *args): + """Print the args preceded by indent spaces""" + print ' ' * indent, ', '.join(args) + \end{verbatim} + + The decorated function does work as expected: + + \begin{verbatim} + >>> print_indented(4, "spam") + print_indented called with (4, 'spam'), {} + spam + >>> + \end{verbatim} + + But \function{print_indented} has lost its function name and docstring + + \begin{verbatim} + >>> print print_indented + + >>> print print_indented.__doc__ + None + >>> + \end{verbatim} + + With \function{decorator.decorator} applied to \function{logged}, this is + corrected: + + \begin{verbatim} + @decorator.decorator + def logged(func) + [same definitions as above] + + >>> print print_indented + + >>> print print_indented.__doc__ + Print the args preceded by indent spaces + >>> + \end{verbatim} + + If you have a decorator that is called with arguments, apply + \function{decorator} to the inner decorator function, for example: + + \begin{verbatim} + def print_before(extra_string): + @decorator.decorator + def deco(func): + def new_func(*args, **kwargs): + print extra_string + return func(*args, **kwargs) + return new_func + return deco + \end{verbatim} + +\end{funcdesc} Index: Lib/test/test_decorator.py =================================================================== --- Lib/test/test_decorator.py (Revision 0) +++ Lib/test/test_decorator.py (Revision 0) @@ -0,0 +1,37 @@ +import unittest +from test import test_support + +import decorator + + +class TestDecoratorModule(unittest.TestCase): + + def testDecorator(self): + def deco(func): + """deco docstring""" + def new_func(*args, **kwargs): + return func(*args, **kwargs) + return new_func + deco.decoattr = "val" + # using old-fashioned decorator application to test transfer of __dict__ + deco = decorator.decorator(deco) + + # assert that decorator.decorator is a proper decorator + self.assertEquals(deco.__name__, "deco") + self.assertEquals(deco.__doc__, "deco docstring") + self.assertEquals(deco.__dict__["decoattr"], "val") + + @deco + def func(spam, eggs): + """func docstring""" + return 42 + + # assert that decorator.decorator made deco a proper decorator + self.assertEquals(func.func_name, "func") + self.assertEquals(func.__doc__, "func docstring") + +def test_main(): + test_support.run_unittest(TestDecoratorModule) + +if __name__=="__main__": + test_main()