#type-hinting
1 messages · Page 47 of 1
(use type or Type depending on your python version)
remember, you can always change an instance to a class with type[] but you can't take a type and make it an instance as easily (i don't actually know how to do that with typing)
i have still not closed the editor
I'll try this in playground, let's hope it works
i would be fine with just using 2 different functions for this, or doing int(f(n)) if its the same thing
its not like you'll be passing a non-constant to this, ever, so there isn't really a point in making it generic
Can always use typing.cast too
There is if all 1000 call sites have already been written prior to adding a new possible return type.
There are currently type warnings everywhere that someone didn't notice
more likely that they are not running a typechecker at all
Exactly
i added basic pyright to my discord bot and recently and... it was so much fun
only 300+ typing issues to get to basic....
Or just didn't open these files in their editor - even more likely
But nevertheless, I would like to learn how to write this function, if possible
Incompatible return type got "int" expected T. Do I need to import from future? 🤔
Holy that's why I type my stuff from the start
Why annotations from future and why typevar from extensions? Can't I just use typevar from typing?
I copied a pydis bot to start....
also don't you contribute to dpy
:^)
-# didn't that start untyped
you could indeed use the standard typevar from typing here, it supports bounds on 3.10, though honestly this is more fitting for constraints because you wont ever pass something thats actually x: type[int] | type[str], or something thats not exactly str or int
but ye under suggestion from @trim tangle I ended up using basedpyright to implement pyright with a baseline file in order to be able to add ci without fixing all of the errors
its so great now to be able to do refactors and have ci complain at me
the problem with doing that on 3.10 arises when you want a "typevar default".
only pyright will agree with
[T: (int, str)](n: str, rtype: type[T] = int)
mypy will reason like "the default has to be fitting for any typevar, so int doesnt fit"
How can I add a default value to the rtype argument?
I'm not sure I understand. What is more fitting?
Aha
so if you want it to work on both pyright and mypy, you'd explicitly use the typevar default feature which is new in 3.13
and for using new typing features on older versions you can use typing_extensions
And if only mypy is requirement?
just @overload
(I would love to go to newer python, but that's not an option in near time)
How does that fit into this?
from typing import overload
@overload # this is making the default
def get_parameter(n: str, rtype: type[str] = ...) -> str:
...
@overload
def get_parameter(n: str, rtype: type[int]) -> int:
...
def get_parameter(n: str, rtype: type[str] | type[int] = str) -> str | int:
return rtype(n)
something like this
(im not a fan of using type when what you really are expressing is a callable constructor but w/e, i get why people do it, its because you're only really thinking of passing the types here)
You can use a Callable[[T],T] instead for most uses
Is the annotations and future to get the python 3.13 behavior? How to express argument default value with it?
Or do I need to fully run python 3.13?
Does the overload method have any drawbacks?
What du you mean by callable constructor?
You're getting the parameters from some serialized form right?
So you could use a Callable that processes that raw value
basically the function is not using the rtype as a type in particular (e.g. in isinstance), but as a callable (function)
but its fine to typehint it as a type if thats the only thing you pass into it
I don't have a full compile time schema for what can be in this serialized data though
To get the type from Type[T]?
Er let's say T is a type already
And I want to type that the method returns an instance of the type of T
in general you cant make something like type Destructure[type[T]] = T but in particular in a method where the typevar is bound in the class you can by doing narrowing on self
class F[T: type]:
def f[U](self: "F[type[U]]") -> U:
...
reveal_type(F[type[int]]().f()) # int
but the proper way ofcourse is to just change the T typevar bound from type[A] to just A, and use type[T] where you want a type
Yeah for sure
How do I return a str or int from this function without causing "incompatible return value type"?
Huh it works for me
what do you mean?
return 100 yields mypy error:
Incompatible return value type (got "int", expected "T")
🤔
gerald symptoms
Well, imagine you have this py def foo[T: int | str](things: list[T]) -> T: return 100 if T is resolved as str, Literal[1, 2, 3], bool etc., this return is wrong
What do you mean?
I posted it as an SO question to be able to link the mypy playground: https://stackoverflow.com/questions/79816102/how-to-use-type-argument-as-return-type
Why can't I return an int when that is one of the types in the union that is T?
def identity[T: object](x: T) -> T:
if random.random():
return x
else:
return "banana"
``` do you see why this is wrong?
No
What about this? ```py
def identity[T](x: T) -> T:
if random.random():
return x
else:
return "banana"
Well, this is a simpler example to demonstrate the idea
?
Do you know what the difference between these two signatures is?
def foo[T: int | str](things: list[T]) -> T: ...
def foo(things: list[int | str]) -> int | str: ...
In the first example, if I do py strings: list[str] = ["a", "b", "c"] s = foo(strings) s has to be a str
One is generic one is not. But this doesn't look like my case at all?
I want to specify the return type with an input argument. Not make a generic function.
If you have this: ```py
T = TypeVar("T", bound=str | int)
def get_parameter(parameter: str, _rtype: type[T]) -> T:
"""Get the parameter"""
if parameter == "SIZE":
return 5
return "potato"
``` and you call x = get_parameter("SIZE", str), what type should x be?
the function signature says that it's str
Maybe I'm misunderstanding what you're trying to do?
Oh I see
how are you currently getting the paremeters?
Yes, and str would be correct
but at runtime you're returning 5
that is not a str
5 should only be returned when the input argument is rtype is int
The literal 5 is just to get a smaller example
Do you want to decide which value to return based on rtype?
Or do you want to validate that it's the right type?
Maybe you meant something like this ```py
T = TypeVar("T", bound=str | int)
def get_parameter_any(parameter: str) -> object:
...
def get_parameter(parameter: str, rtype: type[T]) -> T:
param = get_parameter_any(parameter)
assert isinstance(param, rtype)
return param
Read the question from the start.
I updated the example in stack overflow, hopefully it's more clear
I don't see how this is much different from what I asked?
If you are asking how to ignore the error that mypy is correctly issuing, you can use typing.cast or a # type: ignore comment
In 1000 places?
I'm hoping to use a default rtype. See original question
from __future__ import annotations
from typing import TypeVar, cast
T = TypeVar("T", str, int)
VALUES: dict[str, str] = {"SIZE": "100", "ADDR": "0x100", "NAME": "potato"}
def get_parameter(parameter: str, _rtype: type[T]) -> T:
value: object
if parameter.startswith("N"):
value = int(VALUES[parameter], 0)
else:
value = VALUES[parameter]
return cast("T", value)
x = get_parameter("SIZE", str) # x is a str
y = get_parameter("NAME", int) # y is an int
z = get_parameter("ADDR", int) # z is an int to mypy, but at runtime it's a string
If you want to be able to omit rtype, then you will have to use an @overload with mypy
Does cast do anything when not type checking? Will it infer a function call?
typing.cast is a no-op, it's similar to # type: ignore
Or python 3.13?
No, python 3.13 doesn't add anything that helps with that
Ok
@overload
def get_parameter(parameter: str, _rtype: type[int] = ...) -> int: ...
@overload
def get_parameter(parameter: str, _rtype: type[str]) -> str: ...
def get_parameter(parameter: str, _rtype: type[object] = int) -> object:
if parameter.startswith("N"):
return int(VALUES[parameter], 0)
else:
return VALUES[parameter]
x = get_parameter("SIZE", str) # x is a str
y = get_parameter("NAME", int) # y is an int
y2= get_parameter("NAME") # y2 is an int
z = get_parameter("ADDR", int) # z is an int to mypy, but at runtime it's a string
Why do I need to cast a literal int to T when int is "part of" T?
You mean in this example? ```py
def foo[T: int | str](things: list[T]) -> T:
return 100
No
In this one
Because if you do py x = get_parameter("SIZE", str) x is supposed to be a str, according to the function signature.
🤔
If you have a function with this signature: ```py
T = TypeVar("T")
def identity(x: T) -> T: ...
It means that the return type is the same as the argument type. So if I dopy
number: int = 5
another_number = identity(number)
``` according to the function signature, another_number here has to be an int. Does that make sense?
and this implementation would be incorrect: py def identity(x: T) -> T: return "banana" even though sometimes this function might be called with a str argument
This also gives mypy warning:
def get_parameter(parameter: str, rtype: type[T]) -> T:
"""Get the parameter"""
if rtype is int:
return 5
Are overloads and object needed when the only two types are str and int?
The overload is needed to allow get_parameter("NSOMETHING") to infer the type as int
This doesn't even look like the same case to me.
Okay, let's do this```py
T = TypeVar("T", bound=int | str)
def identity(x: T) -> T:
return 42
``` if I call identity("foo"), I need a string back. So this is an incorrect implementation of identity
Why are there two ellipses in the first overload?
That seems like a limitation of mypy and pyright. This function does not do anything unsafe.
Functions with no bodies are not allowed in Python. @overloads typically have ... as theirbody
Isn't it the last definition (not the overload) that's doing the default value to rtype?
When an @overloaded function is called, type checkers ignore the implementation of the function completely
There are 2 ellipsis in your example, not only the function body?
If could be this or something ```py
@overload
def get_parameter(parameter: str, _rtype: type[int] = ...) -> int: ...
@overload
def get_parameter(parameter: str, _rtype: type[str]) -> str: ...
def get_parameter(*args, **kwargs):
# insert implementation
Or something?
Is the first ... meant to be something else?
@overload is not special syntax. It's just a decorator on a function definition```py
@overload
def get_parameter(parameter: str, _rtype: type[int] = ...) -> int:
print("you could put something here but it's never gonna run")
@overload
def get_parameter(parameter: str, _rtype: type[int] = ...) -> int:
print("same here")
def get_parameter(parameter: str, _rtype: type[object] = int) -> object:
# this is the actual function definition that will be called at runtime
``` ... is mean to literally be ..., the ellipsis object
This is not the case I have, id it? 🤔 The input argument rtype should be a type (int or str). Not a variable of that type.
Can you have an ellipsis as a default argument?
Decorators or overload is not new to me.
Ellipsis is not new to me.
Okay, let's do this ```py
T = TypeVar("T", bound=int | str)
def create(x: type[T]) -> T:
return 42
``` this is incorrect because create(str) cannot return 42
I'm asking about the very first ... in your code
But can that be fixed with if statements or asserts or casts? Or all of the above? Why not?
What's wrong here:
def get_parameter(parameter: str, rtype: type[T]) -> T:
"""Get the parameter"""
if rtype is int:
return 5
that seems like a bug/limitation in mypy, there's nothing wrong with this function
Ok, thanks
maybe people who are used to python and its hybrid approach to typing don't feel this way, but for someone coming from statically typed languages the code feels kind of wrong
you're basically putting a lot of weight on the type checker/"smart casting" here
If I saw real code written this way... I think most likely I'd suggest a different way to write it
but it's hard to say that in a vacuum
In a non-runtime context, such as in stub files, @overload, and Protocol method definitions, ... can be used to mean that there's some default, without specifying what that default is
You can search for = ... in this article for example: https://typing.python.org/en/latest/guides/writing_stubs.html#writing-and-maintaining-stub-files
the issue is that in general, if statements are dynamic. So in general, this kind of code is wrong.
What you're asking is for the type checker to recognize that this particular if statement has a condition that matches the surrounding static context.
Which is a lot like what happens when you do smart casting/type narrow inside an if body.
If you use a constrained type variable instead of a type variable with a bound, this works
T = TypeVar("T", str, int)
def get_thing(name: str, rtype: type[T]) -> T:
if issubclass(rtype, int):
return 69
else:
return "banana"
``` (because of funny mypy jank)
But this isn't quite type narrowing, even though it's similar. So basically you're asking for an extra feature/complexity that some type checkers do not have
or well, I guess they sort of have it. but it's at the edge so to speak, and janky.
If you have a fixed set of parameters, a safer approach would be something like ```py
IntParam = Literal["NROWS", "NCOLS"]
StrParam = Literal["NAME", "DESCRIPTION", "AUTHOR"]
@overload
def get_parameter(p: IntParam) -> int: ...
@overload
def get_parameter(p: StrParam) -> str: ...
@overload
def get_parameter(p: str) -> str | int: ...
(this also doesn't require any code changes in callers)
If you want it to be extra relaxed and gradual, you could do ```py
@overload
def get_parameter(p: IntParam) -> int: ...
@overload
def get_parameter(p: StrParam) -> str: ...
@overload
def get_parameter(p: str) -> Any: ...
3.9 added support for the generics in standard collections instead of from typing.
When 3.13 came out, it was already fine to use type[...], because 3.9 became the lowest supported version, but now even 3.9 is EOL with the release of 3.14.
Unfortunately I don't have a fixed set of parameters
So if this works because of jank, I should not use it.
Is bounds in contrast to constraints? And what's the difference?
Pasting the th overload suggestion to avoid scrolling past yesterday's discussion.
from typing import overload
@overload # this is making the default
def get_parameter(n: str, rtype: type[str] = ...) -> str:
...
@overload
def get_parameter(n: str, rtype: type[int]) -> int:
...
def get_parameter(n: str, rtype: type[str] | type[int] = str) -> str | int:
return rtype(n)
Right, so I guess casting here might be the thing to do. Doing the casting based on the input argument is a convenience i was hoping to utilize.
Keeping the T = str | int as default return type would be needed to support writing generic code.
just curious did you somewhere above post code that shows your actual use case a bit more?
Not in more detail as it's proprietary code
It's about handling serialized data, and with small overhead fix the currently existing mypy errors in the code. And the idea was to be able to fix convey type information with a function argument rather than appending a verbose assert or cast at the calling site.
The get_parameter function is called in many places, even in code I don't have easy access to.
There's no way to map a tuple of types into a tuple of iterables of those types right?
nope
if only Union[*Ts] was allowed
only if you want to have something like this ```py
@overload
def foo(t: tuple[()]) -> tuple[()]: ...
@overload
def foo[A](t: tuple[type[A]]) -> tuple[A]: ...
@overload
def foo[A, B](t: tuple[type[A], type[B]]) -> tuple[A, B]: ...
@overload
def foo[A, B, C](t: tuple[type[A], type[B], type[C]]) -> tuple[A, B, C]: ...
repeat until you're bored
which is what the built-in map and zip do
Please let us have ```py
def foo[Ts](t: tuple[(type[A] for A in Ts)]) -> tuple[*Ts]: ...
honestly
typechecking this will be ass
like, unifying it and understanding what the type will be is fine but
checking that the body is consistent with that signature
uhh
good luck i guess, unless the whole body is a trivial return tuple(c() for c in t)
in TypeScript you have to do quite a lot of // @ts-ignore (or the shorter equivalent with as) to implement functions like that
I think only having an unsafe way to do this is better than having no way of doing this. Just like we've had @overload for 11 years without validation of the implementation
just put the dependent types in the bag bro
like lets say you want for some reason to evaluate it in reverse order (or any order thats not just left-to-right)
now the typechecker needs to understand that return tuple(c() for c in reversed(t))[::-1] is also valid because uh yeah
i otoh just wish there was specifically a way to refer to other callable's paramspec as a special form.
e.g. ```py
type PrintP = ParamsOf[print]
def tee(*args: PrintP.args, other_file: StrPath, **kwargs: PrintP.kwargs) -> None:
print(*args, **kwargs)
print(*args, **{**kwargs, 'file': os.fspath(other_file)})
yeah that's also a cool feature in typescript
well, that's a dependent type as well
not really, it's just a shortcut
but i believe it would be hella useful from time to time
why not? isn't print a value?
we still don't have ParamSpec literals that support keyword arguments :(
print can be anything in code, correct understanding of that ParamsOf type depends on what you read before
it may be the thing from builtin namespace but it may as well be something locally defined
If it's something like that then yes
But then it's also not very useful, because you can only pass print as a value of this type, and also you can't do anything with a value of that type
So a more useful interpretation would be "make PrintP the type that the type checker believes print to be according to stubs or type definitions"
when exactly would i be passing print somewhere
oh wait it's ParamsOf and not just typeof
yup
the point is to capture the paramspec without having to make a decorator or something
to capture it naturally
in that sense it's also a shortcut
Hey there, I was experimenting with some stuff a while ago and found this unexpected behaviour-
import random
from dataclasses import dataclass
@dataclass
class Parent:
a_attribute: int = 10
@dataclass
class Child1(Parent):
b_attribute: str = "Child1 attribute"
@dataclass
class Child2(Parent):
c_attribute_1: str = "Child2 attribute 1"
c_attribute_2: str = "Child2 attribute 2"
group: list[Parent] = [random.choice((Child1, Child2))() for _ in range(10)]
for i in range(10):
if isinstance(group[i], Child1):
print(group[i].b_attribute) # [reportAttributeAccessIssue] error
Even though I've explicitly type narrowed group[i] to Child1, I still get attribute access error. Why is that?
I know I could've done something like for element in group: instead which would fix it, but i found this weird
Type checkers usually don't support this narrowing pattern (subscripting where the subscript is a variable)
It's a bit more tricky to implement than narrowing a subscript with a constant
Oh, I see
But for any isinstance(obj, tp), they could just imagine a typing.cast(tp, obj), no? (Obviously narrowing stuff like all(isinstance(x, y) for x in it) to it_type[y] would be harder i suppose).
you also need to invalidate the narrowing, for example if i gets assigned to
How does one properly type annotate this:
@overload
def fn(key: CT='KEY', value: CT='VALUE') -> dict[DT, DT]: ...
@overload
def fn(key: CT='KEY', value: None='VALUE') -> dict[DT, dict[CT, DT]]: ...
def fn(key: CT='KEY', value: CT|None='VALUE') -> dict[DT, DT|dict[CT, DT]]:
Both mypy and pylance is reporting a series of errors about this "Overloaded function implementation cannot produce return type of signature" and "Overload 1 for "asdict" overlaps overload 2 and returns an incompatible type".
I always get lost on how to write the type hints when the type of the input argument determine the return type.
i suppose CT and DT are typevars of the class (since this had self before you edited it)?
how is 'KEY' supposed to be a valid default value for any CT?
and 'VALUE' is definitely not None
Yes
Return dict[DT, DT] | dict[DT, dict[CT, DT]]
The value: None='VALUE' is because you cannot write value: None since the first argument provides a default
How is CT defined?
It's a TypeVar()
then "KEY" and "VALUE" are definitely not valid values for CT
I'm a bit confused as to what you want
do you have some examples of calls to this function?
is value more like.. key2? so if its not provided, you get a nested dict, and if its provided - you get a specific thing out of it
why not just fn(key)[key2]?
CT = str
DT = TypeVar('DT', default=str | int | float | None)
Ah, CT is not a typevar
So "KEY" and "VALUE" are literal strings
Yeah, I was mistaken about that. Sorry
It is a type alias. Writing CT in an annotation is the same as writing str
What do you want fn(), fn("apple") and fn("apple", "banana") to be?
(or rather, what do they return at runtime?)
A little hard to understand since we're talking multiple dimensions here, but let's try:
fn() -> {data_from_KEY: data_from_VALUE, ...}
fn("apple") -> {data_from_apple: data_from_VALUE, ...}
fn("apple", "banana") -> {data_from_apple: data_from_banana, ...}
fn("apple", None) -> {data_from_apple: {raw_data...}, ...}
key having a default makes this suck to typehint with overloads without making value keyword-only
from typing import overload
class F[T = str | int | float | None]:
def t(self) -> T: ...
@overload
def fn(self, key: str='KEY', value: str='VALUE') -> dict[T, T]: ...
@overload
def fn(self, key: str='KEY', *, value: None) -> dict[T, dict[str, T]]: ...
def fn(self, key: str='KEY', value: str|None='VALUE') -> dict[T, T] | dict[T, dict[str, T]]:
if value is None:
return {self.t(): {"x": self.t()}}
else:
return {self.t(): self.t()}
ah, ok
if only we could do
def fn[V: (str, None) = str](self, key: str = 'KEY', value: V = 'Value') -> dict[T, T if V is str else dict[str, T]]
or something
from typing import overload, reveal_type
class F[T = str | int | float | None]:
def t(self) -> T: ...
@overload
def fn(self, key: str='KEY', value: str='VALUE') -> dict[T, T]: ...
@overload
def fn(self, key: str, value: None) -> dict[T, dict[str, T]]: ...
def fn(self, key: str='KEY', value: str|None='VALUE') -> dict[T, T] | dict[T, dict[str, T]]:
if value is None:
return {self.t(): {"x": self.t()}}
else:
return {self.t(): self.t()}
def test():
f = F[int]()
reveal_type(f.fn()) # dict[int, int]
reveal_type(f.fn("apple")) # dict[int, int]
reveal_type(f.fn("apple", "banana")) # dict[int, int]
reveal_type(f.fn("apple", None)) # dict[int, dict[str, int]]
maybe this, if you dont expect to be able to do fn(value=None) as fn("KEY", None)
hello it is time for my annual/biannual asking of:
- is there an official way to specify
Iterable[str]that isn'tstrnow? - how's the progress on Intersection coming along?
the answer to 1 is no
Bruh I've been using | None for optional keys just to now find out there's typing.NotRequired
Now I'll have to go through all of these typeddicts lul
Wait until you hear about total=False and typing.Required
Doesn't total=False mean that all of the keys are optional? Useless for me lul
Also what the hell is the point of Required, I'd have assumed the default implementation of a key already means it's non-optional
!d typing.Required
typing.Required```
Special typing construct to mark a [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict) key as required.
This is mainly useful for `total=False` TypedDicts. See [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict) and [**PEP 655**](https://peps.python.org/pep-0655/) for more details.
Added in version 3.11.
when you want to undo the total=False for a specific key apparently
Ah makes sense
Hm NotRequired is only available to me via typing_extensions
Why are you below 3.11
It's in typing then
^ added in 3.11
Maybe copilot and not normal intellisense?
Nah
Most of the time I don't even have copilot enabled lol
hey if I have overloaded functions such as these, is there any easy way of finding out which if the two different callables I could be getting I am dealing with?
@overload
def func(cb: Callable[[Path, bool], None]) -> None: ...
@overload
def func(cb: Callable[[Path, bool, T], None], c: T = None) -> None: ...
def func(cb: Callable[..., None], c: T = None) -> None:
# implementation
you could check the parameter count of its inspect.signature, i guess
though that wont work if its varargs
):
Cmon man
Is it cuz str() should always return string?

I'll just do this ig

Still not done sadly
@overload
def func(cb: Callable[[Path, bool], None]) -> None: ...
@overload
def func(cb: Callable[[Path, bool, T], None], c: T) -> None: ...
def func(cb: Callable[[Path, bool], None] | Callable[[Path, bool, T], None], c: T = None) -> None:
callback: Callable[[Path, bool, T], None]
if (len(inspect.signature(cb).parameters) == 2):
callback = lambda a, b, _ : cb(a, b) # pyright: ignore[reportCallIssue, reportUnknownLambdaType]
else:
callback = lambda a, b, _ : cb(a, b, c) # pyright: ignore[reportCallIssue, reportUnknownLambdaType]
callback(Path("test"), False, c)
so this is my best bet? 😔
well now that you dont have a default in the second overload you could just check for a c missing sentinel or something
but otherwise yeah
this is a weird function ngl
Just finished adding __slots__ to where it's needed 😄
I only have message and channel left now.
Slowly I start to find more and more of these typing thingies useful (focus on Literal and Protocol)

Hey, I've got a problem and was wondering if anyone had any advice, i have a PyQt6 threaded worker class, and I'm using the typing module to dynamically create the finished signals with custom types, the logic should work, however I'm not entirely sure how to type hint the code segment, which is required for it to function, any help appreciated, Thanks
You're saying that the function returns a QObject, but instead it returns a class
If you forgot to instantiate the class, do that. If you intended to return a class, specify the return type as type[QObject]
(I don't know anything about Qt, but generating a new class at runtime is generally a sign that there's a better way to do it)
thanks, this fixes the runtime class generator part, but were still getting red squiggly lines with the self.signals inside the run function, do you possibly have any way to type hint so we recognize the attributes of the runtime class? thanks for your help btw
you'll need to show the error messages you're getting
and what tool you're using for type checking
I was wondering if it was possible to type hint such that we can recognize the attributes of the return class, so the following lines are type hinted correctly, the error when we hover over one, im using vs code so it'll be pyright and mypyCannot access attribute "finished" for class "type[QObject]" Attribute "finished" is unknown
commit this smh
having like 15 untracked files, that you are gonna add with one single commit is not really good design (but i do it too often too tbh, im too lazy to make the remote)
they would need to be an attribute of QObject then. But it kind of seems like it would be better if you used dataclasses and return an instace of it instead of creating and returning a class at runtime
Nah I want it to be one release
When I'm done with it which is soon
Oh, well if thats how you want it to go. Its just harder to trace file-changes, make backups and such (and GH has a Releases feature...). But i mean, that works too if you want
I don't have backups tbh 
By saying this I do not intend to say it's a good thing
Screenshot: code from the builtin enum.py.
Is it possible to make something like IntEnum but one that takes any object?
I'd imagine it like:
class MyEnum(AnyEnum[whatever_obj]): ...```
why not just use enum.Enum
Idk I found IntEnum to be useful
Saves doing tons of .values lol
you can subclass ReprEnum along with the type you want it to have. Like-
from enum import ReprEnum
class DictEnum(dict, ReprEnum):
obj = {1: 2}
but i feel like however you're using your enums is a bit odd
if you're having to do a ton of .value on your enum
I know that, was asking if it can be dynamic (user passes just the type, and it acts as (type, ReprEnum))
But actually yea this is good enough too
I'm probably not gonna do that since there's already StrEnum and IntEnum
Nvm typechecker doesn't care (but the functionality of the .value thingy works!!)
why do you need the enum's value for this?
has_flag takes int
oh wait 💀
then change it to UserFlagsTypes
if you can narrow a type of a parameter, then you should always go for it
narrowing the type leaves out ambiguity
Although here there isn't such a "has_flag" func
Same thing, annotate value in the __init__ as StickerTypes
Can somebody please help me with this? I am losing my mind.
I am working on a library where I have created a sentinel called TERMINATE. In vs code, in the library directory, I get Type of "TERMINATE" is "TERMINATE". But when I am trying to use my library from other folder, and importing this sentinel, I get Type of "TERMINATE" is "Sentinel".
This is creating problem with typing in the directory where I am using the library: Variable not allowed in type expression
How do I solve this?
how are you defining TERMINATE?
from typing_extensions import Sentinel
TERMINATE = Sentinel("TERMINATE")
I just realized I had to enable experimental features for pyright in the other directory. I just completely forgot about this little thing over the months.
[tool.pyright]
enableExperimentalFeatures = true
Sorry for the noise
iirc object is better than Any ? So I'm a little curious why it's complaining here
class AutoSyncTree(app_commands.CommandTree):
def __init__(self, *args: object, **kwargs: object) -> None:
super().__init__(*args, **kwargs)
# Argument of type "object" cannot be assigned to parameter "client" of type "Client" in function "__init__"
# "object" is not assignable to "Client"PylancereportArgumentType
unless object means class and not instance
What's the signature of CommandTree.__init__?
def __init__(
self,
client: ClientT,
*,
fallback_to_global: bool = True,
allowed_contexts: AppCommandContext = MISSING,
allowed_installs: AppInstallationType = MISSING,
):
Your signature means that it's legal to call e.g. AutoSyncTree(None, foo="banana", bar=69)
ooh
and so pyright is correctly complaining that you cannot pass those args and kwargs to the parent __init__
there's currently no way to express argument forwarding like that, so you'll have to copy-paste the signature
Note the reason Any works here is a special case - a *args: Any, **kwargs: Any signature is treated like Callable[..., T], suppressing all errors with it.
When I make a function like foo[T](x: Sequence[T]) -> T: …, is there any special name for the thing I’m doing with the x argument that’s like implicitly putting bounds on the types T can have?
Narrowing?
Hm, that works
That’s why I didn’t really want to use it, but also I still can’t think of a better word even if it’s not the most accurate.
I mean, it is narrowing, no?
tho are there actually implicit bounds on T here?
can't Sequences contain any value?
depends on the way you define the T. Doing def foo[T: ...](x: Sequence[T]): ...
e.g. something like this
def foo[T: int](x: Sequence[T]) -> T: ...
foo([1,2,3]) # Works
foo(["1", "2"]) # Errors
That's not implicit then
oh yeah, missread the question
you can do def foo[T](x: Sequence[T]) -> T: ..., you can call it as foo([1,2,3]) and T will implicitly be bound to int. Doing foo([1, "2", 3.0]) however will make T implicitly be bound to Union[int, str, float].
that's what they meant?
Seems like it
That's just typevar binding. It's no different that it's in a sequence.
I mean, some1 already said narrowing, and they responded with it working
Nah that's not narrowing
Yes it is, it narrows a Sequence[T], with T being bound to Any to a Sequence[...], for some T.__bound__ == ..., e.g. Sequence[int] and T.__bound__ == int
Atleast thats how i understood that
Cause now you narrow T: Any -> T: ...
this has no restriction on the types T can be
Did they want one?
is there any special name for the thing I’m doing with the x argument that’s like implicitly putting bounds on the types T can have?
there are no bounds on T here
Otherwise, the foo[T: restriction](x: Sequence[T]) -> T: ... i mentioned would work, no?
... implicitly ...
and that is not implicit
the question is wrong in the first place, so im not sure what are you trying to answer
the T: restriction is explicit
yeah, because you said no restriction, so i though to only answer that part not implicit too
But i suppose the question could be phrased better, yeah
my only guess is what was meant is that x has to be a sequence, but that is not imposing any restrictions on T, its imposing it on the type of x
Well then Sequence[Any] works, right?
from a perspective of "what is allowed to be passed" - yeah, but it will allow the implementation to do anything with the elements
and T is also used in the return type so you cant just remove it
I was trying to experiment with a class that depends on protocols such as SupportsDunderLT, SupportsDunderGT and specifically typeshed's SupportsRichComparison, as opposed to just using a concrete comparable implementation as I usually do, and I am having some issues...
from typing import Any, Protocol
class SupportsDunderLT[T_contra](Protocol):
def __lt__(self, other: T_contra, /) -> bool: ...
class SupportsDunderGT[T_contra](Protocol):
def __gt__(self, other: T_contra, /) -> bool: ...
type SupportsRichComparison = SupportsDunderLT[Any] | SupportsDunderGT[Any]
class Foo[T: SupportsRichComparison]:
def __init__(self, value: T) -> None:
self.value = value
def __gt__(self, other: T) -> bool:
# Operator ">" not supported for types "T@Foo" and "T@Foo"
# Operator ">" not supported for types "SupportsDunderLT[Any]*" and "SupportsDunderGT[Any]*"
# when expected type is "bool" (Pyright reportOperatorIssue)
return self.value > other
def __lt__(self, other: T) -> bool:
# Operator "<" not supported for types "T@Foo" and "T@Foo"
# Operator "<" not supported for types "SupportsDunderGT[Any]*" and "SupportsDunderLT[Any]*"
# when expected type is "bool" (Pyright reportOperatorIssue)
return self.value < other
I tried running down the cases to convince myself that this was reasonable, and it seems it is? Did I make a mistake or do type checkers not special case comparison operations?
# Strict subclass (Non Virtual)?
# strictsub(a, b) = lambda: type(a) in type(b).__mro__[1:]
#
# Case 1: (fx: Foo[SupportsDunderLT[Any]]) < (y: SupportsDunderLT[Any])
# > strictsub(y, fx)
# Case True:
# > y.__gt__(fx) (1)
# Case "Implemented": done
# Case NotImplemented:
# > fx.__lt__(y) (2)
# > x.__lt__(y) ==> x supports LT, done
# Case False: fx.__lt__(y) ==> goto(2)
#
# Case 2: (fx: Foo[SupportsDunderLT[Any]]) > (y: SupportsDunderLT[Any])
# > strictsub(y, fx)
# Case True: > y.__lt__(fx) ==> y supports LT, done
# Case False:
# > fx.__gt__(y) (3)
# > x.__gt__(y)
# Case "Implemented": done
# Case "NotImplemented": > y.__lt__(x) ==> y supports LT, done
#
# Case 3: (y: SupportsDunderLT[Any]) < (fx: Foo[SupportsDunderLT[Any]])
# > strictsub(fx, y)
# Case True: > fx.__gt__(y) ==> goto(3)
# Case False: > y.__lt__(fx) ==> y supports LT
#
# Case 4: (y: SupportsDunderLT[Any]) > (fx: Foo[SupportsDunderLT[Any]])
# > strictsub(fx, y)
# Case True: > fx.__lt__(y) ==> goto(2)
# Case False: > y.__gt__(fx) ==> goto(1)
#
# The same applies if SupportsDunderGT is used instead of SupportsDunderLT
I suppose type-checkers dont understand the type(a) in type(b).__mro__[1:] part.
Is that supposed to be like issubclass(type(b), type(a)) and type(a) is not type(b)?
I suppose, thats exactly it imo
That was just for reasoning, I am not literally doing it. The whole comment part is mostly pseudo code for what steps I believe python internally does in order
The errors and actual code are in the first snippet
Oh I might have misunderstood, if you mean they don't conceptually understand it... Perhaps not, though I don't think that functionality is needed to make it work
Fun thought I just had, is there a type safe way to define a non-empty recursive list? Defining the type is pretty easy, it's just type Recursive = list[Recursive], but can you make it? The issue I see is with the base element that gets swapped, ie in x = x[0] = [1] the initial list is a list[int] that gets turned into a Recursive, which type checkers don't like. So would there be a way to construct one without a ignore/Any/cast?
And by "non-empty" I mean a true recursive list, since x: Recursive = [[]] does work, but it's not a true recursive list where x[0] is x
Figured it out, you can use a helper function to preserve the recursive-ness of the list while making it actually recursive
type Recursive = list[Recursive]
def make_list_recursive(x: Recursive) -> Recursive:
x.append(x)
return x
x: Recursive = make_list_recursive([])
print(x)
x: Recursive = []
x.append(x)
Is type[type] not the same as type? Since no type checkers seems to treat it that way, but isn’t type also a type?
type[type] means any metaclass
Like type or abc.ABCMeta
Hm. So then what would type[type[type]]] be? Would it also be any metaclass, or would it be any metametaclass?
I guess it’s confusing since there’s not a good distinction between the type class and type type system generic thing
type[type[type]] would mean a meta-metaclass
a class whose instances are metaclasses
Hm, but aren’t meta classes also themselves classes?
Yes
So then shouldn’t these all be the same, since they describe the same group of objects?
ABCMeta is a metaclass, but it's not a meta-metaclass
because when you instantiate ABCMeta, you get a plain class which is not a metaclass
my brain is currently fried so I am short circuiting on a more coherent explanation
all metaclasses are classes, but not all classes are metaclasses
same with meta-metaclasses
all meta-metaclasses are metaclasses, but not all metaclasses are meta-metaclasses
Is it better practice to use cast(tp, val) or just some (like 2) # type: ignore[...] comments? Because it might be a little slower at runtime, and if i have to import typing (even lazy), that would still add some overhead. Should i just type: ignore in this case?!
Or assert and then running with the -O flag is an option too
assert does not work with -O (i suppose that's what you mean), and i am using -O. Also, i still can't do something like isinstance(val, tp), as that doesn't work with types, but only classes (e.g. works with dict, not dict[str, int]). So if i have val.update(...), it knows that val is a dict, because of a isinstance(val, dict), but not that val.update(...), recives dict[str, int].
The narrowing doesn't work at runtime
typing.assert_type(val, tp) would work, but that introduces runtime overhead again
Oh, good to know!
I personally do the ignore comment route if I really can't narrow it because of the runtime cost, however small it is
Same, but i wanted to hear other peoples opinions on this, type: ignore[...] is obviously the simplest way aswell, but im not too sure about just ignoring errors, just to have less
What's the proper way of documenting a function signature that yields instead of returns?
Generator[type_you_yield, None, None] for most cases (just read the docs of Generator for the other 2 args)
sad
!doc collections.abc.Generator
class collections.abc.Generator```
ABC for [generator](https://docs.python.org/3/glossary.html#term-generator) classes that implement the protocol defined in [**PEP 342**](https://peps.python.org/pep-0342/) that extends [iterators](https://docs.python.org/3/glossary.html#term-iterator) with the [`send()`](https://docs.python.org/3/reference/expressions.html#generator.send), [`throw()`](https://docs.python.org/3/reference/expressions.html#generator.throw) and [`close()`](https://docs.python.org/3/reference/expressions.html#generator.close) methods.
See [Annotating generators and coroutines](https://docs.python.org/3/library/typing.html#annotating-generators-and-coroutines) for details on using `Generator` in type annotations.
Added in version 3.5.
In recent Python versions you can just write Generator[type_you_yield]
Oh yeah, the TypeVars have defaults, i remember
forgot about that
It is a bit strange that that information was added to the docs of typing.Generator several versions after it had been deprecated
instead of collections.abc.Generator
thought collections.abc.Generator does link to a page which gives examples of that
Yeah, the whole deprecation process for typing exports of collections.abc is a bit wired imo
don't the stubs do that?
Yes, typeshed does contain that information, but typeshed contains a lot of implementation details and is generally not the same as the docs
it's like if you had to read CPython's C code to understand what a public function's defaults are
Yeah
And neither collections/abc.pyi, nor _collections_abc.pyi contain these stubs, abc.pyi just imports the _collections_abc.pyi, which mostly just imports from typing.pyi
(as if the typing stubs werent long enought already xd)
stdlib/typing.pyi lines 542 to 543
_SendT_contra = TypeVar("_SendT_contra", contravariant=True, default=None)
_ReturnT_co = TypeVar("_ReturnT_co", covariant=True, default=None)```
default is None
That's what I was looking for
Thank you guys!
np
I think they wouldn't want to tie the parameters to the runtime guarantees just yet
I have a couple of issues open about this I just never did anything with them
wdym?
like they dont wanna say class Generator[YieldT, SendT = None, ReturnT = None] at runtime because there isnt a stability policy for it afaik
Hello team! I was wondering if folks could chime in to this msgspec PR discussion. I'm trying to wrap my head around, for our library's purposes, if removing our own use of (not support for) typing.[Type|Union|Optional] in favor of the built-in approaches would somehow cause an issue or if there is some nuance we should be aware of.
You don't even need to update python to adopt the latest typing features in pyi files
Would there be a difference in runtime inspection somehow? The library in question uses type information for serialization.
IIRC get_origin(List[X]) will return list in py3.9+
abc.pyi
?
is there any way to annotate a function's arguments as being equivalent to another, without just copying the arguments? for example:
def a(*, foo: bool = False, bar: bool = False) -> None:
...
def b(*args: ???, **kwargs: ???) -> None:
return a(*args, **kwargs)
b(foo="foo") # Type checker error
b(foo=True) # OK
I tried a nasty hack with ParamSpec but the type checker didn't seem to like it and ended up annotating the args as ....
sadly, no, even though its a common thing to want
:(
I've used a decorator that lies in the past
def copy_params[**P, R](source: Callable[P, R]) -> Callable[P, R]:
def decorator(target: Callable[..., Any]) -> Callable[P, R]:
return cast("Callable[P, R]", target)
return decorator
def a(*, foo: bool = False, bar: bool = False) -> None: ...
@copy_params(a)
def b(*args, **kwargs):
return a(*args, **kwargs)
You don't have to lie, e.g. you can do ```py
def same_signature_as[**P, T](fn: Callable[P, T]) -> Callable[[Callable[P, T]], Callable[P, T]]:
...
it will still let you have an (*args: Any, **kwargs: Any) -> Any, but it will prevent from supplying a knowingly incompatible signature
I wish we could infer unannotated parameters with a decorator
Or even via x: ... to infer
yeah, this is common in typescript iirc ```rs
someWebThing.onRequets("bananans", (req, res) => { // req, res are inferred
})
Python only supports that in lambdas
yep
all type systems should be based on hindley milner
.wiki hindley milner
Hindley–Milner type system
A Hindley–Milner (HM) type system is a classical type system for the lambda calculus with parametric polymorphism. It is also known as Damas–Milner or
Type inference
9.0). The majority of them use a simple form of type inference; the Hindley–Milner type system can provide more complete type inference. The ability to
that's propaganda from Big Lambda
Given this construct, how can I tell the type checker that v cannot be of type Missing?
class Missing:
""" Missing value """
MISSING: Final[Missing] = Missing()
"""Missing value singleton"""
def fn(data: dict[str, int], default: str | Missing = MISSING):
v = data.get("key", default) if default is not MISSING else data["key"]
reveal_type(v) # type of "v" is "int | str | Missing"
return v
why not just use None instead of MISSING? str wont overlap with None anyways. sentinels make sense when you want to avoid overlap
Because None is a valid value in the dict. I (erroneously) cropped my example to only use int as value, but it may also be None.
well, there is no guarantee that MISSING is the only value of type Missing, so you dont get narrowing
there is a trick with a single member enum ⬇️
Someone could pass a new Missing() instead of MISSING. Making it an enum would fix it.
Right, because there is no way to say use that object to the type annotator. Ok, I'll see how to do this with an enum.
Or changing it to use isinstance
from enum import Enum
class MISSING_T(Enum):
MISSING = object()
MISSING = MISSING_T.MISSING
def fn(data: dict[str, int], default: str | MISSING_T = MISSING) -> int | str:
v = data.get("key", default) if default is not MISSING else data["key"]
return v
You can use Literal[MISSING_T.MISSING] as well.
Thank you
i like how theres a separate dedicated channel for a simple feature in python.
peak sophistication right here.
i see
Its a pretty deep rabbit hole
how many types are there anyway?
Starts off simple and you end up doing all sorts of wierd stuff
Infinite technically
You can make your own types
tryna make float128 if thats possible.
with what tools really?
Why?
Classes
just saying.
I don't think you can make one, atleast in pure python
ok
How bad of an idea is it to do something like this:
if TYPE_CHECKING:
from abc import ABC, abstractmethod
class Cls(ABC):
def __init__(self) -> None:
self.attr = None
@abstractmethod
def meth(self) -> None:
raise NotImplemented
else:
class Cls:
def __init__(self) -> None:
self.attr = None
def meth(self) -> None:
raise NotImplemented
That is disregarding the "premature optimization" thingy as it is indeed a small thing - was just wondering.
if the first definition of the class is for checkers only, why not define the methods as def <name>(<params>) -> <type>: ... instead?!
Yea, that was just a copy implementation :> an ellipsis is fine as well there
Would that be a somewhat reasonable thing to do for an object that is created often? And yea... ABC enforces at runtime for Cls not to be instantiated.
oh you surely can it will just be slow and use more than (16 object header) + (128 / 8) bytes
software implemented floats in general are bad
floats are good thanks to being implemented in hardware
Damn
Are you sure it's being enforced at runtime? Or at typing time?
And as you say yourself, it's nothing more than premature optimization. If that REALLY matters to you and plays a significant role then sure, if not then avoid it
As long as there are non-implmented methods.
Exception has occurred: TypeError
Can't instantiate abstract class Test without an implementation for abstract method 'test'
Interesting
Which python version are you on?
It was just a thing I wanted to know, as I previously didn't work with TYPE_CHECKING at all. So just a test.
3.13
Maybe there is a misunderstanding - the thing I did is only for static check. My code bypasses the runtime instantiation checks, as at runtime it does not use the ABC.
Ah yeah that's what I was asking
The unfortunate thing about ABC (and Protocol) is the overhead they create :<
People usually use that for imports related to type annotations which they put inside TYPE_CHECKING for it to be avoided in runtime (along with from __future__ import annotations in <3.14 - to convert all annotations to strings)
And in some cases stuff like what you did, although a bit rare
What are you making that needs every bit of performance lol
Erm... in short an N^2 intersection thingy that eats around 90% of time. There are some containers involved there that are created for each intersection.
But the thing I asked will be the very last thing to add 🙂
Lol alright
As a separate question is there a way to work with generic classes without explicitly specifying types?
class Test[_T]:
def __init__(self, t: _T) -> None:
self.t: _T = t
class IntTest(Test): # <<<<< No explicit [int]
def __init__(self) -> None:
super().__init__(5)
i = IntTest()
reveal_type(i.t) # Unknown
No, not with inheritance
you have to explicitly specify that you're inheriting from Test[int] (or Test[a_typevar] if you want your class to be generic too)
Yea, couldn't find anything about implicit one. Thanks
What about TypeVar defaults?
One could define _T = TypeVar("_T", default=int), and use old-style generics (iirc defaults don't always work with new-style ones)
yes
But that wouldn't make it stay modular for this kind of inheritance, although one might be able to do class IntTest[T](Test[T]): ...
There is no need for TypeVar - you can specify defaults in new style as well class Test[_T = int]:
Though this does not implicitly derive the type based on the __init__ arguments - those are just defaults.
you could make this a bit less verbose by doing something like if TYPECHECKING: from abc import ABC else: from builtins import object as ABC
and analogously make a do-nothing decorator called abstractmethod
whether it's a good idea, it may solve some problems but it also makes your type checker less aware of what's actually going on in your program, so it may may become less useful
I actually tried doing exactly this, but
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from abc import ABC
dummy = ABC
else:
dummy = object
class Test[_T](dummy):
def test(self):
pass
t = Test()
However this gives exception:
Exception has occurred: TypeError
Cannot create a consistent method resolution order (MRO) for bases object, Generic
:x: Your 3.13 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File [35m"/home/main.py"[0m, line [35m1[0m, in [35m<module>[0m
003 | class Foo[T](object):
004 | pass
005 | File [35m"/home/main.py"[0m, line [35m1[0m, in [35m<generic parameters of Foo>[0m
006 | class Foo[T](object):
007 | pass
008 | [1;35mTypeError[0m: [35mCannot create a consistent method resolution order (MRO) for bases object, Generic[0m
Man wtf
Why in the illegal are we doing that
When you define a typevar on a class, the new syntax injects typing.Generic into the end of base class list. Probably shouldn't be producing metaclass conflicts though.
When I implemented this I decided it was OK because class Foo(object, Generic[T]): in the old syntax raised the same error
the error message does feel a little painful with the new syntax -- I bet lots of people don't realise that the new syntax implicitly adds Generic[T] to the class's bases. But then again, is it worth fixing? How many people are using PEP-695 syntax for their classes but also explicitly inheriting from object?
some guy on a remote island still using an old version of wemake-python-styleguide
guys isnt this supposed to be immutable?
its literally being anything but immutable
i want const in python rn
Type hints make no impact on runtime, they are only checked by type checkers. I recommend reading https://decorator-factory.github.io/typing-tips/tutorial/0-start-here/ or another similar tutorial
Moreover, you're not reassigning the variable
what is Final() even for then?
Telling the developer that they shouldn't reassign that variable.
its like "as long as you follow our contract, this variable will always refer to the same instance/object"
this is a pretty important point, the code in the screenshot would be fine even in languages that have const or similar
(also it would be Final[int], not ())
ahhh got it
its the IDLE thats making me confused.
It's for type checkers. If you're familiar with C++, they're linters like "clang-tidy". The most popular type checkers for Python are mypy and pyright.
$ cat test.py
from typing import Final
X: Final = 42
X += 1
$ mypy test.py
test.py:5: error: Cannot assign to final name "X" [misc]
Found 1 error in 1 file (checked 1 source file)
no pylance?
the repr automatically prints the result of an expr or expression which is what you're seeing here
pylance is pyright with closed source microsoft sauce
Pylance is a closed-source extension for VSCode that's based on pyright. By default it doesn't do type checking, you have to change it in the settings
iirc it does basic typechecking? Er not pyright basic
pylance seems to be very useless to me too.
it says that "something wrong with sympy.sin(x)" when it obviously isnt
by default it's in "off" mode which isn't actually "off", but rather it reports definitely-incorrect stuff like undefined variables
gimme a vs code extension for a great python type checker please.
show code and error
i dont have a screenshot + it was long ago.
i asked Gemini at that time but it only made the situation worse back then😭🙏
id recommend you to put pylance's typechecker mode to basic
is pyright good or smth?
Should i switch to pyright?
if you wanna go full on type checking use it from the terminal via mypy/pyright
if you want to, but pylance uses pyright
pylance is pyright..
im confused
in terms of typechecking at least
pyright is a static typecheker. You can use this tool via your terminal also. Pylance on the other hand is a vsc extension which uses pyright under the hood with some extra stuff (it's closed source)
so what im saying is that configure your pylance's typechecker mode to basic so you don't have to deal with ugly squiggly lines all the time
Pylance uses Pyright for the Type Checking feature.
effectively is pyright
pylance bundles pyright
Well no because Pylance is not just type checking 
erm akshually
erm akshually this is the type checking channels and when checking if something is an instance of another object, pylance would therfore be a subclass of pyright in type modes

I have a dependency on a library (pint) which recently updated some of its main types to be generic. I would like to keep supporting older versions, but it seems like it might not be possible just because pint>0.25 wants Quantity[float] and pint<=0.25 wants Quantity. Other than just pinning the version or ignoring the types, am I missing any obvious solutions to this predicament?
I think what I might do is define _Quantity: TypeAlias = Quantity[float] # type: ignore[type-arg]. That seems to be working, but it's not ideal since I have to turn off warn_unused_ignore because it's fine in newer versions.
You can do x: Foo[Bar] = Foo() if the generic can't be inferred from an argument
That exact situation was throwing the type-arg error, saying that Foo is not generic (for the old version of the library)
You can still support and test the old version and type check against the new version.
That's a good point, and likely what I'll do in the CI. I appreciate the feedback!
object vs Any for annotation?
Object requires type narrowing before it can be useful. Any disables type checking on the variable.
thanks
hi everyone
How do I type annotate a function that takes a variable list of types and returns a sequence of instances of those types respectively?
def get(*types: type[V]) -> tuple[V]:
return tuple(t() for t in types)
get(int, str, dict) # -> (0, '', {}) should be typed as tuple[int, str, dict]
I tried using TypeVarTuple and Unpack.
You'll have to make an overload ```py
@overload
def get() -> tuple[()]: ...
@overload
def get[T0](t0: type[T0], /) -> tuple[T0]: ...
@overload
def get[T0, T1](t0: type[T0], t1: type[T1], /) -> tuple[T0, T1]: ...
@overload
def get[T0, T1, T2](t0: type[T0], t1: type[T1], t2: type[T2], /) -> tuple[T0, T1, T2]: ...
repeat until you're bored...
def get(*types: type[Any]) -> tuple[Any, ...]:
# actual implementation
(if you can't use 3.12 yet you'll need to use the old syntax for typevars)
Is this a bug in mypy? Or am I just a beginner who doesn't really know what's up?
from typing import Any
def my_func(s: Any) -> str:
assert isinstance(s, str)
reveal_type(s)
s = s.replace(": ", ":")
reveal_type(s)
return s
❯ mypy --version
mypy 1.18.2 (compiled: yes)
❯ mypy t.py
t.py:6: note: Revealed type is "builtins.str"
t.py:8: note: Revealed type is "Any"
Success: no issues found in 1 source file
Why does the s.replace call "un-narrow" the type?
That seems like a bug to me. Pyright has the same output
Thanks for checking against pyright! Didn't think about that. Okey dokes, might check the mypy issue tracker later
you can check out https://mypy-play.net/, https://pyright-play.net, https://basedpyright.com for easy reproductions
A different question (just xposting here): https://discord.com/channels/267624335836053506/1447580803658813501
That's cool! Will keep in mind for future typechecking posts 🙂
you can avoid that bug by using another variable instead of reassigning it back to s
if multiple type checkers have the same problem it's often a problem in typeshed
though that seems less likely here
By "same output" do you mean:
- Has the same output to mypy
- Has unchanged output (i.e., both reveal_type calls have the same response)
both reveal_types show the same thing
pyright and mypy have different error codes and such otherwise
Actually, I was just checking myself with pyright playground and they have the same output as mypy...?
yeah, I meant in general
there's no unified formatting or convention for type checking errors
T = typing.TypeVar("T")
DisjointSet = dict[T, [tuple[int, int]]]
mypy says error: Bracketed expression "[...]" is not valid as a type [valid-type] on that second line. Why? What's not valid about it? Do I need to use Hashable or something?
Why are you putting the tuple inside brackets
Ah. Good question 😄 I think that's meant to be a set[tuple] 😄 Thanks!
Yuh
Or just drop the [] actually. But yes. I couldn't see that. Thank you!
3.13
well the newer syntax still uses typevars
Hmmm I think it was actually not deprecated, my bad
Why would you delete your msgs tho
even if they're wrong
Just strike through them
Sorry I'm just used to doing that lmao
Good point
Anyway at some places it'd be more convenient to use the new syntax instead of a typevar directly
what theme u r using?
It's because you explicitly tell it that s: Any. After the assert isinstance it may then know it as str but it looses that once you re-assign. It goes back to Any.
Doesn't happen with other types though, e.g. if you do py def my_func(s: object) -> str: assert isinstance(s, str) reveal_type(s) s = s.replace(": ", ":") reveal_type(s) return s it shows str both times
strange. But still, s is a str and should be typed as such. Also it does then output the right types.
The 3.12+ syntax really makes my life easier
That's just some random code I converted from java
yes we love type parameters 💛
well, you can delete all those get and set methods. Unpythonic.
and of course the staticmethod there
I agree I just wanted to copy 1:1 for the sake of it
Just bored lol
I would've also just made a copy() function instead of having to use temps
The school-work requirements are weird
wdym temps?
Temporary queue instances
Oh! No longer Node(Generic[T])
Indeed
occasionally I come across the [T] notation next to classes and methods.
What does it mean?
Can change on context, but say it's a plain T on a class - it means the class takes a generic type argument, which can be used inside of it for whatever. for example:
class Foo[T]:
def bar(v: T) -> T:
return type(v)
f = Foo[int]()
print(f.bar(1))
print(f.bar("string")) # type checker tells you it expects int
There's probably a better way to explain it but that's what I got
so its just like a general type element, so this function / class can work on generic types
similar to haskell if you know that
function :: (a,b) -> (b,a)
function (x,y) = (y,x)
the function can work on any type
:: hmm
In Haskell it would be a generic parameter of a type ```hs
data List a = Nil | Cons a (List a)
-- ^
I've implemented an abstract base class which I'm implementing in several derived classes. However, I get an error from mypy when I use overload on the derived class to specify what type is returned. What can I do to make mypy happy here? See https://bpa.st/RVJQ
Everything is good when @overload isn't used. No errors. Except that the static type checker is unable to resolve statically the type it returns. Which is why I have the @overload here.
To make it more compact. This is giving no errors:
class Memory(ABC):
@abstractmethod
def view(self, padding: int | None = None) -> Memory: ...
class Segment(Memory):
def view(self, padding: int | None = None) -> Segment: ...
class MemoryMap(Memory):
def view(self, padding: int | None = None) -> MemoryMap | Segment: ...
While this is:
class MemoryMap(Memory):
@overload
def view(self, padding: None = None) -> MemoryMap: ...
@overload
def view(self, padding: int) -> Segment: ...
def view(self, padding: int | None = None) -> MemoryMap | Segment: ...
Is there a way to combine @overload and abstract methods like this?
from __future__ import annotations
from typing import Protocol, overload
class Memory(Protocol):
def view(self, padding: int | None = None) -> Memory: ...
class Segment(Protocol):
def view(self, padding: int | None = None) -> Segment: ...
class MemoryMap(Protocol):
@overload
def view(self, padding: None = None) -> MemoryMap: ...
@overload
def view(self, padding: int) -> Segment: ...
def view(self, padding: int | None = None) -> MemoryMap | Segment: ...
The classes in the example where reductions of the actual classes, not protocol classes. Do I need to have a declarative protocol class for each of them?
I wonder if you can just have a single protocol and have your classes adhere to that protocol
from typing import Protocol
class Memory(Protocol):
def view(self, padding: int | None = None) -> Memory: ...
class A:
def view(self, padding: int | None = None) -> Memory:
return A()
class B:
def view(self, padding: int | None = None) -> Memory:
return B()
class C:
def view(self, padding: int | None = None) -> Memory:
return C()
def f(m: Memory):
m.view()
f(A())
f(B())
f(C())
seems to work 🤔
Yes, except B.view() and C.view() differ in the type it returns and the type it statically infers.
I think I prefer that e.g. B.view() declares that it returns B type, when B is an implementation of the generic A type.
My code above works runtime as ABC. It's the typing annotation that's not completely like I want it.
What if view returned Self?
could be implemented via type(self)() or self.__class__()
It's not always returning Self thou
I'm surprised that even @override doesn't work
but override overrides implementation not type signature
maybe you should just rename it to view_map 😅
Here's a complete general example
class A(ABC):
@abstractmethod
def view(self, x: int | None = None) -> A: ...
class B(A):
def view(self, x: int | None = None) -> B:
return B()
class C(A):
@overload # mypy complains: "Signature of "view" incompatible with supertype "A""
def view(self, x: int) -> B: ...
@overload
def view(self, x: None = None) -> C: ...
def view(self, x: int | None = None) -> B | C:
if x is None:
return C()
else:
return B()
# This works as expected
reveal_type(C().view(None)) # Revealed type is C
reveal_type(C().view(5)) # Revealed type is B
Everything works as it should be, except for that single mypy error on the @overload. I'm considering to silence it. I think its a bug.
No definitely not a bug, C inheriting from A is a problem
because you've got the same method returning two completely different things
if you change class C(A) to class C(ABC) it'll work
Look at B. It's view() reduce the type from A to B. Is that permitted according to the Liskov principle? Because neither mypy nor pyright is complaining about that reduction.
It's returning a compatible type, B | C is definitely assignable to A
pyright accepts this code, mypy just doesn't understand that the signatures are compatible
Both are happy if I remove the overloads thou.
Oh hmm
Wrong channel, see #python-discussion or #career-advice
Not sure this is the right channel for that. Good luck.
that's definitely a bug/limitation of mypy
one possible hack would be checking if the implementation signature is compatible with the parent, to cover cases like yours
I can post this example in a bug report to mypy if it is
yeah
definitely search for existing issues though
there are over 2600 open issues in mypy
Yeah, I see that 😄
pyright only has a backlog of 177, which is achieved by marking some bugs/inconsistencies as 
e.g. from randomly opening the list of recently closed issues: https://github.com/microsoft/pyright/issues/11165
Let's see how it goes: https://github.com/python/mypy/issues/20418
Just wanted to say, that I love type hints!
There is a principle to keep try...except loops small and confined to what can fail. So its tempting to do a small try block which sets flags that the code later can act on.
def do_something() -> int: return 42
def do_something_else() -> str: return "fallback"
try:
data = do_something()
primary = True
except Exception:
primary = False
alternative = do_something_else()
if primary:
print(f"Primary succeeded with data: {data}")
else:
print(f"Primary failed, alternative data: {alternative}")
However pyright doesn't seem to be able to deduce that the type of data is known when the state of primary is True. Is there an overall better pattern for this type of setup?
in this example, you can just check if it's an int or str
(and use the same variable name)
pyright can't even deduce this... ugh ```py
try:
data = True, do_something()
except Exception:
data = False, do_something_else()
#data is inferred as tuple[Literal[True], int] | tuple[Literal[False], str]
if data[0]:
reveal_type(data[1]) # int | str...
It's not good practice to do a general exception catch like that. You should narrow it or remove it
Unless that goes way out of our topic lmao
i think it's just a minimal example
Yeah lemme reread that thang
That's not the point. Just an example for the other discussion. So don't worry 😄
For more complicated scenarios, you can introduce a "tagged union" ```py
@dataclass(frozen=True, slots=True)
class Primary:
foo: int
@dataclass(frozen=True, slots=True)
class Secondary:
foo: str
bar: bool
try:
thing = get_primary()
except MyException:
thing = get_secondary()
if isinstance(thing, Primary):
...
else:
...
data: int | str
try:
data = do_something()
except Exception:
data = do_something_else()
Works, but I need to declare data up front. So these two methods must have accessible types I can type out.
you only need to declare data up front for mypy, not pyright
I'd probably do a try/else. ```py
try:
data = do_something()
except Exception:
alternative = do_something_else()
print(f"Primary failed, alternative data: {alternative}")
else:
print(f"Primary succeeded with data: {data}")
data is available inside else, but not except.
a library function returning a type that a user cannot mention usually leads to annoying situations in general
Yes. Assume then the printrepresents the continued processing. Would you do that in the except?
It does make sense that the type is not known though, data is not guaranteed to be assigned
Sure. The except was already triggered. Raising a new exception will just raise it
if you want, you could even add a nested try/except/else
Yeah, I know. But I've encountered libraries were I'd have to get past underscore files to get to them 🙁
yep
Yeah i've constructed some monstrous types in ts using ReturnType
you could do
def do_something() -> int: return 42
def do_something_else() -> str: return "fallback"
data: None | int = None
alternative: None | str = None
try:
data = do_something()
except Exception:
alternative = do_something_else()
if data:
print(f"Primary succeeded with data: {data}")
elif alternative:
print(f"Primary failed, alternative data: {alternative}")
I have a library with a lot of those 😭😭
I don't want to export them to the main part of the lib
Or at all
Maybe I should..
You know, the user cannot type check the result without them
Why are you not exporting them already?
If I have code like this: py thing = somelib.get_thing() thing.prefetch() for i, thong in enumerate(thing.thongs(), start=0x3f00): thong.frobnicate(i) I want to be able to extract the loop into a function, so it will be extremely annoying if I cannot mention the return type of thing
Because from personal experience it becomes a confusing mess, you got a library like discord.py where there's many exports including privates lol
So I wanted to not be like that
Yk technically I can make a dedicated import for them
like
from lib import types
I think the return type of a public function should not be allowed to mention non-public types. For the reason above
If you don't want to expose some internal class, that's fine, you can use a protocol or base class as the return type
I can also do the classes directly since you can't get privates from them in my case
I might just do this
Damn I still got like 20 commits I haven't pushed yet on there
For example, in Rust this will produce a compiler warning: ```rs
struct Thing; // not marked as pub
pub fn make_thing() -> Thing {
Thing
}
``` because in some cases it will make the make_thing function unusable
Alternatively I can make functions raise if they don't return the exact thing they should
But that's annoying
Okie saving this info for later
interesting, they really listed everyone who has at least one commit as a contributor
oh damn
We appreciate all our contributors! Couldn't have happened without ya 😃
mom, I'm on television 
what does beta actually mean? no more @Todo?
It means we're now confident ty should understand your code pretty well and catch most errors mypy/pyright would catch
There are still some Todo types left to work out. But there are many fewer than there used to be
I'm augmenting int(obj) in a function with some preprocessing. The IDE told me the int() expected a type ConvertibleToInt, so I searched up and down in cpython for it, only to realize that its in typeshed :D. It is ok to import it via from _typeshed import ConvertibleToInt, or is that the back door we frown at? Is there a more correct way to import it?
you should probably define your own protocol
If I'm doing
def myint(a: T) -> int:
return int(a)
How do I type this? What does T look like? Protocol sounds complicated for something like this
Actually, it's not a protocol... it's some eldritch contraption https://github.com/python/typeshed/blob/8d96801533918957fb194e101cb321bfe1f836f8/stdlib/_typeshed/__init__.pyi#L368-L372
stdlib/_typeshed/__init__.pyi lines 368 to 372
# Anything that can be passed to the int/float constructors
if sys.version_info >= (3, 14):
ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex
else:
ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc```
you could use typing.SupportsInt | str if you don't care about covering all edge cases
Why can't I simply use the already available alias in typeshed for this? Isn't that what its for?
the stability is very limited: https://github.com/python/typeshed/tree/8d96801533918957fb194e101cb321bfe1f836f8/stdlib/_typeshed#api-stability
ConvertibleToInt is not marked as stable
So you can use it, but it might stop working tomorrow. Even if it is stable, it might stop working after 366 days.
(of course, the code will still run, but that would only contribute to the image of python's typing ecosystem being bolted on and not having similar backwards compatibility)
OK, thanks. -- I'm looking forward to the day when I get "copy-this-function's-signature" in a compact way.
yeah, something like ArgumentsOf[int][0] would be cool
Heh, mypy have zero trust. One could infer that if k exists in d it is int, but no.
def f(k: int|str, d: dict[int, str]):
if k in d: # error: Invalid index type "int | str" for "dict[int, str]"; expected type "int" [index]
return d[k]
similar to this https://mypy-play.net/?mypy=latest&python=3.12&gist=e88ade320f80f26fa750ce065fd74af7
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
What's the relationship between the static type bytes and isinstance(x, bytes)? It seems the latter is leaving out bytearray and memoryview. What's the right way to make sure they are consistent?
Its a pyright thing. mypy have no problem with it
I think it's similar to the whole int | float thing
define have problem in this context
def fn(v: bytes|int|None) -> None:
if isinstance(v, bytes):
v = None
reveal_type(v)
# mypy: Revealed type is "Union[builtins.int, None]"
# pyright: Type of "v" is "bytearray | memoryview[_I@memoryview] | int | None"
oh, mypy doesn't do this promotion in --strict mode because it implies --strict-bytes
In pyright, there's a disableBytesTypePromotions setting
ty
you can imagine type checker authors have very worn down i and f keys on their keyboards 🥴
Doing full type annotation on py code is honest and real work. Especially when there's dynamic types involved.
I made an interesting observation at work. C++ ppl (in particular) likes to rant about what they call "Python typelessness" (and they are wrong). But at the same time, when the same C++ devs write any Python snippet, they are not consistent with type usage. I find that ironic and amusing.
to be fair, python's type system is a second thing you need to learn on top of python, and it's not sometimes you'll find extensive docs on, compared to just "learning to write Python scripts as a sane replacement for Bash"
we can't map a dict to an attribute at type checking without hardcoding overloads right?
class MyData(TypedDict):
example: int
class MyClass:
def __init__(self, data: MyData) -> None:
self.__data: MyData = data
def __getattr__(self, name: str) -> Any: # ???
...
reveal_type(MyClass({"example": 1}).example) # <class 'int'>
What would you like differently here? It seems the type checker is able to infer the right type returned by __getattr__, isn't it?
that's how I want it to be haha
the actual type there would be Any
i wonder if it's even possible to type __getitem__ without overloads
What does __ before attr name indicate
This is theoretically possible, if you accept a generic typeddict: ```py
from typing import TypedDict, Protocol, LiteralString
class HasKeyK, V:
def getitem(self, key: K, /) -> V: ...
class Record[TD]:
def init(self, d: TD) -> None:
self._d = d
def __getattr__[K: LiteralString, V](self: Record[HasKey[K, V]], key: K) -> V:
raise NotImplementedError
class Fruits(TypedDict):
apple: int
banana: list[str]
f: Fruits = {"apple": 420, "banana": ["b", "a", "n"]}
thing = Record(f)
reveal_type(thing.apple) # theoretically, should be "int"
reveal_type(thing.banana) # theoretically, should be "list[str]"
``` unfortunately, no type checker currently understands this, both mypy and pyright say that the types are object and object
ugh damn
How to pass type as a Literal[] value instead of general int based on function variable?py def fetch_cursor_paginated_response[M: Schema, S: int]( cursor: CursorWrapper, model: type[M], status_response: S = status.HTTP_200_OK, ) -> tuple[Literal[S], Paginated_Response[M]]: This have an error on return type Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value
If I use S instead in called function it would be typed as int
If the user already has an S, why do they need an S from the return value?
didn't get your question
the lib use return types in the endpoint to handle responses
so if endpoint have type like tuple[Literal[200], ...] then it handle it as so
but when in endpoint function I call return fetch_cursor_paginated_response(...) it is erroring that Tuple entry 1 is incorrect type "int" is not assignable to type "Literal[200]"
Why does the endpoint type need to be tuple[Literal[200], Resp] and not tuple[int, Resp]?
because it handle other statuses, more specifically tuple[Literal[200], Type1] | tuple[Literal[403], ErrorType]
so actual response is only on 200, but 400ish statuses use different models
Why does the fetch_ function even care about response statuses? Can't you just do ```py
def fetch_cursor_paginated_response[M: Schema](
cursor: CursorWrapper,
model: type[M],
) -> Paginated_Response[M]:
...
...
return 200, fetch_cursor_pagniated_response(...)
If you really want a status code to pass through the function like that, the best you can do is an overload:
@overload
def fetch_cursor_paginated_response[M: Schema](
cursor: CursorWrapper,
model: type[M],
) -> tuple[Literal[200], Paginated_Response[M]]: ...
@overload
def fetch_cursor_paginated_response[M: Schema, S: int](
cursor: CursorWrapper,
model: type[M],
status: S,
) -> tuple[S, Paginated_Response[M]]: ...
well, this is more for convenience, the statuses that is being used is not numbers itself, but constants, like status.HTTP_200_OK, status.HTTP_403_FORBIDDEN, etc., so by default it add a shortcut version if status is 200 which it is in most cases, plus in IDE it still highlight it not as just 200, but as status.HTTP_200_OK
You could allow returning a bare Schema from a request handler, which would imply a 200 status code
there are some cases where I use 201, so it was reserved for that
I'd never expect 201 Created to be the default behaviour tbh. That's going to be surprising for people coming from other popular libraries (like Flask and FastAPI)
(as you said, 200 is the status returned in most cases on the happy path)
Fwiw I write both languages professionally and I find this to be a pretty inaccurate generalization
If anything I still often need to explain to python devs (i.e. people who have only ever done python), some of the issues with things like Literal and TypedDict, which don't exist with things like enums and dataclasses
If you've done stuff with constexpr in C++ then these distinctions are natural but if you haven't done it or something similar it may not be as clear why things are murky when you have types that rely on statically known values but no rules for deciding when a value is statically known
Man why do I gotta pass the instance to a method (in a class) that returns a type guard
It does just what I need except for that part
Show code
from typing import TypeGuard, Protocol
class Guild
name: str
class WithGuild(Protocol):
guild: Guild
class AppAuthorized:
def __init__(self) -> None:
self.guild: Guild | None = None
def is_guild_install(self) -> TypeGuard[WithGuild]:
return self.guild is not None
Problem:
User-defined type guard functions and methods must have at least one input parameter
I can fix it by doing the following, which I don't like:
class AppAuthorized:
...
def is_guild_install(self, _: AppAuthorized) -> TypeGuard[WithGuild]: # must add an arg, with this type annotation to avoid problems
return self.guild is not None
a = AppAuthorized()
if a.is_guild_install(a): # must pass a and nothing else
print(a.guild.name) # works
I just thought it'd be nice for the check to narrow .guild to not be None
Wouldn't it be cool if TypeGuard functions considered self as the input arg (:
They noted that they specifically disallowed this to prevent typeguard implementation from being too complicated

Allowing it to serve as a class method may make it too complicated?
Well all I can say is that it'd be convenient for me
You mean method? Class method is a method decorated with @classmethod
instance method
That would be a better way to emphasize just a plain-old method yeah, my bad
That's fair. I was deliberately stigmatizing. I won't do so good in C++ either. I'm very rusty (no pun intended). There are a lot of skilled devs out there.
When did Python introduce the usage of using the native type for type annotation? E.g. use listinstead of List?
3.9 iirc
Assume designing an ABC MyBase with one or more implementations, MyImpl*. I have a function which is get_instance() (terrible name). Should this function be specfic or generic in its return type? I.e. generic as in -> MyBase or specific -> MyImpl1 | MyImpl2. When do you chose one over the other?
Does it just return its own instance?
I'm assuming you mean the return type that you'd write in the subclasses, because on the base it should be -> Self
Personally I'd do -> ThisImpl, but Idk what's the professional thing to do here
Yeah, thanks
How can I declare that a function is mimicking another function without having the actual function, like you would in a decorator? Consider
def fn(a: int, *, b: int = 1) -> None:
...
# This type annotation doesn't work
def replica(*args: P.args, **kwargs: P.kwargs) -> T:
return fn(*args, **kwargs)
I'm targeteting minimum py3.10
I was toying with this hack which seems to work runtime and statically. However pyright definitely doesn't like it:
def replica(*args: P.args, _cls: Callable[P, T] = fn, **kwargs: P.kwargs) -> T:
return _cls(*args, **kwargs)
Google AI suggests making a mimic decorator to copy the signature
I'm not too fond of it when it becomes needed to make runtime adoptions to describe the static typing (however how small) tbh. I think it highlights a shortcoming in the language. - With respect, because I don't know how it could be solved purely statically.
If you have control of both functions and only care about the kwargs, you can unpack a typeddict into **kwargs.
Sure would be nice if we had a @typing.satisfies(some_function) decorator
Could even allow usage on classes to avoid introducing a Protocol metaclass
Oo yeah
Hello, I’ve published a PR that adds an unsafe-subtype error to mypy, primarily to address unsafe date/datetime use cases. I would appreciate any reviews or feedback. Thanks! https://github.com/python/mypy/pull/20448
yonks ago I was young and naive, how much effort would it be to change this
class Baz(Bar[Self]): ... # Rejected```in the typing spec to allow it inside of nested classes?
i think its still in the spirit of 673 to do this cause of the way it desugars
I just don't get the logic of the very first thing
Oh wait, it's passing T which is the subclasser type
When is that useful
class PyLocalObject[ParentT]:
parent: ParentT
class Parent:
class Child(PyLocalObject[Self]):
def meth(self):
reveal_type(self.parent) # Parent
So here the Self doesn't work?
So what you're trying to do is to pass definition's type to what it is subclassing
Isn't that recursive
no it shouldnt be recursive
sorry realise the names were unintentionally very confusing
Well you're passing the type of a class before even getting to the class's contents which may affect its type behavior
Maybe I'm wrong
Maybe the type checker doesn't prioritize like this
you can already do that no?
class PyLocalObject[ParentT]:
parent: ParentT
class Parent:
class Child(PyLocalObject[Parent]):
parent: Parent
def meth(self):
reveal_type(self.parent) # Parent```
i dont think this would significantly affect performance
See that's different
By Self, do you mean Child or Parent
Parent
Self is the type of the enclosing class Child hasnt been defined yet
Hmm but you wouldn't really need Self here since you can just reference Parent
At least on 3.12+
yes you dont technically but i dont wanna mess it up accidentally
Generally (3.12+) isn't Self only useful for a base class
Unless I misunderstand its purpose
can you give an example?
i'd suggest looking at the typing docs cause that shows off some of the usecases
If Foo isn't meant to be subclassed, then you don't necessarily need Self (especially if it means importing it just for that)
However, if I were to subclass it as it is, type of the subclasser's bar() would be Foo instead of Bar. That's where using Self is helpful
It's not a huge deal xD
yeah thats one of the main usecases
Hmm so then if someone is subclassing Parent, you'd want their type to be passed
I do wonder why it doesn't allow it then
But should you ever even make a nested class 🤔
Does that follow any PEP
is this a bug?
Missing a Literal[]?
no im refferring to the class here
confused cause it works fine for SurfaceDefn but not the definition
That's not a type checker thing I believe
Check if you have a spell checker or whatever
Unless you mean the errors on the bottom
Oh you do
Btw on 3.14+ you wouldn't need to pass them in quotes I believe
3.14+
!e
from typing import Module
:x: Your 3.14 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File [35m"/home/main.py"[0m, line [35m1[0m, in [35m<module>[0m
003 | from typing import Module
004 | [1;35mImportError[0m: [35mcannot import name 'Module' from 'typing' (/snekbin/python/3.14/lib/python3.14/typing.py)[0m
types.ModuleType
What's the types module for
Why can't there just be one library for all of this
Instead of splitting it to typing, collections.abc, types and maybe more that Idk of
types is way older than type hints, it's a module that provides classes for all kinds of built in objects, like functions and modules
!d types
Source code: Lib/types.py
This module defines utility functions to assist in dynamic creation of new types.
It also defines names for some object types that are used by the standard Python interpreter, but not exposed as builtins like int or str are...
Alright then
yes
What's annotationslib for
Man no matter what I do I can't get rid of those errors 
data may always be Unknown no matter how i type it
Not to mention read is unknown
Guess that's how it is when their typing is like this
Fork it
I was just about to ask if anyone knows of a different sound lib xD
probably created via typegen
but nah not gonna fork that whole thing
Ah, it's a single module file. We can't provide typing for those.
How come
Because it requires a py.typed marker file
or pyi?
or providing a pyi file, yes
Can you not just do typing regularly inside the __init__ huh
Would like to know (:
Might search myself but we'll see
latest main has types in the source. Try installing from git
interesting lets see
Probably won't help in mypy, but pyright should pick them up
pyright (:
IIRC pyright doesn't care about py.typed
mypy has gotten faster in recent releases
in my tests it is faster than pyright somehow
(for type checking from scratch)
it's not python vs typescript. it's c transpiled code vs nodejs
do type checkers run in a loop
I think most type checkers will make several passes.
tysm
thats clean
welp
its whatever
uhhh oh
If I install from git then it's missing some DLL stuff ;-;
why'd pypi or pywheels be outdated tho
Because they haven't done a release yet.
Because they didn't bump the version
hi ist hier jemand deutsch?
!rule English
4. Use English to the best of your ability. Be polite if someone speaks English imperfectly.
I'm trying to overload a decorator, and but only half of it is working. My goal is the classic decorator that can be called to pass kwargs, or not called and use defaults. Right now I have the following:
@overload
def provides[**P, T](
factory: Callable[P, AsyncIterator[T]],
*,
level: int | Enum = 1,
coverage: list[type[T] | type[Any]] | None = None,
cover_parents: bool = True,
never_cache: bool = False,
) -> AsyncProviderGen[P, T]: ...
@overload
def provides[**P, T](
factory: Callable[P, Iterator[T]],
*,
level: int | Enum = 1,
coverage: list[type[T] | type[Any]] | None = None,
cover_parents: bool = True,
never_cache: bool = False,
) -> SyncProviderGen[P, T]: ...
@overload
def provides[**P, T](
factory: Callable[P, T],
*,
level: int | Enum = 1,
coverage: list[type[T] | type[Any]] | None = None,
cover_parents: bool = True,
never_cache: bool = False,
) -> SyncProvider[P, T]: ...
@overload
def provides[**P, T](
*,
level: int | Enum = 1,
coverage: list[type[T] | type[Any]] | None = None,
cover_parents: bool = True,
never_cache: bool = False,
) -> ProviderWrapper[P, T]: ...
where ProvideWrapper is a Protocol with an overloaded __call__
class ProviderWrapper[**P, T](Protocol):
@overload
@staticmethod
def __call__(factory: Callable[P, Awaitable[T]]) -> AsyncProvider[P, T]: ...
@overload
@staticmethod
def __call__(factory: Callable[P, AsyncGenerator[T]]) -> AsyncProviderGen[P, T]: ...
@overload
@staticmethod
def __call__(factory: Callable[P, Generator[T]]) -> SyncProviderGen[P, T]: ...
@overload
@staticmethod
def __call__(factory: Callable[P, T]) -> SyncProvider[P, T]: ...
@staticmethod
def __call__(factory: Callable[P, Awaitable[T]] |\
Callable[P, AsyncGenerator[T]] |\
Callable[P, Generator[T]] |\
Callable[P, T]
) -> \
AsyncProvider[P, T] |\
AsyncProviderGen[P, T] |\
SyncProviderGen[P, T] |\
SyncProvider[P, T]: ...
However, if I use the decorator and pass in kwargs, it has bad typing. If I don't pass kwargs, the typing is good.
@provides(never_cache=True)
def create_a() -> A:
called_counter["A"] += 1
return A(value=10)
@provides
def create_b(a: A) -> B:
called_counter["B"] += 1
return B(value=10 + a.value, name="B")
It's possible I've just hit the ceiling of python LSPs, but hopefully I'm wrong
This is what the type looks like when it's working
For now, I'm just going to remove the good typing and replace with Any. ```py
class ProviderWrapper**P, T:
@overload
@staticmethod
def __call__(factory: Callable[P, Awaitable[T]]) -> AsyncProvider[..., Any]: ...
@overload
@staticmethod
def __call__(factory: Callable[P, AsyncGenerator[T]]) -> AsyncProviderGen[..., Any]: ...
@overload
@staticmethod
def __call__(factory: Callable[P, Generator[T]]) -> SyncProviderGen[..., Any]: ...
@overload
@staticmethod
def __call__(factory: Callable[P, T]) -> SyncProvider[..., Any]: ...
in my experience: currently mypy is faster than pyright at checking code, but pyright is cleverer about not checking code. in codebases with many lightly used deps, pyright will be faster.
is there any convenient way to type struct.unpack? I dont want to cast() everywhere in case I make mistakes and forget to change something
also, how do I type mixin classes for mypy? im not sure how to do this
Am I wrong in thinking that the mypy pre-commit hook checks only what's part of the commit? Here, I am finding myself in a situation where mypy on pre-commit complains about type errors in a file that is present in the worktree but unknown to Git. Doesn't pre-commit generate a temporary checkout to test and then this unstaged file shouldn't even be in there?
mypy pre-commit hook checks only what's part of the commit
I don't think that would make sense. Imagine that you have ```py
a.py
import b
print(b.foo() + 1)
b.py
def foo() -> int:
return 41
``` if you change foo to return a str, or if you remove foo from b.py, you'd want mypy to show an error in a.py
I don't use pre-commit, but you can pass what files you want checked tomypy
oh, you mean that it's not supposed to see files that are not checked into git?
Yes of course, if I change b and try to commit b without including the according change to a in the commit, then the commit is inconsistent and mypy should complain and I need to include an updated a in the commit. That is the whole point. Imagine I forget to stage a. The commit then is broken. Mypy should complain about that, and not consider the worktree instead.
Exactly.
I think it somehow special-cases this:
[WARNING] Unstaged files detected.
[INFO] Stashing unstaged files to /home/madduck/.cache/pre-commit/patch1766573681-1402765.
If I change b and don't stage it, pre-commit runs without errors. If I change b and stage it, then pre-commit includes files that are not part of git.
This does not seem right at all
pep 718 maybe one day :(
When will cast be builtin :(
Still a draft? 😭
https://peps.python.org/pep-0718/
Why not use assert
Not inline and raises an error by default
I guess the the error part is good as it's safe but 
Oh and it's also at runtime by default
You're using it wrong if it does an error xD
Lol true
Though here I was not able to convince type checker that data is not Unknown
Probably due to the fact that read() may be Unknown too ☠️
They should just bump the version that has typing already
(This)
This seems like a weird error from mypy:
error: Non-required key "colour" not explicitly found in any ** item [typeddict-item]
The TypedDict is:
class TCPlayer(PlayerExportStruct):
shortname: str
colour: NotRequired[str]
and I am calling that like TCPlayer(shortname="foo", **exported_player), where the latter is an instance of PlayerExportStruct.
So yeah, colour is NotRequired. Why then would I need to explicitly provide it?
What's inside PlayerExportStruct?
class PlayerExportStruct(TypedDict):
name: str
club: NotRequired[str]
country: NotRequired[str]
I think NotRequired[str] means: this key is not required, but if it is present, then it must be a str
Remember: typeddicts are "open" by default. So you could have a situation like this:
class CoolPlayerExportStruct(PlayerExportStruct):
colour: int
cool: CoolPlayerExportStruct = {"name": "john banana", "colour": 0xff0015} # ok
def foo(pes: PlayerExportStruct) -> None:
tcplayer = TCPlayer(shortname="foo", **pes)
foo(cool) # this would create an invalid TCPlayer
typeddicts are kinda annoying...
Fair, but now what? colour is not part of the **exported_player I am using. If I have to explicitly provide colour, then what do I set it to? None is not a str, but I also don't have a str that is the same as "no colour provided"
I don't know if there's any good solution
If you know this is fine, you could # type: ignore[typeddict-item] it, I guess
or maybe ditch the TypedDict
yeah
if you don't mind, you can just copy the fields manually```py
def foo(pes: PlayerExportStruct) -> None:
tcplayer = TCPlayer(shortname="foo", name=pes["name"])
if "club" in pes:
tcplayer["club"] = pes["club"]
if "country" in pes:
tcplayer["country"] = pes["country"]
TypedDicts are great in theory, but unfortunately there are not many tools for generic or flexible processing of them
I could do that. I just don't get the error.
did you understand this example?
PlayerExportStruct is open, so it could contain a colour, but of the wrong type
yeah, I do. Right, and then it makes sense.
I think mypy just doesn't support closed typeddicts yet
Otherwise you could do ```py
class PlayerExportStructClosed(PlayerExportStruct, closed=True):
pass
pyright does support closed, but it doesn't complain in your original code in the first place (which is unsound, but it allows a bunch of unsounds things with typeddicts)
I tried that but it didn't work, as you suspected
I've resorted to #type:ignore
closed does what?
class Foo(TypedDict, closed=True):
x: int
y: str
``` means that if you have a `foo: Foo`, the only keys it can contain are `"x"` and `"y"`. It cannot contain `"z"` for example.
if I want to make a function wrapper that works for free functions, instance methods and class methods too, how would I type annotate it?
I've spent hours upon hours on this problem without a solution
the core problem seems to be that type checkers basically ignore the @classmethod decorator that follows my wrapper
it works just fine at run time
can you provide a code sample?
here: https://bpa.st/7KTA
why can't type checkers just respect the @classmethod and determine the correct overload for __get__()?
I think you might be running into this
wow this is a very non-minimal sample
and how would you minimize it?
the MethodWrapper helper class is needed when you need to inject self or cls
case in point: I tried to implement lru_cache in a slightly different manner
what version of python are you targetting btw? I'm confused why you aren't importing Self and reveal_type from typing
I see
I've seen other implementations of a LRU cache and they all basically gave up on the annotations, including the stdlib one (typeshed)
I wanted to believe that it was at least possible but it would seem like it's not
@soft matrix seems like you were quite familiar with this issue already, thanks for letting me know that this remains unsolved
now I can rest easy and give up myself 🙂
Is there a way to do this correctly in 3.14 without ParamSpec
It does that even with A not having a value
Oh and I'll remove the Any assignment, dunno why I added it
Or maybe I do want it there so it's optional and therefore can be none
Will assign to none in that case
If you only want positional arguments, you can use a typevartuple
from collections.abc import Callable
type Fn[*Args, Ret] = Callable[[*Args], Ret]
type F0 = Fn[int]
type F1 = Fn[int, str, bool]
Hm but then R cant be optional
I guess it doesn't help if you want to specify ...
that's ParamSpec
type Fn[**Args, Ret] = Callable[Args, Ret]
type F0 = Fn[[], int]
type F1 = Fn[..., bool]
Oh wait
I was talking about not using the ParamSpec from typing
Not entirely not using it as I didn't know that
I'll use that
I can't seem to figure out the typing for self.event_handler_map, pylance reports for the initialisation of event_handler_map:
Type variable "E" has no meaning in this context
from typing import Awaitable, Callable, TypeVar
from github.webhook.event import Event
E = TypeVar("E", bound=Event)
Handler = Callable[[E], None] | Callable[[E], Awaitable[None]]
class Webhook:
def __init__(self):
self.event_handler_map: dict[type[E], Handler[E]] = {}
def on(self, event: type[E]) -> Callable[[Handler[E]], Handler[E]]:
def decorator(handler: Handler[E]) -> Handler[E]:
self.event_handler_map[event] = handler
return handler
return decorator
any help would be appreciated, for reference, this is the intended usage:
@webhook.on(PushEvent)
def push(event: PushEvent):
print(event)
There's no way to express a dict such that there's some generic relationship between the key type and the value type. You could make your own, like this ```py
type Handler[E] = Callable[[E], None | Awaitable[None]]
class EventHandlerDict:
def init(self) -> None:
self._data: dict[type[Any], Handler[Any]] = {}
def put[E: Event](self, key: type[E], value: Handler[E]) -> None:
self._data[key] = value
def get[E: Event](self, key: type[E]) -> Handler[E]:
return self._data[key]
interesting, I've never seen the type Something[T] = ... syntax before, thank you!
That's new in Python 3.12, together with class Name[T]: and def func[T]():
I see. just to confirm, is there any functional difference between these?
Handler = Callable[[E], None] | Callable[[E], Awaitable[None]]
type Handler[E] = Callable[[E], None | Awaitable[None]]
I get the irony of asking the functional difference of a python typehint xD
I more meant if it communicated any difference to the developer or a type checking software
E = TypeVar("E")
Handler = Callable[[E], None] | Callable[[E], Awaitable[None]]
#---
type Handler[E] = Callable[[E], None] | Callable[[E], Awaitable[None]]
these have the same meaning for type checkers, but type-based type aliases are lazily evaluated
Callable[[E], None | Awaitable[None]] is different from Callable[[E], None] | Callable[[E], Awaitable[None]], but I don't think any existing type checkers are able to make use of that
interesting, how are they different? I think I understand what each represents (the longer one explicitly typing sync and async functions and the shorter one type a function that could be either sync or async). what difference are you thinking off?
If you have f: Callable[[], int] | Callable[[], str] and you do py x = f() y = f() it's not possible that x is an int and y is a str. But with Callable[[], int | str] it's possible