Skip to content

Commit a5a7cea

Browse files
authored
gh-100039: enhance __signature__ to work with str and callables (GH-100168)
Callables should be either class- or static-methods. Enum now uses the classmethod version to greatly improve the help given for enums and flags.
1 parent 5234e1c commit a5a7cea

File tree

5 files changed

+73
-4
lines changed

5 files changed

+73
-4
lines changed

Lib/enum.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,7 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
685685
'member order does not match _order_:\n %r\n %r'
686686
% (enum_class._member_names_, _order_)
687687
)
688-
688+
#
689689
return enum_class
690690

691691
def __bool__(cls):
@@ -1083,6 +1083,13 @@ class Enum(metaclass=EnumType):
10831083
attributes -- see the documentation for details.
10841084
"""
10851085

1086+
@classmethod
1087+
def __signature__(cls):
1088+
if cls._member_names_:
1089+
return '(*values)'
1090+
else:
1091+
return '(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)'
1092+
10861093
def __new__(cls, value):
10871094
# all enum instances are actually created during class construction
10881095
# without calling this method; this method is called by the metaclass'

Lib/inspect.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -2443,10 +2443,18 @@ def _signature_from_callable(obj, *,
24432443
pass
24442444
else:
24452445
if sig is not None:
2446+
# since __text_signature__ is not writable on classes, __signature__
2447+
# may contain text (or be a callable that returns text);
2448+
# if so, convert it
2449+
o_sig = sig
2450+
if not isinstance(sig, (Signature, str)) and callable(sig):
2451+
sig = sig()
2452+
if isinstance(sig, str):
2453+
sig = _signature_fromstr(sigcls, obj, sig)
24462454
if not isinstance(sig, Signature):
24472455
raise TypeError(
24482456
'unexpected object {!r} in __signature__ '
2449-
'attribute'.format(sig))
2457+
'attribute'.format(o_sig))
24502458
return sig
24512459

24522460
try:

Lib/test/test_enum.py

+23-2
Original file line numberDiff line numberDiff line change
@@ -4259,7 +4259,7 @@ class TestEnumTypeSubclassing(unittest.TestCase):
42594259
Help on class Color in module %s:
42604260
42614261
class Color(enum.Enum)
4262-
| Color(value, names=None, *values, module=None, qualname=None, type=None, start=1, boundary=None)
4262+
| Color(*values)
42634263
|
42644264
| Method resolution order:
42654265
| Color
@@ -4315,7 +4315,7 @@ class Color(enum.Enum)
43154315
Help on class Color in module %s:
43164316
43174317
class Color(enum.Enum)
4318-
| Color(value, names=None, *values, module=None, qualname=None, type=None, start=1)
4318+
| Color(*values)
43194319
|
43204320
| Method resolution order:
43214321
| Color
@@ -4462,6 +4462,27 @@ def test_inspect_classify_class_attrs(self):
44624462
if failed:
44634463
self.fail("result does not equal expected, see print above")
44644464

4465+
def test_inspect_signatures(self):
4466+
from inspect import signature, Signature, Parameter
4467+
self.assertEqual(
4468+
signature(Enum),
4469+
Signature([
4470+
Parameter('new_class_name', Parameter.POSITIONAL_ONLY),
4471+
Parameter('names', Parameter.POSITIONAL_OR_KEYWORD),
4472+
Parameter('module', Parameter.KEYWORD_ONLY, default=None),
4473+
Parameter('qualname', Parameter.KEYWORD_ONLY, default=None),
4474+
Parameter('type', Parameter.KEYWORD_ONLY, default=None),
4475+
Parameter('start', Parameter.KEYWORD_ONLY, default=1),
4476+
Parameter('boundary', Parameter.KEYWORD_ONLY, default=None),
4477+
]),
4478+
)
4479+
self.assertEqual(
4480+
signature(enum.FlagBoundary),
4481+
Signature([
4482+
Parameter('values', Parameter.VAR_POSITIONAL),
4483+
]),
4484+
)
4485+
44654486
def test_test_simple_enum(self):
44664487
@_simple_enum(Enum)
44674488
class SimpleColor:

Lib/test/test_inspect.py

+32
Original file line numberDiff line numberDiff line change
@@ -3614,6 +3614,38 @@ def foo(): pass
36143614
self.assertEqual(signature_func(foo), inspect.Signature())
36153615
self.assertEqual(inspect.get_annotations(foo), {})
36163616

3617+
def test_signature_as_str(self):
3618+
self.maxDiff = None
3619+
class S:
3620+
__signature__ = '(a, b=2)'
3621+
3622+
self.assertEqual(self.signature(S),
3623+
((('a', ..., ..., 'positional_or_keyword'),
3624+
('b', 2, ..., 'positional_or_keyword')),
3625+
...))
3626+
3627+
def test_signature_as_callable(self):
3628+
# __signature__ should be either a staticmethod or a bound classmethod
3629+
class S:
3630+
@classmethod
3631+
def __signature__(cls):
3632+
return '(a, b=2)'
3633+
3634+
self.assertEqual(self.signature(S),
3635+
((('a', ..., ..., 'positional_or_keyword'),
3636+
('b', 2, ..., 'positional_or_keyword')),
3637+
...))
3638+
3639+
class S:
3640+
@staticmethod
3641+
def __signature__():
3642+
return '(a, b=2)'
3643+
3644+
self.assertEqual(self.signature(S),
3645+
((('a', ..., ..., 'positional_or_keyword'),
3646+
('b', 2, ..., 'positional_or_keyword')),
3647+
...))
3648+
36173649

36183650
class TestParameterObject(unittest.TestCase):
36193651
def test_signature_parameter_kinds(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve signatures for enums and flags.

0 commit comments

Comments
 (0)