#type-hinting
1 messages ยท Page 17 of 1
Optional means something can be None
Oh ok, so just type hint like normal. Withoit considering sometimes it can be empty?
I'm having an issue where two Protocols with @runtime_checkable are incorrectly returning True for isinstance checks for instances that are not using the protocols. Am I doing something wrong here, and/or is there some way to fix this: https://gist.github.com/mypy-play/0e4651f7aca3d94b002154db00ea7f12
@runtime_checkable only checks whether the method exists, not the argument count or types
ouch, ok. I guess in this case I should switch to using something like an abstract class instead of protocol?
that won't make isinstance() work any better
hmm, well then I guess I could make a non-abstract base class and just make the functions raise NotImplementedError?
Is there a way to type hint that my decorator also acts like a staticmethod decorator? Specifically to be recognized by pyright.
ABCs can help ya here if you override the __subclasshook__. Refer to their docs for more info. Note, however, that you will need to implement matching the types, names, and the number of arguments yourself so Jelle is generally right here :)
(You can also customise isinstance checks against protocols by adding __subclasshook__ methods if you want to โ this isn't well documented but it is well tested in the CPython test suite)
Huh, didn't know that. Cool.
Alex, maybe you know the solution to my issue too? :D
Show me what your decorator looks like now? ๐
Worst comes to few worst, you could always do the ol'
if TYPE_CHECKING:
from builtins import staticmethod as my_awesome_decorator
else:
def my_awesome_decorator(func):
...
hack
Already do in one of my libraries actually :D
But in this case I wish I could do something better
This is a rough typing-only draft of the thing I'm working on. Essentially I want to say that my decorator applies staticmethod to the function and then returns a class that has a similar signature to that function.
class AlterResponseInstruction:
def __call__(self, data: dict[str, Any]) -> None:
...
def alter_response(
model: type[BaseModel], *changes: Change
) -> Callable[[Callable[[dict[str, Any]], None]], AlterResponseInstruction]:
...
class CollapseEventRequest:
@alter_response(
EventAPIResource,
Change("request", type_old=str, type_new=uuid.UUID),
)
def alter_event_api_response(data: dict[str, Any]):
data["request"] = data["request"]["id"]
I don't want my users to have access to self on any of these "alter" methods but I also don't want them to write staticmethod manually every time
I guess this decorator is applied at import time so I could just check the signature and raise the error but type hint it as staticmethod because the users are not supposed to call this method anyways but I really wanted to type hint it nicely :(
It returns an instance of AlterResponseInstruction, not the AlterResponseInstruction class itself, right?
Yup
Adding a __get__ method (inside an if TYPE_CHECKING block if necessary) to the AlterResponseInstruction class might help
You'd want to give it the same signature as staticmethod in typeshed: https://github.com/python/typeshed/blob/8e4595a1682b5daf44498f81b95fbc8d1a651657/stdlib/builtins.pyi#L135
stdlib/builtins.pyi line 135
def __get__(self, __instance: _T, __owner: type[_T] | None = None) -> Callable[_P, _R_co]: ...```
Not sure though; I'm on my phone and don't have pyright handy to experiment with ๐
It doesn't because the error happens at the definition of alter_event_api_response. Tried just now.
I.e. It indicates that we should use a different type for "data" argument.
Feels like this behavior is hardcoded into pyright.
Hi all, I was looking at the threading module and thinking, why is Thread not using ParamSpec to type check that the callable arguments are passed?
Something like this (simplified on purpose)
P = ParamSpec("P")
T = TypeVar("T")
class Thread:
def __init__(target: Callable[P, T], args: P.args, kwargs: P.kwargs): ...
can i use kwargs in Annotated?
I guess you can use a dict
but slicing notation doesn't support keywords
!pep 637
(rejected)
relatable ๐
why tf does everything keep getting rejected
anyways i'd be looking into dataclass_transform
Make the field() function do somethings
Unfortunately P.args and P.kwargs can only be used to annotate variadic parameters, and threading.Thread takes exactly three parameters (target; args tuple; kwargs dict). If threading.Thread.__init__ had the signature def __init__(target, *args, **kwargs), we'd be able to use ParamSpec.
Wouldn't target need to be pos only?
yes, that too
TypeVarTuple should work then
I mean, if we ignore kwargs
๐
Conclusion: just use partial or a lambda
Ah yes, partial, famously easy to type: https://github.com/python/typeshed/issues/8703#issuecomment-1240022635
What is the correct way to tyoe hint any kind of number? (int, float, decimal.Decimal, np.int32, np.int64 and so on)
It depends on what you want to do with that number
It's gonna be written into a value in a dict which in turn is gonna be transformed to a CSV file
No further operations on it
IIRC pyright just hard-codes it lol
not sure about mypy but maybe too
for kwargs support, I think
Yeah both mypy and pyright have many many lines of special-cased logic for functools.partial
Why is this not allowed btw? Just for simplicity in initial implementation?
no
typing.SupportsFloat is a pretty good approximation for most numeric types
But excludes complex, which doesn't have a __float__ method
Ahh, so it's not all numeric types, then? ๐
lol, all real numeric types
my bad
Since I'm dealing with finances complex numbers didn't even come to mind honestly haha
I think they do have some application in economics
This is partly why I was asking what you wanted to do with them. The spectrum of "numeric types" in Python is so broad, that there's loads and loads of ways in which they can interoperate badly, so you have to think specifically about how your "numbers" in your specific use case need to be used in order to know how to annotate them.
Need to call float() on them before adding them together? Use typing.SupportsFloat. Need to call abs() on them? Use SupportsAbs. Etc. etc.
The problem with numbers.Number is it's an excessive generalisation that doesn't actually tell you anything useful about the type you're working with -- this is [one of several reasons] why type checkers don't support it.
I see, thank you so much! ๐
no worries!
accept Decimal and propagate the headache to the caller
They do in research I think, not really my area
less polymorphism = often less headache ๐
True dat
a concrete type is definitely less mental straining than INumberatable
lol
While I'm here, what is the correct way to type hint a str that MUST only be three upper case characters?
a huge literal

Can't really be done currently
lies
omw to create a literal with 26^3 or statements
Anyway, thank you everyone! ๐
at least I would consider a 3-tuple
@jade viper you might be interested in this feature proposal: https://github.com/python/typing/issues/1202
I love template string types in TypeScript
Is there a way to type hint a str that must be only characters at least?
they are also hellishly complicated
Ah, so only English uppercase letters?
not 1951^3? lol
In [43]: ilen(1 for cp in range(0x110000) if chr(cp).isupper())
Out[43]: 1951

LOL
i wish they had one for dataclasses.replace as well
Mypy has an open PR to add that actually https://github.com/python/mypy/pull/14849
I think I should put some limits... otherwise some smartass will eat all my cloud money
that smartass also might be me

Also, another curiosity question
Would Pydantic work with stuff like NewType?
I don't use pydantic but I can think of a way to find out
oh nice, i love using replace but it's more awkward to use w/ types which makes it more error prone
I only run mypy on friday because of how long it takes.
You could say my code is weekly typed
likely won't work w/ pyright, since they are opposed to non-pep ideas ๐ฅฒ
pyright does special-case some stdlib stuff
like partial mentioned above
also NamedTuple, Enums and such
Just merged last big PR for our monorepo migration from mypy to pyright. ๐
Lots of respect and kudos to mypy team. Pyright seems like a better fit for our org (we did some extensive research).
Type checks are much faster now ๐
@upper flame Would you kindly explain why you picked pyright instead?
# Protocol
@runtime_checkable
class SupportsGet(Protocol[_T_co]):
"""Protocol for a read-only descriptor."""
@overload
def __get__(self, obj: None, objtype: Any = None) -> Self: ...
@overload
def __get__(self, obj: EventProxy, objtype: Any = None) -> _T_co | None: ...
@runtime_checkable
class SupportsSet(Protocol[_T_contra]):
"""Protocol for a read-write descriptor."""
def __set__(self, obj: EventProxy, value: _T_contra) -> None: ...
# Impl
class PropBase(abc.ABC, SupportsGet[_T_co], SupportsSet[_T_contra]):
def __get__(self, obj: EventProxy | None, objtype: Any = None) -> _T_co | Self | None: ...
results in:
Method "__get__" overrides class "SupportsGet" in an incompatible manner
Return type mismatch: base method returns type "Self@SupportsGet[_T_co@SupportsGet]", override returns type "_T_co@PropBase | Self@PropBase[_T_co@PropBase, _T_contra@PropBase] | None"
Type "object* | PropBase[_T_co@PropBase, _T_contra@PropBase] | None" cannot be assigned to type "SupportsGet[_T_co@SupportsGet]"
"object*" is incompatible with protocol "SupportsGet[_T_co@SupportsGet]"
"__get__" is not present
And pyright is right here. You added another item in the union: Self
hmm?
i don't understand
Oh wait, I misread the code. I get it now
Feels like you'll need to provide overloads on the implementation too.
huh?
why?
Because the implementation is not necessarily compatible with these kinds of overloads
it should be
class PropBase(abc.ABC, _SupportsGet[_T_co], _SupportsSet[_T_contra]):
def __init__(self, *ids: EventEnum, default: _T_co | None = None, readonly: bool = False):
self._ids = ids
self._default = default
self._readonly = readonly
def _get_event(self, ins: EventProxy) -> AnyEvent | None:
for id in self._ids:
if id in ins.events:
return ins.events.first(id)
@property
def default(self) -> _T_co | None: # Configure version based defaults here
return self._default
@abc.abstractmethod
def _get(self, event: AnyEvent) -> _T_co | None: ...
@final
def __get__(self, obj: EventProxy | None, objtype: Any = None) -> _T_co | Self | None:
if obj is None:
return self
event = self._get_event(obj)
if event is not None:
return self._get(event)
return self.default
shouldn't this be a bug in typecheckers?
Why doesn't functools.lru_cache preserve function parameters? That's so annoying...
because pep 612 didnt give a way to do it
hmm, I see, thanks
how do I concatenate two types (not Union, that's an OR, I want an AND)
depends the answer is probably you cant
the only way to currently do it is to subclass
fuck me, TIL I could use descriptor fields in dataclasses
class _SupportsGetSetItem(Protocol[_KT_contra, _KV]):
def __getitem__(self, key: _KT_contra) -> _KV: ...
def __setitem__(self, key: _KT_contra, value: _KV) -> None: ...
why is this protocol not compatible with list and dict both?
it should be. what are you seeing exactly?
oki i have this code
class _SupportsGetSetItem(Protocol[_KT_contra, _KV]):
def __getitem__(self, key: _KT_contra) -> _KV: ...
def __setitem__(self, key: _KT_contra, value: _KV) -> None: ...
class _ValidationMixin(_SupportsGetSetItem[_T, Any]):
subcons: _SupportsGetSetItem[_T, c.Construct[Any, Any]]
def __getitem__(self, key: _T) -> Any:
child = super().__getitem__(key)
childcon = self.subcons[key]
if TYPE_CHECKING:
assert isinstance(childcon, c.Struct)
if isinstance(child, dict):
return _ValidatingDict(childcon, **child)
elif isinstance(child, list):
return _ValidatingList(childcon, *child)
return child
def __setitem__(self, key: _T, value: Any) -> None:
self.subcons[key].build(value)
super().__setitem__(key, value)
class _ValidatingDict(StrDict, _ValidationMixin[str]):
def __init__(self, struct: c.Struct[Any, Any], **kwds: Any) -> None:
self.subcons = struct._subcons # Dict[str, c.Construct[Any, Any]] --- ERROR
super().__init__(**kwds)
class _ValidatingList(AnyList, _ValidationMixin[SupportsIndex]):
def __init__(self, struct: c.Struct[Any, Any], *args: Any) -> None:
self.subcons = struct.subcons # List[c.Construct[Any, Any]] --- ERROR
super().__init__(*args)
it says
Cannot assign member "subcons" for type "_ValidatingDict"
"Dict[str, Construct[Any, Any]]" is incompatible with protocol "_SupportsGetSetItem[str, Construct[Any, Any]]"
"__getitem__" is an incompatible type
Type "(__key: str, /) -> Construct[Any, Any]" cannot be assigned to type "(key: _KT_contra@_SupportsGetSetItem) -> _KV@_SupportsGetSetItem"
Position-only parameter mismatch; expected 1 but received 0
"__setitem__" is an incompatible type
Type "(__key: str, __value: Construct[Any, Any], /) -> None" cannot be assigned to type "(key: _KT_contra@_SupportsGetSetItem, value: _KV@_SupportsGetSetItem) -> None"
Position-only parameter mismatch; expected 2 but received
and
Cannot assign member "subcons" for type "_ValidatingList"
"List[Construct[Any, Any]]" is incompatible with protocol "_SupportsGetSetItem[SupportsIndex, Construct[Any, Any]]"
"__getitem__" is an incompatible type
No overloaded function matches type "(key: _KT_contra@_SupportsGetSetItem) -> _KV@_SupportsGetSetItem"
"__setitem__" is an incompatible type
No overloaded function matches type "(key: _KT_contra@_SupportsGetSetItem, value: _KV@_SupportsGetSetItem) -> None"
oh you need to make the params positional-only in your protocol
sorry I missed that
wow it worked
the protocol says you can do _SupportsGetSetItem().__getitem__(key=42)
and list doesn't let you do that
sounds like it might be, your protocols don't have pos-only parameters there
oh but there's also an object, that sounds like mypy incorrectly inferring a type
it is pyright actually
Sure! Mostly these 2 things:
- Speed.
- Alignment of VSCode (our standart editor for devs) feedback loop with the CI feedback loop with minimal configuration.
I agree, the faster the feedback loop, the more eager devs are to run tests / static analysis early and often
For me the killer feature in pyright is the language server
Hints on hover, autocomplete and so on
Can relate
Is it possible to type-hint a function in a way that tells the type checker that code doesn't proceed after the function is called?
Like how would the end function be annotated here for example?
(the type checker still tells me that None is not subscriptable even though it can't be None)
import sys
def end():
sys.exit()
def func(l: list | None):
if l is None:
end()
return l[4]
Never or Noreturn
Hello, I'm trying to make a different return value depending on it's child. I've tried this:
class Base:
def __call__(self, *args, **kwargs):
subclasses = {
Foo: "a",
Bar: "b"
}
return subclasses[self.__class__]
class Foo(Base): ...
class Bar(Base): ...
But Pyright complains:
Argument of type "Type[Base]" cannot be assigned to parameter "__key" of type "Type[Foo] | Type[Bar]" in function "__getitem__"
Type "Type[Base]" cannot be assigned to type "Type[Foo] | Type[Bar]"
"Type[Base]" is incompatible with "Type[Foo]"
Type "Type[Base]" cannot be assigned to type "Type[Foo]"
"Type[Base]" is incompatible with "Type[Bar]"
Type "Type[Base]" cannot be assigned to type "Type[Bar]"
``` Any Idea?
class Base:
@overload
def __call__(self: Foo, *args, **kwargs) -> Literal["a"]:
@overload
def __call__(self: Bar, *args, **kwargs) -> Literal["b"]:
def __call__(self, *args, **kwargs):
subclasses = {
Foo: "a",
Bar: "b"
}
return subclasses[self.__class__]
class Foo(Base): ...
class Bar(Base): ...```this is dumb but will work
not really sure why you cant just override call in the subclass
that's kinda messy
thats one of the main draws of subclassing, it allows for separation of logic lol
I could, but much longer
consider using just normal methods then that allow you to take out bits
basically I'm using lambda for each class
subclasses = {
Equals: lambda first, second: kwargs["first"] == kwargs["second"],
NotEquals: lambda first, second: kwargs["first"] != kwargs["second"],
LowerThan: lambda first, second: kwargs["first"] < kwargs["second"],
GreaterThan: lambda first, second: kwargs["first"] > kwargs["second"],
# and some more here
}
If I don't use this, then I would need to make __call__ function on each subclass
you can definitely write a higher order function that does this for you
and you can just do something like __call__ = this_higher_order_function(operator.eq) inside the body of the class
Is there a way to "replace" a superclasses' method on a subclass so that mypy doesn't complain about mismatching method signature?
@override?
# type: ignore
@override doesn't let you make incompatible overrides
# type: ignore is correct because an incompatible override is type unsafe, so you should explicitly mark it as being unsafe
ah, your right
Why is it unsafe?
if you have a function def f(a: A): a.meth() and a class B(A): def meth(self, some_arg): pass
then f(B()) will fail at runtime
what if A is an abstract class
ah true, makes sense.
doesn't make a difference
ye, now that I think about it, it does seem like a weird thing to do
yeah in my case I'm "clobbering" some classes from a library and exposing my own versions to make things cleaner/better/more convenient, so the "base" class is not meant to be exposed so it should be "safe" in this case, so i'll just use # type: ignore[override]
sounds like you shouldnt use inheritance here at all in this case
should definitely be composition
there are other complications with that, where at the moment I believe this is the best way
well if everything is incompatible then passing it to any method that takes its superclass is problematic
yeah in this case its not a problem because the superclass is not exposed, only my version, and everything that is exposed uses my version
What is the most ergonomic way to statically validate that a class implements a Protocol? Ideally I'd like this validation to happen close to the class keyword
just subclass the protocol then your type checker should let you know
unfortunately that is not possible due to the reasons we discussed here: https://discord.com/channels/267624335836053506/1119407419471171676
Declaring a read-only @property on the protocol prevents subclasses from having a writable attribute with that name
And yet when I do validation like the following hack, it proves that typecheckers do consider the class to be compatible with the protocol:
__typecheck: MyProtocol = cast(MyClass, None)
Is the dummy assignment here the most ergonomic approach?
One problem is it needs to be wrapped in quotes if declared near the class keyword:
__typecheck: MyProtocol = cast('MyClass', None)
class MyClass:
does it have to be an instance var?
could just do a settable property proxying the attribute
has to be an instance var, yeah
the goal is to implement a protocol that says "object must gave a gettable attribute named bar that will give you an int"
Then to write classes that implement that protocol, with static verification that they do. So that any drift between protocol and implementing class are caught statically
well im not sure then you might just be stuffed
I'll stick to this trick, seems like the best option. __typecheck: MyProtocol = cast('MyClass', None)
The forward reference issue with type declarations continues to be a real sore point in python typechecking
Currently to cast from one type to another in typing you have to use the function typing.cast which requires a runtime cost and is not very pleasant to use. i propose a built-in syntax for this: a as Type this re-uses the as keyword from imports and with statements, this is better over the existing typing.cast because it can be a no-op at runt...
Yeah coming from TypeScript, IMO python really needs better syntax for zero-cost typing
i suppose you couldn't e.g. just make the protocol not have anything at runtime (e.g. gate stuff on typing.TYPE_CHECKING?)
there is typing.assert_type
so you can do ```py
typing.assert_type(MyClass(), MyProto)
or typing.assert_type(typing.cast(MyClass, None), MyProto) (i case MyClass doesnt support creation without arguments)
no, that checks the types are exactly the same
๐ญ
Also, constructing the class is often more complex than passing zero params
Even if it is a hack solely for the typechecker, typechecker expects params to be passed.
Wrapping in if TYPE_CHECKING also doesn't help because the typecheckers will raise an error.
They're correct to complain, because subclassing from a protocol inherits the protocol's methods and properties
ah alright
Typescript has a way to declare read-only via annotations, it'd be nice if python had similar
interface Foo {
readonly bar: number;
}
there will likely be a PEP for this soon
Are you saying that it is currently being discussed? If so, is there somewhere to read about that ongoing discussion?
!pep 705
the discussion for this on discuss.python.org is now leaning to switching to ReadOnly
Something like foo: ReadOnly[int]?
yes
Oh and the discussion says that pyright already supports it, that's cool
Is this only for typeddict? Some of the discussion makes it seem like it's specific to typeddict, can't be used on normal classes and protocols
the PEP isn't rewritten yet, but I think it will only be for TypedDict
Oh so it's actually not at all relevant to my protocol issue?
yes, i use dummy assignments for this kind of thing. the cast seems like a reasonable additional trick
(and i don't know why people worry about the cost of cast, it's very very cheap. esp if the annotation is a string)
there's also Final for readonly variables from a type checker POV
anyone knows why 'operation' variable is like that?
Means unused variable likely
which means you assign to it but don't use it somewhere else
Did you try putting it outside the function then calling it inside the function?
Final is not applicable here, it does something different.
The Protocol needs to declare the attribute as read-only without assigning it a value. Final won't allow that.
Hi friends! I have this toy example of a real thing I need to type...
I don't see what's wrong with it.
from typing import Any, Callable, TypeVar,Generic
from typing_extensions import ParamSpec
P = ParamSpec("P")
R = TypeVar("R")
class Future(Generic[P, R]):
def __init__(self, f: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> None:
self.f = f
self.args = args
self.kwargs = kwargs
def get(self) -> R:
return self.f(*self.args, **self.kwargs)
def resolve(self) -> Any:
res = self.get()
while isinstance(res, Future):
res = res.get()
return res
def transformer(func: Callable[P, R]) -> Callable[P, Future[P, R]]:
def wrapper(*args: P.args, **kwargs: P.kwargs):
return Future(func, *args, **kwargs)
return wrapper
@transformer
def b() -> int:
return 1
@transformer
def a() -> int:
return b() # type error on this line: main.py:33: error: Incompatible return value type (got "Future[[], int]", expected "int") [return-value]
print(a().resolve())
Both mypy and pyright report the same error...
It works just fine in runtime
Well, b is a function that takes no arguments and returns a Future
but you're saying that a() returns an integer
what are you trying to do?
Right, but would not
@transformer
def a() -> int:
applying the transformer decorator should change the return type of a as well, should not it?
it does change the return type
Oh we have this complex system with decorators, I'm trying to improve VSCode expirience to avoid red squiggless everywhere
your issue is that you are returning that return from a
I guess to make things easier visually here is another example
@transformer
def sum(x: int, y: int) -> int:
return x + y
@transformer
def a(x: int, y: int) -> int:
return sum(x, y)
print(a(2, 3).resolve())
Decorators are syntax sugar for a function call: ```py
def transformer(func: Callable[P, R]) -> Callable[P, Future[P, R]]:
...
def pre_b() -> int:
return 1
pre_b is Callable[[], int]
b = transformer(pre_b) # b is Callable[[], Future[[], int]]
def pre_a() -> int:
result_of_b = b() # is a future
return result_of_b
# ^ here you're returning a Future[[], int]. But the function signature says you're returning an int!
a = transformer(pre_a)
ok groking on this....
ok would it be fair to say that the return type of the function inside pre_a should MATCH the actual return type? But THEN decorator is applied on top of it (because it's like the outer call on the function)
The b = transformer(pre_b) outer call
The return type of the pre_a function only refers to that particular function. The decorator does not impact that
An example from the stdlib would be contextlib.contextmanager
from collections.abc import Iterator
from typing import IO
from contextlib import contextmanager
@contextmanager
def foo() -> Iterator[IO[str]]:
with open("file.txt", encoding="utf-8") as file:
print("Opened file!")
yield file
print("Closing file!")
reveal_type(foo)
the inner function returns an Iterator while the type of foo for an outsider is something like ContextManager[IO[str]]
https://pyright-playground.decorator-factory.su/?gzip=H4sIAIE-j2QC_12PzQ6CMBCE732KtaeSiGdjojHxxIkHIMaUssUmpUvK-vf2FhEOzHG-neyMjdSBIe_RsKMw7HRtwHU9RYaCMWqmKOx4xJ_ehXZh5eQaCoxv9q6eyd_pdNAtRiHOK6NBC5ZIZZCflhdVUVYDx-v1ICDp5fgO1GNQ0jqPO36z3AIGQ03qcJQPtvleZqAHGPkUGtVHF1jJMkWx-bGNzBb6cegndx24eBrGdXNCRHyi9rc0GlVqm4kvDZc6dSkBAAA%3D
In your case what you really have is: the function under the transformer can return either the result R or a Future[P, R]
So something like ```py
def transformer(func: Callable[P, R | Future[P, R]]) -> Callable[P, Future[P, R]]:
not sure if it will work tbh
also I hope you don't have spin-looping futures in actual code ๐
Cool, let me try something with overloads...
and a union
oh actually no overloads needed?
spin-looping futures in actual code
the system is similar to https://www.sematic.dev/ -- it runs every transformer on a different k8s pod
And resolve lives also in a separate resolver which orchestrates the execution (and sometimes it spin-loop I think, but I didn't look into this part)
If you want to do asynchronous I/O with futures, there's already stuff for that in the standard library ๐
though I don't really understand what you're doing so
yeah it doesn't matter that much honestly ๐
Thank you for the help!
ok I kind of suspecting that this is not typable...
Tried playing around with
@overload
def transformer(func: Callable[P, R]) -> Callable[P, Future[P, R]]:
...
@overload
def transformer(func: Callable[P, Future[P, R]]) -> Callable[P, Future[P, R]]:
...
What errors do you get?
ah I see, if you return a future it will still fall under the first overload
so put the second overload first
same problem
Expression of type "Future[(x: int, y: int), int]" cannot be assigned to return type "int"
"Future[(x: int, y: int), int]" is incompatible with "int"P
I think what I really want to express is:
have one return type IF you are under @transformer
And otherwise have a different return type
Well this is still incorrect:
def a() -> int:
return b()
you need to change it to -> Future[something, int]
actually I'm not sure how to specify ParamSpec like that
Future[[], int] I think
The desire is to have uniformed composable transformers: it should not matter is b called from a or from the non-transformer code
but the return types would be different
depending on is the caller function decorated or not
I think I am really confused about what you're trying to do
I don't think so
I'll come back in like 30minutes btw
thank you
- There was a design choice that pre-dates my time, to use the same https://peps.python.org/pep-3107/ annotations that we all love for PEP484 for another purpose: they are used in runtime.
- In this example above that would correspond to doing a runtime checks inside the
resolve()loop. So something like that
def resolve(self) -> Any:
res = self.get()
while isinstance(res, Future):
res = res.get()
# check that what we returned matches the signature
assert type(res) == res.f.get_return_type() # <-- this is just a psudo-code, not real code
return res
- I'm trying to make type hints double-purposed which already sounds like an somewhat alarming idea, but that's the objective. I'm not sure how far I can move the needle, but hopefully some? Maybe with some type lies and trickery. The main objective is not to have red squiggles for developers in VSCode without applying type-ignore, even if that means just giving up on any strictness of the checker (i.e. return Any all other the place is also in the space of possibilities).
- We have many many lines of code written without much respect to the type checker but they do conform to (2).
- That was kind of a high-level narrative, now to the details of this particular
@transformerdecorator. The way (1) dictates the type hints to be written and (4) conforms to it is that you can write the following things and they will work in runtime more or less how you'd expect
def sum_non_transformer(a: int, b: int) -> int:
return a + b
@transformer
def sum_transformer(a: int, b: int) -> int:
return a + b
def user_non_transformer():
assert sum_non_transformer(1, 2) == 3
# we are outside of a transformer and heance we need to call resolve explicitly
assert sum_transformer(1, 2).resolve() == 3
@transformer
def user_transformer() -> Tuple[int, int]:
# this example illustrates that when inside the trasnformer you can use another transformer without thinking about how it would be executed
# the execution is orchestrated by Resolver on a remote machine and there is a lot of things going on
# but as a user, it seems just like a normal function call.
# we can mix transofrmer and non-transforemer calls,
# note the return type is `Tuple[int, int]` -- that's what the runtime system expects and uses, not the `Tuple[int, Future[int]]`
return (sum_non_transformer(1, 2, sum_transformer(4, 5))
def user_user_non_transformer():
assert user_transformer().resolve() == (3, 9)
- It's not out of the table to try to change how the system works, or maybe force all the users to type their return types with
Future[int]instead ofint. Maybe that's the right way? But it's kind of like a big ergonomic hit.
So the question is can something be improved without shading too much blood.
Python Enhancement Proposals (PEPs)
Here is actually sematic's version of @transformer decorator -- @func decortor https://github.com/sematic-ai/sematic/blob/main/sematic/function.py#L287:L287
I guees their answer is to type it like this:
def func(func: Optional[Callable] = None) -> Union[Function, Callable]:
Not sure I was able to make it concise ๐คฃ
I don't think there's a non-invasive way of doing that tbh
maybe you could use async/await syntax
yeah I was typing and just though about it ๐
it would make so much sense to have explicit async / await and not try to hide it
yeah, mixing differently "coloured" functions like this doesn't really work out
There's stuff like green threads and gevent but not sure where it's at nowadays
ok thank you for baring with me -- it's a very useful exercise to bounce this with someone who understands the python typing better then me ๐
I would be careful with assuming that a random stranger knows something more than you
lol, why?
I might just be really confident in what I'm saying ๐
I think that's what I will do
from typing import Any, Callable, TypeVar,Generic, Union, overload
from typing_extensions import ParamSpec
P = ParamSpec("P")
R = TypeVar("R")
class Future(Generic[P, R]):
def __init__(self, f: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> None:
self.f = f
self.args = args
self.kwargs = kwargs
def get(self) -> R:
return self.f(*self.args, **self.kwargs)
def resolve(self) -> Any:
res = self.get()
while isinstance(res, Future):
res = res.get()
return res
def transformer(func: Callable[P, Any]) -> Callable[P, Any]:
def wrapper(*args: P.args, **kwargs: P.kwargs):
return Future(func, *args, **kwargs)
return wrapper
@transformer
def sum(x: int, y: int) -> int:
return x + y
@transformer
def a(x: int, y: int) -> int:
return sum(x, y)
print(a(2, 3).resolve())
At least we are going to have auto-complete for the transformer arguments
and the return types -- whatever
there was a post on hn talking about asyncio being bad and gevent being better, colored functions in all that
but tbh, i think colored functions are only annoying for library writers
i didnt think anyone would ever defend gevent over asyncio lol
but we can all agree asyncio is bad
trio <3
can you explain (or point me to some article) why asyncio is bad?
Here's a systematic method for understanding why asyncio is bad:
- Try to use it.
I don't want to be too critical of it. It's really impressive, actually, and it's a whole lot better than nothing. But it's also confusing and hard to use. Sometimes that's the way things go when you're on the cutting edge.
honestly it's fine for 70% of people. for library writers, even trio doesn't solve the colored function problems, and imo sans-io approaches aren't explored enough (beyond protocol use-cases).
they've been adding more trio related ideas in the latest asyncio anyways, like nurseries iirc
there's a lib called anyio which basically just has an api the same as trio but allows u to use asyncio as a backend
im probably very biased right now considering ive been debugging asyncio for the past few days, all i can say is that i dont like the way it deals with cancellation/ctrl+c and asyncios lack of structured concurrency really annoyed me for a while
as a user of asyncio I can confirm that it's ๐ ฟ๏ธain
why is there an inconsistency in the naming of protocols?
__len__: Sized__bytes__: SupportsBytes
Sized isn't a protocol it's an abstract base class
What is the difference?
You can use the abc with isinstance
ABCs use nominal typing whereas Protocols use structural typing
(That being said, many of the ABCs in collections.abc have their issubclass check overridden to essentially support structural typing)
(Fixed my statement)
You can use @runtume_checkable protocols with isinstance too
Sized could have been named SupportsLen, but I guess "Sized" was seen as a sufficiently nice name to prefer it
For all intents and purposes, you should think of Sized as a runtime-checkable protocol. It behaves in basically exactly the same way as a runtime-checkable protocol at runtime, and typecheckers think it's a protocol because of some lies we tell them in typeshed. The only significant difference between Sized and the typing protocols is that it's implemented slightly differently at runtime and doesn't have Protocol in its mro โ and that's basically because we can't have the collections.abc module import typing, or Python's startup time could slow down somewhat
There's also the fact that Sized is a lot older than the typing protocols โ we probably didn't have the Supports* naming convention back then
Thanks for the clarification ๐
It does appear to be that the ones in typing use Supports* while the ones in collections.abc have a name made up for them.
Ideally there should have been a Protocol for every dunder method, to avoid that jank in our code.
class X:
@property
def x(self) -> float: ...
class Y(X):
x: float # "x" incorrectly overrides property of same name in class "Position"
``` why this is invalid? how can i force it to be valid?
hmm, i just realized this code wont run (because Y().x = ... would cause X.x.__set__ logic, but it is not defined) ๐
nvm
thank you guys
Is there a way to know if a Callable takes a param or not? Something like this contrived example: https://mypy-play.net/?mypy=latest&python=3.11&gist=0917a093e15b354432ca47559d1b3de8
no
I guess I could use the inspect module to check the signature?
sometimes
i guess it also won't satisfy the type checker without doing an ignore on it
why only sometimes?
inspect.signature doesn't (and can't) work on all callables
ok
I recently started to use type hinting and I use vscode. I noticed it comes with pyright. I have internal company lib that does not provide type hinting and I generated them using stubgen but it's been a challenge since I decided to be serious and enable "strict mode", I had to ignore couple of rules because whole code turned red. I fixed most of inconsistencies but there are others where I can't do much and I don't want to continue ignoring rules. Now, my first question is: what do you guys use with vscode, pyright or mypy?
Pyright
But why'd you use strict if your code doesn't conform to it?
Just set it to basic until you have all the stubs complete
learning purpose mostly. And about the wait until I get the stubs, is it the only way? it might take some time for that. Does that mean projects that use this libs are doomed to use basic mode?
Curious, what are the current state-of-the-art techniques for increasing the presents of the type hints in the codebase?
I know about
- pyre infer
- monkeytype
Are there any ML models that can add type hints?
Steven Troxler had a talk about this at the typing summit this year. Meta has tried some ML models but so far hasn't productionized any
Any chance it was recorded?
https://docs.google.com/document/d/17iqV7WWvB0IwA43EPlIqlUS6Xuvk08X3sEudAA-gQIo/edit#heading=h.btsfybnycgt9 has notes and a recording (though poor quality)
Typing Meetup Notes PyCon Typing Summit: April 20, 2023 Jelle Zijlstra, The State of Typing 2023 Slides Recording (this one unfortunately starts from the middle of the talk) Alex Waygood, Common Typing Pain Points from User Questions Slides Recording Someone: What about class decorators? And c...
Python Type Hints are Turing Complete

jelle also has https://github.com/jelleZijlstra/autotyping/ for all the simple things (it integrates with pyanalyze for some more complex things)
openai's models can also reliably add basic type hints zero shot (disclosure: i work at openai)
is there a tool to make openai's models do this in a user-friendly way, like a command-line tool or editor integration?
not that i'm aware of. for editor integration there's copilot and some of the fancier copilot beta things, like copilot chat
Maybe this ^ could help

kill me
async def get_table_content(filename: str, tablename: str, _from: int, to: int) -> Dict[str, List[Dict[str, str | int | bool | None]] | List[Any]]:```
have you tried a TypedDict?
Hey there, i'm confused about this, is it me or mypy has a bug ?
This sounds like a bug, but show more code
(also this is Pylance and not mypy)
I guess Pylance leverages other tools including Mypy, but I'm not quite sure about that
The code is a simple condition that checks if len(someList) is the same as the count() of a Django QuerySet :
# Erreur si toutes les ressources ne sont pas trouvรฉes
if len(resource_ids) != await resources_qs.acount():
return 400, MessageResponse(
message="Une ou plusieurs ressources sont introuvables"
)
type of resource_ids is set[int]
I guess Pylance leverages other tools including Mypy
Pylance usespyright(well... kinda), butmypyis totally unrelated
https://github.com/microsoft/pylance-release
Though you will need to come up with an example other people can run
I'll try to make this quickly
unfortunately I can't reproduce this in a minimal test case. It must have to do with Django ORM / Pydantic.. I'll leave it as is for now.
Have you tried restarting the lsp?
Which one is generally preferred,
@overload
def func(a: str, highest: Literal[False] = False) -> list[int]: ...
@overload
def func(a: str, highest: Literal[True] = True) -> int: ...
or
@overload
def func(a: str, highest: bool = False) -> list[int]: ...
@overload
def func(a: str, highest: bool = True) -> int: ...
second is wrong
first is also not good i think
@overload
def func(a: str, highest: Literal[False]) -> list[int]: ...
@overload
def func(a: str, highest: Literal[True]) -> int: ...
@overload
def func(a: str, highest: bool = ...) -> int | list[int]: ...
def func(a: str, highest: bool = False) -> int | list[int]: # your impl
this works too, I'll just use this one I think
from the signature i feel like these should just be two different functions
Yes. overload is very useful for describing some APIs, but if you're writing a new function and need @overload to express the type, that's a mild sign that your API design isn't great.
overload is best for describing C++ API bindings
I just did like max(thing) if highest else thing. That shouldn't be separated to two different functions right?
my subjective opinion is yes they should
if you think about it, overloading solves the problem of sending the same message (invoking the same function) with parameters that are a union of types (a union of typed tuples, actually)
i guess python has millions other ways to do the same, without the complexity of overload
overloads and multiple inheritance are the two ways to perform type intersections
Random example but hopefully this demonstrates my point. Is it possible to do this? Unpack a TypeVarTuple to retrieve an element from the tuple.
from typing_extensions import Unpack
from typing_extensions import TypeVarTuple
from typing import Generic
Ts = TypeVarTuple("Ts")
def check_something(x): ...
class Car(Generic[Unpack[Ts]]):
def start(self, direction: str, *args: Unpack[Ts]) -> None:
some_variable, *args = args # Here
check_something(some_variable)
self.stop(*args)
def stop(self, *args: Unpack[Ts]) -> None:
...
Just experimenting with this to understand it a little better
the issue might be that youre using the parameter name still (args = args)
try a different name and theres a chance it might work?
Yeah, looked into that and got something else with the same list[Never] type.
some_variable, *t_args = args
check_something(some_variable)
self.stop(*t_args) # Unpacked argument cannot be used for TypeVarTuple parameter
maybe your signature should be def start(self, direction: str, some_variable: str, *args: *Ts)
How do I get mypy to return an ast with typing information? I can't find any docs on it
Thanks that works!
i dont think thats really a thing
good luck on mypy docs for internals though :p
you can probably hack something together that does something vaguely along those lines but not all nodes in ast are annotatable
this is something that might be a decent starting point for what you want https://github.com/Gobot1234/steam.py/blob/main/docs/extensions/annotations.py but i dont think it works anymore
Yeah you helped me about 6 months ago with a similar issue, I managed to get types for variables but I wouldn't know what scope they're in which made it a bit useless if you had shadow named variables
I don't understand how an ide like pycharm knows the type of a variable if you click on it if they don't use an ast with typing information
it guesses if its pycharm
but in all seriousness it will go through the ast a some point but its more complicated than just that
class Foo[T]:
x: T
class Bar[T](Foo[T]): ...
class Baz(Foo[str]): ...
class Spam(Baz, Bar[int]): ...
def foo(bar: Bar[int]):
...
foo(Spam())```how come this doesnt warn you about incompatible types/lsp violations?
cause Spam() is allowed to be passed to a Bar taking function where x would be an int or something which seems wrong
yes that seems like it should be disallowed
what does it think Spam().x is?
str
guess it uses the first base
yeah it seems that way
i remember @hallow flint did something similar, did you ever do anything about it?
why would it?
is there a proof in the python TS that there couldnt be subtype of both str and int?
(are generic invariant in python?)
this class is invariant
!e py class str_int(str, int): ...
@soft matrix :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 1, in <module>
003 | class str_int(str, int): ...
004 | TypeError: multiple bases have instance lay-out conflict
type checkers don't know that though
they did i thought
but your object could be passed to a function that takes a Foo[str] and do foo.x = "x", and also passed to a function that takes a Foo[int] that does foo.x = 3
oh maybe not
!e ```py
class X: slots = 'a',
class Y: slots = 'b','c'
class Z(X, Y): ...
@tranquil turtle :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 3, in <module>
003 | class Z(X, Y): ...
004 | TypeError: multiple bases have instance lay-out conflict
Do typecheckers know about this?
pyright doesnt seem to
the generic you mean?
yes, the base class Foo
fwiw c++ doesnt like this
and every generic type is invariant by default? or you mean, this example in particular ?
okay! get it
in general if the type parameter is an output type it's covariant, if it's an input type it's contravariant, if it's both it's invariant
so because of invariance in that case, there could/shoud be no instance of Spam, if the type checker can infer non equality of int/str
in this case it's an attribute that supports both getting and setting, so it's invariant
but the issue is more about Spam here, rigth? that type actually shouldnt be instantiated, that's what you meant?
yes, Spam is where a type checker should error
Hey folks, I've got a strange situation. I'm trying to make a "GenericMask" container, basically something that wraps an ordered set of a type T to statically know if a member of the type T is in the Mask. So, I've got
T = TypeVar('T')
class GenericMask(Collection[Hashable[Generic[T]]]):
def __init__(self, mask: Iterable[T]):
self._mask: dict[T: None] = {t: None for t in mask}
def __contains__(self, item) -> bool:
return item in self._mask
def __iter__(self) -> Iterator[T]:
return iter(self._mask)
def __len__(self):
return len(self._mask)
def __str__(self):
return repr(self)
def __repr__(self):
return f"{self.__class__.__name__}({', '.join(k.name for k in self._mask)})"
def __eq__(self, other):
return not set(self).symmetric_difference(other)
def __hash__(self):
return hash(tuple(c for c in self))
Attempting to have GenericMask be a Collection of Generic Hashables, so I can write
class ConstellationMask(GenericMask[Constellation]):
...
etc
- why are you using dict?
- dict[T: None] is not valid
But, PyCharm keeps giving me a type error when I call
next(iter(constellation)).some_field_of_Constellation
saying it's a Hashable
Collection[Hashable[Generic[T]] isn't a valid base either
You should bound T to Hashable
But it wasn't giving me the right type hints before
Oh, bound, I fail to use the keyword
What's the difference?
It's acting like a sorted set
it should have read dict[T, None]
There's basically no point though because Hashable is pointless because lsp is ignored on hash
What do you mean?
oh, language server protocol?
So I won't get error checking if I pass it something without __hash__ ... Well that's annoying
no, Liskov substitution principle
... wat
object defines __hash__ but some subclasses of object are not hashable
Right, I guess I could have figured that out. But the results are the same
I just checked, lists aren't hashable, but I can make a
class ListMask(GenericMask[list]):
...
and the type checker doesn't complain
Okay, well, I guess I'll just make it generic on T and not worry about binding to to Hashable. Actually, I will bind it to Hashable, because it's acting the way I want it to now, there's just no point.
sorry, didn't follow, what did i do 
have a similar issue with generics and subclassing where things should have been incompatible but werent
oh right, i think i fixed https://github.com/python/mypy/pull/13714 . and also some issues with self types
why do TypeVars need a name parameter? seems a little bit redundant
looking at the source it seems that it is only used in 4 contexts:
- pickling
__reduce____repr__- introspection
!e
For example, for printing
from typing import TypeVar
T = TypeVar("T")
print(list[T])
@trim tangle :white_check_mark: Your 3.11 eval job has completed with return code 0.
list[~T]
a little bit
try:
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') # for pickling
except (AttributeError, ValueError):
def_mod = None
if def_mod != 'typing':
self.__module__ = def_mod
maybe this is more of an old solution and could be done better now
NewType, collections.namedtuple and such also accept a name
it might make debugging easier at very little expense, I guess
so does creating classes with type()
What would you print instead of list[~T]? list[<TypeVar object>]?
could the name be retrieved from the instance name by using a metaclass or something?
no
No, you can't introspect assignments like this
watch me getsource()
yeah it's not possible without hacks ๐
yeah i just skimmed through this pep while looking for type params, and thats what sparked my curiosity with typevars
doesn't a metaclass's __new__ constructor accept a name param?
since it inherits from type
or wait, that only applies when using the class Foo: syntax doesnt it
yep
so if you call type or other metaclass manually, you will need to provide the name manually
class is kinda like calling a metaclass and then assigning it to a variable
yup, but in that case it is able to grab the name of the 'variable' (where the variable is an instance of the metaclass)
that's what the class syntax does
yep
class Foo(A): pass is (approximately) syntactic sugar for Foo = type('Foo', (A,), {})
i understand
One trick I've seen in TypeScript is to use object destructuring (pseudocode):
const {T} = typeVar();
// same as:
const T = typeVar().T;
``` but we don't have that
Like the example in this readme https://github.com/practical-fp/union-types ```ts
import { impl, matchExhaustive, Variant } from "@practical-fp/union-types"
type Shape =
| Variant<"Circle", { radius: number }>
| Variant<"Square", { sideLength: number }>
const { Circle, Square } = impl<Shape>()
Because Python doesn't have module level descriptors yet. Else the __set_name__ method could be used
what use is annotating self or cls?
flake8-annotations ANN101 wants you to
theres basically never any point
only thing i can think of is for overloads on generic types or when decorators do funky things that alter the type
x: float = True
``` this is valid ๐ง
i understand why, but it is weird
def __imod__(self: P, other: float | _PointCommon, /) -> P:
if isinstance(other, _PointCommon):
...
return self
if isinstance(other, float): # error Unnecessary isinstance call; "float" is always an instance of "float"
...
return self
raise TypeError
``` pyright complains about second `isinstance` check
is there a way to avoid this?
mypy is happy with this code
float accept ints, bool is a subclass of int
for i in range(1):
pass
try:
print(i)
except:
pass
``` Why does pyright still says `"i" is possibly unbound`? (btw, sorry to interrupt your question denball)
because it cant statically determine that range will always iterate
i mean its not wrong?
It's not smart enough to determine that i will be bound. Pycharm is capable of checking simple cases like that iirc.
But I have caught it though?
Type checkers don't really know what will happen if you do something wrong
They just flag stuff that's "invalid" to do. In runtime it might cause an exception, cause something bad down the line, or not cause any trouble at all
I would just put # type: ignore, or initialize i = 0 before the loop
okay, thanks
It is a bit unfortunate, but there currently isn't a way to specify non-empty iterables for this
I see, will they possibly add support for that?
You can start a discussion at https://github.com/python/typing/discussions ๐
i find that hard to believe lol
yeah it just doesnt give you squiggles it just says it in a dialouge box
How do I type-hint a string that has a default value?
x:str=""
Just the regular str type hint then?
You can use Literal and @overload if you want
How'd I use @overload in this case?
can you show your code?
also does anyone have any thoughts/feedback on https://gist.github.com/Gobot1234/71e4d948b02c7a6f04cf484455c6d971
chances are you don't need overloads, that's generally only needed when there's some complex dependency between arguments/return values, like if particular string values produce a different return type
it would look something like this ```python
@overload
def f(x: Literal["int"] = ...) -> int: ...
@overload
def f(x: Literal["str"]) -> str: ...
def f(x: str) -> int | str:
if x == "int":
return 0
elif x == "str":
return ""
raise ValueError("wat")
but you can. range(1) is always gonna iterate (true for any integer literal > 0) (unless ofcourse you have it redefined)
are there any plans for integrating ruff with type checkers for such issues?
or maybe a mypy plugin?
whyd it need to be integrated into the type checker?
for what it's worth pyanalyze recognizes that iterating over a nonempty range() always enters the loop. It's hardcoded though, along with a few other types.
for better analysis of such issues / lesser false positives?
i honestly dont think ive seen any false +ves from ruff
also i think doing ipc between rust and python would be hellish
i mean lesser false positives from mypy
oh lol

ipc isn't the only solution, you can use some inproc solution
but IPC overhead won't be much probably?
yeah but that doesnt mean its fun to work with
what specifically would ruff be able to help with?
your specification doesn't say what FunctionType.__getitem__ will return
the stuff about __orig_class__ feels very random, could use some context to explain why that's a problem
Can I make a shortcut for Annotated[int, ClassName] like E[ClassName]?
is the code below not enough?
it only makes sense if you know what GenericAlias normally does
and I don't think a lot of people know that off the top of their head
I only know because we discussed this point a little while ago
ok ill do a bit more build up to it then
I guess the audience for PEPs is basically the Steering Council. People on the SC are expected to have a strong general knowledge of how Python works, but they may not know every detail of how the type system or the runtime works.
so you shouldn't assume too much knowledge from readers
Any idea why this doesn't work guys?
!e
from typing import IO
from io import BytesIO
def validate_subtype(*args: any, subtype: type):
for item in args:
if not issubclass(type(item), subtype):
raise TypeError(f"Object '{item}' must be a child of type '{subtype().__class__.__name__}', but is of type '{item.__class__.__name__}'.")
print(validate_subtype(BytesIO(), subtype=IO))
@jade viper :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 9, in <module>
003 | print(validate_subtype(BytesIO(), subtype=IO))
004 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
005 | File "/home/main.py", line 7, in validate_subtype
006 | raise TypeError(f"Object '{item}' must be a child of type '{subtype().__class__.__name__}', but is of type '{item.__class__.__name__}'.")
007 | TypeError: Object '<_io.BytesIO object at 0x7f193f1b8ea0>' must be a child of type 'IO', but is of type 'BytesIO'.
BytesIO inherits from BinaryIO, which inherits from IO
it doesn't at runtime, the stub is a lie
I'm not sure I understand, sorry lol
You mean this stub from my linter isn't the actual BytesIO class definition?
yes, that's the typeshed stub. The actual class definition is in C
Ooooh I see
I wanted to check if an object I receive as a function parameter is any kind of "buffer"
I thought there'd be more types than BytesIO and StringIO so I thought up this solution
But guess I'll just check if it's one of those two then
Ty:)
a naive implementation would be:
!e
from fishhook import hook
from typing import TypeVar
T = TypeVar("T")
@type
def FunctionType(): ...
@hook(FunctionType)
def __getitem__(self, _):
return self
def f(x: T, y: T) -> None:
print(x, y)
f[int](3, 5)
@viscid spire :white_check_mark: Your 3.11 eval job has completed with return code 0.
3 5
and just let typecheckers handle the rest
returning self is no good imo
also using fishhook is no good
well I did say the naive implementation ๐
it was to make the eval work
fishhook is funny haha.
reminds me of kotlin's extension functions
tho those are scoped, why are sweet
Make a runtime checkable Protocol?
is there a way to typehint what exceptions may be raised from a function?
No
You can document exceptions raised from the function body itself
perhaps NoReturn could get a subscript with the exception type (or a union of types)?
from typing import NoReturn
def f() -> NoReturn[ValueError]:
raise ValueError()
I thought about something like this a while ago.
and those empty parentheses aren't needed
I assume you mean the second pair of empties lol
ik and I thought about that and idk decided to include them
but it comes to the point where with overloads and stuff, you basically describe you function so much, that the actual logic is represented by typing itself
even if NoReturn accepted generic parameters, its exact scope would be?
only allowed in return typehints
The function body itself or an exception raised and not caught from another function?
because if its the first, its like an incomplete hint
it would be any error that can arise when the function is called
and the second can be too vast to cover
ik, it's true
how do languages which require this typing go?
cause like doesn't Java require you to write what errors can arise?
java has it and you only mention the exceptions explicitly thrown by the function
hey, if it ain't broke, don't fix it!
C++ has a noexcept keyword which optimizes code gen for functions where its used
exception handling isn't cheap, and requires extra code
NoReturn (now it is the same as Never) is supposed to declare that function never returns:
def f() -> NoReturn: ...
f()
print('hello') # unreachable!
i think it would be better to declare possible exceptions in different way: ```py
-> Raises[int, KeyError, ValueError] # int is a return type (in case it does return)
-> Raises[int, (KeyError, ValueError)] # alternative syntax
-> Raises[Never, (KeyError, ValueError)] # never returns, raises KeyError, ValueError or hangs
-> int | Raises[Exception] # raises or returns int
-> Raises[Exception] # raises always
-> Never | Raises[Exception] # raises or hangs
-> Raises[E1] | Raises[E2] # raises E1 or E2
-> Raises[E1, E2] # same as previous
@Raises[KeyError, ValueError] # maybe decorator would be better?
raises int lol
If Python had a ResultOrError idiom like Rust, it would have been much better to type hint
int | Raises[KeyError, ValueError]
here Raises works just like Annotated. First arg is an actual type, everything else can be ignored
oh, yeah! it is a lot better
I also thought of writing Raises before I posted xd
but thought maybe something else that already exists can be used instead
Raises is certainly a good word
putting it into the return type hint is not the correct place. because an exception isn't returned, its raised, a decorator makes more sense
if in future it can be used for optimization rather than have a purely aesthetic purpose, it would be good
what would the decorator actually do?
in the beginning it can be just a no-op, like typing.cast
I was thinking as such
typechecker can treat Raises[...] as Never, so T | Never is just like T and everything works like before
but typeckeckers can also do additional logic
but tools like beartype can use it to check whether an exception other than those gets raised
problem might be if you want to write overloads for exceptions
Never is not returned nor raised, but it is still in return type annotation! ๐
Never as a return type hint actually makes sense
i doubt how much really exception information can be useful for type checkers, like what will they enforce in terms of how type hints are enforced?
Never is not an exception and thus that's an ill-formed type
or at least that's my interpretation
i believe mostly, exceptions aren't raised explicitly but are propagated, and sometimes you want this propagation
i think it's more useful as a human thing, and having it checked makes sure you can't forget about the propagated ones in the docs
-> Raises[X] means that this function always raises, and if it raises, then exception is of type X
-> Raises[Never] means that raised exception would be of type Never (which is impossible). Contradiction!
right, but the entire concept of Raises[Never] is ill formed
so that declaration should be what's invalid
typecheckers AFAIK do only two things related to our topic: ```py
1
def f() -> Never: ...
f()
print(':(') # unreachable!
2
raise Exception
print(':(') # unreachable!
And that's no exclusive to exceptions either
anyway, it is fun to thing about it)
annotating exceptions just seems annoying in general tbh
just because there are so many and you don't care to write them out
annotating entire stdlib and forcing all lib maintainers to annotate their exceptions too would be very painful
i mean, typeshed could help, but it'd be annoying to write out all the possibilities without libraries liberally aliasing
and then there's functions which use return values to indicate success or failure
Imo type checkers make you write code which is more lbyl than eafp
-> Raises[...] (or similar syntax) would be useful to declare that function propagates all exceptions from underlying functions (it is not raising by itself, it is not cathing anything)
def f1() -> int | Raises[E1]: ...
def f2() -> int | Raises[E2]: ...
def f() -> int | Raises[...]: # same as `-> int | Raises[E1] | Raises[E2]`
return f1() + f2()
because almost all functions have no try-except block or raise statements, so set of possible raised exceptions is a union of all sets of exceptions that can be raised from functions used inside
right
yea like without type checkers that piece of code would have been a try... except, but since the type checker knows the types now, we need to use isinstance and such
and annotating that would be painful without ^
LBYL: look before you leap
EAFP: easier to ask forgiveness than permission
oh i see
yeah I'm a thoroughly static type-pilled person so
Like the more typed you go, the more LBYL your code gets
Previously what would have been a direct dict key lookup wrapped in a try except, now becomes an if key in typed_dict for TypedDict
EAFP is sometimes unavoidable (sometimes in concurrency, for example)
also EAFP is sometimes faster because you dont have to ask for permission, you just do what you want and ask for forgiveness in 1% of cases
i mean that should really be a .get(...) and then handling the None in some concise manner (or something of the same manner)
but i see your point
EAFP is faster in python because the asking for permission part can be costlier due to lookups, but handling exceptions is definitely much more costlier
If we annotated all exceptions, then everything would need Raises[KeyboardInterrupt] and that would be extremely annoying. Maybe it's a special case?
Or maybe Raises should require a subclass of Exception and not a subclass of BaseException.
Also you'd need to figure out ExceptionGroup. I guess that's effectively a union.
in general exception annotations are really annoying for static typing
C++17 removed throw in function signatures (and it was considered bad practice), and everyone hates java's checked exceptions (c# removed its own checked exceptions!)
the big statically typed langs have tried it and decided wholeheartedly that it sucks
(ignoring effect systems for now)
is there a reason why https://peps.python.org/pep-0604 decided to use | instead of or?
can you provide an example of that
easy, you can't overload it
a or b either returns a or b, never anything including both
oh you can't
i was gonna check but i couldn't find a good list
yeah, the operators and, or, is, not, is not can't be overloaded (and not in / in can only return booleans)
i think comparisons might be restricted to only booleans as well? not sure
so it's because they wanted t1 | t2 to be overloadable and usable in expressions without changing the semantics of or
yep
What line do you get the error on?
according to the signature, It is valid for your function to return e.g. None or float, but Flask will not accept that
In your case you can mark the return type as -> str because you're always returning a string
wdym?
-> Flask.Response, I presume
You can check the source code of the library to see all the type annotations
editors like VSCode/PyCharm let you press a button on a variable and jump to its definition in library code
I think, mypy + returns allows you to do a similar thing. Though a lot more convoluted, functional, and monad-based :D
monad = based
Nope. Basedmypy = based
anyone aware aware of a way to define callable protocols with different overloads that have some keyword only arguments which are permissible as passed in function beside having multiple callable protocol classes and taking the union?
Just use typing.overload on __call__ of that protocol? Maybe I'm missunderstanding something, could you give some more detail to what you're trying to do?
i have a config object that has a optional function attribute which must match one of the calling conventions - a overloaded protocol couldnt match that as i need a one of, not a all of
it might be best to introduce a wrapper type that unifies the call patterns so that the outer type is mosre straightforward
yeah, probably, I don't think there's a way to do this in any other way than a union of protocol classes, each with their own __call__, sadly, there's no way to simplify that as Callable doesn't support specifying kw only arguments directly afaik
just hide that behind a type alias and use that, but yeah, it is annoying
Why do you need to support various function signatures though? That's pretty rare when taking in a function as parameter
the parameter gets resolved from imports and old calling conventions need to be supported for a while for backward compat
Greetings from #pyqtgraph
I come with a head-scratcher, not sure if this is feasible at all, but right now, I'm trying to address a pyright unknown import symbol
For a little context, pyqtgraph has a Qt-abstraction layer, where Qt.py can be overly simplified as ....
try:
from PyQt6 import QtCore, QtWidgets, QtGui
except ImportError
try:
from PySide6 import QtCore, QtWidgets, QtGui
except ImportError:
print("PyQtGraph needs Qt bindings to work...")
Throughout the library, we reference QtCore, QtWidgets, and QtGui via
from pyqtgraph.Qt import QtCore, QtWidgets, QtGui
This results in the each of the imports (QtCore, QtWidgets and QtGui) having a pyright error for unknown import symbol
What would be a good way to get type-annotations working here? For now, just trying to get pyright, might try pylance or mypy later...
Does adding an __all__ help?
it may be giving such error because the QtCore, QtWidgets, isQObjectAlive will be indefined in one of the branches
(just a conjecture)
hmm... hand't considered doing it, suppose I can do that.. (if it makes any difference, the layer is actually inside pyqtgraph/Qt/__init__.py)
I'm not?
in your first code snippet
look at the from X
from PyQt6 import QtCore, QtWidgets, QtCore
ah, I assume that's not a thing in the real code
we did add a stub file; but that was mostly because pycharm was able to use it: https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/Qt/__init__.pyi
I'll try __all__ and see what happens
still same error ๐ฆ
if you just try copying this into a file separately does pyright think its ok?
I am surprised how this works later on, considering both have slight differences in their APIs.
Have you taken a look at QtPy?
!pip QtPy
Iโm familiar with QtPy. We donโt use it for a variety of reasons, we may adopt it in the future instead of rolling out our own abstraction layer, but weโre not there yet
can you elaborate what you mean?
Do you think it's possible to use typing.overload on a property?
I.e. The property's value will change based on the generic arguments to self
oh interesting. i'd bet that no type checker handles that well if you tried
that sounds like pain yeah
use a method ๐
I usually write a method unless I need to be compatible with some interface
I can't. I am type hinting an old library (redis-py)
maybe thereโs some descriptor trick you could use
I don't think so because overload behavior is built into the type checkers
Code:
def __gt__(self, other: Self) -> bool:
if not isinstance(other, self):
raise TypeError(
f"'>' not supported between instances of "
f"'{self.__class__.__name__}' and '{other.__class__.__name__}'"
)
return self.index > other.index
mypy error
error: Argument 2 to "isinstance" has incompatible type "Self"; expected "_ClassInfo" [arg-type]
How do i use it then.
You could use overloads for get and set dunders in a descriptor
Huh. Interesting. Will check it out.
did you mean isinstance(other, type(self))?
Do I subclass existing protocols from _typeshed in type stubs, or do I add stuff like dunder methods manually instead?
the stubgen have generated stuff like this:
def __add__(self, other): ...
__radd__ = __add__
def __iadd__(self, other): ...
def __mul__(self, num): ...
__rmul__ = __mul__
def __imul__(self, num): ...
so if I subclass, do I subclass from both SupportsAdd and SupportsRAdd or just the first and leave __radd__ = __add__ in the stubs?
it seems I should use class MyAdder(SupportsAdd[Iterable[_T], Self]): ..., as MyAdder cannot be referenced there?
it did
i am using mypy + pylance both
!pep 673
pyright preview
class Bar(Generic[T]):
def bar(self) -> T: ...
class Baz(Bar[Self]): ... # Rejected```
oh well if it works i wouldnt complain
its unknown
i think its that at least
how can i avoid this
if TYPE_CHECKING or sys.version_info >= (3, 9):
IDEventMap = dict[int, AnyEvent] # Pyright Error
else:
IDEventMap = dict
class MyMap(IDEventMap): ...
Pyright: Subscript for class "dict" will generate runtime exception; enclose type annotation in quotes
if you want compatibility with <3.9, use typing.Dict
as a baseclass?
yep
How do i provide type hinting in this case
https://paste.pythondiscord.com/ijaquzopow
Here the N can be any child of the parent node Node.
just type them as Node
๐ Worked!
do i understand correctly that when type hinting callables, i must put the most specific type hint in the parameters according to the rule of contravariance? e.g. my type hint would be Callable[[Dict[str, Any]], None], so the user's function's param can be a dict, Mapping, MutableMapping, etc?
The user's function's param can be one of those yes, though you might want to make it MutableMapping, to allow yourself to change what type you pass to the function in the future if you need to.
if i type my decorator's parameter as Callable[[MutableMapping[str, Any]], None], the user cannot type their function's parameter as dict
so i guess the solution here is to either A. explicitly specify that the object being passed around is a Dict, or B. tell the user if they want to add a type hint themselves it has to be MutableMapping
now, if pyright would automatically infer a decorated function's param types, the second option wouldnt be needed
They would be forced to use MutableMapping, which might be good anyway - since later you could decide to pass a different type if that's easier for your code, a ChainMap for instance. Might not be worth it though.
maybe the easiest thing to do is provide a type alias that users can type-hint with for convenience
like
# exported from my package
HandlerInput = MutableMapping[str, Any]
# user's code
from mypkg import HandlerInput
@actions.action(...)
async def hello(payload: HandlerInput):
return f"Hello, {payload.get('name', 'world')}!"
what do you think?
however there remains one problem. if the user wants to create a TypedDict to type what fields their function expects in the dict parameter, the types become incompatible again
You could just use a dict, if you are fine with guaranteeing that.
if i change my type alias to be HandlerInput = Dict[str, Any], it is still incompatible with any user-defined TypedDict
so i guess the only way to allow the user to use their own mapping subclasses is to make the argument type hint Any on my side?
since i can't guarantee my package code will pass a value that is compatible with some arbitrary user-defined type
Well, that is quite true, it's not certain that the payload your package passes is compatible.
You could, bound=dict, but that would just push the type-unsafety into your own code. I'm not sure, is type[T] where T is a TypedDict valid? You could have your decorator take the TypedDict, then validate it matches on your side...
ideally, my code would validate that it is compatible and throw a TypeError if it isnt, without much code and no deps
so the user can use any dict subclass as a type hint, and my decorator would accept that and throw an error later on if the data received doesn't validate
something else that just came to mind: the fields in the payload param depend on the inputs defined in the decorator's params, e.g. if i add an input named "name", then the payload should contain a field accessible with payload["name"]. is there a way to generate this type from the param and use it for type hinting?
Mapping[str, Any] is fine, but Dict is invariant in keys and values since it's mutable.
If your callable is itself a parameter, then actually you should make the parameters of that callable as general as possible (you're basically applying contravariance twice, so it flips back to being covariant)
If it was assignable, this would be possible:
class MyTD(TypedDict):
spam: int
a_dict: MyTD = {'spam': 42}
alias: dict[str, Any] = a_dict
alias['spam'] = "non_int"
so, (dict[str, Any]) -> None can be assigned to (Mapping[str, Any]) -> None, but not the other way round
this is my decorator
def action(self, *, name: str = "", title: str = "", inputs: Sequence[Input] = tuple()):
"""Decorator to add an action."""
def decorator(handler: ActionHandler):
return self.add(
handler=handler,
name=name,
title=title,
inputs=inputs,
)
return decorator
these are my type aliases
HandlerInput = Mapping[str, Any]
ActionHandler = Callable[[HandlerInput], Awaitable[Any]]
here's my decorator being used
@actions.action(
title="Say hello",
inputs=[
Input(
name="name",
type=InputType.STRING,
optional=False,
),
],
)
async def hello(payload: ??):
return f"Hello, {payload['name']}!"
hopefully this gives us a better starting point to look for solutions
So do the Input objects correspond to payload keys?
TypedDict can't be assigned to Mapping[str, Any]. Consider this function:
def foo(m: Mapping[str, Any]) -> Any:
return m['k']
Pyright allows such a function since Mapping[str, Any] lets you pass any string key. On the other hand, TypedDicts don't necessarily have a key k, so it would result in an error if you passed a TypedDict into foo.
yep, they are used to define the api json structure, which a client should follow. its not guaranteed however, so ideally there would be some check somewhere later on
That's not really able to be typed as it is right now, since there isn't a way for type checkers to understand the correspondance.
What you could do instead is use dataclass_transform, perhaps?
So the user code would be something like this:
@actions.payload(
title="Say hello",
)
class Hello:
name: string = Input(optional=False)
@actions.action(Hello, title="Say hello")
def hello(payload: Hello):
return f"Hello, {payload.name}!"
Yeah, in Python, the type hints only go one-way (in your case, from the function that is passed into the decorator to the output of the decorator), not the other way round, unless you use a lambda function
so you can't infer the type of payload based on what is in the decorator
you would have to give payload a type in the input function. the most you could do afterwards would be to check that your given type for payload matches what is in the decorator
which you might be able to achieve via dataclass_transform
You can yes. Your field class can do whatever, so that can just be the options in Input.
You wouldn't need to reimplement dataclasses though - just remove the Input objects from the namespace temporarily, call dataclass() on the class, put them back (or in your own data structure).
Hi,
I am trying to achieve completion via type hinting with pydantic models.
here is an example:
from dataclasses import dataclass
from typing import Type
from pydantic import BaseModel
class OrderReceived(BaseModel):
order_id: str
@dataclass
class Event:
source: str
detail_type: str
detail: Type[BaseModel]
OrderReceivedEvent = Event(
source="Atlas.OrderReceiver",
detail_type="OrderReceived",
detail=OrderReceived,
)
what i want to be able to do is to do this and get completion for the needed parameters:
OrderReceivedEvent.detail(orde...)
Does anyone know if this is possible and if so how?
maybe you can make the type of detail generic?
so OrderReceivedEvent would be an instance of Event[OrderReceived]
like this?
T = TypeVar("T", bound=BaseModel)
class OrderReceived(BaseModel):
order_id: str
@dataclass
class Event:
source: str
detail_type: str
detail: Type[T]
tried it but doesn't work unfortuanetly
you need to make Event a subclass of Generic
that was the missing piece, thank you!
Didn't work that much with generics in python as they still feel weird to me
i read a bit about dataclass_transform just now. to be honest i didnt fully understand, but now im thinking why not just use a regular dataclass to define the expected payload beforehand?
something like
@dataclass
class HelloPayload:
name: Input(optional=False)
@actions.action(payload_type=HelloPayload, title="Say hello")
def hello(payload: HelloPayload):
return f"Hello, {payload.name}!"
What dataclass_transform does is tell the type checker that your function behaves just like @dataclass, causing the checker to apply the same special casing rules.
The problem there is that Input isn't a dataclass.field, so you need your own function.
right
You could use field(..., metadata={'actions': Input(...)}).
metadata just gets exposed on the field object, and is intended for this sort of thing.
alright... i will look into it more tomorrow, thanks for all the help so far!
why i forgot the logic of the code that i wrote some days ago
This isn't really related to this channel, but I'll just answer it quick: it's perfectly normal to forget the exact logic of your own code after a while. This is why you should document your code ๐
Thx i thought it was general
thats funny
mypy has the same issue
idk how to check the type of a for other type checkers though
how are you checking that?
I mean I know pycharm uses a custom version of mypy so it makes sense
๐ that's a whole other process but you can basically get it through this: https://github.com/pyastrx/pyastrx/tree/main/pyastrx/inference
these are the types it thinks the vars are
{'location': {'start': {'line': 14, 'column': 6}, 'stop': {'line': 14, 'column': 7}}, 'name': 'a', 'fullname': '__main__.a', 'node_name': 'NameExpr', 'annotation': 'builtins.int', 'attrs': []}
you can see at line 14 it thinks it's an int
not a bug since it reports an error earlier
you may want to use --allow-redefinitions
but let's say it does allow redefining the type then it should be considered a bug no? since it gets the type wrong
Pycharm has its own checker, unrelated to mypy
ah my bad
you can forward declare it
a: Union[int, str] (or a: int | str)
Type checkers by default freeze variable type to the first declaration
Delete a = 1
To fix that error
In that case a is at global scope, so when you use inside the function you are overwriting the variable. If you declare first the function you will use a as local scope and dont have colision problems
How can I disable that?
Umm idk let me check
you can ignore type check by using no_type_check decorator
from typing libarary
but I don't recommend it
I'd like to do it without modifying the source code
There is no option to ignore reasign
if he use Any lose types properties
Yes, but he is already doomed
That's the same as not having types
pyright doesn't support that.
add # type: ignore on the line of the return instead.
but it must know that a is a str at the end otherwise it wouldnt be able to tell me it expected a str, so why would it show me that a is an int?
type checking is good, but doesnt superseeds code quality in general.
do you have an actual use case where reassignment is legit and variable has a meaningful name?
in my case I'm making an obfuscator and type information is very useful for more advanced features so I can't rely on the customer writing quality code
not sure to get it. you expect customers to write non-mypy-valid code (since it seems to be the case here), and you hope enough relevant type info in order to obfuscate?
I mean not valid is a big word since all we're doing here is reassigning a variable to a different type
in the sense of mypy, I said ๐
I agree with you, it should have been understood, if python was as smart as smth made for that kind of things
(but that's where I stop believing in python, personnaly)
sure but the thing is that it does actually know that a is a str otherwise it wouldnt be able to tell us the unsupported operation cuz of str + int, so why would that info not appear in the type list it generates?
so perhaps there is some processing done while it's checking for these issues that isn't done when generating the type list
but wait, you talk about the runtime crash, or the pycharm warning?
the pycharm/mypy warning
Is there a way to type torch.nn.Module better?
def __getattr__(self, name: str) -> Union[Tensor, 'Module']:
Currently we have this annoying problem
https://github.com/microsoft/pyright/issues/4213
Ideally we would know when we get Tensor and when Module.
how does it decide at runtime whether to return a Tensor or a Module?
if we know that, maybe we can figure out a way to express it to the type checker
It's kind of bad
def __getattr__(self, name: str) -> Union[Tensor, 'Module']:
if '_parameters' in self.__dict__:
_parameters = self.__dict__['_parameters']
if name in _parameters:
return _parameters[name]
if '_buffers' in self.__dict__:
_buffers = self.__dict__['_buffers']
if name in _buffers:
return _buffers[name]
if '_modules' in self.__dict__:
modules = self.__dict__['_modules']
if name in modules:
return modules[name]
raise AttributeError("'{}' object has no attribute '{}'".format(
type(self).__name__, name))
torch/nn/modules/module.py line 1620
def __getattr__(self, name: str) -> Union[Tensor, 'Module']:```
um yes, that looks pretty hopeless
pyre tries do tricks https://pyre-check.org/docs/features/
Pyre has custom support for Python idioms that would otherwise not be supported by the usual type annotations.
Going to propose a patch to return -> Any
can i define a type hint for a class method based on one of the constructor params (or some other means dynamically)?
the snippet below doesn't work, but hopefully illustrates what i am trying to achieve
class Ham:
T = TypeVar("T")
def __init__(self, type: Type[T]) # type accepts a type object, e.g. Tuple
self.type = type
# param's type hint is based on a value recieved in __init__ or otherwise retrieved somehow
def method(self, param: T):
pass
ham = Ham(Tuple)
ham.method((1, 2)) # valid
ham.method("string") # invalid
i think this is supposed to be solved by pep 695, but i'm wondering if there is a way to do it in python 3.8 (admittedly any solution will probably not be very elegant)
You'll need to make the class generic over T by inheriting from Generic[T]. I think it should work after that
yup that idea just materialized in my head, testing it right now
it looks a bit cursed but type checker is not complaining
ham = Ham[Tuple](args)
is there a way to type hint that an argument has to be literally a Union[int, float] object?
as in,
def func(param: TypeHint):
pass
func(Union[int, float])
my solution right now is to put in in an enum, like so
class ParamType(Enum):
TYPE = Union[int, float]
def func(param: ParamType):
pass
func(ParamType.TYPE)
a littlemore context is needed there - is this something like cast, ot something different?
for example pytest has a storage + storagekey concept so one can do typesafe stashing of own object by encoding a type in the storage key
i want my function to accept one of 3 types as an argument: bool, str, or Number, where Number is equal to Union[int, float]. the function cannot accept int or float by themselves
but one can also use something like a type[T] and a typevar
so the function takes something like Literal[type[bool], type[str], type[Union[int, float]]] - whats the intent there, it seems a little scetchy and confusing at first
(i'd like o avoid the XY-problem)
the function is a class constructor, the class in question is a helper to define some JSON structure as it would appear in API usage, and the parameter is so the class instance knows what type it is
the API in question supports 3 types: "string", "number", and "boolean", roughly equivalent to the javascript types of the same name
do you have a example of the api you envision, and is there any reason for not using something like pydantic,chattrs or one of the other schema toolkits as to me this reads like you are invention your own, and i made that invention your own as a mistake often enough to be very compelled to tell others not to repeat my mistake ^^
the API already exists, this is simply a package to make it easier to define an interface with it
to give you the full truth here, there already exists a javascript package that does this for the API, and i'm just writing an equivalent in python
based on the javascript package
packages like pydantic are simply there for helping with that - i strongly recomend a little discourse into tools for dealing with apis and schemas, as transcribing a javascript package to python is most likely to cause pain and suffering to users of said package
is the package open source/public or something internal?
open source at https://github.com/lemonyte/deta-space-actions
this suspiciously looks like a proprietary mutilated openapi clone
are you looking for typing.NewType?
!d typing.NewType
class typing.NewType(name, tp)```
Helper class to create low-overhead [distinct types](https://docs.python.org/3/library/typing.html#distinct).
A `NewType` is considered a distinct type by a typechecker. At runtime, however, calling a `NewType` returns its argument unchanged.
Usage:
```py
UserId = NewType('UserId', int) # Declare the NewType "UserId"
first_user = UserId(1) # "UserId" returns the argument unchanged at runtime
almost, but Pylance tells me Expected class as second argument to NewType
You need typing.TypeForm which isn't a thing yet
I'm ashamed of what I have done. ```py
class classproperty(Generic[S, T]):
def init(self, func: Callable[[type[S]], T]):
self.fget = func
def __get__(self, instance: None, owner: type[S]) -> T:
return self.fget(owner)
class Foo(BaseModel):
if TYPE_CHECKING:
# doesn't work well with pydantic at runtime
@classproperty
@classmethod
def bar(cls) -> list[str]:
...
else:
# mypy complains about this
@classmethod
@property
def bar(cls)-> list[str]:
return []
my brain melts
is there a way to add a type parameter to a function such that it will return the given type? the equivalent of this typescript declaration in python:
declare function foo<T>(key: string): T;
i can do cast(T, foo(key)) of course, but definitely not elegant
That should work without the cast
wdym?
Unless this isn't inside a class
if it knows the assignment, it will work.
ah, so something like this?
my_value: T = foo(key)
Why are you converting to T here?
T = TypeVar("T")
def foo(key: str) -> T:
pass
x: int = foo("12")
Though generics are usuless if the var is only used once.
in actual code, this is a registry which will return a class based on a key, and i need to be able to cast it to a more specific type if i want to be able to do anything with it.
Maybe something like this. ```py
class MyKey(Generic[T], str): pass
MY_KEY = MyKeyX
def foo(key: MyKey[T]) -> T: ...
value = foo(MY_KEY)
I think what you're doing is just wrong in this case, if you know the type of key then the type should be the same not T
ah, this is a cool idea! thank you!
(Unless key is of type T but even then you shouldn't be casting)
the key is just a string in this case
i think @rare scarab's solution is an elegant one here
another question: is there a way to express a protocol class itself as a type? i want to have a function which accepts a runtime-checkable protocol class and does some stuff which includes an issubclass call with that protocol, but mypy says Variable "typing.Protocol" is not valid as a type
Now if only we could have generic enum values
No because the type is erased at runtime
Best you can do is probably just type
fair enough
now that i think about it, it wouldn't be of much use to know the type anyway, since the code using the type is generic
Landed a patch in PyTorch https://github.com/pytorch/pytorch/pull/104321
Is there a way to get mypy to infer union types instead of the common base?
Now, how do I properly configure a default value for a typevar?
default=?
This doesn't work. ```py
from typing_extensions import TypeVar
TNode = TypeVar("TNode", bound=BaseNode, default=Node)
def foo(cls: type[TNode] = Node) -> TNode:
pass
mypy says
Incompatible default for argument "cls" (default has type "type[Node]", argument has type "type[TNode]")
it was saying the same thing before I added default=
theres no way that ik of that will work with both mypy and pyright here
without a type ignore
pyright is fine with the thing youve sent but pyright doesnt like cast(TNode, Node)
I'll just remove the default argument. I only used it in one place anyway
I'm down to 3 mypy type errors in my project
Oh, of course it's inferring the dict to be dict[str, type[payload.type]]
kwargs = {}
if payload.type:
kwargs["type"] = payload.type
if payload.dup:
kwargs["dup"] = True
This feels nice.
Success: no issues found in 58 source files
Now if only adding a metaclass to my pydantic models didn't break pyright
Found a workaround for that.
I had my models configured like this. ```py
from pydantic import BaseModel as PydBaseModel
from pydantic.main import ModelMetaclass
class BaseModel(PydBaseModel):
pass
class MyModelMeta(ModelMetaclass):
pass
class MyBaseModel(BaseModel, metaclass=MyModelMeta):
pass
class MyModel(MyBaseModel):
foo: str
MyModel(foo="bar") # No parameter named "foo"
Setting the base of MyBaseModel to PydBaseModel makes it work.
But that's not what I want.
The better solution I found was to declare MyModelMeta as class MyModelMeta(type(BaseModel))
Hey guys! What is the most user friendly way to type hint an object that must inherit from some class? Is it just Type[Class]? Doesn't seem very intuitive
Class works. type[Class] means it expects the class instance.
if Class is abstract, it should all work out
then it would also be a valid type
I'm writing a function that receives a streamable object (inherits from typing.IO) and does some parsing and uploading
The correct type hint then would be just stream: typing.IO?
either IO[str] or IO[bytes]
yes.
maybe consider TextIO | BytesIO
use typing.Union instead of |
Yeah, that's what I do haha
or use from __future__ import annotations or wrap it in quotes
Ain't no way I could've used from __future__ import annotations this whole time
it doesn't work if you use typing.get_type_hints() or inspect.get_annotations()
Looks like typevar defaults broke the pydantic mypy plugin
Oh... That's what it was. Pyright doesn't inherit metaclass dataclass_transform
microsoft/pyright#3907 seems to be the closest match, though I don't think it quite matches.
Oh well. Decorating my metaclass with @dataclass_transform(kw_only_default=True, field_specifiers=(Field, FieldInfo)) seems to be a workaround.
microsoft/pyright#5402
Guys, how do I type hint a parameter that must be a value from an enum?
with the name of the enum class
Simple as that?
Unless you mean any enum from any enum class
Nope, a specific enum
