8000 When using`--disallow-any-expr`, silence complaints about Any where object would work · Issue #9153 · python/mypy · GitHub
[go: up one dir, main page]

Skip to content

When using--disallow-any-expr, silence complaints about Any where object would work #9153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ou 8000 r terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Dreamsorcerer opened this issue Jul 15, 2020 · 21 comments
Labels
topic-disallow-any The disallow-any-* family of flags

Comments

@Dreamsorcerer
Copy link
Contributor
Dreamsorcerer commented Jul 15, 2020

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
Copy link
Collaborator
JukkaL commented Jul 22, 2020

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
Copy link
Contributor Author

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.

8000

@JukkaL
Copy link
Collaborator
JukkaL commented Sep 11, 2020

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
Copy link
Contributor Author

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
Copy link
Contributor Author
Dreamsorcerer commented Oct 19, 2020

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
Copy link
Contributor Author
Dreamsorcerer commented Dec 17, 2020

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
Copy link
Member

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor
gwerbin commented Feb 8, 2021

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
Copy link
Contributor Author

I have in fact fixed that particular example already:
https://github.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
Copy link
Contributor Author
Dreamsorcerer commented Jul 24, 2021

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://github.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
Copy link
Member
gvanrossum commented Jul 25, 2021 via email

@Dreamsorcerer
Copy link
Contributor Author

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
Copy link
Contributor Author

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://github.com/aio-libs/aiohttp/pull/5893/files#diff-29db742fa68fd59f39e1ca70379c4d478101a8ed9af8895c8fc7604bbf1b4b11R94

@Dreamsorcerer
Copy link
Contributor Author
Dreamsorcerer commented Oct 17, 2021

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.

@hauntsaninja hauntsaninja 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
@JelleZijlstra JelleZijlstra added the topic-disallow-any The disallow-any-* family of flags label Mar 19, 2022
@KotlinIsland
Copy link
Contributor

@gjcarneiro
Copy link
gjcarneiro commented Dec 3, 2023

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
Copy link
Contributor Author
Dreamsorcerer commented Dec 3, 2023

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
Copy link
Contributor Author
Dreamsorcerer commented Dec 3, 2023

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
Copy link
Contributor Author

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 GitHub. Already have an account? Sign in to comment
Labels
topic-disallow-any The disallow-any-* family of flags
Projects
None yet
Development

No branches or pull requests

7 participants
0