Making Any a builtin object, referring to typing.Any

Has there been active discussion of making Any a keyword in the language?

Now that 3.6 is EOL, I’m finding a lot of my projects are making changes like so:

+from __future__ import annotations
+
-from typing import Optional, Any, Dict
+from typing import Any

class Foo:
-    bar: Optional[Dict[str, Any]]
+    bar: dict[str, Any] | None

This is happening in many modules across several different kinds of projects – libraries and applications. The only import from typing needed is Any.

If Any were added as a soft keyword in 3.12, we could maybe start to see these imports disappear.

Is there (ahem) Any appetite for this?

1 Like

The high-level question is above my pay grade, but just to note, if this was done it presumably wouldn’t be as a keyword but as a built-in constant like None, ..., True and False. Making it reassignable, like NotImplemented but unlike the former examples, shouldn’t break essentially any existing working code (with a very few corner-case exceptions that try-except for whether Any is defined). However, this would be the first time a typing-only construct is exposed as a built-in outside the typing standard library module, so there might be resistance on that grounds.

@Jelle , you might be interested in responding to this.

1 Like

Good point. I’ll refrain from editing the original to avoid making the thread confusing, but I should have been suggesting it as a builtin, not a keyword. I thought of keywords because all new keywords have been soft (e.g. match), and I couldn’t think of a builtin which can be redefined. NotImplemented is a good example.

Yes, and I can understand the fears about beginners writing x = Any and getting confused…


Still, Any is uniquely prevalent in annotations. I can’t help but feel like it would be nice to always have access to it.

Pulling typing that far into the language is not a step we have taken yet. If type checkers assume from typing import * and you use from __future__ import annotations then having it as a built-in isn’t quite so critical.

4 Likes

No strong opinion here, but a few thoughts since I’ve been mentioned:

  • @CAM-Gerlach is right that this would be implemented as a new builtin like NotImplemented, not a keyword. That would also imply making Any implemented in C.
  • We’re working on making Any be acceptable as a base class (https://github.com/python/cpython/pull/31841), so we’d have to do some contortion to make that work in C too.
  • There may be concerns about confusion between the any() builtin and Any.
  • While Any is common in type annotations, ideally it shouldn’t be, because we’d rather have more precise types. So maybe it’s good to make it a bit harder to reach for Any compared to more precise types.

And replying to @brettcannon:

If type checkers assume from typing import * and you use from future import annotations then having it as a built-in isn’t quite so critical.

Type checkers do not assume from typing import *. from __future__ import annotations isn’t a panacea because Any will regularly appear outside of annotations, e.g. in type aliases or TypeVar bounds.

2 Likes

I’d think that PEP 585 (list[int] etc.) went much farther than adding a new built-in Any. It certainly was a lot bigger effort. :wink: There was also PEP 526 (variable annotations).

I’d be +0 on adding a built-in Any. I expect that some folks on typing-sig would have stronger opinions (including perhaps Eric Traut, who doesn’t seem to like it much :-).

By Jelle Zijlstra via Discussions on Python.org at 29Mar2022 23:16:

  • While Any is common in type annotations, ideally it shouldn’t be,
    because we’d rather have more precise types. So maybe it’s good to make
    it a bit harder to reach for Any compared to more precise types.

That was my immediate gut concern: making it easier to effectively make
things nearly untyped. Surely if you’re type annotating your code, this
is the least valueable name to make available for free.

Just my 2c,
Cameron Simpson cs@cskk.id.au

1 Like

I agree that Any is an undesirable type for annotations. However, there are still lots of cases where it is necessary.

The json module comes to mind in particular. Without a recursive type for “valid JSON”, dict[str, Any] is common. And object_hook provides a good example of a callback which can return Any.

There are also cases for code which must accept any input, like typeguards:

def is_X(val: Any) -> TypeGuard[X]:
    return isinstance(val, X)

or which can accept generics over any:

def is_empty_sequence(x: Sequence[Any]) -> bool:
    return not len(x)

Some of this is informed by which type checker you use and even which flags you set. mypy --strict enforces list[Any] over list, and def f(x: Any) over def f(x).
So by rolling out mypy --strict on codebases that do a bunch of serialization and deserialization of data, I’m probably “asking for more Anys”.

1 Like

Both of these examples would work even if you used object instead of Any.

I think there are lots of places where people use Any unnecessarily like this and don’t realize that object is a valid annotation, and should be used when your code really accepts literally “any” object.
And it is already built-in.

Any is for the rare cases when you don’t really want to say “any object”, but writing down the precise type is impossible or too cumbersome.

So it is fine to leave Any in typing,since it should be rarely used over object.
And the Any vs object distinction should be clarified more, maybe in the docs for typing.Any (and use of object should be encouraged more).

4 Likes

In the examples above, I was trying to keep it brief, maybe too brief. We can construct cases where object doesn’t work, but it’s likely to take this thread off-topic.

Perhaps object should be encouraged more, but I’m not fully convinced. The difference is subtle enough that I’d be concerned giving that advice to new users of type annotations.

If the docs were to start recommending object more strongly, they would also need to explain more about when to use one vs the other.

Consider:

data = json.load(stream)
if not isinstance(data, dict): raise ValueError("boo")
typed_data = cast(dict[str, object], data)  # uh-oh!
try:
    return typed_data["foo"]["bar"][0]  # fails
except LookupError as e:
    raise ValueError("sorry") from e
1 Like

Sure, but I view that change as motivated by typing, but not only for typing; there are at least other potential use-cases for that functionality. An Any built-in is a bit harder to motivate for anything but typing.

1 Like

In this case the part that fails doesn’t work for all objects, so the point about object doesn’t apply.
object doesn’t work as a replacement for Any everywhere, only in cases where you really meant “any object”.

That line works only for some types of objects which allow __getitem__ with a str which in turn returns another object that allows __getitem__ with an int. That could be a Protocol or a TypedDict or just dict[str, dict[str, list[object]]

So this is the case where you use Any because you don’t know how to define the precise type and think it is impossible (or you just want to be lazy and feel defining TypedDict or Protocol is too cumbersome).
Ideally these cases should be avoided, and I think maybe giving easy access to Any as built-in would encourage it more.

I’m surprised at this, I personally use Iterable, Sequence, Generator, Callable, Mapping, … all more often than Any. Is there something I’m missing to suggest the usage of these other types from typing are less common, or being replaced with something that doesn’t require an import from typing (hopefully not just being replaced with Any!)?

All of these can be imported from collections.abc instead as of Python 3.9.

Of the symbols in typing, the only ones I considered suggesting were Any and Final. There are plenty of other useful names there, but I’m looking at the ones which I think will have the greatest potential positive impact on the language with the smallest possible change.

Final is often omitted from code where it could, and arguably should, be used simply because nobody bothered to import it.

Many modules can’t be annotated without importing typing. When I look at those which only import a single value from typing, it tends to be Any more often than any other.

All that said, it currently seems that the support for this idea is weaker than the concerns about it.

1 Like

+1 (for NOT making Any a builtin). I’m not in favor of making it easy to introduce a code smell.

I’m going to come out as a strong -1 for sneaking anything typing-related (even Any) into builtins. Python looks to me more and more like a language with type declarations as a builtin part of the language. It’s beauty has always been its simplicity. Such is, I’m afraid, not the case anymore. I believe it was Fredrik Lundh who opined that as a language Python reached its zenith in 1.5.2. That’s surely a debatable proposition, but I’m sure the Effbot wouldn’t be happy with “optional” type declarations at all.

I know I don’t count, but I thought someone had to lone voice in the wilderness on this one.

3 Likes

I would say you’re hardly a lone voice. I (and others on typing-sig) are sensitive to the desire to avoid typing-related features & annotations complicating the rest of Python by default, despite me personally being an advocate of typing for large codebases.

I will briefly quote from PEP 484 (Type Hints):

It should also be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention. [Bolding from original text]

The intent here still remains.

3 Likes