__new__ in tuple subclasses
pytype issues a wrong-arg-count message for the following Python code:
class Color(tuple):
def __new__(cls, r=0x0, g=0x0, b=0x0, a=0xff):
return tuple.__new__(cls, (r, g, b, a))
def witha(self, a):
(r, g, b, olda) = self
return self.__class__(r, g, b, a)
The output looks like this:
$ pytype pytypebug.py
Computing dependencies
Analyzing 1 sources with 0 local dependencies
ninja: Entering directory `/Users/walter/.pytype'
[1/1] check pytypebug
FAILED: /Users/walter/.pytype/pyi/pytypebug.pyi
/Users/walter/pyvenvs/default/bin/python3 -m pytype.single --imports_info /Users/walter/.pytype/imports/pytypebug.imports --module-name pytypebug -V 3.7 -o /Users/walter/.pytype/pyi/pytypebug.pyi --analyze-annotated --nofail --quick /Users/walter/pytypebug.py
File "/Users/walter/pytypebug.py", line 7, in witha: Function Color.__init__ expects 1 arg(s), got 5 [wrong-arg-count]
Expected: (self)
Actually passed: (self, _, _, _, _)
For more details, see https://google.github.io/pytype/errors.html#wrong-arg-count.
ninja: build stopped: subcommand failed.
However this is perfectly valid Python code:
>>> from pytypebug import *
>>> c = Color(0x33, 0x66, 0x99, 0xcc)
>>> c.witha(0xff)
(51, 102, 153, 255)
This happens because pytype's builtins file defines tuple.__init__ instead of tuple.__new__: https://github.com/google/pytype/blob/9e315b940720c5734bcf8c7d683c310202938199/pytype/pytd/builtins/3/builtin.pytd#L555.
This has come up before internally, and we weren't able to fix it at the time because we couldn't figure out the right way to define __new__. If it's written as, say,
def __new__(cls, Iterable[_T]) -> tuple[_T]: ...
then subclasses of tuple will be incorrectly modeled as returning instances of tuple rather than themselves. But if we use:
def __new__(cls: Type[_T], ...) -> _T: ...
then tuple(...) calls will be modeled as returning Tuple[Any, ...], when we can currently infer a more specific element type.
Of course I call always annotate __new__ with:
def __new__(cls, r=0x0, g=0x0, b=0x0, a=0xff) -> "Color":
Is there a workaround for this issue?
I would probably just disable the wrong-arg-types error, but if you'd like to work around the issue without disabling type-checking, something like this should work:
import typing
class Color(tuple):
def __new__(cls, r=0x0, g=0x0, b=0x0, a=0xff):
return tuple.__new__(cls, (r, g, b, a))
if typing.TYPE_CHECKING:
def __init__(self, *args, **kwargs):
pass
def witha(self, a):
(r, g, b, olda) = self
return self.__class__(r, g, b, a)
(This tricks pytype into thinking there exists an __init__ method with the signature it expects. At runtime, TYPE_CHECKING evaluates to False.)