WIP gh-103741 - PEP 713 - Callable modules#103742
WIP gh-103741 - PEP 713 - Callable modules#103742amyreese wants to merge 4 commits intopython:mainfrom
Conversation
When making calls to core types, if no `tp_call` value is found (the type isn't default callable) and the type in question is a `ModuleType`, then `_PyObject_MakeTpCall` will use getattr to check for a `__call__` attribute on the module object, and attempt to call that instead. When checking if an object is callable, the initial `tp_call` lookup doesn't find a value, and the object type is a module, a similar getattr for `__call__` is made, and a callable check is made against that value instead. This is implemented this way, rather than by adding a `tp_call` method directly to the base module type, as not all modules will be callable, and the existence of a method wrapper makes it difficult to determine if the module in question is actually callable, or just has a method wrapper. This eliminates the need for isinstance checks and special error handling in said method wrapper to make non-callable modules continue to appear as non-callable objects.
Fidget-Spinner
left a comment
There was a problem hiding this comment.
Thanks for the effort in writing this PEP. I have some minor comments below.
|
Adding the do not merge label, will remove when the PEP is approved. |
|
Also, I'm unsure why CI is failing during build; I can build this PR on my Mac Mini and my Debian box without any errors. |
|
CI is failing because of the test suite. run |
Adds a method wrapper for module objects that makes them callable. When called, checks for a __call__ object on the module and calls that instead, otherwise raises type error declaring the module is not callable. Adds a check in module_getattro to prevent returning the method wrapper when looking for __call__, to prevent circular lookups when calling the module and checking for __call__ set by module authors. Adds PyModule_Callable helper function to determine if a module is callable, by looking for a __call__ property and calling the normal PyCallable_Check on that object, if it exists. Updates PyCallable_Check to use the new PyModule_Callable helper when checking module objects, to correctly look for a callable __call__ property rather than the normal tp_call attribute on the module's type struct. Tested invariants: - For modules without __call__ (eg, stdlib): - callable(mod) -> False - mod.__call__ -> AttributeError - mod() -> TypeError - For module with __call__ or __getattr__ returning __call__ object - callable(mod) -> callable(mod.__call__) - mod.__call__ -> real object - mod() -> mod.__call__() Setting (or unsetting) __call__ on existing module objects makes them callable (or uncallable) accordingly, without changing the type or class of the module object. Co-authored-by: Hood Chatham <[email protected]>
|
Replaced the first draft with an implementation based on setting |
erlend-aasland
left a comment
There was a problem hiding this comment.
You also need to add NEWS and What's New entries.
| del sys.__call__ | ||
|
|
||
| def test_callable_modules(self): | ||
| from . import callable_module_a, callable_module_b, callable_module_c |
There was a problem hiding this comment.
I'd prefer inlining these instead of polluting the Lib/test namespace with the added files. Alternatively, put them in a subdirectory.
| { | ||
| if (x == NULL) | ||
| return 0; | ||
| if (PyModule_CheckExact(x)) { |
There was a problem hiding this comment.
This excludes subclasses of ModuleType.
| #endif | ||
| PyAPI_FUNC(PyModuleDef*) PyModule_GetDef(PyObject*); | ||
| PyAPI_FUNC(void*) PyModule_GetState(PyObject*); | ||
| PyAPI_FUNC(int) PyModule_Callable(PyObject *); |
There was a problem hiding this comment.
This should be in Include/cpython/*.h or in an #ifndef Py_LIMITED_API block.
Or, if you want to add it to the limited API, see docs in the devguide.
|
It looks like it did not make it to 3.12. |
The SC has not even decided about the PEP yet (see python/steering-council#191). |
|
Closing for now, since the PEP was rejected by the Steering Council. But thank you for writing PEP-713 -- it was a really interesting discussion, I thought! |
Draft implementation of PEP 713:
__call__property.Adds a method wrapper for module objects that makes them callable.
When called, checks for a
__call__object on the module and callsthat instead, otherwise raises type error declaring the module
is not callable.
Adds a check in
module_getattroto prevent returning the methodwrapper when looking for
__call__, to prevent circular lookupswhen calling the module and checking for
__call__set by moduleauthors.
Adds
PyModule_Callablehelper function to determine if a moduleis callable, by looking for a
__call__property and calling thenormal
PyCallable_Checkon that object, if it exists.Updates
PyCallable_Checkto use the newPyModule_Callablehelperwhen checking module objects, to correctly look for a callable
__call__property rather than the normaltp_callattribute on themodule's type struct.
Tested invariants:
For modules without
__call__(eg, stdlib):callable(mod)->Falsemod.__call__->AttributeErrormod()->TypeErrorFor module with
__call__or__getattr__returning__call__objectcallable(mod)->callable(mod.__call__)mod.__call__-> real objectmod()->mod.__call__()Setting (or unsetting)
__call__on existing module objects makesthem callable (or uncallable) accordingly, without changing the type
or class of the module object.