mirror of https://github.com/python/cpython
gh-118285: Fix signatures of operator.{attrgetter,itemgetter,methodcaller} instances (GH-118316)
* Allow to specify the signature of custom callable instances of extension type by the __text_signature__ attribute. * Specify signatures of operator.attrgetter, operator.itemgetter, and operator.methodcaller instances.
This commit is contained in:
parent
51c70de998
commit
444ac0b7a6
|
@ -2692,6 +2692,13 @@ def _signature_from_callable(obj, *,
|
||||||
# An object with __call__
|
# An object with __call__
|
||||||
call = getattr_static(type(obj), '__call__', None)
|
call = getattr_static(type(obj), '__call__', None)
|
||||||
if call is not None:
|
if call is not None:
|
||||||
|
try:
|
||||||
|
text_sig = obj.__text_signature__
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if text_sig:
|
||||||
|
return _signature_fromstr(sigcls, obj, text_sig)
|
||||||
call = _descriptor_get(call, obj)
|
call = _descriptor_get(call, obj)
|
||||||
return _get_signature_of(call)
|
return _get_signature_of(call)
|
||||||
|
|
||||||
|
|
|
@ -239,7 +239,7 @@ class attrgetter:
|
||||||
"""
|
"""
|
||||||
__slots__ = ('_attrs', '_call')
|
__slots__ = ('_attrs', '_call')
|
||||||
|
|
||||||
def __init__(self, attr, *attrs):
|
def __init__(self, attr, /, *attrs):
|
||||||
if not attrs:
|
if not attrs:
|
||||||
if not isinstance(attr, str):
|
if not isinstance(attr, str):
|
||||||
raise TypeError('attribute name must be a string')
|
raise TypeError('attribute name must be a string')
|
||||||
|
@ -257,7 +257,7 @@ class attrgetter:
|
||||||
return tuple(getter(obj) for getter in getters)
|
return tuple(getter(obj) for getter in getters)
|
||||||
self._call = func
|
self._call = func
|
||||||
|
|
||||||
def __call__(self, obj):
|
def __call__(self, obj, /):
|
||||||
return self._call(obj)
|
return self._call(obj)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -276,7 +276,7 @@ class itemgetter:
|
||||||
"""
|
"""
|
||||||
__slots__ = ('_items', '_call')
|
__slots__ = ('_items', '_call')
|
||||||
|
|
||||||
def __init__(self, item, *items):
|
def __init__(self, item, /, *items):
|
||||||
if not items:
|
if not items:
|
||||||
self._items = (item,)
|
self._items = (item,)
|
||||||
def func(obj):
|
def func(obj):
|
||||||
|
@ -288,7 +288,7 @@ class itemgetter:
|
||||||
return tuple(obj[i] for i in items)
|
return tuple(obj[i] for i in items)
|
||||||
self._call = func
|
self._call = func
|
||||||
|
|
||||||
def __call__(self, obj):
|
def __call__(self, obj, /):
|
||||||
return self._call(obj)
|
return self._call(obj)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -315,7 +315,7 @@ class methodcaller:
|
||||||
self._args = args
|
self._args = args
|
||||||
self._kwargs = kwargs
|
self._kwargs = kwargs
|
||||||
|
|
||||||
def __call__(self, obj):
|
def __call__(self, obj, /):
|
||||||
return getattr(obj, self._name)(*self._args, **self._kwargs)
|
return getattr(obj, self._name)(*self._args, **self._kwargs)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
|
@ -4090,6 +4090,28 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
((('a', ..., ..., "positional_or_keyword"),),
|
((('a', ..., ..., "positional_or_keyword"),),
|
||||||
...))
|
...))
|
||||||
|
|
||||||
|
def test_signature_on_callable_objects_with_text_signature_attr(self):
|
||||||
|
class C:
|
||||||
|
__text_signature__ = '(a, /, b, c=True)'
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(self.signature(C), ((), ...))
|
||||||
|
self.assertEqual(self.signature(C()),
|
||||||
|
((('a', ..., ..., "positional_only"),
|
||||||
|
('b', ..., ..., "positional_or_keyword"),
|
||||||
|
('c', True, ..., "positional_or_keyword"),
|
||||||
|
),
|
||||||
|
...))
|
||||||
|
|
||||||
|
c = C()
|
||||||
|
c.__text_signature__ = '(x, y)'
|
||||||
|
self.assertEqual(self.signature(c),
|
||||||
|
((('x', ..., ..., "positional_or_keyword"),
|
||||||
|
('y', ..., ..., "positional_or_keyword"),
|
||||||
|
),
|
||||||
|
...))
|
||||||
|
|
||||||
def test_signature_on_wrapper(self):
|
def test_signature_on_wrapper(self):
|
||||||
class Wrapper:
|
class Wrapper:
|
||||||
def __call__(self, b):
|
def __call__(self, b):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
import inspect
|
||||||
import pickle
|
import pickle
|
||||||
import sys
|
import sys
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
@ -602,6 +603,28 @@ class OperatorTestCase:
|
||||||
if dunder:
|
if dunder:
|
||||||
self.assertIs(dunder, orig)
|
self.assertIs(dunder, orig)
|
||||||
|
|
||||||
|
def test_attrgetter_signature(self):
|
||||||
|
operator = self.module
|
||||||
|
sig = inspect.signature(operator.attrgetter)
|
||||||
|
self.assertEqual(str(sig), '(attr, /, *attrs)')
|
||||||
|
sig = inspect.signature(operator.attrgetter('x', 'z', 'y'))
|
||||||
|
self.assertEqual(str(sig), '(obj, /)')
|
||||||
|
|
||||||
|
def test_itemgetter_signature(self):
|
||||||
|
operator = self.module
|
||||||
|
sig = inspect.signature(operator.itemgetter)
|
||||||
|
self.assertEqual(str(sig), '(item, /, *items)')
|
||||||
|
sig = inspect.signature(operator.itemgetter(2, 3, 5))
|
||||||
|
self.assertEqual(str(sig), '(obj, /)')
|
||||||
|
|
||||||
|
def test_methodcaller_signature(self):
|
||||||
|
operator = self.module
|
||||||
|
sig = inspect.signature(operator.methodcaller)
|
||||||
|
self.assertEqual(str(sig), '(name, /, *args, **kwargs)')
|
||||||
|
sig = inspect.signature(operator.methodcaller('foo', 2, y=3))
|
||||||
|
self.assertEqual(str(sig), '(obj, /)')
|
||||||
|
|
||||||
|
|
||||||
class PyOperatorTestCase(OperatorTestCase, unittest.TestCase):
|
class PyOperatorTestCase(OperatorTestCase, unittest.TestCase):
|
||||||
module = py_operator
|
module = py_operator
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Allow to specify the signature of custom callable instances of extension
|
||||||
|
type by the :attr:`__text_signature__` attribute. Specify signatures of
|
||||||
|
:class:`operator.attrgetter`, :class:`operator.itemgetter`, and
|
||||||
|
:class:`operator.methodcaller` instances.
|
|
@ -966,6 +966,18 @@ static struct PyMethodDef operator_methods[] = {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
text_signature(PyObject *self, void *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
return PyUnicode_FromString("(obj, /)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyGetSetDef common_getset[] = {
|
||||||
|
{"__text_signature__", text_signature, (setter)NULL},
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
/* itemgetter object **********************************************************/
|
/* itemgetter object **********************************************************/
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -1171,6 +1183,7 @@ static PyType_Slot itemgetter_type_slots[] = {
|
||||||
{Py_tp_clear, itemgetter_clear},
|
{Py_tp_clear, itemgetter_clear},
|
||||||
{Py_tp_methods, itemgetter_methods},
|
{Py_tp_methods, itemgetter_methods},
|
||||||
{Py_tp_members, itemgetter_members},
|
{Py_tp_members, itemgetter_members},
|
||||||
|
{Py_tp_getset, common_getset},
|
||||||
{Py_tp_new, itemgetter_new},
|
{Py_tp_new, itemgetter_new},
|
||||||
{Py_tp_getattro, PyObject_GenericGetAttr},
|
{Py_tp_getattro, PyObject_GenericGetAttr},
|
||||||
{Py_tp_repr, itemgetter_repr},
|
{Py_tp_repr, itemgetter_repr},
|
||||||
|
@ -1528,6 +1541,7 @@ static PyType_Slot attrgetter_type_slots[] = {
|
||||||
{Py_tp_clear, attrgetter_clear},
|
{Py_tp_clear, attrgetter_clear},
|
||||||
{Py_tp_methods, attrgetter_methods},
|
{Py_tp_methods, attrgetter_methods},
|
||||||
{Py_tp_members, attrgetter_members},
|
{Py_tp_members, attrgetter_members},
|
||||||
|
{Py_tp_getset, common_getset},
|
||||||
{Py_tp_new, attrgetter_new},
|
{Py_tp_new, attrgetter_new},
|
||||||
{Py_tp_getattro, PyObject_GenericGetAttr},
|
{Py_tp_getattro, PyObject_GenericGetAttr},
|
||||||
{Py_tp_repr, attrgetter_repr},
|
{Py_tp_repr, attrgetter_repr},
|
||||||
|
@ -1863,6 +1877,7 @@ static PyType_Slot methodcaller_type_slots[] = {
|
||||||
{Py_tp_clear, methodcaller_clear},
|
{Py_tp_clear, methodcaller_clear},
|
||||||
{Py_tp_methods, methodcaller_methods},
|
{Py_tp_methods, methodcaller_methods},
|
||||||
{Py_tp_members, methodcaller_members},
|
{Py_tp_members, methodcaller_members},
|
||||||
|
{Py_tp_getset, common_getset},
|
||||||
{Py_tp_new, methodcaller_new},
|
{Py_tp_new, methodcaller_new},
|
||||||
{Py_tp_getattro, PyObject_GenericGetAttr},
|
{Py_tp_getattro, PyObject_GenericGetAttr},
|
||||||
{Py_tp_repr, methodcaller_repr},
|
{Py_tp_repr, methodcaller_repr},
|
||||||
|
|
Loading…
Reference in New Issue