Labels
topic-disallow-anyThe disallow-any-* family of flags

Comments

@Dreamsorcerer

Problem

Using Any is not type safe, therefore Mypy provides options to disallow Any. However, there are many uses of Any from third-party and stdlib, so these options tend to result in a lot of errors even when they are not relevant.

I'm finding many situations where I get an error about Any, but the code would be deemed safe if it was object.

Examples

This produces errors about Any on both lines even though the value is not used:

type_hints = typing.get_type_hints(...)  # Dict[str, Any]
if k in type_hints:
    ...

This produces errors as well, but object would not produce errors, as Mypy can infer everything necessary here to be type safe.

origin = typing.get_origin(...)  # Any
if origin is Literal:
    ...
elif origin is list:
    ...

Solution

Add an option, or change the behaviour of the current disallow Any options, such that Any is treated like object. Then, rather than producing errors when Any is present, just show normal errors with Any (as if it was object) in them (e.g. 'Any has no attribute foo').

@JukkaL

This seems fairly ad hoc to me. If something is annotated as Any and we'd replace it with object, it wouldn't necessarily make the code any safer -- consider a function argument type, for example. We'd still accept arbitrary arguments. You need a better explanation of how this would help in general, not just in hand-picked use cases.

@Dreamsorcerer

Obviously, this makes no difference to parameters of third-party functions, I don't see any improvements there.
But, for your own functions, if the function argument gets set to object, then you would still get errors inside the function, thus requiring you to check the code and cast etc.

It just seems to me that these checks for Any are being used to require strict checking, but often throw errors when the type of the object makes no difference. This isn't making the code safer, it's just reducing the number of false positives. If my code will work for any type (i.e. it works if set to object), I don't expect to get errors from Mypy complaining that it is Any.

Maybe function arguments should be excluded from this check, and still give an error if Any is present in the parameters, but I think it would work fine if not.

@JukkaL

Hmm I'm still not sure that blanket replacement is going to work in a useful way. Can you provide more examples (preferably from real code) with more context about how this would help?

@Dreamsorcerer

I just don't see any purpose to giving an error about an Any type being present if there's no possibility it would cause an error.

I don't have a lot of code that's checked with disallow Any, so I've only got this one example. I can't remember for sure, but I think each of the 6 objects here (in type annotations and casts) are all unnecessary if this change were to be implemented.

        ann: Dict[str, object] = get_type_hints(dict_type)

        for k, v in api_data.items():
            if k not in ann:
                logging.warning("Key missing from %s: %s (%s)", dict_type, k, v)
                continue

            ann_type = ann[k]
            origin: object = get_origin(ann_type)
            args: Tuple[object, ...] = get_args(ann_type)
            if origin is Literal:
                if not any(v == arg for arg in args):
                    logging.warning("Missing Literal value for (%s) %s: %s", dict_type, k, v)
                continue
            elif origin is list:
                v = cast(List[object], v)
                if v and not isinstance(v[0], cast(Tuple[type, ...], args)[0]):
                    logging.warning("Wrong type for (%s) %s: %s", dict_type, k, v)
                continue
            elif origin is None and issubclass(cast(type, ann_type), dict):  # TypedDict
                ann_type = cast(Type[Dict[str, object]], ann_type)
                await self._check_typing(cast(Dict[str, object], v), ann_type)
                continue

            if origin is Union:
                ann_type = args

            if not isinstance(v, ann_type):  # type: ignore[arg-type]
                logging.warning("Incorrect annotation for (%s) %s: %s", dict_type, k, v)

The code is basically runtime checking type annotations against API responses, which is used to help reverse engineer the API (and alert me to changes).

@Dreamsorcerer

Going through my previous bug reports, I believe this would also be fixed by treating Any as object:
#8884

The Any is within an await statement, so requires a messy cast to work around it. If it were object, the cast could come after the await without triggering an error.

@Dreamsorcerer

I think many libraries (and typeshed) also use Any in many places where object would suffice. So, this again causes more errors than needed.

asyncio.get_event_loop().add_signal_handler(SIGTERM, asyncio.create_task, self._shutdown())

Causes: error: Expression type contains "Any" (has type "Callable[[Union[Generator[Any, None, _T], Awaitable[_T]], DefaultNamedArg(Optional[str], 'name')], Task[_T]]") [misc]

Due to the typeshed definition:
def create_task(coro: Union[Generator[Any, None, _T], Awaitable[_T]], *, name: Optional[str] = ...) -> Task[_T]: ...

This seems to be pretty common, where Any is used to say that any value is valid, even though that should probably be object.

Basically, when I enable the Any warnings on Mypy, what I am expecting is to be warned of any potentially unsafe operations. However, mypy seems to use these options to cause errors when the Any object is present anywhere, regardless of type safety. As a developer, there is no advantage to me being alerted that Any is present if it has no impact on the code. I just want to know if there`s a risk of unsafe typing in my code.

@gvanrossum

If you think typeshed should change its usages of Any to object, please file a PR (or several) against that repo.

If the Any warnings in mypy are not useful to you, don't use them. I don't think it's reasonable to expect mypy to be able to tell the difference between unsafe and safe uses of Any.

I propose to close this issue.

@Dreamsorcerer

It's still useful, but it creates extra warnings that are simply unnecessary and require workarounds. I'm just not seeing a reason these extra warnings would be useful to developers. If there is a reason, then a new option that allows this strict typing without the extra warnings would be still be good.
Treating Any as object would solve the several examples I've shown.

If strict typing (i.e. warn about Any):
Any should be treated like object and trigger errors when unsafe.
else:
Any results in dynamic typing.

@Dreamsorcerer

In other words, warning about any, results in catching potentially unsafe operations 90% of the time, but produces pointless error messages the other 10% of the time. I'd like to keep the 90%, while losing the 10%. This could be achieved by treating Any as object.

@gwerbin

In other words, warning about any, results in catching potentially unsafe operations 90% of the time, but produces pointless error messages the other 10% of the time. I'd like to keep the 90%, while losing the 10%. This could be achieved by treating Any as object.

I think van Rossum's argument is that, if this usage of Any is incorrect, then we should endeavor to fix it where it is incorrectly used, instead of modifying Mypy to behave in a way that's inconsistent with how Any is supposed to be used.

So e.g. this type signature maybe should be considered buggy:

def create_task(
    coro: Union[Generator[Any, None, _T],
    Awaitable[_T]],
    *,
    name: Optional[str] = None)
-> Task[_T]:

and should be fixed by changing it to:

def create_task(
    coro: Union[Generator[object, None, _T],
    Awaitable[_T]],
    *,
    name: Optional[str] = None)
-> Task[_T]:

That said, I think maybe the intended meaning of Any ("unconstrained") isn't obvious to people who don't already know what it means, especially if they're new to using type annotations. Maybe there should be an admonition under https://docs.python.org/3/library/typing.html#typing.Any telling people that they should probably use object instead?

@Dreamsorcerer

I have in fact fixed that particular example already:
https://.com/python/typeshed/pull/4840/files

However, there are still many cases where it doesn't make sense to change this, particularly with return types. See the 6 object annotations/casts used in #9153 (comment)

We can't assume a return type of object as this is incorrect and would cause lots of errors for everyone. But, every use case in that example works regardless of what the return type is, so why do we need to get errors about Any? The errors don't highlight any potential issues with the code, and if Any was treated as object then no errors would be emitted.

@Dreamsorcerer

Another example, this time from a more typical use case:

        async for msg in resp:
            reveal_type(msg)
            if msg.type == web.WSMsgType.TEXT:
                for ws in request.app.state["sockets"]:
                    if ws is not resp:
                        await ws.send_str(msg.data)

https://.com/aio-libs/aiohttp/blob/master/examples/web_ws.py#L30-L34

Gives:

examples/web_ws.py:38: error: Expression type contains "Any" (has type "WSMessage")  [misc]
examples/web_ws.py:39: error: Expression type contains "Any" (has type "WSMessage")  [misc]
examples/web_ws.py:39: note: Revealed type is "Tuple[aiohttp.http_websocket.WSMsgType, Any, Union[builtins.str, None], fallback=aiohttp.http_websocket.WSMessage]"
examples/web_ws.py:40: error: Expression type contains "Any" (has type "WSMessage")  [misc]
examples/web_ws.py:43: error: Expression type contains "Any" (has type "WSMessage")  [misc]
examples/web_ws.py:43: error: Expression has type "Any"  [misc]
Found 5 errors in 1 file (checked 18 source files)

That's 4 errors (ignoring the one from reveal_type()), but only one seems to have any relation to type safety.
The Any here is the data attribute, only the last error actually relates to using the data attribute.
If the Any were treated as object, the other 3 errors would not occur (all the ones that end with (has type "WSMessage")), therefore saving me a bunch of awkward # type: ignores.

@gvanrossum

@Dreamsorcerer

As I said before, the stricter options are useful, I just don't see why it needs to raise errors when they are irrelevant and have no effect on type safety. 90% of the additional errors are good and help eliminate undesired dynamic typing, but the other 10% just seem to be pointless noise.

Also, my previous comment is not meant as a complaint, but a response to @JukkaL, who requested more examples from real code. I don't use disallow-any-expr often, hence why I've not reported many examples yet. But, this is the first and only file in the aiohttp repository that has had this option enabled, and has already produced 3 unnecessary errors in ~50 lines of code. I'm sure if I enabled this for the entire repository and tried to go through all of them, I'd find 100s more.

@Dreamsorcerer

or set them up so that they only apply to your own code (where you can write 'object' instead of 'Any' to make them go away).

In this case, I do have access to change the code, but using object here would cause lots of type errors for users of aiohttp who do not want to use strict (no Any) typing, so that's not really an option. I've used Any because I'm not sure how to type that object correctly (it looks like it would need some kind of tagged union, but I don't think it's possible with a namedtuple like that).
https://.com/aio-libs/aiohttp/pull/5893/files#diff-29db742fa68fd59f39e1ca70379c4d478101a8ed9af8895c8fc7604bbf1b4b11R94

@Dreamsorcerer

Using disallow_any_expr = True on another small project (<500 lines of code), I've got one more example:

from pathlib import Path
home = Path.home()
some_path = Path("foo")

Just the reference to Path causes an error, maybe due to **kwargs: Any, which is not even used.

@hauntsaninjahauntsaninja changed the title Option to treat Any as object When using--disallow-any-expr, silence complaints about Any where object would work Feb 14, 2022
@JelleZijlstraJelleZijlstra added the topic-disallow-anyThe disallow-any-* family of flagslabel Mar 19, 2022
@KotlinIsland

@gjcarneiro

I am haunted by the same problem. On one hand, Any is creeping in from unexpected places and whenever an Any is present it seems to short-circuit type checking and then no checking at all is done. On the other hand, if I enable disallow_any_expr then it starts complaining about Any in lots and lots of places, most of which in 3rd party libs or typeshed that I cannot avoid.

In my own code, I am trying to educate myself to use object instead of Any whenever I can. Perhaps a recommendation to use object instead of Any should be part of the mypy docs, in this section?

@Dreamsorcerer

It might be useful to mention it in the docs, but for third-party libraries, you'll likely still want Any in most cases. e.g. If json.loads() were to return object, then everyone would get errors all the time. disallow_any_expr would force you to handle any type errors without causing problems for anybody else.

The focus of this issue is that there are quite a few places where the error is triggered due to an Any being present, even if the operations are actually type safe. Making this option treat Any as if it were object would result in less errors with the same degree of type safety. e.g. It's clear that json.loads() shouldn't be marked as returning object, but print(json.loads()) is type safe, and shouldn't produce an error with any strictness setting.

@Dreamsorcerer

It might be useful to mention it in the docs

I think the most obvious place where the docs can help improve this, would be in relation to Coroutine or Generator.
For example, mypy docs suggest that an async def is inferred as Coroutine[Any, Any, T] (which if still accurate should probably be a separate issue to change that?). I've seen a bunch of annotations like that in the past, where they should really have been Coroutine[None, None, T].
https://mypy.readthedocs.io/en/stable/more_types.html#async-and-await

@Dreamsorcerer

Another case this would resolve is the regression in #17171.

wyattscarpenter added a commit to wyattscarpenter/mypy that referenced this issue May 17, 2025
This implements a suggestion in python#9153 (comment), which I thought was a good idea.
Sign up for free to join this conversation on . Already have an account? Sign in to comment
topic-disallow-anyThe disallow-any-* family of flags
None yet

No branches or pull requests