You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This (1) doesn't refer to the name of the NewType, (2) doesn't refer to the wrapped type, (3) doesn't refer to the module the NewType was defined in, and (4) is confusing.
If a function is defined with the NewType in its signature, this repr appears in inspect.getsignature(signature_of_function).
For example:
This change would also make it less opaque how to test whether an object is a NewType (I think currently the best way to make this check is to say hasattr(x, "__qualname__") and x.__qualname__ == 'NewType.<locals>.new_type' which is not exactly transparent.
I imagine this change could break typecheckers which are likely to care about the implementation details of NewType, but the fix for the type checkers shouldn't be too complicated.
Since NewType is 99.9% meant for the benefit of static type checkers, I'm not sure that I care much about how it is rendered at runtime. If you have a specific issue with this related to Jedi maybe it's best to solve this in Jedi.
In principle, NewType should be beneficial for both the static type checker and for people using the API. If say Union[A,B,C] of several types comes up a lot, replacing Union[A,B,C] with a semantic name improves the code clarity a lot (particularly if the types A, B, and C have long names). If you say MySemanticName = Union[A,B,C] then you can use MySemanticName in the code but when Jedi looks up the documentation for the function it will show Union[A,B,C] instead of MySemanticName. I had hoped that with NewType I could get Jedi to show MySemanticName but instead Jedi shows <function NewType.<locals>.new_type at 0xblahblahblah>.
In practice, I think these fiddly ergonomics details can make a huge difference to how easy it is to use an API.
For my personal needs a satisfactory solution is to monkey patch NewType conditioned on TYPE_CHECKING:
What about the possibility of updating formatannotation to know about NewType?
defformatannotation(annotation, base_module=None):
# Add the following two lines? # If the annotation is a NewType, return the name of the NewTypeifhasattr(annotation, "__qualname__") andannotation.__qualname__=='NewType.<locals>.new_type':
returnannotation.__name__ifgetattr(annotation, '__module__', None) =='typing':
returnrepr(annotation).replace('typing.', '') # <== the path taken in NewType case.ifisinstance(annotation, type):
ifannotation.__module__in ('builtins', base_module):
returnannotation.__qualname__returnannotation.__module__+'.'+annotation.__qualname__returnrepr(annotation)
The more I read about your use case the more I think this should be solved in your tooling, not in typing.
Regarding whether it's better to show MySemanticName or Union[A, B, C] (or A | B | C with PEP 604 :-), I'm not sure that I would prefer seeing MySemanticName in the docs, unless that is a very well-known name. Otherwise it just sends me on a wild goose chase looking up the definition of MySemanticName.
But as I am not a Jedi user, I'll take your work for it -- I just don't think we should fix this in typing.
I'm not sure that I would prefer seeing MySemanticName in the docs, unless that is a very well-known name. Otherwise it just sends me on a wild goose chase looking up the definition of MySemanticName.
Absolutely a fair point.
What about the normal use case for NewType? If I say UUID = NewType("UUID", str) for example and then I define a function with signature do_something(uuid : UUID), to inspect.formatannotation, the signature of do_something looks like garbage -- if it either showed the annotation as UUID or str that would be acceptable but all NewTypes show more or less identical junk, and it's impossible to tell from this junk whether the function was expecting an int or as str.
Isn't the point of formatannotation to allow access to human readable type annotations at runtime? Currently it doesn't do that when the type annotation contains a NewType.
It seems the inspect module was not really updated to deal with PEP 484 -- and because PEP 484 does not really care about runtime use of annotations very much.
By the time we could fix this we wouldn't need the fix any more, since in 3.10 we'll have from __future__ import annotations on by default (per PEP 563).
I worry that your proposed fix (turning NewType into a class) would slow down common usage -- the docstring promises "almost zero runtime overhead." I fear that an instance with a __call__ method comes out slower than the new function created by the current implementation. OTOH maybe instances are cheaper than function objects -- this deserves some benchmarking. Maybe if this is decided in favor of the class we can fix it after all.
In [83]: from typing import NewType
In [84]: nt = NewType("nt", int)
In [85]: class NewTypeClass:
...: def __init__(self, name, supertype):
...: self.name = name
...: self.supertype = supertype
...: def __call__(self, obj):
...: return obj
...: def __repr__(self):
...: return f"<NewType: {self.name}>"
...:
In [86]: ntc = NewTypeClass("ntc", int)
In [87]: ntc
Out[87]: <NewType: ntc>
In [88]: %timeit nt(3)
211 ns ± 2.27 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [89]: %timeit ntc(3)
253 ns ± 5.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
I do agree that it would be useful to get a nicer repr for NewTypes; at my company we use NewTypes heavily in annotations and the current repr() makes it harder to read function signatures in IPython.
I wonder if it would be faster if we implemented NewTypeClass in C.
( the repo isn't ready-for-public, and needs a lot more work on refactoring, renaming, etc,
-- but it's tested for python 3.6-3.8 )
note that I couldn't find a way to get modules from this (relevant issue: #757 )
ps: are you building some kind of 'runtime-type-extraction' for codegens/etc?
I'm also hoping python will get standardized codegen to sort out all kinds of "mypy-plugin" vs "pyright-plugin" vs "pycharm-plugin" -- something like dart's codegens: https://github.com/dart-lang/source_gen
@devdoomari3 Thanks for the suggestion, I also made a "is this a NewType" test but it is worse than the one you linked. I think from __future__ import annotations is sufficient for my needs though, I was just trying to get Jedi type annotations to work correctly when NewType occurs in the type signature.
Activity
gvanrossum commentedon Aug 14, 2020
Since NewType is 99.9% meant for the benefit of static type checkers, I'm not sure that I care much about how it is rendered at runtime. If you have a specific issue with this related to Jedi maybe it's best to solve this in Jedi.
hoodmane commentedon Aug 14, 2020
In principle,
NewTypeshould be beneficial for both the static type checker and for people using the API. If sayUnion[A,B,C]of several types comes up a lot, replacingUnion[A,B,C]with a semantic name improves the code clarity a lot (particularly if the typesA,B, andChave long names). If you sayMySemanticName = Union[A,B,C]then you can useMySemanticNamein the code but when Jedi looks up the documentation for the function it will showUnion[A,B,C]instead ofMySemanticName. I had hoped that withNewTypeI could get Jedi to showMySemanticNamebut instead Jedi shows<function NewType.<locals>.new_type at 0xblahblahblah>.In practice, I think these fiddly ergonomics details can make a huge difference to how easy it is to use an API.
For my personal needs a satisfactory solution is to monkey patch
NewTypeconditioned onTYPE_CHECKING:hoodmane commentedon Aug 14, 2020
What about the possibility of updating
formatannotationto know aboutNewType?gvanrossum commentedon Aug 14, 2020
The more I read about your use case the more I think this should be solved in your tooling, not in
typing.Regarding whether it's better to show
MySemanticNameorUnion[A, B, C](orA | B | Cwith PEP 604 :-), I'm not sure that I would prefer seeingMySemanticNamein the docs, unless that is a very well-known name. Otherwise it just sends me on a wild goose chase looking up the definition ofMySemanticName.But as I am not a Jedi user, I'll take your work for it -- I just don't think we should fix this in
typing.hoodmane commentedon Aug 14, 2020
Absolutely a fair point.
What about the normal use case for
NewType? If I sayUUID = NewType("UUID", str)for example and then I define a function with signaturedo_something(uuid : UUID), toinspect.formatannotation, the signature ofdo_somethinglooks like garbage -- if it either showed the annotation asUUIDorstrthat would be acceptable but allNewTypes show more or less identical junk, and it's impossible to tell from this junk whether the function was expecting anintor asstr.Isn't the point of
formatannotationto allow access to human readable type annotations at runtime? Currently it doesn't do that when the type annotation contains aNewType.gvanrossum commentedon Aug 15, 2020
It seems the inspect module was not really updated to deal with PEP 484 -- and because PEP 484 does not really care about runtime use of annotations very much.
By the time we could fix this we wouldn't need the fix any more, since in 3.10 we'll have
from __future__ import annotationson by default (per PEP 563).I worry that your proposed fix (turning NewType into a class) would slow down common usage -- the docstring promises "almost zero runtime overhead." I fear that an instance with a
__call__method comes out slower than the new function created by the current implementation. OTOH maybe instances are cheaper than function objects -- this deserves some benchmarking. Maybe if this is decided in favor of the class we can fix it after all.JelleZijlstra commentedon Aug 16, 2020
It's about 20% slower:
I do agree that it would be useful to get a nicer repr for NewTypes; at my company we use NewTypes heavily in annotations and the current repr() makes it harder to read function signatures in IPython.
I wonder if it would be faster if we implemented NewTypeClass in C.
gvanrossum commentedon Aug 16, 2020
Maybe we could have a
NewTypefunction that special-casesintandstr(which I presume are the bulk ofNewTypecalls):devdoomari3 commentedon Sep 26, 2020
@hoodmane
you can solve 'is this type a
NewType'? like this:https://github.com/pycodegen/python-type-extractor/blob/develop/py_type_extractor/type_extractor/middlewares/newtype_found.py#L16
( the repo isn't ready-for-public, and needs a lot more work on refactoring, renaming, etc,
-- but it's tested for python 3.6-3.8 )
note that I couldn't find a way to get modules from this (relevant issue: #757 )
ps: are you building some kind of 'runtime-type-extraction' for codegens/etc?
I'm also hoping python will get standardized codegen to sort out all kinds of "mypy-plugin" vs "pyright-plugin" vs "pycharm-plugin" -- something like dart's codegens: https://github.com/dart-lang/source_gen
hoodmane commentedon Sep 27, 2020
@devdoomari3 Thanks for the suggestion, I also made a "is this a
NewType" test but it is worse than the one you linked. I thinkfrom __future__ import annotationsis sufficient for my needs though, I was just trying to get Jedi type annotations to work correctly whenNewTypeoccurs in the type signature.uriyyo commentedon Jul 30, 2021
Since python
3.11NewTypeis a class and it has nice repr, not like it was before. I think we can close this issue.It was done in scope of this issue - https://bugs.python.org/issue44353.
hoodmane commentedon Aug 1, 2021
@uriyyo Thanks for the update!