Make the built-in callable function perform double duty of typing.Callable, and iter for Iterable

Just like how tuple and the likes were made to perform double duty of typing.Tuple, etc. for code cleanliness, I think typing.Callable/collections.abc.Callable is used often enough that having to import it wherever it’s used becomes an unnecessary boilerplate, and that it is justified to turn the built-in function callable into a generic protocol class that performs both the original functionality of callable when its __new__ method is called and the functionality of Callable when its __instancecheck__ method and its __class_getitem__ method are called.

The only backwards incompatibility would be that callable would become a class, but since AFAIK the type of callable is really an undocumented implementation detail, I think changing its type should be fine.

Similarly, we can make iter perform double duty of typing.Iterable/collections.abc.Iterable, also an often needed type hint.

What does everyone think?

4 Likes

callable: I like it, although unlike (I think) all other such classes, callable() does not produce a callable.

iter: Produces an Iterator, not an Iterable. It would be odd to use it to stand in for Iterable, but Iterator is almost certainly rarer. I doubt the potential confusion is worth it.

6 Likes

Yes, I can totally see how iter can theoretically be confused as collections.abc.Iterator, but the latter is used so rarely that I think most will be able to quickly come to terms with iter standing for typing.Iterable when used in the context of a type hint. That’s just my gut feeling however, so we’ll see how the consensus goes.

Regarding callable(obj) not producing a callable, I think we can draw parallel to how type(obj) is also used like a regular function that does not produce a new type and is implemented by abusing __new__.

1 Like

The convenience of this (or some other way to have the same) is very desirable and tempting to me.

However, the inconsistencies pointed out are just slightly too big.

But it does produce a new type when called with more arguments. And the return of type(obj) is of type type. callable is quite far away from it in terms of fitness for such.

Having that said, I am much more positive on callable than iter. I think I am slightly positive on it by now.

There is no issue of semantics with it. It checks for callable and would represent a callable.

While iter extracts iterator, but would represent an iterable.

It has taken me a fair bit of time to properly memorise which is which. Although I myself would be able to live with this now, but this is a big risk to produce a fair bit of additional confusion for those who are just learning these concepts.

1 Like

There’s not a perfect correlation with tuple since tuple creates tuples (just as some class C typically creates objects of type C) whereas callable doesn’t create callables, but rather checks if an object is callable. Therefore, I prefer the semantic separation of Callable the type annotation with callable the function. Saving an import is not worth it to me to add any confusion in the code.

I also find it a bit odd that so many ideas revolve around saving imports. Do your editors not auto-import for you? That seems to me like a better solution to the pain of writing the import. And I don’t see there being any pain to reading imports.

However, now that type annotations are so incredibly common, I wonder how realistic it would it be for all of collections.abc to be merged into builtins? Would this cause any unwanted collisions? Does it slow down startup time too much?

3 Likes

Rather than changing the actual builtins, it will be nicer if type checkers just assume that if some annotation is not defined, it is imported from typing (and enforce from __future__ import annotations to avoid runtime errors).

That unfortunately won’t be compatible with PEP 649!

1 Like

There has been a discussion about this idea here already.

I would suggest to not move this thread off topic by discussing this further here.

1 Like

My intention was to piggyback on existing built-in names to avoid any namespace pollution, but making some commonly used typing aliases built-in sounds like a good alternative. It was why itertools.izip was made a built-in name after realizing that at some point having to import izip everywhere became a boilerplate.

Other than Callable and Iterable, the only other names in collections.abc that I think are used commonly enough to justify being made readily available are Sequence and Mapping. And perhaps typing.Any. Making all of collections.abc names part of builtins would be too much IMHO.

Since the builtins namespace is looked up only after locals and globals, I don’t think adding new built-in names would cause meaningful name collision issues to existing code base.

I like the idea of making callable be treated the same as Callable by type checkers. It would be nice to avoid the import.

I’m a bit less sure of iter as an alias for either Iterable or Iterator. There’s already an ambiguity RE which of the latter items is being aliased. I don’t personally use either Iterable or Iterator very often so neither is “obviously” the right choice from my POV.

3 Likes