GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||
GeezerVison is showing github.com — [functools] Support binding wrapped methods by AlexWaygood · Pull Request #16515 · python/typeshed Conversation
|
|
Diff from mypy_primer, showing the effect of this PR on open source code: xarray (https://github.com/pydata/xarray) + xarray/tests/test_plot.py:235: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:238: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:241: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:244: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:254: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:257: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:261: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:264: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:275: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:279: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:313: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:316: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:326: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:403: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:532: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:620: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:623: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:634: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:646: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:656: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:663: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:666: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:673: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:687: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:690: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:693: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:696: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:700: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:703: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:706: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:709: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:716: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:723: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:728: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:733: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:738: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:752: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:762: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:763: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:764: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:775: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:779: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:783: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:789: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:795: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:882: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:887: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:930: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:935: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:940: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:945: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:952: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:961: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:965: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:1270: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:1340: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:1344: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:1770: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:1773: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:2638: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:2641: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:2654: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:2673: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:2682: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:2699: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_plot.py:3727: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_units.py:5723: error: Unused "type: ignore" comment [unused-ignore] + xarray/tests/test_units.py:5748: error: Unused "type: ignore" comment [unused-ignore] ibis (https://github.com/ibis-project/ibis) - ibis/expr/types/relations.py:3858: error: Too few arguments for "__call__" of "_Wrapped" [call-arg] |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
The mypy_primer report here seems excellent. Here's a codex-authored summary of the changes: Detailstypeshed PR #16515 ecosystem summaryThe mypy-primer report shows that the change makes mypy recognize instance binding for wrapped methods in two projects. In xarray, 68 existing ignores become unused: 67 suppressed missing-argument errors and one suppressed an argument-type error. In ibis, one missing-argument error disappears from a call to a wrapped join method. Diagnostic changes (1 section)1. mypy binds instances when checking wrapped methods68 added Report entries
xarray's The following example captures the three patterns. With the old stubs, the ignores suppress the errors; with the PR's stubs, mypy reports each ignore as unused. from functools import wraps def plot_data_array(darray): ... class DataArrayPlotAccessor: @wraps(plot_data_array) def __call__(self, **kwargs): ... @wraps(plot_data_array) def hist(self, *args, **kwargs): ... def tests(plot: DataArrayPlotAccessor, dim) -> None: # Base: no diagnostic; PR: Unused "type: ignore" comment [unused-ignore] plot() # type: ignore[call-arg] # Base: no diagnostic; PR: Unused "type: ignore" comment [unused-ignore] plot.hist() # type: ignore[call-arg] # Base: no diagnostic; PR: Unused "type: ignore" comment [unused-ignore] plot(**{dim: "dim_0_bins"}) # type: ignore[arg-type] Without the ignores, mypy reports In ibis, from functools import wraps class Table: def asof_join(self, right): ... class Join(Table): @wraps(Table.asof_join) def asof_join(self, right, /): ... # Base: Too few arguments for "__call__" of "_Wrapped" [call-arg] # PR: no diagnostic Join().asof_join(Join()) ReproductionComparison details
I also checked the impact this PR would have on users of ty by experimentally applying the changes here to ty's vendored stubs in astral-sh/ruff#29301. The ecosystem impact seems to be even better with ty. Again, another codex-authored ecosystem summary: DetailsPR #29301 ecosystem summaryThe experimental In pydantic, the added descriptor declaration makes an existing Diagnostic changes (3 sections)1. Calls to wrapped methods bind the receiver69 removed Report entries
In xarray, For xarray: from functools import wraps def plot(darray): ... def hist(darray): ... class DataArrayPlotAccessor: @wraps(plot) def __call__(self, **kwargs): ... @wraps(hist) def hist(self, **kwargs): ... def test(plot: DataArrayPlotAccessor, dim): # Merge base: error[invalid-argument-type] Argument to bound method `_Wrapped.__call__` is incorrect: Expected `DataArrayPlotAccessor`, found `str` # PR: no diagnostic plot(**{dim: "dim_0_bins"}) # Merge base: error[missing-argument] No argument provided for required parameter `self` of bound method `_Wrapped.__call__` # PR: no diagnostic plot(col="col") # Merge base: error[missing-argument] No argument provided for required parameter `self` of bound method `_Wrapped.__call__` # PR: no diagnostic plot.hist() The ibis call has the same binding problem with a different parameter. For ibis: from functools import wraps class Table: def asof_join(self, right): # Merge base: error[missing-argument] No argument provided for required parameter `right` of bound method `_Wrapped.__call__` # PR: no diagnostic return Join().asof_join(right) class Join: @wraps(Table.asof_join) def asof_join(self, right): ... 2. Wrapped method overrides are compared as bound methods4 removed Report entries
The ibis For example: import functools class Table: def join(self, right: "Table") -> "Table": return self class Join(Table): @functools.wraps(Table.join) # Merge base: error[invalid-attribute-override] Invalid override of attribute `join`: Type `_Wrapped[(self, right: Table), Table, (self, right), Unknown]` is not assignable to inherited type `(right: Table) -> Table` # PR: no diagnostic def join(self, right): return self In pytest-autoprofile, the overriding method wraps its parent method, which itself wraps the overloaded builtin The reduced example: import functools from typing import overload class CodeType: ... class AST: ... @overload def compile(source) -> CodeType: ... @overload def compile(source, flags) -> AST: ... def compile(source, flags=0): return AST() if flags else CodeType() class _RunnerMixin: @functools.wraps(compile) def compile_doctest_example(self, source) -> CodeType: return compile(source) class _DocTestMixin(_RunnerMixin): @functools.wraps(_RunnerMixin.compile_doctest_example) # Merge base: error[invalid-attribute-override] Invalid override of attribute `compile_doctest_example`: Type `_Wrapped[(self, source), CodeType, (self, source), Unknown]` is not assignable to inherited type `_Wrapped[Overload[(source) -> Unknown, (source, flags) -> Unknown], CodeType | AST, (self, source), CodeType]` # PR: no diagnostic def compile_doctest_example(self, source): ... 3. A class-level descriptor access makes an ignore unnecessary1 added Report entries
Pydantic uses import functools from typing import Any def create_dataclass(cls: type[Any]): @functools.wraps(cls.__setattr__) def validated_setattr(instance, name, value) -> None: pass # Merge base: no diagnostic # PR: warning[unused-type-ignore-comment] Unused blanket `type: ignore` directive cls.__setattr__ = validated_setattr.__get__(None, cls) # type: ignore Reproduction
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
## Summary <img width="331" height="155" alt="image" src="https://github.com/user-attachments/assets/8e1b5c40-57b7-44ec-a197-3770e9fc3236" align=right /> This PR adds return type inference as a new feature to ty. The feature is currently *disabled by default*. Users can activate the feature by setting `analysis.return-type-inference` to `"always"`. In a standalone function, the inferred return type is simply the union of the types of all reachable `return`ed expressions, as shown in the image on the right. If control flow can reach the end of the function scope, we add `None` to the union. In a (non-final) method on a (non-final) class, we additionally union with `Unknown` to allow child classes to widen the intended return type. For example, imagine ```py class TaskBase: def compute(self): return None class IntTask(TaskBase): def compute(self): return 42 ``` Here, we infer a return type of `None | Unknown` for `TaskBase.compute`. Otherwise, if we inferred `None`, we would prohibit child classes from returning something else, which seems undesirable. Another interesting scenario is when the base class method is annotated, but the child class method is not: ```py class Base: def method(self) -> int: return 0 class Child(Base): def method(self): return 1 ``` Here, we respect the base class definition and infer `Child.method` as returning `int`, not `int | Unknown`. This work is based on #17371 by @mtshiba (thank you!) There are a few notable things that are _not_ implemented here: - The type of the function itself currently stays unchanged, that is: we do not infer a signature with the inferred return type. Concretely: `my_function()` may be inferred as `int`, but `my_function` itself would still be inferred as `def my_function() -> Unknown`. We have existing tests with TODOs for this and I plan to tackle this in a follow up. - We do not yet have return type inference for generator functions or async functions - We do not attempt to solve the problem of "escaping" inner types here (see tests with TODOs) - We do not add LSP/playground features related to this feature, but plan to do so soon (display return type hints). - There is a limitation around `functools.wraps` that's present in the ecosystem report. This will hopefully be solved by python/typeshed#16515 - There is a [limitation around `NotImplemented`](astral-sh/ty#1150 (comment)) which hasn't been resolved yet. The solution is described [here](astral-sh/ty#1150 (comment)). - Fixpoint iteration currently uses `Divergent` as a provisional value. I did think about using the new μ-types, but that currently leaves too many questions unanswered: How would we represent an anonymous μ-type? How would the (provisional) recursive marker `R` interact with other types during inference (especially since those operations require "closed" recursive types, and `R` would still be open)? closes astral-sh/ty#128 ## Test Plan - Changed assertions in the previously reviewed & merged mdtests - Studied the ecosystem impact and addressed many problems --------- Co-authored-by: Shunsuke Shibayama <[email protected]>
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.