`__new__` signature should take precedence over `__init__`
- Dominant language
- Rust
- Stars
- 7k
- Forks
- 516
- PR merge metrics
- No merged PRs in 30d
Description
This repro is distilled from the (strange) way that `scipy.sparse.linalg.LinearOperator` implements its `__new__` and `__init__` methods ([src](https://github.com/scipy/scipy/blob/ba8b7d300e3afcd412bc9eb91b03d087c678161e/scipy/sparse/linalg/_interface.py#L207-L227)):
```py
class A:
def __new__(cls, a: int):
if cls is A:
return super().__new__(B)
else:
return super().__new__(cls)
def __init__(self) -> None: ...
class B(A):
def __init__(self, a: int) -> None:
self.a = a
A(1) # ❌ Expected 0 positional arguments, got 1 in function `A.__init__` [bad-argument-count]
```
[sandbox](https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIAxlKnHAAQCCiAOuvR-ZjGPQPp90MAO4CAFNTgAaeqkT0I6AC4BKVu06aIvSQobM2mo5wBOMJQFcT7OBeIwTYlYQFDRfMQCEVh4-RhQcDDqfkZmltb0tvaOzq4i4pI%2B6L4c3LwCihBK4kFQYCr0ALQAfPQAcrhC8oS1bGzUtAyeYoxqqVw8-HxZOR55YDJyCsqFpRVVwR2aA4So9AC8svXojGIAjMkgUiBkZmBQpIRKuAC2UBQAxPQACqT7h1EYOAT0lFWQAOZWqEoQVYQ2NcAMowGD0AAWSiUxDgiAA9PC9jxDoRcCZPvCYOh4ZhcJQ4PD3ugvj8-lV4fQwOjZAA3VDQVDYWBvD4Qb4mX7-di4Yjk9BwQHoMhKCFVIq0hxwbmLegsEAAZkI6wATPK2ABtBwmdFwAC6bAs6Agp2I6KUMEwRUwEDMlD%2BktlAHJ2eh0TAnWw3UoimYAI4WW2WooAaxgpCKqEolHgDCWTuEqGsnvQIAAvjsow6YAAxaAwChoLB4IhkdNAA)
But at runtime this works fine:
```pycon
>>> class A:
... def __new__(cls, a: int):
... if cls is A:
... return super().__new__(B)
... else:
... return super().__new__(cls)
...
... def __init__(self) -> None: ...
...
... class B(A):
... def __init__(self, a: int) -> None:
... self.a = a
...
... A(1)
...
<__main__.B object at 0x718a77ee2f90>
```
Mypy also has issue with this (IMO awkward) pattern (https://github.com/python/mypy/issues/17251), which, by the way, is why scipy-stubs can't get 100% type coverage at the moment (https://github.com/scipy/scipy-stubs/blob/2ac5dfb1fc3edf8f43eff922cf94410b6e70a8d2/scipy-stubs/sparse/linalg/_interface.pyi#L199-L217). Pyright accurately models this behavior.
Contributor guide
Research direction
Start by running the linked sandbox repro and compare it with scipy/sparse/linalg/_interface.py and the scipy-stubs sparse/linalg/_interface.pyi references. Trace how the checker chooses between __new__ and __init__; done means the example no longer reports the argument-count error and has regression coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100