From 01d7eba3162af8a0f62d7636cbb44440901b3909 Mon Sep 17 00:00:00 2001
From: Benjamin Peterson <benjamin@python.org>
Date: Sun, 19 Feb 2012 01:10:25 -0500
Subject: allow arbitrary attributes on classmethod and staticmethod (closes
 #14051)

---
 Lib/test/test_descr.py | 13 +++++++++++++
 Misc/NEWS              |  3 +++
 Objects/funcobject.c   | 30 +++++++++++++++++++++++++++---
 3 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 2b2026c..2227779 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -1443,6 +1443,13 @@ order (MRO) for bases """
         else:
             self.fail("classmethod shouldn't accept keyword args")
 
+        cm = classmethod(f)
+        cm.x = 42
+        self.assertEqual(cm.x, 42)
+        self.assertEqual(cm.__dict__, {"x" : 42})
+        del cm.x
+        self.assertFalse(hasattr(cm, "x"))
+
     @support.impl_detail("the module 'xxsubtype' is internal")
     def test_classmethods_in_c(self):
         # Testing C-based class methods...
@@ -1474,6 +1481,12 @@ order (MRO) for bases """
         self.assertEqual(d.goo(1), (1,))
         self.assertEqual(d.foo(1), (d, 1))
         self.assertEqual(D.foo(d, 1), (d, 1))
+        sm = staticmethod(None)
+        sm.x = 42
+        self.assertEqual(sm.x, 42)
+        self.assertEqual(sm.__dict__, {"x" : 42})
+        del sm.x
+        self.assertFalse(hasattr(sm, "x"))
 
     @support.impl_detail("the module 'xxsubtype' is internal")
     def test_staticmethods_in_c(self):
diff --git a/Misc/NEWS b/Misc/NEWS
index 1da9d8a..61b4035 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
 Core and Builtins
 -----------------
 
+- Issue #14051: Allow arbitrary attributes to be set of classmethod and
+  staticmethod.
+
 - Issue #13020: Fix a reference leak when allocating a structsequence object
   fails.  Patch by Suman Saha.
 
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index b93f340..5db86a6 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -754,6 +754,7 @@ PyTypeObject PyFunction_Type = {
 typedef struct {
     PyObject_HEAD
     PyObject *cm_callable;
+    PyObject *cm_dict;
 } classmethod;
 
 static void
@@ -761,6 +762,7 @@ cm_dealloc(classmethod *cm)
 {
     _PyObject_GC_UNTRACK((PyObject *)cm);
     Py_XDECREF(cm->cm_callable);
+    Py_XDECREF(cm->cm_dict);
     Py_TYPE(cm)->tp_free((PyObject *)cm);
 }
 
@@ -768,6 +770,7 @@ static int
 cm_traverse(classmethod *cm, visitproc visit, void *arg)
 {
     Py_VISIT(cm->cm_callable);
+    Py_VISIT(cm->cm_dict);
     return 0;
 }
 
@@ -775,6 +778,7 @@ static int
 cm_clear(classmethod *cm)
 {
     Py_CLEAR(cm->cm_callable);
+    Py_CLEAR(cm->cm_dict);
     return 0;
 }
 
@@ -827,11 +831,19 @@ cm_get___isabstractmethod__(classmethod *cm, void *closure)
     Py_RETURN_FALSE;
 }
 
+static PyObject *
+cm_get___dict__(classmethod *cm, void *closure)
+{
+    Py_INCREF(cm->cm_dict);
+    return cm->cm_dict;
+}
+
 static PyGetSetDef cm_getsetlist[] = {
     {"__isabstractmethod__",
      (getter)cm_get___isabstractmethod__, NULL,
      NULL,
      NULL},
+    {"__dict__", (getter)cm_get___dict__, NULL, NULL, NULL},
     {NULL} /* Sentinel */
 };
 
@@ -891,7 +903,7 @@ PyTypeObject PyClassMethod_Type = {
     0,                                          /* tp_dict */
     cm_descr_get,                               /* tp_descr_get */
     0,                                          /* tp_descr_set */
-    0,                                          /* tp_dictoffset */
+    offsetof(classmethod, cm_dict),             /* tp_dictoffset */
     cm_init,                                    /* tp_init */
     PyType_GenericAlloc,                        /* tp_alloc */
     PyType_GenericNew,                          /* tp_new */
@@ -930,6 +942,7 @@ PyClassMethod_New(PyObject *callable)
 typedef struct {
     PyObject_HEAD
     PyObject *sm_callable;
+    PyObject *sm_dict;
 } staticmethod;
 
 static void
@@ -937,6 +950,7 @@ sm_dealloc(staticmethod *sm)
 {
     _PyObject_GC_UNTRACK((PyObject *)sm);
     Py_XDECREF(sm->sm_callable);
+    Py_XDECREF(sm->sm_dict);
     Py_TYPE(sm)->tp_free((PyObject *)sm);
 }
 
@@ -944,6 +958,7 @@ static int
 sm_traverse(staticmethod *sm, visitproc visit, void *arg)
 {
     Py_VISIT(sm->sm_callable);
+    Py_VISIT(sm->sm_dict);
     return 0;
 }
 
@@ -952,6 +967,7 @@ sm_clear(staticmethod *sm)
 {
     Py_XDECREF(sm->sm_callable);
     sm->sm_callable = NULL;
+    Py_CLEAR(sm->sm_dict);
 
     return 0;
 }
@@ -1003,11 +1019,19 @@ sm_get___isabstractmethod__(staticmethod *sm, void *closure)
     Py_RETURN_FALSE;
 }
 
+static PyObject *
+sm_get___dict__(staticmethod *sm, void *closure)
+{
+    Py_INCREF(sm->sm_dict);
+    return sm->sm_dict;
+}
+
 static PyGetSetDef sm_getsetlist[] = {
     {"__isabstractmethod__",
      (getter)sm_get___isabstractmethod__, NULL,
      NULL,
      NULL},
+    {"__dict__", (getter)sm_get___dict__, NULL, NULL, NULL},
     {NULL} /* Sentinel */
 };
 
@@ -1046,7 +1070,7 @@ PyTypeObject PyStaticMethod_Type = {
     0,                                          /* tp_hash */
     0,                                          /* tp_call */
     0,                                          /* tp_str */
-    PyObject_GenericGetAttr,                    /* tp_getattro */
+    0,                                          /* tp_getattro */
     0,                                          /* tp_setattro */
     0,                                          /* tp_as_buffer */
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
@@ -1064,7 +1088,7 @@ PyTypeObject PyStaticMethod_Type = {
     0,                                          /* tp_dict */
     sm_descr_get,                               /* tp_descr_get */
     0,                                          /* tp_descr_set */
-    0,                                          /* tp_dictoffset */
+    offsetof(staticmethod, sm_dict),            /* tp_dictoffset */
     sm_init,                                    /* tp_init */
     PyType_GenericAlloc,                        /* tp_alloc */
     PyType_GenericNew,                          /* tp_new */
-- 
cgit v0.12

