Skip to content

Remove ModuleType.__getattr__ #6357

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed

Conversation

JukkaL
Copy link
Contributor

@JukkaL JukkaL commented Nov 22, 2021

This caused false negatives for mypy -- mypy would consider that any
module would have any attribute. The method also does not exist at
runtime.

I've also updated builtins.__import__, importlib.__import__, and
importlib.import_module to return Any. I don't have a strong
opinion on this change, but this arguably minimizes the impact of the
removal, and maintains the return types of these functions consistent.

Also, it could be argued that since arbitrary objects can be stuffed
into sys.modules (and I've heard people actually suggest taking
advantage of this), the Any type best reflects this.

There may be other reasonable ways to deal with module objects, but I
think that adding ModuleType.__getattr__ regresses things too much.

This caused false negatives for mypy -- mypy would consider that any
module would have any attribute. The method also does not exist at
runtime.

I've also updated `builtins.__import__`, `importlib.__import__`, and
`importlib.import_module` to return `Any`. I don't have a strong
opinion on this change, but this arguably minimizes the impact of the
removal, and maintains the return types of these functions consistent.

Also, it could be argued that since arbitrary objects can be stuffed
into `sys.modules` (and I've heard people actually suggest taking
advantage of this), the `Any` type best reflects this.

There may be other reasonable ways to deal with module objects, but I
think that adding `ModuleType.__getattr__` regresses things too much.
@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

pylint (https://github.com/pycqa/pylint.git)
- pylint/checkers/utils.py:326: error: unused "type: ignore" comment
- pylint/checkers/utils.py:326: error: Module has no attribute "__iter__" (not iterable)  [attr-defined]

isort (https://github.com/pycqa/isort.git)
- isort/_future/__init__.py:9: error: unused "type: ignore" comment
- isort/_future/__init__.py:10: error: unused "type: ignore" comment
- isort/output.py:504: error: unused "type: ignore" comment
- isort/output.py:516: error: unused "type: ignore" comment
- isort/output.py:523: error: unused "type: ignore" comment

pip (https://github.com/pypa/pip.git)
- src/pip/_internal/locations/__init__.py:58: error: unused "type: ignore" comment
- src/pip/_internal/network/session.py:106: error: unused "type: ignore" comment

sphinx (https://github.com/sphinx-doc/sphinx.git)
- sphinx/util/typing.py:228: error: unused "type: ignore" comment
- sphinx/util/typing.py:428: error: unused "type: ignore" comment
- sphinx/util/inspect.py:433: error: unused "type: ignore" comment
- sphinx/util/inspect.py:436: error: unused "type: ignore" comment
- sphinx/util/inspect.py:439: error: unused "type: ignore" comment
- sphinx/util/docutils.py:52: error: unused "type: ignore" comment
- sphinx/util/docutils.py:53: error: unused "type: ignore" comment
- sphinx/util/docutils.py:67: error: unused "type: ignore" comment
- sphinx/util/docutils.py:81: error: unused "type: ignore" comment
- sphinx/util/docutils.py:95: error: unused "type: ignore" comment
- sphinx/util/docutils.py:110: error: unused "type: ignore" comment
- sphinx/domains/python.py:1452: error: unused "type: ignore" comment
- sphinx/writers/manpage.py:110: error: unused "type: ignore" comment
- sphinx/testing/util.py:126: error: unused "type: ignore" comment
- sphinx/testing/util.py:127: error: unused "type: ignore" comment
+ sphinx/ext/napoleon/__init__.py:467: error: unused "type: ignore" comment
- sphinx/util/compat.py:29: error: unused "type: ignore" comment

graphql-core (https://github.com/graphql-python/graphql-core.git)
+ tests/pyutils/test_inspect.py:24: error: unused "type: ignore" comment
+ tests/pyutils/test_inspect.py:28: error: unused "type: ignore" comment
+ tests/pyutils/test_inspect.py:33: error: unused "type: ignore" comment
+ tests/pyutils/test_inspect.py:37: error: unused "type: ignore" comment
+ tests/pyutils/test_inspect.py:42: error: unused "type: ignore" comment
+ tests/pyutils/test_inspect.py:46: error: unused "type: ignore" comment

kornia (https://github.com/kornia/kornia.git)
+ kornia/testing/__init__.py:16: error: Module has no attribute "util"  [attr-defined]

python-chess (https://github.com/niklasf/python-chess.git)
- chess/engine.py:122: error: unused "type: ignore" comment
- chess/engine.py:123: error: unused "type: ignore" comment
- chess/engine.py:1209: error: unused "type: ignore" comment

core (https://github.com/home-assistant/core.git)
- homeassistant/components/http/forwarded.py:91: error: unused "type: ignore" comment
- homeassistant/auth/mfa_modules/__init__.py:136: error: unused "type: ignore" comment
- homeassistant/auth/providers/__init__.py:145: error: unused "type: ignore" comment
- homeassistant/config.py:648: error: unused "type: ignore" comment
- homeassistant/config.py:651: error: unused "type: ignore" comment
- homeassistant/config.py:662: error: unused "type: ignore" comment
- homeassistant/config.py:672: error: unused "type: ignore" comment
- homeassistant/config.py:673: error: unused "type: ignore" comment
- homeassistant/config.py:753: error: unused "type: ignore" comment
- homeassistant/config.py:895: error: unused "type: ignore" comment
- homeassistant/setup.py:241: error: unused "type: ignore" comment
- homeassistant/setup.py:246: error: unused "type: ignore" comment
- homeassistant/helpers/check_config.py:161: error: unused "type: ignore" comment
- homeassistant/config_entries.py:310: error: unused "type: ignore" comment
- homeassistant/config_entries.py:449: error: unused "type: ignore" comment
- homeassistant/config_entries.py:488: error: unused "type: ignore" comment
- homeassistant/config_entries.py:525: error: unused "type: ignore" comment
- homeassistant/helpers/condition.py:929: error: unused "type: ignore" comment
- homeassistant/helpers/condition.py:977: error: unused "type: ignore" comment
- homeassistant/helpers/script.py:260: error: unused "type: ignore" comment
- homeassistant/helpers/script.py:262: error: unused "type: ignore" comment
- homeassistant/helpers/script.py:269: error: unused "type: ignore" comment
- homeassistant/helpers/reload.py:81: error: unused "type: ignore" comment
- homeassistant/helpers/reload.py:82: error: unused "type: ignore" comment
- homeassistant/components/device_tracker/legacy.py:241: error: unused "type: ignore" comment
- homeassistant/components/device_tracker/legacy.py:246: error: unused "type: ignore" comment
- homeassistant/components/device_tracker/legacy.py:251: error: unused "type: ignore" comment
- homeassistant/components/device_tracker/legacy.py:256: error: unused "type: ignore" comment
- homeassistant/components/notify/legacy.py:61: error: unused "type: ignore" comment
- homeassistant/components/notify/legacy.py:66: error: unused "type: ignore" comment

streamlit (https://github.com/streamlit/streamlit.git)
- lib/streamlit/script_runner.py:432: error: unused "type: ignore" comment
- lib/streamlit/script_runner.py:439: error: unused "type: ignore" comment

aiohttp (https://github.com/aio-libs/aiohttp.git)
- aiohttp/connector.py:1306: error: unused "type: ignore" comment
- aiohttp/web_runner.py:181: error: unused "type: ignore" comment
- aiohttp/pytest_plugin.py:244: error: unused "type: ignore" comment

edgedb (https://github.com/edgedb/edgedb.git)
- edb/server/pgconnparams.py:75: error: unused "type: ignore" comment

werkzeug (https://github.com/pallets/werkzeug.git)
- src/werkzeug/_reloader.py:20: error: unused "type: ignore" comment
+ tests/test_wrappers.py:856: error: Function is missing a return type annotation

@AlexWaygood
Copy link
Member

(As the person who introduced this change), I don't have a strong opinion on what the return type of builtins.__import__, importlib.__import__, and
importlib.import_module should be, as long as they remain consistent 🙂

@JelleZijlstra
Copy link
Member

Could mypy instead be changed so that if an object is a specific, known module, it ignores the __getattr__? I feel like that would still give a better overall system than returning Any from all the import_module-like functions.

@AlexWaygood
Copy link
Member

A slightly more "out there" suggestion, but —something else I was wondering about a few weeks ago, when we discussed this last, was whether type-checkers could be special-cased to recognise that the return type of __import__("collections") (for example) will be, well, the literal collections module. There doesn't seem to be that much difference between the following two snippets in terms of the information available to mypy, but the inferred type is obviously quite different, and it doesn't seem to me like something that's solveable in the stubs:

(1)

import collections as c
reveal_type(c.deque())  # Revealed type is "collections.deque[<nothing>]"

(2)

c = __import__("collections")
reveal_type(c.deque())  # Revealed type is "Any"

But I appreciate that this probably isn't the right place to discuss the feasibility of that idea.

@JelleZijlstra
Copy link
Member

I imagine most cases where people call these functions the argument is going to be dynamic.

@AlexWaygood
Copy link
Member

AlexWaygood commented Nov 22, 2021

I imagine most cases where people call these functions the argument is going to be dynamic.

No, you're right. I thought I remembered there being more instances in the diff here where people were just __import__ as a one-liner way to import a module and immediately access an object from the module. But it seems I misremembered — so, that idea of mine obviously shouldn't be a priority.

@JukkaL
Copy link
Contributor Author

JukkaL commented Nov 22, 2021

Could mypy instead be changed so that if an object is a specific, known module, it ignores the __getattr__?

I'll have a look. If this is straightforward to implement, I'll abandon this PR.

JukkaL added a commit to python/mypy that referenced this pull request Nov 23, 2021
python/typeshd#6302 added `__getattr__`, but we don't want to always
use it, as it can result in undefined references to module attributes
being undetected.

This is an alternative to python/typeshed#6357.

Tested manually with a recent typeshed.
JukkaL added a commit to python/mypy that referenced this pull request Nov 23, 2021
python/typeshd#6302 added `__getattr__`, but we don't want to always
use it, as it can result in undefined references to module attributes
being undetected.

This is an alternative to python/typeshed#6357.

Tested manually with a recent typeshed.
@JukkaL
Copy link
Contributor Author

JukkaL commented Nov 23, 2021

The issue was actually very simple to work around in mypy, so this PR is no longer needed (python/mypy#11597).

@JukkaL JukkaL closed this Nov 23, 2021
@JukkaL JukkaL deleted the fix-module-type branch November 23, 2021 11:33
tushar-deepsource pushed a commit to DeepSourceCorp/mypy that referenced this pull request Jan 20, 2022
)

python/typeshd#6302 added `__getattr__`, but we don't want to always
use it, as it can result in undefined references to module attributes
being undetected.

This is an alternative to python/typeshed#6357.

Tested manually with a recent typeshed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants