mirror of
https://github.com/python/cpython
synced 2024-09-16 02:19:59 +00:00
bpo-43682: @staticmethod inherits attributes (GH-25268)
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.
This commit is contained in:
parent
150af75432
commit
507a574de3
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
===========
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from test import support
|
||||
import unittest
|
||||
|
||||
def funcattrs(**kwds):
|
||||
|
@ -76,11 +77,28 @@ 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, method_wrapper, format_str):
|
||||
def func(x):
|
||||
return x
|
||||
self.assertRaises(TypeError, notamethod, 1)
|
||||
wrapper = method_wrapper(func)
|
||||
|
||||
self.assertIs(wrapper.__func__, func)
|
||||
self.assertIs(wrapper.__wrapped__, func)
|
||||
|
||||
for attr in ('__module__', '__qualname__', '__name__',
|
||||
'__doc__', '__annotations__'):
|
||||
self.assertIs(getattr(wrapper, attr),
|
||||
getattr(func, attr))
|
||||
|
||||
self.assertEqual(repr(wrapper), format_str.format(func))
|
||||
|
||||
self.assertRaises(TypeError, wrapper, 1)
|
||||
|
||||
def test_staticmethod(self):
|
||||
self.check_wrapper_attrs(staticmethod, '<staticmethod({!r})>')
|
||||
|
||||
def test_classmethod(self):
|
||||
self.check_wrapper_attrs(classmethod, '<classmethod({!r})>')
|
||||
|
||||
def test_dotted(self):
|
||||
decorators = MiscDecorators()
|
||||
|
|
|
@ -1545,7 +1545,9 @@ class D(C):
|
|||
self.assertEqual(d.foo(1), (d, 1))
|
||||
self.assertEqual(D.foo(d, 1), (d, 1))
|
||||
# Test for a specific crash (SF bug 528132)
|
||||
def f(cls, arg): return (cls, arg)
|
||||
def f(cls, arg):
|
||||
"f docstring"
|
||||
return (cls, arg)
|
||||
ff = classmethod(f)
|
||||
self.assertEqual(ff.__get__(0, int)(42), (int, 42))
|
||||
self.assertEqual(ff.__get__(0)(42), (int, 42))
|
||||
|
@ -1571,10 +1573,16 @@ def f(cls, arg): return (cls, arg)
|
|||
self.fail("classmethod shouldn't accept keyword args")
|
||||
|
||||
cm = classmethod(f)
|
||||
self.assertEqual(cm.__dict__, {})
|
||||
cm_dict = {'__annotations__': {},
|
||||
'__doc__': "f docstring",
|
||||
'__module__': __name__,
|
||||
'__name__': 'f',
|
||||
'__qualname__': f.__qualname__}
|
||||
self.assertEqual(cm.__dict__, cm_dict)
|
||||
|
||||
cm.x = 42
|
||||
self.assertEqual(cm.x, 42)
|
||||
self.assertEqual(cm.__dict__, {"x" : 42})
|
||||
self.assertEqual(cm.__dict__, {"x" : 42, **cm_dict})
|
||||
del cm.x
|
||||
self.assertNotHasAttr(cm, "x")
|
||||
|
||||
|
@ -1654,10 +1662,10 @@ class D(C):
|
|||
self.assertEqual(d.foo(1), (d, 1))
|
||||
self.assertEqual(D.foo(d, 1), (d, 1))
|
||||
sm = staticmethod(None)
|
||||
self.assertEqual(sm.__dict__, {})
|
||||
self.assertEqual(sm.__dict__, {'__doc__': None})
|
||||
sm.x = 42
|
||||
self.assertEqual(sm.x, 42)
|
||||
self.assertEqual(sm.__dict__, {"x" : 42})
|
||||
self.assertEqual(sm.__dict__, {"x" : 42, '__doc__': None})
|
||||
del sm.x
|
||||
self.assertNotHasAttr(sm, "x")
|
||||
|
||||
|
|
|
@ -1142,7 +1142,8 @@ def sm(x, y):
|
|||
'''A static method'''
|
||||
...
|
||||
self.assertEqual(self._get_summary_lines(X.__dict__['sm']),
|
||||
"<staticmethod object>")
|
||||
'sm(...)\n'
|
||||
' A static method\n')
|
||||
self.assertEqual(self._get_summary_lines(X.sm), """\
|
||||
sm(x, y)
|
||||
A static method
|
||||
|
@ -1162,7 +1163,8 @@ def cm(cls, x):
|
|||
'''A class method'''
|
||||
...
|
||||
self.assertEqual(self._get_summary_lines(X.__dict__['cm']),
|
||||
"<classmethod object>")
|
||||
'cm(...)\n'
|
||||
' A class method\n')
|
||||
self.assertEqual(self._get_summary_lines(X.cm), """\
|
||||
cm(x) method of builtins.type instance
|
||||
A class method
|
||||
|
|
|
@ -203,9 +203,9 @@ def test_descriptors(self):
|
|||
class C:
|
||||
def foo(cls): pass
|
||||
x = staticmethod(C.foo)
|
||||
self.assertTrue(repr(x).startswith('<staticmethod object at 0x'))
|
||||
self.assertEqual(repr(x), f'<staticmethod({C.foo!r})>')
|
||||
x = classmethod(C.foo)
|
||||
self.assertTrue(repr(x).startswith('<classmethod object at 0x'))
|
||||
self.assertEqual(repr(x), f'<classmethod({C.foo!r})>')
|
||||
|
||||
def test_unsortable(self):
|
||||
# Repr.repr() used to call sorted() on sets, frozensets and dicts
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
@ -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();
|
||||
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,
|
||||
|
@ -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 */
|
||||
};
|
||||
|
||||
|
@ -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\
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
};
|
||||
|
||||
|
@ -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\
|
||||
|
@ -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 */
|
||||
|
|
Loading…
Reference in a new issue