# How frequently does *untyped* Python code use
complex callbacks?
I'd shared stats earlier about Callable usage in typed Python
code [1]. By looking at the source code for stubbed files, we'd
seen that practically all of the Callable uses in typed Python
projects were either (a) expressible using positional-only
parameters, (b) expressible using ParamSpec or TypeVarTuple, or
(c) simply too dynamic.
The remaining possible source of callables is *untyped* Python
code. Perhaps such code has a lot of callbacks that requires
named parameters, default values, etc. Untyped Python is not
constrained by our Callable type syntax, so if we are going to
find complex uses anywhere, it will be in untyped Python.
I tested the above claim by looking at a few large, untyped,
open-source Python projects (Django with 80k+ LOC of Python,
Sentry with 150k+ LOC of Python).
Findings [2]:
+ About half of the callbacks were called with just positional
arguments: `func(a, b)`.
+ More than a third of callbacks were called with `*args,
**kwargs`: `func(*args, **kwargs)` or `func(prefix, *args,
**kwargs)`. These would need ParamSpec.
+ The remaining ~13% were called with only `*args` or were too
dynamic.
This is in line with the pattern I saw for typed Python projects
[3]: 90+% of callbacks were either positional-only or ParamSpec.
# How often are named arguments used for callbacks?
Very rarely. I saw a couple of examples and they were basically
all cases where the "callback" was a class, not a function:
```
def _generate_altered_foo_together(self, operation):
x = operation(
name=model_name,
**{option_name: new_value}
)
self._generate_altered_foo_together(operations.AlterUniqueTogether)
self._generate_altered_foo_together(operations.AlterIndexTogether)
```
In other words, the type would not be `Callable`. It would be
`Type[ModelOperation]`:
```
def _generate_altered_foo_together(self, operation:
Type[ModelOperation]):
```
# Conclusion
Based on the above stats, I feel confident that the vast
majority of cases can be handled by the shorthand syntax
proposal:
+ positional-only parameters: `(int, str) -> bool` instead
of Callable[[int, str], bool]
+ ParamSpec: `(**P) -> R` instead of Callable[P, R]
Given how frequently ParamSpec is needed (20-30% of all
callbacks), the simpler syntax above would lower the barrier to
better types and increase type safety. More details are in the
syntax proposal doc under the name "shorthand syntax". [4]
Finally, in order to support any remaining edge cases, we could
adopt
Łukasz's
proposal about using the name of a function as a type (like we
do with classes). This would be easier to use than a callback
protocol.
At this point, if we want to add extra syntax for things like
named parameters, default values, etc., I think we should
justify the extra complexity using real-world cases. If not, I
don't feel it's worth adding them and going through a ton of
bikeshedding for something that is not going to add much value
to the above. None of the syntax proposals handle overloads, so
no syntax will capture all the cases that a callback protocol
can. We have to draw the line somewhere; I suggest we draw it at
the shorthand proposal.
# Footnotes
[1]: Earlier mail with stats for typeshed:
https://mail.python.org/archives/list/typing-sig@python.org/message/TTPVR7QUK6MGSYRYMHHR4O23OZG6B6VP/
[2]: Summary of stats for untyped projects:
https://github.com/pradeep90/annotation_collector#projects-with-no-types
List of callbacks in Django:
https://github.com/pradeep90/annotation_collector/blob/master/data/django-callback-parameters.txt
I updated the libcst script to collect functions that called one
of their parameters. This helped automatically analyze untyped
code. I cross-checked that by sampling a significant number of
functions (~400) from each project and manually checking how
callbacks were used. I also looked for functions and parameters
that had suggestive names like `func` or `callback` and checked
how they were used.
[3]: Stats for many large, typed Python projects:
https://github.com/pradeep90/annotation_collector#stats
[4]: Callable syntax proposals document:
https://docs.google.com/document/d/11VkNk9N8whxS1SrBv2BNN1Csl5yeTlgG7WyLrCB5wgs/edit#heading=h.s6u1i3i5ct8r