Skip to content
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

json.load can also take IO[bytes] in py36+ #2188

Merged
merged 1 commit into from
Jun 4, 2018
Merged

json.load can also take IO[bytes] in py36+ #2188

merged 1 commit into from
Jun 4, 2018

Conversation

asottile
Copy link
Contributor

@asottile asottile commented Jun 3, 2018

testcase

import io
import json
json.load(io.BytesIO(b'{}'))

before

$ mypy test.py
test.py:3: error: Argument 1 to "load" has incompatible type "BytesIO"; expected "IO[str]"

after

$ mypy --custom-typeshed-dir . test.py
$

@asottile
Copy link
Contributor Author

asottile commented Jun 3, 2018

hmm, it appears there's still more to fix -- still not getting this to resolve correctly:

import json
import urllib.request
json.load(urllib.request.urlopen('https://pypi.org/pypi/pre-commit/json'))
$ mypy --custom-typeshed-dir . test2.py
test2.py:3: error: Argument 1 to "load" has incompatible type "Union[HTTPResponse, addinfourl]"; expected "IO[Any]"

@@ -41,7 +41,11 @@ def loads(s: Union[str, bytes, bytearray],
object_pairs_hook: Optional[Callable[[List[Tuple[Any, Any]]], Any]] = ...,
**kwds: Any) -> Any: ...

def load(fp: IO[str],
if sys.version_info >= (3, 6):
_LoadIO = IO[AnyStr]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnyStr is not correct here, since it is a typevar and there is nothing by which to constrain the typevar. You probably want IO[Any] or Union[IO[str], IO[bytes]].

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, the comment above AnyStr seems to indicate "with constraints" -- I can't say I know what that means specifically but it does seem to indicate that it is not "nothing by which to constrain" -- please help me understand your comment :)

### (from cpython 3.6.5)

# A useful type variable with constraints.  This represents string types.
# (This one *is* for export!)
AnyStr = TypeVar('AnyStr', bytes, str)

IO is also defined by default as:

### (from cpython 3.6.5)

class IO(Generic[AnyStr]):

Is IO ~= IO[Any] ~= IO[AnyStr]?

You probably want IO[Any] or Union[IO[str], IO[bytes]]

If IO or IO[AnyStr] is not appropriate, which of these suggestions would you prefer?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeVars are used when there are multiple types involved in a signature that need to have the same value. For example, a function that takes either str or bytes and returns the same type could be usefully as typed as def f(s: AnyStr) -> AnyStr: ....

In the definition of IO, the fact that it is generic over AnyStr means that its type argument is constrained to be either str or bytes. IO is the same as IO[Any]; IO[AnyStr] is probably equivalent in practice, but doesn't make sense in most contexts.

I think IO[Any] is best.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neat, I tried to convince myself of the same thing:

import io
from typing import IO, Any, AnyStr, Union


io_nothing: IO
io_any: IO[Any]
io_union: Union[IO[str], IO[bytes]]

io_nothing = io_any = io_union = io.BytesIO(b'')
io_nothing = io_any = io_union = io.StringIO('')


def f_takes_nothing(x: IO): ...
def f_takes_any(x: IO[Any]): ...
def f_takes_union(x: Union[IO[str], IO[bytes]]): ...

f_takes_nothing(io.BytesIO(b''))
f_takes_any(io.BytesIO(b''))
f_takes_union(io.BytesIO(b''))

f_takes_nothing(io.StringIO(''))
f_takes_any(io.StringIO(''))
f_takes_union(io.StringIO(''))


# error: Invalid type "typing.AnyStr"
#io_anystr: IO[AnyStr] = io.BytesIO(b'')
#io_anystr: IO[AnyStr] = io.StringIO('')


# why does this pass mypy?

def f_takes_anystr(x: IO[AnyStr]): ...

f_takes_anystr(io.BytesIO(b''))
f_takes_anystr(io.StringIO(''))

oddly enough, I get an error in one spot for IO[AnyStr] but not in a function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my view mypy is less strict than it should be in rejecting certain usages of type variables.

@JelleZijlstra JelleZijlstra merged commit 2d4bb04 into python:master Jun 4, 2018
@asottile asottile deleted the json_load_any_io_py36 branch June 4, 2018 02:06
@gvanrossum
Copy link
Member

gvanrossum commented Jun 4, 2018 via email

yedpodtrzitko pushed a commit to yedpodtrzitko/typeshed that referenced this pull request Jan 23, 2019
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.

None yet

3 participants