[issue45665] Problems caused by isinstance(list[int], type) returning True
Joseph Perez added the comment: There is also https://bugs.python.org/issue44293 about inspect.isclass -- nosy: +joperez ___ Python tracker <https://bugs.python.org/issue45665> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45418] types.UnionType is not subscriptable
Joseph Perez added the comment: Indeed, sorry, my example was bad. My library was raising at several place, and I've extrapolated about generic substitution. I've indeed other substitutions (without `TypeVar`), and because they were failing, I've assumed that all of my substitutions were failing; I was wrong about generic one. For example, if I want to substitute `int | Collection[int]` to `int | list[int]`, I will have to replace `types.UnionType` by `typing.Union` or use `reduce`, while it was not necessary in 3.9 where I could just write `get_origin(tp)[new_args]`. So I'll have to add some `if` in my code. -- stage: -> resolved status: pending -> closed ___ Python tracker <https://bugs.python.org/issue45418> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45418] types.UnionType is not subscriptable
New submission from Joseph Perez : `types.UnionType` is not subscriptable, and this is an issue when type manipulations are done. A common maniputation I've to do is to substitute all the `TypeVar` of a potential generic type by their specialization in the given context. For example, given a class: ```python @dataclass class Foo(Generic[T]): bar: list[T] baz: T | None ``` in the case of `Foo[int]`, I want to compute the effective type of the fields, which will be `list[int]` and `int | None`. It could be done pretty easily by a recursive function: ```python def substitute(tp, type_vars: dict): origin, args = get_origin(tp), get_args(tp) if isinstance(tp, TypeVar): return type_vars.get(tp, tp) elif origin is Annotated: return Annotated[(substitute(args[0], type_vars), *args[1:])] else: return origin[tuple(substitute(arg) for arg in args)] # this line fails for types.UnionType ``` And this is not the only manipulation I've to do on generic types. In fact, all my library (apischema) is broken in Python 3.10 because of `types.UnionType`. I've to substitute `types.UnionType` by `typing.Union` everywhere to make things work; `types.UnionType` is just not usable for dynamic manipulations. I've read PEP 604 and it doesn't mention if `types.UnionType` should be subscriptable or not. Is there a reason for not making it subscriptable? -- messages: 403554 nosy: joperez priority: normal severity: normal status: open title: types.UnionType is not subscriptable versions: Python 3.10 ___ Python tracker <https://bugs.python.org/issue45418> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue44353] PEP 604 NewType
New submission from Joseph Perez : `typing.NewType` doesn't support PEP 604. ``` >>> import typing >>> A = typing.NewType("A", int) >>> B = typing.NewType("B", str) >>> A | B Traceback (most recent call last): File "", line 1, in TypeError: unsupported operand type(s) for |: 'function' and 'function' ``` -- messages: 395359 nosy: joperez priority: normal severity: normal status: open title: PEP 604 NewType versions: Python 3.10 ___ Python tracker <https://bugs.python.org/issue44353> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue44293] PEP 585 breaks inspect.isclass
Joseph Perez added the comment: @Jelle Zijlstra Thank you for the explanation. > The current implementation of GenericAlias has been around for a few releases > by now, though, so that change might break some use cases. I agree that a "fix" could have unexpected side-effect, my issue comes quite late indeed. By the way, Python typing is so much unstable (every version breaks the previous one), it's very complicated to write code that support multiple versions, so whatever the typing internal implementation, we must adapt. > This is not true; it is the same for e.g. `set[int]`. Unless you meant > something else here. I have chosen `list[int]` as an example of `types.GenericAlias` introduced by PEP 585 (i could have chosen `set[int]` or `collections.abc.Collection[int]`). But other generic aliases, e.g. `typing.List[int]` or `MyClass[int]` (where `MyClass` inherits `Generic[T]`), are not instances of `type`. > @Joseph Perez, is there a specific library or pattern that is broken by this? Because `issubclass` requires a "class" as arg 1, I use the pattern `if isinstance(tp, type) and issubclass(tp, SomeClass)` (`isinstance` check being equivalent to `inspect.isclass`). With PEP 585, it breaks for `list[int]` and other builtin generic aliases. > FWIW I did think rather carefully about which attributes to delegate or not, > and delegating __class__ was intentional. I don't have the context of the decision, so I can quite understand that delegating `__class__` was the right thing to do, especially when `__mro__` and other `type` attributes are also delegated. I mainly wanted to highlight this side effect, especially on the pattern mentioned above. (My issue title is a little bit excessive in this regard) But as I've written, I've already so many wrappers to maintain compatibility between Python versions of typing that I can write a new one to handle this particularity of PEP 585. So this issue is not critical to me. -- ___ Python tracker <https://bugs.python.org/issue44293> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue44293] PEP 585 breaks inspect.isclass
New submission from Joseph Perez : PEP 585 has the side-effect of making `list[int]` an instance of `type`. This is not the case for other generic aliases. It also implies that `inspect.isclass(list[int]) is True`, while `list[int]` is not a class; as a proof of this statement `issubclass(list[int], collections.abc.Collection)` raises `TypeError: issubclass() arg 1 must be a class`. By the way, there is the awkward thing of having `isinstance(list[int], type) is True` while `issubclass(type(list[int]), type) is False`. -- messages: 394950 nosy: joperez priority: normal severity: normal status: open title: PEP 585 breaks inspect.isclass versions: Python 3.9 ___ Python tracker <https://bugs.python.org/issue44293> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue42921] Inferred Optional type of wrapper function arguments
New submission from Joseph Perez : `typing.get_type_hints` gives a different result for a wrapper created with `functools.wraps` in case of inferred `Optional` arguments (when the default value of the argument is None) ```python from functools import wraps from typing import get_type_hints def foo(bar: int = None): ... @wraps(foo) def foo2(*args, **kwargs): ... print(get_type_hints(foo)) # {'bar': typing.Optional[int]} print(get_type_hints(foo2)) # {'bar': } ``` This is because `get_type_hints` use the defauts of the wrapper (`foo2`) and not those of the wrapped function (`foo`). This is not consistent with some other tools like `inspect.signature` which gives the same signature (and thus same default argument) for the wrapped function and its wrapper. I think this case has simply been forgotten in the resolution of https://bugs.python.org/issue37838 (fixing `get_type_hints` not taking `wraps` in account at all), because inferred `Optional` is a kind deprecated feature (https://github.com/python/typing/issues/275). -- messages: 385005 nosy: joperez priority: normal severity: normal status: open title: Inferred Optional type of wrapper function arguments type: behavior ___ Python tracker <https://bugs.python.org/issue42921> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41644] builtin type kwargs
Joseph Perez added the comment: That's why it's not an interpreter issue but a lack in the official documentation where the signature is documented. I quote https://docs.python.org/3/library/functions.html#type: > class type(object) > class type(name, bases, dict) The second line should be "class type(name, bases, dict, **kwargs)". (I've mentioned Pycharm and Mypy, but i think it's a kind of side-effect of the incomplete official documentation on which is based their typeshed) In fact, I've raised this issue because I've found myself needing to instantiate a class using `type` and kwargs, and I've not found in the documentation an example of it. -- ___ Python tracker <https://bugs.python.org/issue41644> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41644] builtin type kwargs
New submission from Joseph Perez : Class definition can have kwargs which are used by `__init_subclass__` (and `__prepare__`). However, passing these kwargs using `type` builtin function instead of class definition syntax is not documented; kwargs are not mentioned in the function signature. https://docs.python.org/3/library/functions.html#type However, passing kwargs to `type` works: ```python class Foo: def __init_subclass__(cls, **kwargs): print(kwargs) Bar = type("Bar", (Foo,), {}, bar=None) # mypy and Pycharm complain #> {'bar': None} ``` By the way, the possibility to pass kwargs in `type` call is not documented in https://docs.python.org/3/reference/datamodel.html#customizing-class-creation too. -- assignee: docs@python components: Documentation messages: 375936 nosy: docs@python, joperez priority: normal severity: normal status: open title: builtin type kwargs versions: Python 3.10, Python 3.6, Python 3.7, Python 3.8, Python 3.9 ___ Python tracker <https://bugs.python.org/issue41644> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41370] PEP 585 and ForwardRef
Joseph Perez added the comment: However, PEP 563 will not solve the recursive type alias issue like `A = list["A"]` but this is a minor concern. -- ___ Python tracker <https://bugs.python.org/issue41370> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41370] PEP 585 and ForwardRef
New submission from Joseph Perez : PEP 585 current implementation (3.10.0a0) differs from current Generic implementation about ForwardRef, as illustrated bellow: ```python from dataclasses import dataclass, field from typing import get_type_hints, List, ForwardRef @dataclass class Node: children: list["Node"] = field(default_factory=list) children2: List["Node"] = field(default_factory=list) assert get_type_hints(Node) == {"children": list["Node"], "children2": List[Node]} assert List["Node"].__args__ == (ForwardRef("Node"),) assert list["Node"].__args__ == ("Node",) # No ForwardRef here, so no evaluation by get_type_hints ``` There is indeed no kind of ForwardRef for `list` arguments. As shown in the example, this affects the result of get_type_hints for recursive types handling. He could be "fixed" in 2 lines in `typing._eval_type` with something like this : ```python def _eval_type(t, globalns, localns, recursive_guard=frozenset()): if isinstance(t, str): t = ForwardRef(t) if isinstance(t, ForwardRef): ... ``` but it's kind of hacky/dirty. It's true that this issue will not concern legacy code, 3.9 still being not released. So developers of libraries using get_type_hints could add in their documentation that `from __future__ import annotations` is mandatory for recursive types with PEP 585 (I think I will do it). By the way, Guido has quickly given his opinion about it in PR 21553: "We probably will not ever support this: importing ForwardRef from the built-in generic alias code would be problematic, and once from __future__ import annotations is always on there's no need to quote the argument anyway." (So feel free to close this issue) -- messages: 374105 nosy: BTaskaya, eric.smith, gvanrossum, joperez, levkivskyi, lukasz.langa, vstinner priority: normal severity: normal status: open title: PEP 585 and ForwardRef type: behavior versions: Python 3.9 ___ Python tracker <https://bugs.python.org/issue41370> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41341] Recursive evaluation of ForwardRef (and PEP 563)
Joseph Perez added the comment: Ok, I admit that I did not think about recursive type when proposing this "fix". I've tried an implementation that just stop when recursion is encountered in a PR. -- ___ Python tracker <https://bugs.python.org/issue41341> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41341] Recursive evaluation of ForwardRef (and PEP 563)
Change by Joseph Perez : -- keywords: +patch pull_requests: +20699 stage: -> patch review pull_request: https://github.com/python/cpython/pull/21553 ___ Python tracker <https://bugs.python.org/issue41341> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue41341] Recursive evaluation of ForwardRef (and PEP 563)
New submission from Joseph Perez : (This issue is already broached in https://bugs.python.org/issue38605, and a in some way in https://bugs.python.org/issue35834, but only as a secondary subject, that's why I've opened a ticket on this particular issue) ForwardRef of ForwardRef are not currently evaluated by get_type_hints, only the first level is, as illustrated in these examples: ```python from typing import ForwardRef, Optional, get_type_hints def func(a: "Optional[\"int\"]"): pass assert get_type_hints(func)["a"] == Optional[ForwardRef("int")] # one would expect get_type_hints(func)["a"] == Optional[int] ``` ```python from __future__ import annotations from typing import ForwardRef, Optional, get_type_hints def func(a: Optional["int"]): pass assert get_type_hints(func)["a"] == Optional[ForwardRef("int")] # one would expect get_type_hints(func)["a"] == Optional[int] (which is the case without the import of __future__.annotations!) ``` On the one hand I find this behavior quite counter-intuitive; I rather think ForwardRef as kind of internal (and wonder why there is no leading underscore, like _GenericAlias where it's used) and I don't understand the purpose of exposing it as the result of the public API get_type_hints. By the way, if ForwardRef can be obtained by retrieving annotations without get_type_hints, stringified annotations (especially since PEP 563) make get_type_hints kind of mandatory, and thus make ForwardRef disappeared (only at the first level so …) On the other hand, the second example show that adoptions of postponed annotations can change the result of get_type_hints; several libraries relying of get_type_hints could be broken. An other issue raised here is that if these ForwardRef are not evaluated by get_type_hints, how will be done their evaluatation by the user? It would require to retrieve some globalns/localns — too bad, it's exactly what is doing get_type_hints. And if the ForwardRef is in a class field, the class globalns/localns will have to be kept somewhere while waiting to encounter these random ForwardRef; that's feasible, but really tedious. Agreeing with Guido Von Rossum (https://bugs.python.org/msg370232), this behavior could be easily "fixed" in get_type_hints. Actually, there would be only one line to change in ForwardRef._evaluate: ```python # from self.__forward_value__ = _type_check( eval(self.__forward_code__, globalns, localns), "Forward references must evaluate to types.", is_argument=self.__forward_is_argument__) # to self.__forward_value__ = _eval_type( _type_check( eval( self.__forward_code__, globalns, localns), "Forward references must evaluate to types.", is_argument=self.__forward_is_argument__, ), globalns, localns, ) And if this fix could solve the "double ForwardRef" issue mentionned in https://bugs.python.org/issue38605, it would also resolve https://bugs.python.org/issue35834 in the same time, raising NameError in case of unknown ForwardRef with postponed annotation. -- messages: 373960 nosy: BTaskaya, eric.smith, gvanrossum, joperez, levkivskyi, lukasz.langa, vstinner priority: normal severity: normal status: open title: Recursive evaluation of ForwardRef (and PEP 563) type: behavior ___ Python tracker <https://bugs.python.org/issue41341> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com