8000 gh-118285: Fix signatures of operator.{attrgetter,itemgetter,methodca… · python/cpython@444ac0b · GitHub
[go: up one dir, main page]

Skip to content

Commit 444ac0b

Browse files
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.
1 parent 51c70de commit 444ac0b

File tree

6 files changed

+76
-5
lines changed

6 files changed

+76
-5
lines changed

Lib/inspect.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,6 +2692,13 @@ def _signature_from_callable(obj, *,
26922692
# An object with __call__
26932693
call = getattr_static(type(obj), '__call__', None)
26942694
if call is not None:
2695+
try:
2696+
text_sig = obj.__text_signature__
2697+
except AttributeError:
2698+
pass
2699+
else:
2700+
if text_sig:
2701+
return _signature_fromstr(sigcls, obj, text_sig)
26952702
call = _descriptor_get(call, obj)
26962703
return _get_signature_of(call)
26972704

Lib/operator.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ class attrgetter:
239239
"""
240240
__slots__ = ('_attrs', '_call')
241241

242-
def __init__(self, attr, *attrs):
242+
def __init__(self, attr, /, *attrs):
243243
if not attrs:
244244
if not isinstance(attr, str):
245245
raise TypeError('attribute name must be a string')
@@ -257,7 +257,7 @@ def func(obj):
257257
return tuple(getter(obj) for getter in getters)
258258
self._call = func
259259

260-
def __call__(self, obj):
260+
def __call__(self, obj, /):
261261
return self._call(obj)
262262

263263
def __repr__(self):
@@ -276,7 +276,7 @@ class itemgetter:
276276
"""
277277
__slots__ = ('_items', '_call')
278278

279-
def __init__(self, item, *items):
279+
def __init__(self, item, /, *items):
280280
if not items:
281281
self._items = (item,)
282282
def func(obj):
@@ -288,7 +288,7 @@ def func(obj):
288288
return tuple(obj[i] for i in items)
289289
self._call = func
290290

291-
def __call__(self, obj):
291+
def __call__(self, obj, /):
292292
return self._call(obj)
293293

294294
def __repr__(self):
@@ -315,7 +315,7 @@ def __init__(self, name, /, *args, **kwargs):
315315
self._args = args
316316
self._kwargs = kwargs
317317

318-
def __call__(self, obj):
318+
def __call__(self, obj, /):
319319
return getattr(obj, self._name)(*self._args, **self._kwargs)
320320

321321
def __repr__(self):

Lib/test/test_inspect/test_inspect.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4090,6 +4090,28 @@ class C:
40904090
((('a', ..., ..., "positional_or_keyword"),),
40914091
...))
40924092

4093+
def test_signature_on_callable_objects_with_text_signature_attr(self):
4094+
class C:
4095+
__text_signature__ = '(a, /, b, c=True)'
4096+
def __call__(self, *args, **kwargs):
4097+
pass
4098+
4099+
self.assertEqual(self.signature(C), ((), ...))
4100+
self.assertEqual(self.signature(C()),
4101+
((('a', ..., ..., "positional_only"),
4102+
('b', ..., ..., "positional_or_keyword"),
4103+
('c', True, ..., "positional_or_keyword"),
4104+
),
4105+
...))
4106+
4107+
c = C()
4108+
c.__text_signature__ = '(x, y)'
4109+
self.assertEqual(self.signature(c),
4110+
((('x', ..., ..., "positional_or_keyword"),
4111+
('y', ..., ..., "positional_or_keyword"),
4112+
),
4113+
...))
4114+
40934115
def test_signature_on_wrapper(self):
40944116
class Wrapper:
40954117
def __call__(self, b):

Lib/test/test_operator.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
import inspect
23
import pickle
34
import sys
45
from decimal import Decimal
@@ -602,6 +603,28 @@ def test_dunder_is_original(self):
602603
if dunder:
603604
self.assertIs(dunder, orig)
604605

606+
def test_attrgetter_signature(self):
607+
operator = self.module
608+
sig = inspect.signature(operator.attrgetter)
609+
self.assertEqual(str(sig), '(attr, /, *attrs)')
610+
sig = inspect.signature(operator.attrgetter('x', 'z', 'y'))
611+
self.assertEqual(str(sig), '(obj, /)')
612+
613+
def test_itemgetter_signature(self):
614+
operator = self.module
615+
sig = inspect.signature(operator.itemgetter)
616+
self.assertEqual(str(sig), '(item, /, *items)')
617+
sig = inspect.signature(operator.itemgetter(2, 3, 5))
618+
self.assertEqual(str(sig), '(obj, /)')
619+
620+
def test_methodcaller_signature(self):
621+
operator = self.module
622+
sig = inspect.signature(operator.methodcaller)
623+
self.assertEqual(str(sig), '(name, /, *args, **kwargs)')
624+
sig = inspect.signature(operator.methodcaller('foo', 2, y=3))
625+
self.assertEqual(str(sig), '(obj, /)')
626+
627+
605628
class PyOperatorTestCase(OperatorTestCase, unittest.TestCase):
606629
module = py_operator
607630

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Allow to specify the signature of custom callable instances of extension
2+
type by the :attr:`__text_signature__` attribute. Specify signatures of
3+
:class:`operator.attrgetter`, :class:`operator.itemgetter`, and
4+
:class:`operator.methodcaller` instances.

Modules/_operator.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,18 @@ static struct PyMethodDef operator_methods[] = {
966966

967967
};
968968

969+
970+
static PyObject *
971+
text_signature(PyObject *self, void *Py_UNUSED(ignored))
972+
{
973+
return PyUnicode_FromString("(obj, /)");
974+
}
975+
976+
static PyGetSetDef common_getset[] = {
977+
{"__text_signature__", text_signature, (setter)NULL},
978+
{NULL}
979+
};
980+
969981
/* itemgetter object **********************************************************/
970982

971983
typedef struct {
@@ -1171,6 +1183,7 @@ static PyType_Slot itemgetter_type_slots[] = {
11711183
{Py_tp_clear, itemgetter_clear},
11721184
{Py_tp_methods, itemgetter_methods},
11731185
{Py_tp_members, itemgetter_members},
1186+
{Py_tp_getset, common_getset},
11741187
{Py_tp_new, itemgetter_new},
11751188
{Py_tp_getattro, PyObject_GenericGetAttr},
11761189
{Py_tp_repr, itemgetter_repr},
@@ -1528,6 +1541,7 @@ static PyType_Slot attrgetter_type_slots[] = {
15281541
{Py_tp_clear, attrgetter_clear},
15291542
{Py_tp_methods, attrgetter_methods},
15301543
{Py_tp_members, attrgetter_members},
1544+
{Py_tp_getset, common_getset},
15311545
{Py_tp_new, attrgetter_new},
15321546
{Py_tp_getattro, PyObject_GenericGetAttr},
15331547
{Py_tp_repr, attrgetter_repr},
@@ -1863,6 +1877,7 @@ static PyType_Slot methodcaller_type_slots[] = {
18631877
{Py_tp_clear, methodcaller_clear},
18641878
{Py_tp_methods, methodcaller_methods},
18651879
{Py_tp_members, methodcaller_members},
1880+
{Py_tp_getset, common_getset},
18661881
{Py_tp_new, methodcaller_new},
18671882
{Py_tp_getattro, PyObject_GenericGetAttr},
18681883
{Py_tp_repr, methodcaller_repr},

0 commit comments

Comments
 (0)
0