The json.load and json.loads both return typing.Any but why? Shouldn’t they be dict[str, Any]?
Reference: typeshed/stdlib/json/__init__.pyi at main · python/typeshed · GitHub
The json.load and json.loads both return typing.Any but why? Shouldn’t they be dict[str, Any]?
Reference: typeshed/stdlib/json/__init__.pyi at main · python/typeshed · GitHub
>>> json.loads('1')
1
Don’t forget hooks and custom decoders:
>>> import json
>>> json.loads('{}', object_hook = lambda _: 42)
42
>>> class D(json.JSONDecoder):
... def decode(self, s):
... return 42
...
>>> json.loads('{}', cls = D)
42
Thank you! I knew I was missing something and that explains it.
That’s pretty solvable with overloads (passing cls or any of the hooks uses an overload which returns Any). Last I heard, the issue with the return type (and the input to dump) was due to recursion.
Without nesting, the type would be List[Any] | Dict[str, Any] | str | float | int | None | bool (also I think the input can contain tuples). With nesting, replace the Any with this type ad infinitum.
Recursive type aliases are fine. The reason we use Any is that the full recursive type is not ergonomic to use for most users.
I understand that some users may want to opt in to this stricter check, though. It might be worth considering ideas like Introduce `typing.STRICTER_STUBS` · Issue #1096 · python/typing · GitHub (typing.STRICTER_STUBS) to allow people to use knobs to go from the more type-safe to the more ergonomic behavior.
It’s too bad we don’t have syntax to bind type variables at function call time, yet. Otherwise we could make load() generic:
def load[R=Any](...) -> R: ...
JSON: TypeAlias = dict[str, JSON] | list[JSON] | str | float | bool
x = load[R=dict[str, Any]](...) # I know it must be a dict.
y = load[R=object](...) # I want strict checking.
z = load[R=JSON](...) # I also want strict checking, but have a type alias.
s = load[R=SomeShape](...) # I have a TypedDict and trust the input.
What difference does it make to do
data = load[R=X](...)
vs
data: X = load(...)
?
If we had type variable binding syntax (which we haven’t), we could define load() in such a way that it’s possible to specify the return type. I.e., currently load() returns Any, but this could be overriden to make load() return whatever you define it to return:
x = load(...) # x is Any
y = load[R=object](...) # y is object
That doesn’t really answer my question. It’s not like binding the type variable makes the function any safer than just assigning the return value to a variable of constrained type.
And that’s precisely what the return type of Any allows, since Any is assignable to anything.
I think your argument makes more sense if the default for R was the recursive JSON type alias you defined, not Any.
It’s set to Any, because otherwise you can’t do this:
from typing import Any
JSON = dict[str, JSON] | list[JSON] | str | int | float | None
data: JSON = {"a": 1, "b": 2, "c": 3}
dct: dict[str, Any] = data
Output: (mypy Playground):
main.py:5: error: Incompatible types in assignment (expression has type "JSON", variable has type "dict[str, Any]") [assignment]
3 posts were split to a new topic: My solution to typing json.loads