Skip to content

Commit ffa6852

Browse files
authored
pythongh-144330: Initialize classmethod and staticmethod in new (python#144469)
Move classmethod and staticmethod initialization from __init__() to __new__(). PyClassMethod_New() and PyStaticMethod_New() now copy attributes of the wrapped functions: __module__, __name__, __qualname__ and __doc__. Change static type initialization: initialize PyStaticMethod_Type and PyCFunction_Type earlier. Remove test_refleaks_in_classmethod___init__() and test_refleaks_in_staticmethod___init__() tests from test_descr since classmethod and staticmethod have no __init__() method anymore.
1 parent b53fc7c commit ffa6852

File tree

4 files changed

+116
-55
lines changed

4 files changed

+116
-55
lines changed

Lib/test/test_descr.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,18 @@ def annotated(cls) -> int: pass
17271727
del method.__annotate__
17281728
self.assertIs(method.__annotate__, original_annotate)
17291729

1730+
def test_classmethod_without_dict_access(self):
1731+
class Spam:
1732+
@classmethod
1733+
def method(cls, x, y):
1734+
pass
1735+
1736+
obj = Spam.__dict__['method']
1737+
self.assertIsInstance(obj, classmethod)
1738+
self.assertEqual(obj.__annotations__, {})
1739+
self.assertEqual(obj.__name__, 'method')
1740+
self.assertEqual(obj.__module__, __name__)
1741+
17301742
def test_staticmethod_annotations_without_dict_access(self):
17311743
# gh-125017: this used to crash
17321744
class Spam:
@@ -1737,15 +1749,8 @@ def __new__(cls, x, y):
17371749
obj = Spam.__dict__['__new__']
17381750
self.assertIsInstance(obj, staticmethod)
17391751
self.assertEqual(obj.__annotations__, {})
1740-
1741-
@support.refcount_test
1742-
def test_refleaks_in_classmethod___init__(self):
1743-
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
1744-
cm = classmethod(None)
1745-
refs_before = gettotalrefcount()
1746-
for i in range(100):
1747-
cm.__init__(None)
1748-
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
1752+
self.assertEqual(obj.__name__, '__new__')
1753+
self.assertEqual(obj.__module__, __name__)
17491754

17501755
@support.impl_detail("the module 'xxsubtype' is internal")
17511756
@unittest.skipIf(xxsubtype is None, "requires xxsubtype module")
@@ -1822,15 +1827,6 @@ class D(C):
18221827
del sm.x
18231828
self.assertNotHasAttr(sm, "x")
18241829

1825-
@support.refcount_test
1826-
def test_refleaks_in_staticmethod___init__(self):
1827-
gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
1828-
sm = staticmethod(None)
1829-
refs_before = gettotalrefcount()
1830-
for i in range(100):
1831-
sm.__init__(None)
1832-
self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)
1833-
18341830
@support.impl_detail("the module 'xxsubtype' is internal")
18351831
@unittest.skipIf(xxsubtype is None, "requires xxsubtype module")
18361832
def test_staticmethods_in_c(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Move ``classmethod`` and ``staticmethod`` initialization from ``__init__()``
2+
to ``__new__()``. Patch by Victor Stinner.

Objects/funcobject.c

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1466,33 +1466,59 @@ static PyObject *
14661466
cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
14671467
{
14681468
classmethod *cm = (classmethod *)self;
1469-
1470-
if (cm->cm_callable == NULL) {
1471-
PyErr_SetString(PyExc_RuntimeError,
1472-
"uninitialized classmethod object");
1473-
return NULL;
1474-
}
14751469
if (type == NULL)
14761470
type = (PyObject *)(Py_TYPE(obj));
14771471
return PyMethod_New(cm->cm_callable, type);
14781472
}
14791473

14801474
static int
1481-
cm_init(PyObject *self, PyObject *args, PyObject *kwds)
1475+
cm_set_callable(classmethod *cm, PyObject *callable)
14821476
{
1483-
classmethod *cm = (classmethod *)self;
1484-
PyObject *callable;
1477+
assert(callable != NULL);
1478+
if (cm->cm_callable == callable) {
1479+
// cm_init() sets the same callable than cm_new()
1480+
return 0;
1481+
}
14851482

1486-
if (!_PyArg_NoKeywords("classmethod", kwds))
1487-
return -1;
1488-
if (!PyArg_UnpackTuple(args, "classmethod", 1, 1, &callable))
1489-
return -1;
14901483
Py_XSETREF(cm->cm_callable, Py_NewRef(callable));
1484+
return functools_wraps((PyObject *)cm, cm->cm_callable);
1485+
}
1486+
1487+
static PyObject *
1488+
cm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1489+
{
1490+
if (!_PyArg_NoKeywords("classmethod", kwds)) {
1491+
return NULL;
1492+
}
1493+
PyObject *callable; // borrowed ref
1494+
if (!PyArg_UnpackTuple(args, "classmethod", 1, 1, &callable)) {
1495+
return NULL;
1496+
}
14911497

1492-
if (functools_wraps((PyObject *)cm, cm->cm_callable) < 0) {
1498+
classmethod *cm = (classmethod *)PyType_GenericAlloc(type, 0);
1499+
if (cm == NULL) {
1500+
return NULL;
1501+
}
1502+
if (cm_set_callable(cm, callable) < 0) {
1503+
Py_DECREF(cm);
1504+
return NULL;
1505+
}
1506+
return (PyObject *)cm;
1507+
}
1508+
1509+
static int
1510+
cm_init(PyObject *self, PyObject *args, PyObject *kwds)
1511+
{
1512+
if (!_PyArg_NoKeywords("classmethod", kwds)) {
14931513
return -1;
14941514
}
1495-
return 0;
1515+
PyObject *callable; // borrowed ref
1516+
if (!PyArg_UnpackTuple(args, "classmethod", 1, 1, &callable)) {
1517+
return -1;
1518+
}
1519+
1520+
classmethod *cm = (classmethod *)self;
1521+
return cm_set_callable(cm, callable);
14961522
}
14971523

14981524
static PyMemberDef cm_memberlist[] = {
@@ -1623,7 +1649,7 @@ PyTypeObject PyClassMethod_Type = {
16231649
offsetof(classmethod, cm_dict), /* tp_dictoffset */
16241650
cm_init, /* tp_init */
16251651
PyType_GenericAlloc, /* tp_alloc */
1626-
PyType_GenericNew, /* tp_new */
1652+
cm_new, /* tp_new */
16271653
PyObject_GC_Del, /* tp_free */
16281654
};
16291655

@@ -1632,8 +1658,12 @@ PyClassMethod_New(PyObject *callable)
16321658
{
16331659
classmethod *cm = (classmethod *)
16341660
PyType_GenericAlloc(&PyClassMethod_Type, 0);
1635-
if (cm != NULL) {
1636-
cm->cm_callable = Py_NewRef(callable);
1661+
if (cm == NULL) {
1662+
return NULL;
1663+
}
1664+
if (cm_set_callable(cm, callable) < 0) {
1665+
Py_DECREF(cm);
1666+
return NULL;
16371667
}
16381668
return (PyObject *)cm;
16391669
}
@@ -1699,31 +1729,57 @@ static PyObject *
16991729
sm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
17001730
{
17011731
staticmethod *sm = (staticmethod *)self;
1732+
return Py_NewRef(sm->sm_callable);
1733+
}
17021734

1703-
if (sm->sm_callable == NULL) {
1704-
PyErr_SetString(PyExc_RuntimeError,
1705-
"uninitialized staticmethod object");
1735+
static int
1736+
sm_set_callable(staticmethod *sm, PyObject *callable)
1737+
{
1738+
assert(callable != NULL);
1739+
if (sm->sm_callable == callable) {
1740+
// sm_init() sets the same callable than sm_new()
1741+
return 0;
1742+
}
1743+
1744+
Py_XSETREF(sm->sm_callable, Py_NewRef(callable));
1745+
return functools_wraps((PyObject *)sm, sm->sm_callable);
1746+
}
1747+
1748+
static PyObject *
1749+
sm_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
1750+
{
1751+
if (!_PyArg_NoKeywords("staticmethod", kwds)) {
17061752
return NULL;
17071753
}
1708-
return Py_NewRef(sm->sm_callable);
1754+
PyObject *callable; // borrowed ref
1755+
if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable)) {
1756+
return NULL;
1757+
}
1758+
1759+
staticmethod *sm = (staticmethod *)PyType_GenericAlloc(type, 0);
1760+
if (sm == NULL) {
1761+
return NULL;
1762+
}
1763+
if (sm_set_callable(sm, callable) < 0) {
1764+
Py_DECREF(sm);
1765+
return NULL;
1766+
}
1767+
return (PyObject *)sm;
17091768
}
17101769

17111770
static int
17121771
sm_init(PyObject *self, PyObject *args, PyObject *kwds)
17131772
{
1714-
staticmethod *sm = (staticmethod *)self;
1715-
PyObject *callable;
1716-
1717-
if (!_PyArg_NoKeywords("staticmethod", kwds))
1773+
if (!_PyArg_NoKeywords("staticmethod", kwds)) {
17181774
return -1;
1719-
if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable))
1720-
return -1;
1721-
Py_XSETREF(sm->sm_callable, Py_NewRef(callable));
1722-
1723-
if (functools_wraps((PyObject *)sm, sm->sm_callable) < 0) {
1775+
}
1776+
PyObject *callable; // borrowed ref
1777+
if (!PyArg_UnpackTuple(args, "staticmethod", 1, 1, &callable)) {
17241778
return -1;
17251779
}
1726-
return 0;
1780+
1781+
staticmethod *sm = (staticmethod *)self;
1782+
return sm_set_callable(sm, callable);
17271783
}
17281784

17291785
static PyObject*
@@ -1858,7 +1914,7 @@ PyTypeObject PyStaticMethod_Type = {
18581914
offsetof(staticmethod, sm_dict), /* tp_dictoffset */
18591915
sm_init, /* tp_init */
18601916
PyType_GenericAlloc, /* tp_alloc */
1861-
PyType_GenericNew, /* tp_new */
1917+
sm_new, /* tp_new */
18621918
PyObject_GC_Del, /* tp_free */
18631919
};
18641920

@@ -1867,8 +1923,12 @@ PyStaticMethod_New(PyObject *callable)
18671923
{
18681924
staticmethod *sm = (staticmethod *)
18691925
PyType_GenericAlloc(&PyStaticMethod_Type, 0);
1870-
if (sm != NULL) {
1871-
sm->sm_callable = Py_NewRef(callable);
1926+
if (sm == NULL) {
1927+
return NULL;
1928+
}
1929+
if (sm_set_callable(sm, callable) < 0) {
1930+
Py_DECREF(sm);
1931+
return NULL;
18721932
}
18731933
return (PyObject *)sm;
18741934
}

Objects/object.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,13 +2446,17 @@ static PyTypeObject* static_types[] = {
24462446
&PyBaseObject_Type,
24472447
&PyType_Type,
24482448

2449+
// PyStaticMethod_Type and PyCFunction_Type are used by PyType_Ready()
2450+
// on other types and so must be initialized first.
2451+
&PyStaticMethod_Type,
2452+
&PyCFunction_Type,
2453+
24492454
// Static types with base=&PyBaseObject_Type
24502455
&PyAsyncGen_Type,
24512456
&PyByteArrayIter_Type,
24522457
&PyByteArray_Type,
24532458
&PyBytesIter_Type,
24542459
&PyBytes_Type,
2455-
&PyCFunction_Type,
24562460
&PyCallIter_Type,
24572461
&PyCapsule_Type,
24582462
&PyCell_Type,
@@ -2509,7 +2513,6 @@ static PyTypeObject* static_types[] = {
25092513
&PySetIter_Type,
25102514
&PySet_Type,
25112515
&PySlice_Type,
2512-
&PyStaticMethod_Type,
25132516
&PyStdPrinter_Type,
25142517
&PySuper_Type,
25152518
&PyTraceBack_Type,

0 commit comments

Comments
 (0)