Skip to content

Commit c01affa

Browse files
committed
allow arbitrary attributes on classmethod and staticmethod (closes #14051)
1 parent d5eb0f4 commit c01affa

3 files changed

Lines changed: 43 additions & 3 deletions

File tree

Lib/test/test_descr.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,13 @@ def f(cls, arg): return (cls, arg)
14431443
else:
14441444
self.fail("classmethod shouldn't accept keyword args")
14451445

1446+
cm = classmethod(f)
1447+
cm.x = 42
1448+
self.assertEqual(cm.x, 42)
1449+
self.assertEqual(cm.__dict__, {"x" : 42})
1450+
del cm.x
1451+
self.assertFalse(hasattr(cm, "x"))
1452+
14461453
@support.impl_detail("the module 'xxsubtype' is internal")
14471454
def test_classmethods_in_c(self):
14481455
# Testing C-based class methods...
@@ -1474,6 +1481,12 @@ class D(C):
14741481
self.assertEqual(d.goo(1), (1,))
14751482
self.assertEqual(d.foo(1), (d, 1))
14761483
self.assertEqual(D.foo(d, 1), (d, 1))
1484+
sm = staticmethod(None)
1485+
sm.x = 42
1486+
self.assertEqual(sm.x, 42)
1487+
self.assertEqual(sm.__dict__, {"x" : 42})
1488+
del sm.x
1489+
self.assertFalse(hasattr(sm, "x"))
14771490

14781491
@support.impl_detail("the module 'xxsubtype' is internal")
14791492
def test_staticmethods_in_c(self):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.3 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #14051: Allow arbitrary attributes to be set of classmethod and
14+
staticmethod.
15+
1316
- Issue #13020: Fix a reference leak when allocating a structsequence object
1417
fails. Patch by Suman Saha.
1518

Objects/funcobject.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -754,27 +754,31 @@ PyTypeObject PyFunction_Type = {
754754
typedef struct {
755755
PyObject_HEAD
756756
PyObject *cm_callable;
757+
PyObject *cm_dict;
757758
} classmethod;
758759

759760
static void
760761
cm_dealloc(classmethod *cm)
761762
{
762763
_PyObject_GC_UNTRACK((PyObject *)cm);
763764
Py_XDECREF(cm->cm_callable);
765+
Py_XDECREF(cm->cm_dict);
764766
Py_TYPE(cm)->tp_free((PyObject *)cm);
765767
}
766768

767769
static int
768770
cm_traverse(classmethod *cm, visitproc visit, void *arg)
769771
{
770772
Py_VISIT(cm->cm_callable);
773+
Py_VISIT(cm->cm_dict);
771774
return 0;
772775
}
773776

774777
static int
775778
cm_clear(classmethod *cm)
776779
{
777780
Py_CLEAR(cm->cm_callable);
781+
Py_CLEAR(cm->cm_dict);
778782
return 0;
779783
}
780784

@@ -827,11 +831,19 @@ cm_get___isabstractmethod__(classmethod *cm, void *closure)
827831
Py_RETURN_FALSE;
828832
}
829833

834+
static PyObject *
835+
cm_get___dict__(classmethod *cm, void *closure)
836+
{
837+
Py_INCREF(cm->cm_dict);
838+
return cm->cm_dict;
839+
}
840+
830841
static PyGetSetDef cm_getsetlist[] = {
831842
{"__isabstractmethod__",
832843
(getter)cm_get___isabstractmethod__, NULL,
833844
NULL,
834845
NULL},
846+
{"__dict__", (getter)cm_get___dict__, NULL, NULL, NULL},
835847
{NULL} /* Sentinel */
836848
};
837849

@@ -891,7 +903,7 @@ PyTypeObject PyClassMethod_Type = {
891903
0, /* tp_dict */
892904
cm_descr_get, /* tp_descr_get */
893905
0, /* tp_descr_set */
894-
0, /* tp_dictoffset */
906+
offsetof(classmethod, cm_dict), /* tp_dictoffset */
895907
cm_init, /* tp_init */
896908
PyType_GenericAlloc, /* tp_alloc */
897909
PyType_GenericNew, /* tp_new */
@@ -930,20 +942,23 @@ PyClassMethod_New(PyObject *callable)
930942
typedef struct {
931943
PyObject_HEAD
932944
PyObject *sm_callable;
945+
PyObject *sm_dict;
933946
} staticmethod;
934947

935948
static void
936949
sm_dealloc(staticmethod *sm)
937950
{
938951
_PyObject_GC_UNTRACK((PyObject *)sm);
939952
Py_XDECREF(sm->sm_callable);
953+
Py_XDECREF(sm->sm_dict);
940954
Py_TYPE(sm)->tp_free((PyObject *)sm);
941955
}
942956

943957
static int
944958
sm_traverse(staticmethod *sm, visitproc visit, void *arg)
945959
{
946960
Py_VISIT(sm->sm_callable);
961+
Py_VISIT(sm->sm_dict);
947962
return 0;
948963
}
949964

@@ -952,6 +967,7 @@ sm_clear(staticmethod *sm)
952967
{
953968
Py_XDECREF(sm->sm_callable);
954969
sm->sm_callable = NULL;
970+
Py_CLEAR(sm->sm_dict);
955971

956972
return 0;
957973
}
@@ -1003,11 +1019,19 @@ sm_get___isabstractmethod__(staticmethod *sm, void *closure)
10031019
Py_RETURN_FALSE;
10041020
}
10051021

1022+
static PyObject *
1023+
sm_get___dict__(staticmethod *sm, void *closure)
1024+
{
1025+
Py_INCREF(sm->sm_dict);
1026+
return sm->sm_dict;
1027+
}
1028+
10061029
static PyGetSetDef sm_getsetlist[] = {
10071030
{"__isabstractmethod__",
10081031
(getter)sm_get___isabstractmethod__, NULL,
10091032
NULL,
10101033
NULL},
1034+
{"__dict__", (getter)sm_get___dict__, NULL, NULL, NULL},
10111035
{NULL} /* Sentinel */
10121036
};
10131037

@@ -1046,7 +1070,7 @@ PyTypeObject PyStaticMethod_Type = {
10461070
0, /* tp_hash */
10471071
0, /* tp_call */
10481072
0, /* tp_str */
1049-
PyObject_GenericGetAttr, /* tp_getattro */
1073+
0, /* tp_getattro */
10501074
0, /* tp_setattro */
10511075
0, /* tp_as_buffer */
10521076
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
@@ -1064,7 +1088,7 @@ PyTypeObject PyStaticMethod_Type = {
10641088
0, /* tp_dict */
10651089
sm_descr_get, /* tp_descr_get */
10661090
0, /* tp_descr_set */
1067-
0, /* tp_dictoffset */
1091+
offsetof(staticmethod, sm_dict), /* tp_dictoffset */
10681092
sm_init, /* tp_init */
10691093
PyType_GenericAlloc, /* tp_alloc */
10701094
PyType_GenericNew, /* tp_new */

0 commit comments

Comments
 (0)