-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-108901: Add Signature.from_code
method
#108902
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
Changes from all commits
566790c
20875cb
604f25b
d69623b
b3e7978
efdb617
77da9c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3674,6 +3674,43 @@ def test_signature_on_noncallable_mocks(self): | |
with self.assertRaises(TypeError): | ||
inspect.signature(mock) | ||
|
||
def test_signature_from_code(self): | ||
def func1( | ||
a, b: int, | ||
/, | ||
c, d: str = 'a', | ||
*args: int, | ||
e, f: bool = True, | ||
**kwargs: str, | ||
) -> float: | ||
... | ||
|
||
def func2(a: str, b: int = 0, /): ... | ||
def func3(): ... | ||
def func4(*a, **k): ... | ||
def func5(*, kw=False): ... | ||
|
||
known_sigs = { | ||
func1: '(a, b, /, c, d, *args, e, f, **kwargs)', | ||
func2: '(a, b, /)', | ||
func3: '()', | ||
func4: '(*a, **k)', | ||
func5: '(*, kw)', | ||
} | ||
Comment on lines
+3693
to
+3699
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I worry that the reason this wasn't implemented in the original PEP-362 implementation is because the signatures produced for
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmmm... well, they are actually valid signatures for the code object, since if you pass the code object to >>> def foo(x=42): print(x)
...
>>> foo()
42
>>> c = foo.__code__
>>> exec(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() missing 1 required positional argument: 'x' Why anybody would care what the signature of the code object itself is, though, I'm not sure -- ISTM that everybody trying to get the signature from a code object is just using the code object as the next-best-thing for the function the code object belongs to. I don't think many people are passing code objects directly to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we just need to flag more forcefully in the docs the limitations of this new constructor method. I can offer some suggestions once you've addressed the review comments I've already posted 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The more I think about this, the more concerned I am :/ The existing
This new function, however, claims to be able to give you a complete and accurate signature from a code object (in that it constructs a I'm not sure having a prominent note in the docs is sufficient to fix this issue: adding a new constructor that we know gives inaccurate results seems very troubling to me. Perhaps a better solution would be to document There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True. This new API would provide helpful information that is not currently offered by However. Does it make sense to think of code objects even having a "signature"? Functions have signatures, and you can construct functions from code objects. But as your snippet above demonstrates, you can't directly call code objects (except via Which "signature" are we trying to get here? The signature of the function from which the code object originally came (if it came from a code object), or the signature a new function object would have if we created one from the code object? Getting a "signature from a code object" feels like quite a confusing concept to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option: we could document that if people want to get "signature information" from a code object inspect.signature(types.FunctionType(co, {})) That way, they are forced to be explicit that code objects don't really have a "signature": you need to construct a new function from the code object in order to get a code object that actually has a signature There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. >>> import types, inspect
>>> def a(x: int, /, *, y: str = '') -> None: ...
...
>>> inspect.signature(types.FunctionType(a.__code__, {}))
<Signature (x, /, *, y)> It is up to you to decide whether or not we should do it. I consider this PR done from my side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently, I'm leaning towards not adding this alternative constructor, and just documenting that people should call One of the third-party projects that uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any code using that qcore function should really be migrated to use I think we don't need |
||
|
||
for test_func, expected_sig in known_sigs.items(): | ||
with self.subTest(test_func=test_func, expected_sig=expected_sig): | ||
self.assertEqual( | ||
str(inspect.Signature.from_code(test_func.__code__)), | ||
expected_sig, | ||
) | ||
|
||
with self.assertRaisesRegex( | ||
TypeError, | ||
"code object was expected, got 'int'", | ||
): | ||
inspect.Signature.from_code(1) | ||
|
||
def test_signature_equality(self): | ||
def foo(a, *, b:int) -> float: pass | ||
self.assertFalse(inspect.signature(foo) == 42) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Add :meth:`inspect.Signature.from_code` to be able | ||
to construct :class:`inspect.Signature` objects from :class:`types.CodeType`. |
Uh oh!
There was an error while loading. Please reload this page.