8000 bpo-43682: @staticmethod inherits attributes by vstinner · Pull Request #25268 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-43682: @staticmethod inherits attributes #25268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
bpo-43682: @staticmethod inherits method attributes
Static methods (@staticmethod) and class methods (@classmethod) now
inherit the method attributes (__module__, __name__, __qualname__,
__doc__, __annotations__) and have a new __wrapped__ attribute.

Changes:

* Add a repr() method to staticmethod and classmethod types.
* Add tests on the @classmethod decorator.
  • Loading branch information
vstinner committed Apr 7, 2021
commit b50eb0714a1f5836e1d881067d6ca72c628c8100
10 changes: 10 additions & 0 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ are always available. They are listed here in alphabetical order.
Class methods can now wrap other :term:`descriptors <descriptor>` such as
:func:`property`.

.. versionchanged:: 3.10
Class methods now inherit the method attributes (``__module__``,
``__name__``, ``__qualname__``, ``__doc__`` and ``__annotations__``) and
have a new ``__wrapped__`` attribute.

.. function:: compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)

Compile the *source* into a code or AST object. Code objects can be executed
Expand Down Expand Up @@ -1632,6 +1637,11 @@ are always available. They are listed here in alphabetical order.

For more information on static methods, see :ref:`types`.

.. versionchanged:: 3.10
Static methods now inherit the method attributes (``__module__``,
``__name__``, ``__qualname__``, ``__doc__`` and ``__annotations__``) and
have a new ``__wrapped__`` attribute.


.. index::
single: string; str() (built-in function)
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.10.rst
8000
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,12 @@ Other Language Changes
respectively.
(Contributed by Joshua Bronson, Daniel Pope, and Justin Wang in :issue:`31861`.)

* Static methods (:func:`@staticmethod <staticmethod>`) and class methods
(:func:`@classmethod <classmethod>`) now inherit the method attributes
(``__module__``, ``__name__``, ``__qualname__``, ``__doc__``,
``__annotations__``) and have a new ``__wrapped__`` attribute.
(Contributed by Victor Stinner in :issue:`43682`.)


New Modules
===========
Expand Down
37 changes: 33 additions & 4 deletions Lib/test/test_decorators.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from test import support
import unittest

def funcattrs(**kwds):
Expand Down Expand Up @@ -76,11 +77,39 @@ def foo(): return 42
self.assertEqual(C.foo(), 42)
self.assertEqual(C().foo(), 42)

def test_staticmethod_function(self):
@staticmethod
def notamethod(x):
def check_wrapper_attrs(self, wrapper, func):
self.assertIs(wrapper.__wrapped__, func)

for attr in ('__module__', '__qualname__', '__name__',
'__doc__', '__annotations__'):
self.assertIs(getattr(wrapper, attr),
getattr(func, attr))

def test_staticmethod(self):
def func(x):
return x
wrapper = staticmethod(func)

self.assertIs(wrapper.__func__, func)
self.check_wrapper_attrs(wrapper, func)

self.assertEqual(repr(wrapper),
f'<staticmethod({func!r})>')

self.assertRaises(TypeError, wrapper, 1)

def test_classmethod(self):
def func(x):
return x
self.assertRaises(TypeError, notamethod, 1)
wrapper = classmethod(func)

self.assertIs(wrapper.__func__, func)
self.check_wrapper_attrs(wrapper, func)

self.assertEqual(repr(wrapper),
f'<classmethod({func!r})>')

self.assertRaises(TypeError, wrapper, 1)

def test_dotted(self):
decorators = MiscDecorators()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Static methods (:func:`@staticmethod <staticmethod>`) and class methods
(:func:`@classmethod <classmethod>`) now inherit the method attributes
(``__module__``, ``__name__``, ``__qualname__``, ``__doc__``,
``__annotations__``) and have a new ``__wrapped__`` attribute.
Patch by Victor Stinner.
80 changes: 71 additions & 9 deletions Objects/funcobject.c
9E88
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ static PyObject*
func_repr(PyFunctionObject *op)
{
return PyUnicode_FromFormat("<function %U at %p>",
op->func_qualname, op);
op->func_qualname, op);
}

static int
Expand Down Expand Up @@ -715,6 +715,50 @@ PyTypeObject PyFunction_Type = {
};


static int
functools_copy_attr(PyObject *wrapper, PyObject *wrapped, PyObject *name)
{
PyObject *value = PyObject_GetAttr(wrapped, name);
if (value == NULL) {
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
Comment on lines +721 to +724
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to use _PyObject_LookupAttr() here. See #106303.

return 0;
}
return -1;
}

int res = PyObject_SetAttr(wrapper, name, value);
Py_DECREF(value);
return res;
}

// Similar to functools.wraps(wrapper, wrapped)
static int
functools_wraps(PyObject *wrapper, PyObject *wrapped)
{
#define COPY_ATTR(ATTR) \
do { \
_Py_IDENTIFIER(ATTR); \
PyObject *attr = _PyUnicode_FromId(&PyId_ ## ATTR); \
if (attr == NULL) { \
return -1; \
} \
if (functools_copy_attr(wrapper, wrapped, attr) < 0) { \
return -1; \
} \
} while (0) \

COPY_ATTR(__module__);
COPY_ATTR(__name__);
COPY_ATTR(__qualname__);
COPY_ATTR(__doc__);
COPY_ATTR(__annotations__);
return 0;

#undef COPY_ATTR
}


/* Class method object */

/* A class method receives the class as implicit first argument,
Expand Down Expand Up @@ -798,11 +842,16 @@ cm_init(PyObject *self, PyObject *args, PyObject *kwds)
return -1;
Py_INCREF(callable);
Py_XSETREF(cm->cm_callable, callable);

if (functools_wraps((PyObject *)cm, cm->cm_callable) < 0) {
return -1;
}
return 0;
}

static PyMemberDef cm_memberlist[] = {
{"__func__", T_OBJECT, offsetof(classmethod, cm_callable), READONLY},
{"__wrapped__", T_OBJECT, offsetof(classmethod, cm_callable), READONLY},
{NULL} /* Sentinel */
};

Expand All @@ -821,13 +870,17 @@ cm_get___isabstractmethod__(classmethod *cm, void *closure)

static PyGetSetDef cm_getsetlist[] = {
{"__isabstractmethod__",
(getter)cm_get___isabstractmethod__, NULL,
NULL,
NULL},
(getter)cm_get___isabstractmethod__, NULL, NULL, NULL},
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, NULL, NULL},
{NULL} /* Sentinel */
};

static PyObject*
cm_repr(classmethod *cm)
{
return PyUnicode_FromFormat("<classmethod(%R)>", cm->cm_callable);
}

PyDoc_STRVAR(classmethod_doc,
"classmethod(function) -> method\n\
\n\
Expand Down Expand Up @@ -860,7 +913,7 @@ PyTypeObject PyClassMethod_Type = {
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
0, /* tp_repr */
(reprfunc)cm_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
Expand Down Expand Up @@ -980,11 +1033,16 @@ sm_init(PyObject *self, PyObject *args, PyObject *kwds)
return -1;
Py_INCREF(callable);
Py_XSETREF(sm->sm_callable, callable);

if (functools_wraps((PyObject *)sm, sm->sm_callable) < 0) {
return -1;
}
return 0;
}

static PyMemberDef sm_memberlist[] = {
{"__func__", T_OBJECT, offsetof(staticmethod, sm_callable), READONLY},
{"__wrapped__", T_OBJECT, offsetof(staticmethod, sm_callable), READONLY},
{NULL} /* Sentinel */
};

Expand All @@ -1003,13 +1061,17 @@ sm_get___isabstractmethod__(staticmethod *sm, void *closure)

static PyGetSetDef sm_getsetlist[] = {
{"__isabstractmethod__",
(getter)sm_get___isabstractmethod__, NULL,
NULL,
NULL},
(getter)sm_get___isabstractmethod__, NULL, NULL, NULL},
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict, NULL, NULL},
{NULL} /* Sentinel */
};

static PyObject*
sm_repr(staticmethod *sm)
{
return PyUnicode_FromFormat("<staticmethod(%R)>", sm->sm_callable);
}

PyDoc_STRVAR(staticmethod_doc,
"staticmethod(function) -> method\n\
\n\
Expand Down Expand Up @@ -1040,7 +1102,7 @@ PyTypeObject PyStaticMethod_Type = {
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_as_async */
0, /* tp_repr */
(reprfunc)sm_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
Expand Down
0