#type-hinting
1 messages · Page 48 of 1
why isn't it possible x is an int and y is a str? sorry, I'm not following that one. to me, one just seems like a shortened version of the other
f is either "a function returning an int" or "a function returning a str".
If it's the former, x and y will be int. If it's the latter, x and y will be str.
ahhh I see, it collapses the union to either one or the other
thank you, you've helped me many times in this channel over the years :)
with that knowledge, I suppose you could replace this with:
type EventHandlerDict[E: Event] = dict[type[E], Handler[E]]
wait no
ignore me
This is still just a generic type alias. If you have e.g. EventHandlerDict[MyEvent], it s a dict[type[MyEvent], Handler[MyEvent]]
yeah, mb 😅
Is this for Discord webhook events 🤨
Ah wait it's imported from a module called github
If a class that subclasses Event implements __init__, is it needed to call super().__init__()?
Note: Event has a single purpose: serving as a generic type annotation in function that takes any of the classes that subclass it.
If you do ```py
class FooEvent(Event):
...
`super().__init__()` doesn't just call `Event`'s `__init__`, it calls the `__init__` of the next event in the inheritance chain
not very relevant if you don't plan to support multiple inheritance
In this example, does it call the init of both BaseLobbyMessage and Event?
I only really need it here to call BaseLobbyMessage's init
Now I'm unsure of why I even have an init for Event
Considering the purpose that it serves, as noted here
When you do LobbyMessageUpdate(x), it will call BaseLobbyMessage.__init__(self, x).
"Speaker: Raymond Hettinger
Python's super() is well-designed and powerful, but it can be tricky to use if you don't know all the moves.
This talk offers clear, practical advice with real-world use cases on how to use super() effectively and not get tripped-up by common mistakes.
Slides can be found at: https://speakerdeck.com/pycon2015 and ...
I'd recommend a classmethod like from_dict
__init__ is a bit stinky because it's often considered exempt from substitutability cheks
Yeah I can, but this logic means that Event is now used as an instance
Do you mean an abstract classmethod?
Thing is I want the subclassers to have their own attributes
The idea is to have from_dict to be part of the Event API. If you have an unknown Event class event_cls, you instantiate it from a dict with event_cls.from_dict(...)
because the __init__ can have an arbitrary unknown signature
So I'd still need to implement init on each one
And have from_dict return a new instance
For example, if you have an event that just stores a single integer (like <PingEvent id=64>), it's a pretty strange API to require PingEvent({"id": 64})
so you could have PingEvent(id=64) and have another method for when you need to parse a dict
maybe I'm misunderstanding the information flow in your project though
class Event: ...
class ApplicationAuthorized(Event):
__slots__ = (
"user",
"scopes",
"guild",
"is_guild_install",
"is_user_install"
)
def __init__(self, data: ApplicationAuthorizedData) -> None:
self.user = User(data["user"])
self.scopes = data["scopes"]
self.guild = Guild(guild) if (guild := data.get("guild")) else None
...
event_cls = EVENTS_MAP[event_type]
event = event_cls(event_data)
await handler(event, timestamp)
Wait, why did you remove __init__ from Event
and what signature does it even have
Wdym signature
argument types and return type
Event's init takes data which is a dict[str, Any] (Would be better to make that a union type of the right TypedDicts but that's not relevant at the moment)
All subclassers follow that with their inits
As I said though, Event is used solely for typing
I can just go back to this stage and answer: no
And I'm also not sure if it even needs to be an ABC, if its just for typing
I'm building my own github webhook event handling system as part of my dissertation project
Made myself a bucket list lol
It's nice that I can just do this instead of using an overload

Wuh am I supposed to be looking at
All of it
I wonder how'd that be implemented in 3.12+ syntax if even
Toppings not being in a collection is weird
I was reading astral-sh/ty#2277 , and saw that in PEP 589 it says that no methods are allowed on the class. I then wanted to look at the Typed dictionaries typing specs page to see what it says on the topic, but couldn't find anything mentioning methods being disallowed. Am I just blind or is this something missing from the spec?
TypedDicts are just normal dicts at runtime
I found it, I’m just blind
(It changed from no methods to no other non-annotation statements are allowed)
I think classmethods on typeddicts are also dubious
because you can typically call a classmethod through an instance
Yeah, any non-annotation statement in a typeddict is of questionable use
Though I guess theoretically shouldn’t that also include docstrings?
Oh wait I’m still blind
Though actually, shouldn’t that not include …? Or I guess is that grouped in with “pass statements”?
What am I doing wrong
Oh and yeah I realized this is a runtime issue
not about typing
but its alriiiight
this is not a runtime issue
TypeError: private() missing 1 required positional argument: 'func'
Well this being runtime doesn't help me xd
but yeah that's not how decorator factories work
and typing one correctly is not easy
Yea i assume if top level takes func, it must not take anymore arguments
so the inner level must take func and not args, kwargs, i think
yes
This is how a decorator factory works:```py
def outer(param1, param2):
def decorator(func):
def inner(*args, **kwargs):
... # use func in some way, e.g.:
return func(*args, **kwargs)
return inner
return decorator
@outer(param1="a", param2="b")
def func(arg1, kwarg1):
...
ohhh it goes another level in
It's the same as ```py
decorator = outer(param1="a", param2="b")
@decorator
def func(arg1, kwarg1):
...
so outer is a function that returns a decorator, hence a "decorator factory"
def decorator
def wrapper
return wrapper
or alternatively
def top
def decorator
def wrapper
return wrapper
return decorator
The correct type for this is something like ```py
class IdDecorator(Protocol):
def call[P, R](self, func: Callable[P, R], /) -> Callable[P, R]: ...
def outer(param1: str, param2: int) -> IdDecorator:
def decorator[**P, R](func: Callable[P, R]) -> Callable[P, R]:
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
... # use func in some way, e.g.:
return func(*args, **kwargs)
return inner
return decorator
That's horrible
So you really have to use a protocol every time?
Yes. The only way to describe a generic funciton is with a callable protocol
there is some support for the old way where this "works": ```py
def make_identityT -> Callable[[T], T]:
...
Inspect is very cool (but isn't everything accessible already via obj.__code__)
I might actually use this one to save time
Would be annoying to users without type checker lol
Is this not something they can just... fix?
Or is it intended
That needs new syntax. E.g. in typescript you can do ```ts
function makeIdentity(label: string): <T>(arg: T) => T {
return a => a
}
note that T is scoped to the return type
I really doubt paramspec has any real use here
this is the kind of bike shed it's going to require
Although that way may not be obvious at first unless you're Dutch.
Is it because he's taking the top off first or
Wait that’s actually genius, I never realized how good of a joke that line is until just now
Bogstandard underground train station bicycle parking
here it'd be a fence outside the station that you chain your bike to
(hopefully the bike is cheaper than the fence)
Can someone help me understand why does this type hint not work when trying to annotate an async property?
class AsyncStateManager(Generic[S]):
def __init__(...) -> None:
self._global_on_enter: Optional[Callable[[S, Optional[S]], Awaitable[None]]] = None
@property
async def global_on_enter(
self,
) -> Optional[Callable[[S, Optional[S]], Awaitable[None]]]:
return self._global_on_enter
@global_on_enter.setter
async def global_on_enter(
self, value: Optional[Callable[[S, Optional[S]], Awaitable[None]]]
) -> None:
if value:
on_enter_signature = inspect.signature(value)
pos_args = self._get_pos_args(on_enter_signature)
kw_args = self._get_kw_args(on_enter_signature)
if (
len(on_enter_signature.parameters) != _GLOBAL_ON_ENTER_ARGS
or kw_args != 0
):
raise TypeError(
f"Expected {_GLOBAL_ON_ENTER_ARGS} positional argument(s) only "
f"for the function to be assigned to global_on_enter. "
f"Instead got {pos_args} positional argument(s)"
+ (
f" and {kw_args} keyword argument(s)."
if kw_args > 0
else "."
)
)
self._global_on_enter = value
I get this type warning for it from basedpyright-
warning: Property setter value type is not assignable to the getter return type
Type "((S@AsyncStateManager, S@AsyncStateManager | None) -> Awaitable[None]) | None" is not assignable to type "CoroutineType[Any, Any, ((S@AsyncStateManager, S@AsyncStateManager | None) -> Awaitable[None]) | None]"
"None" is not assignable to "CoroutineType[Any, Any, ((S@AsyncStateManager, S@AsyncStateManager | None) -> Awaitable[None]) | None]" (reportPropertyTypeMismatch)
the setter cannot be async
in your case, the getter shouldn't be async either
oh
how do i get around it then
wait
im dumb
i just have to remove the async
thanks btw
This is yet another example of implicit Coroutine being confusing
If you had to do ```py
@property
async def global_on_enter(self) -> Coroutine[Optional[Callback[S]]]: ...
@global_on_enter.setter
async def global_on_enter(value: Optional[Callback[S]]) -> Coroutine[None]: ...
I wonder why basedpyright doesn't reject the async setter though...
🫠
Does any type checker
Whoops
no
@tribal heart
from typing import Self
class Test:
def func1(self, t: Self):
print(t)
def call1(self):
t = Test()
self.func1(t) # <<<<<<<< Type "Test" is not assignable to type "Self@Test"
I definitely am missing something here...
I was expecting func1 to take anything of type Test?
t is a new instance, not self
What if you do t: Test
Accepting a parameter of type Self is wrong, but unfortunately type checkers don't warn about it
Definitions can only use Test with quotes
Do you know the difference between t: "Test" and t: Self?
The origin of the question:
def func1(self, t: Optional[Test]):
def func1(self, t: Test | None): # << Nope
def func1(self, t: "Test" | None): # Nope as well
def func1(self, t: "Test | None"):
As it happens no 🙂
Or at least not the thing I thought it was
If you don't like the quotes, you can add from __future__ import annotations at the top of your file. It will automatically stringify all your annotations (on python <3.14), so you'll be able to do t: Test | None
Self varies when you make a subclass. For example:
class Box:
def __init__(self, value: int):
self.value = value
def copy(self) -> Box:
return cls(self)(self.value)
class CoolBox(Box): pass
b = CoolBox(42).copy() # b is inferred as `Box` (because that's what Box.copy is annotated to return )
class Box:
def __init__(self, value: int):
self.value = value
def copy(self) -> Self:
return cls(self)(self.value)
class CoolBox(Box): pass
b = CoolBox(42).copy() # b is inferred as `CoolBox` now
So it does not allow Self to be used for arguments for basically the same reason as List is invariant?
Maybe. You can use Self as an argument, but it's dubious:
class Foo:
def f(self, x: Self) -> None:
print(x)
class Bar(Foo):
def f(self, x: Self) -> None:
# are we allowed to assume that `x` is Bar in here? That's what Self means after all
...
def test(x1: Foo, x2: Foo) -> None:
x1.f(x2)
test(Bar(), Foo()) # this will provide a `Foo` as the x argument to Bar.f()
``` Pyright actually catches this now, but mypy doesn't
Yea, that is what I meant but worded poorly
Self is actually a shorthand for a type variable. So if you write it out long hand, it makes sense why it's not allowed ```py
class Foo:
def f[S: Foo](self, x: S) -> S:
return x
class Bar(Foo):
def f[S: Bar](self, x: S) -> S: # this has a more restrictive bound than the parent
return x
Shorthand for a bound type variable
yes
pyright still allows subclassing Foo, maybe that can lead to problems anyway, since Bar.f still has a signature incompatible with Foo.f... not sure
Btw, Pylance does not allow subclassing your code
yeah, pylance is based on pyright
I mean it actually produces an error
But I get what you mean.
Guess for now I will use the old Optional for my purposes
Why? You can use from __future__ import annotations
"Test | None" also works
Do not like (as a personal preference) the full quotation.
Might use the Future though, yea
!e
from __future__ import annotations
class Test:
def copy(self) -> Test | None:
return None
print(Test.copy.__annotations__)
:white_check_mark: Your 3.14 eval job has completed with return code 0.
{'return': 'Test | None'}
Yea, just didn't know about this before.
Will update to it a bit later (just for the rest of things to be ocnsistent)
You don't gotta use quotes from a certain Py version
I assume starting from 3.14?
I think so
In my case it is 3.13 so it does not work. Cannot move for now.
does anybody know why this might be happening?
I have an assert before that should say that self.bar is an instance of str yet here it shows it's type is Unknown?
Assert is only relevant for the block it's within
Add a string annotation to bar
what if I have something like this? (I don't think I can add an annotation to tile_size and I can't import the World class because that would lead to a circular import)
I could create a pyi file but wouldn't that be an overkill?
To avoid circular imports you can do:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from world import World
def __init__(self, main, world: "World", pos): ...
You must use quotes otherwise it can cause a NameError
okay thanks
Only before 3.14 or if you don't use from __future__ import annotations
Oh on 3.14 you dont have to use quotes in this context?
Nope. Annotations are lazily evaluated now.
Didn't know that, ty
(And before 3.14 you should use from __future__ import annotations)
What happens if you try to access it without overriding?
Have I coded my Protocol incorrectly, or is there some reason that Literal doesn't support __add__?
I'm trying to do:
import typing
class SupportsAdd(typing.Protocol):
"""A Protocol for classes that have __add__."""
def __add__(self, other): ...
def my_function(input_value: SupportsAdd):
# I know this code only makes sense for str inputs;
# I just needed a stub function for the mock example.
return input_value + "foo"
my_string: str = "This string definitely supports __add__!"
reveal_type(my_string) # Expected: str; got: Literal
_ = my_function(my_string) # Expected no error, but got one
# because Literal[...] is incompatible with SupportsAdd
This code definitely runs IRL, because that string literal absolutely supports addition, so I'm not sure why it's not compatible with the Protocol. (In fact, if it's not compatible with the Protocol, then how can it run without crashing, if it doesn't have an __add__?)
Having said all this, I forcibly typing.casted my_string to a str and I'm still getting the same type error, so I'm now suspecting that I've coded my Protocol incorrectly.
I must implement it otherwise it raises an error
are you using pyright?
Yes.
the error says:
...
Type "(value: str, /) -> str" is not assignable to type "(other: Unknown) -> None"
Missing keyword parameter "other"
Position-only parameter mismatch; parameter "other" is not position-only
Position-only parameter mismatch; expected 1 but received 0
class SupportsAdd(typing.Protocol):
"""A Protocol for classes that have __add__."""
def __add__(self, other, /): ...
"as designed" moment
And add return type to __add__
oh yeah also that
So ```py
class SupportsAdd(typing.Protocol):
def add(self, other, /) -> Any: ...
or object, depends on your use case
Quick question, does NotImplemented get resolved accordingly to the magic method it's used in
Or at all
wdym
__eq__ is meant to return bool. What's happening if I return NotImplemented
Is it falsy?
!e
class C:
def __bool__(self):
return NotImplemented
if C():
print("abc")
: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 [35m5[0m, in [35m<module>[0m
003 | if [31mC[0m[1;31m()[0m:
004 | [31m~[0m[1;31m^^[0m
005 | [1;35mTypeError[0m: [35m__bool__ should return bool, returned NotImplementedType[0m
So, don't do that
!e
from typing import reveal_type
class Foo:
def __init__(self) -> None:
pass
def __eq__(self, other: object) -> bool:
if isinstance(other, int): return True
return NotImplemented
f = Foo()
reveal_type(f == "ok")
:white_check_mark: Your 3.14 eval job has completed with return code 0.
Runtime type is 'bool'
Looks like it's falsy
!e print(bool(NotImplemented))
: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 | print([31mbool[0m[1;31m(NotImplemented)[0m)
004 | [31m~~~~[0m[1;31m^^^^^^^^^^^^^^^^[0m
005 | [1;35mTypeError[0m: [35mNotImplemented should not be used in a boolean context[0m
no, it's not
Then why's this
__eq__ may return False when NotImplemented is returned
How about operations like __mul__
that's generally how NotImplemented works
something special is done by whatever is calling the method
yeah, you're absolutely right.
I had a difficult time parsing/understanding that error-message stack and I think my attention black-holed on "str" is not assignable to "LiteralString" and got convinced that it had something to do with the fact that my_string was being interpreted as a Literal.
I understand its syntax, now, what it means when it's saying that "__add__" is an incompatible type. (I wish that was a bit more verbose to add clarity, but the blame is definitely on me here.)
Thanks for pointing out the obvious.
(calling a.__eq__(b) won't do the transformation, a == b will do that I think)
which is also why passing obj.__eq__ as a function to map/filter is wrong
or any of these magic methods really
makes sense
Would be cool if you could tell type checker it should only work with a certain type, causing it to error-highlight the ==
Maybe an overload
@overload
def __eq__(self, other: Vector) -> bool: ...
@overload
def __eq__(self, other: object) -> Literal[NotImplemented]: ...
Would that work?
If that doesn't work, add a @warnings.deprecated() to one.
cant do Literal[NotImplemented]
its a variable
Figured. Use @deprecated
I'm aware, but there's no other way to create your own type diagnostics
hm typing_extensions got deprecated decorator too
Note it will be a false positive if reveal_type(other) is object but runtime is Vector
nvm its the same class
Yeah, it was added to typing_extensions since it's technically a typing feature
imported from warnings
if you're on an earlier version of python, it will be its own implementation
which one do i add that to
the one you don't want to use.
an overload?
yes.
@overload
def __eq__(self, other: Vector) -> bool: ...
@overload
@deprecated("Vector should only compare with itself")
def __eq__(self, other: object) -> bool: ...
"FunctionType" is not assignable to "LiteralString"
ah wait
ok gotta pass a string
well whats the point
i dont need all that just for a runtime thing
This isn't a runtime thing
Seems to work.
Hm could i make a single decorator for the implemented __eq__ that will automatically do this
i guess not
@deprecate_object
def __eq__(self) -> bool:
pass
Yeah
You can't wrap deprecated
aw man
It's one of those special cased functions used by the type checker
It has to be statically resolvable to the original function
Alright
Anyway I think it's an overkill
Dont like having to define overloads just for this
There's always disjoint_base, but I don't think it's implemented in typecheckers yet
!pep 800
Imported from?
Not sure how that would effect equality though
typing_extensions, but it's not implemented. It's present in typeshed stubs though
I don't think it would be useful here. After all, who can say you didn't implement def __eq__(self, other): return True
How'd I implement that in my context in general lol
from what I can tell, disjoint_base is mainly for creating new classes from multiple bases
Its doc only talks about classes
The #reachability section is similar, but focuses on isinstance instead of eq
I think it's implemented in mypy, ty and Pyrefly at this point. Don't know about pyright
pyright supports it for reachability from what I tested
still not useful for saying something will always return NotImplemented
or False
it's in mypy and ty
oops alex said that already
You're right.
@staticmethod
async def fetch(id: int) -> Self:
Why is this not giving proper typehinting in python 3.12, intellisense?
There's no self context to satisfy the Self typevar
for its return value
use classmethod instead
call cls() instead of using your owning class
@classmethod
async def fetch(cls, id: int) -> Self:
r = cls._cache.get(id)
if r is None:
sql = "SELECT id, points, share_points FROM discord_user WHERE id=$1;"
r = await cls.from_db(await db.fetch_one(sql, id))
cls._cache[id] = r
return r
I assume this also works? I never instantiate a class in this function
Basically
r = User._cache.get(id) -> r = cls._cache.get(id)?
does from_db also return Self?
yes
from_db should instantiate it
it is classmethod that instantiates a User object via the cls argument.
seems to work now thanks
Remember Self is equivalent to this. ```py
class MyClass:
@classmethod
def fetch[Self: MyClass](cls: type[Self]) -> Self: ...
Is that a generic? I have not seen that syntax in python before
nifty
Sentinal types could solve the useless equality check if NotImplemented was one and an overload returned it
Would there be any way to make this function actually safe?
basedpyright playground
def narrow_tagged[*TS, T](tag: T, data: object | tuple[T, *TS]) -> tuple[*TS]:
if isinstance(data, tuple) and data[0] == tag:
return data[1:] # Return type, "tuple[Unknown, ...] | tuple[*TS@narrow_tagged]", is partially unknown
else:
raise ValueError(f"Internal Error: Tried to get tagged valued {tag} on unexpected item {data}")
Basedpyright rightfully complains, since the object part of data could be some random tuple that just so happens to have T as the first element but doesn't have *TS as the last elements, so would there be any way to make it safe? I'm stumped.
Why have object there just for the error case?
I guess never mind since it doesn't even work since the if the tag is a literal it decays to a str and doesn't narrow
That is actually smart, there are other types that could go into this function besides the tuple and I know what they are so I can put them there in place of object and that error goes away. Still doesn't work though :(
I would have thought this would work, but I guess that's not really what LiteralString is meant for ```py
def narrow_tagged[*TS, T: LiteralString](tag: T, data: Regex | tuple[T, *TS]) -> tuple[*TS]:
why can't you just remove object entirely, and only put tuple[T, *Ts]?
Hello. How do you people usually fix this problem? I'm not using async/await
code:
data = json.loads(self.valkey_client.get(key).decode())
warning:
Attribute `decode` may be missing on object of type `Unknown | Awaitable[Any]` (ty possibly-missing-attribute)
the valkey and redis libraries have Any | Awaitable[Any] defined as the return type for the base client class. A cast is needed
you mean like str(client.get(key)) ?
valkey-io/valkey-pi#164
thing = self.valkey_client.get(key)
assert isinstance(thing, str)
data = json.loads(thing.decode())```
Hello? bot?
thank you
Or str(self.valkey_client.get(key)) if that works
I liked the isintance usage
!pip types-redis may be helpful too, but there isn't one for valkey.
Looks like that's being done in https://github.com/valkey-io/valkey-py/pull/134
Can [Bot] raise a NameError due to it being in a type declaration?
Only if you access it at runtime, via Interaction.__value__
am I spoiler by pyright that ignored the type if the func had ellipsis or is that the norm
ty allows this in stub files, in "if TYPE_CHECKING" blocks, for overload-decorated functions, for abstract methods, and for protocol methods. But not in other functions or methods, because it's unsound: the type hint says it returns str, but it actually returns None!
🤔
Mypy has similar behaviour to ty here. I know it's a deliberate decision by pyright to allow this unsoundness, but I don't really understand it personally
Does that show ty not emitting a diagnostic or pyright not emitting a diagnostic?
Pyright
But with pass it does complain
I guess with Ellipsis it doesn't cuz of stub behavior?
Unreasonable for normal functions
But you're allowed to have pass statements in stub files? it's a pretty arbitrary exemption to the general rule IMO, which is why we didn't do that in ty
Well an abstract method might as well return a default according to its return type, or use ellipsis
Yes, you have to be careful to allow the former to be called via super() from concrete subclasses but not allow the latter to be called via super() from concrete subclasses
ty doesn't have this check implemented yet but it's on the roadmap
ahh that's fair!
Ty might only allow this if it detects the function in context is abstract.
Like inside a protocol
Adding @abc.abstractmethod might help
Would it be possible to make some kind of a None check like this:
class Test:
def __init__(self) -> None:
self.t: int | None = None
self.d: dict[int, int] = {}
def has_t(self) -> bool:
if self.t is None:
return False
else:
return True
def func(self):
if self.has_t():
# assert self.t is not None
tmp = self.d[self.t] # <<<<<< Error
else:
tmp = 0
Can use TypeGuard but it will require passing self.t there...
yeah unfortunately you can't have typeguard methods with only self
Unfortunate. Will have to add assertions after...
class _NoValue:
__slots__ = ()
def __eq__(self, other) -> bool:
return False
def __bool__(self) -> bool:
return False
def __hash__(self) -> int:
return 0
def __repr__(self):
return "..."
NoValue: Any = _NoValue()
https://images.soheab.com/G4CtlsGYfLM.png?f=scs hope this is a bug in ty?
param: T = NoValue would also be valid with pyright
oh nvm
param: T | NoValue works
Hi all,
for Python 3.7 to Python 3.9, is it considered good practice to use from __future__ import annotations and the new pipe convention (type1 | type2 | None), or is it better to stick to the convention that was present when that Python version was released (Optional[Union[type1, type2]])?
For projects that should work from Python 3.7 to the most recent version, what should I use for type hints?
Thanks in advance!
did you mean to say "are EOL"?
Yes
I believe it was an either/or question
That means both options are correct
I'd avoid Optional outright and use Union[Type1, Type2, None]
What's the reason for supporting 3.7 if I may ask
It's a library available on Pip. I think it was born with that version and they wanted to maintain retro-compatibility for people that used it
as long as you don't have runtime usage of type hints the from future annotations will work well
What's the difference between None and typing.NoReturn? I see people using None without returning this specific type
NoReturn means the function will only end with an exception
Ah, I understand, directly a raise? Or can it be any return Exception?
Must be raise or while True
Thank you so much for taking the time
if it returned an exception, the return type would be the exception type
You can also use typing.Never for a function that won't return
I personally prefer Never
Does Never also indicate that it'll only raise?
Never and NoReturn mean exactly the same thing
I'd always use Never because it's just a newer alias for the same thing
Ooh
Doesn't Never have more usages
https://typing.python.org/en/latest/spec/special-types.html#never
The Never type is equivalent to NoReturn, which is discussed above. The NoReturn type is conventionally used in return annotations of functions, and Never is typically used in other locations, but the two types are completely interchangeable.
I don't know how real this convention is, but in two other languages I know (Rust and TypeScript) there's no such distinction, never/! is used in both data and control flow contexts
TIL typing.Final exists
Given
def f() -> Any: ...
def g() -> str:
# Assume its known that f() will return str in this context
return f() # mypy: Returning Any from function declared to return "str"
# A possible fix
def g() -> str:
v: str = f()
return v
Is there a more elegant to do this? A temp variable isn't eye candy. This needs to be backwards compatible with 3.10 (but I'm curious to how it can be done in 3.14 too)
just type: ignore[no-return-any] it
You can use typing.cast for this
from typing import Any, cast
def f() -> Any: ...
def g() -> str:
return cast(str, f())
Bad idea. If the type of f() changes to object or str | None or str | int or Any | int, this will eat any warnings
can you give more context? is the assumption made based on an argument type or something?
true.. i guess it does depend on the context
f() is a database lookup and the type is given by the specific query. So by design its given what type it returns, but technically, since the database can return anything, f() returns Any.
You'd typically not just wrap f like that
g should have a check whether the found entry in the db is a string
I think this has been discussed here before. Some propose using a typed proxy function get_str_from_db() -> str or get_int_from_db() -> int and so on
You can use overloads
Yes, in this example that could be done I think, since it can be inferred from the returned type. But there are other cases where it's not that clear
Or just..
def get_from_db() -> Any: ...
result = get_from_db()
if isinstance(result, int): ...
if isinstance(result, str): ...
Based on your needs
Yes, and then you introduce runtime code to please the type checker
This isn't what I put the isinstance there for
Based on your needs
If you actually want to know if it's an int, do this and that, and so on
yeah, sure, in that case. Agreed
Also, if you're assuming that f() returns str, might as well make it the return type of it
I'm not controlling the signature of f()
I mean that's the obvious though. I can't think of more complex scenarios
Doesn't that mean it always returns the same type?
Unless it's from an input for example
And then again I'd use isinstance 
The general function can return anything, thus Any. But in my context it will return str.
If you're sure it's going to be string (your specific example wouldn't make sure), then you may just cast, assert, whatever.
There's no way to make a TypedDict (or like) class for integer keys? It doesn't seem so at least
Nawr, you can use an overload for that with Literal.
Is there a way to type annotate certain members of a dict-like object? Some types like: {1: A, 2: B, 3: C}
Yes
You want to annotate them specifically as their values?
dict[int, Any] vs dict[Literal[1, 2, 3], Any]
Perhaps this is how it can be done: dict[Literal[1], A] | dict[Literal[2], B] | dict[Litera[3], C]. Gets messy quickly thou
no, that doesn't work
it doesn't fit e.g. {1: A(), 2: B()}
But you'd almost never need to do this generally
This behavior is a little too specific
So type[A]
I'm assuming sveinse wants 1 to correspond to an instance of A and 2 to an instance of B
My use is not generic. It's specific. I know all types. Specifically, what I'm trying to do with the non-working code:
TPDOParameterRecord = TypedDict('TPDOParameterRecord', {
1: 'LtPDOCobid',
2: 'LtPDOTransmissionType',
3: 'LtPDOInhibitTime',
5: 'LtPDOEventTimer',
6: 'LtInt',
})
Yeah but once you have a dict that is more than a few keys this gets very impractical
You can use overloads to do it
Is that for some existing Python interface? Or are you designing something new?
Keep in mind
This is something existing. It's from a CAN communication standard, where one gets a dictlike object which is indexed with a __getitem__() call. Its given (by the standard) that index 2 returns an object of that specific type.
Reading the discussion, I think overload of __getitem__ is the way to go here.
Yeah it works for a dict of this size
I believe you cannot make this dynamic at all
You could reify keys as instances like this:
class Key[T]:
def __init__(self, number: int) -> None:
self.number = number
def __invariance_hack(self) -> list[T]: raise NotImplementedError # variance inference was a mistake >:(
K_APPLE = Key[int](1)
K_BANANA = Key[list[int]](2)
K_CHERRY = Key[str](4)
class Store:
def __getitem__[T](self, key: Key[T]) -> T: ...
def get[T](self, key: Key[T]) -> T | None: ...
def __setitem__[T](self, key: Key[T], value: T) -> None: ...
def f(s: Store) -> None:
x = s.get(K_APPLE) # x is `int | None`
s[K_CHERRY] = 42 # error
s[K_CHERRY] = "aaa" # ok
``` but it effectively makes every key optional
though if you already have an existing API, it might be hard to retrofit
__invariance_hack() ? Is that something from typing?
Unrelated:
Why does Search, being a class, not trigger type checker?
classes are callable
No, it's just a hack to make T an invariant type variable, as opposed to covariant
new-style generic syntax doesn't let you specify variance explicitly
thanks
Yeah this wouldn't make sense, at most I'd need to apply it on __init__
Ok it does work, it auto applies it on __init__
you probably don't want it on a class, because Search is now not a class
which is probably confusing to users (and also doesn't let you use isinstance etc.)
It works good here.. 
Foo is not a class
You don't see the result in this pic
you understand how decorators work, right?
Rather a what?
Not fully I guess? 
Ohh, it converts it into a decorator function
it means the same as ```py
class Foo:
...
Foo = enforce_annotations(Foo)
``` so Foo is whatever enforce_annotations returns, in this case a function
Yep
try doing print(Foo)
function
but type(foo_instance) is <class '__main__.Foo'>
right
the class still exists, it's just not bound to the name Foo
If you want to use class methods, class attributes, inheritance, or isinstance/issubclass
or if you want to use Foo in a type annotation
Could I change behavior of enforce_annotation to return a type and not a function when it's a class?
Yer right
I'd make it a different function, because it would have different semantics
enforce_class_annotations, whether it only changes the __init__ or all the methods, will have to mutate the class. But enforce_annotations doesn't mutate the argument
Should work only for init
Imagine that you do py x = map(enforce_annotations(some_user_callable), things) you wouldn't expect this to permanently alter RandomClass when passed through some_user_callable
Maybe I should leave it as is and put straight on init when I want it
yeah, that's the least surprising behaviour
if I put it on a class I'd expect it to enforce annotations on all the methods (and I would also wonder what happens to subclasses)
I can also raise if its a class
What's the paste limit here? Is 25 lines ok?
class A: ...
class B: ...
class C: ...
class DictLike(MutableMapping[KT, VT]):
""" Some placeholder dict-like class """
class D(DictLike[int, Any]):
@overload # type: ignore[override]
def __getitem__(self, key: Literal[1]) -> A: ...
@overload
def __getitem__(self, key: Literal[2]) -> B: ...
@overload
# Using `int` for catching all other int keys doesn't work, mypy gives:
# "Overloaded function signatures 1 and 3 overlap with incompatible return types"
def __getitem__(self, key: int) -> C: ...
# Had to do this proxy to avoid mypy error
def __getitem__(self, key: int) -> Any:
return super().__getitem__(key)
d=D()
reveal_type(d[1]) # revealed type is "A"
reveal_type(d[2]) # Revealed type is "B"
reveal_type(d[3]) # Revealed type is "C" if the 3rd overload is used
Here's my attempt. But it does get messy (i.e. wordy) and its not error free yet.
Baah. I think I need to rethink this whole setup. It's too complicated.
Real talk
CPython Docs with type info and typeshed links:
https://guoci.github.io/typesheded_CPython_docs/all_signatures/library/functions.html
The Python interpreter has a number of functions and types built into it that are always available. They are listed here in alphabetical order.,,,, Built-in Functions,,, A, abs(), aiter(), all(), a...
range looks a bit strange
(the cls: type[range] bit)
That cls argument should not be there, that is the signature of __new__.
@trim tangle
?
How dare you ping our typing proficient
Updated, removed the cls parameter.
https://guoci.github.io/typesheded_CPython_docs/all_signatures/library/functions.html#func-range
!e
Can someone explain what I'm doing wrong here?
class SupportsSlots:
def __instancecheck__(self, instance):
print("SS.__")
return hasattr(instance.__class__, '__slots__')
class Foo:
__slots__ = ['a', 'b']
def __init__(self, a, b):
self.a = a
self.b = b
obj = Foo(1, 2)
print(f'{hasattr(Foo, "__slots__")=}')
print(f'{hasattr(obj.__class__, "__slots__")=}')
print(f'{isinstance(obj, SupportsSlots)=}')
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | hasattr(Foo, "__slots__")=True
002 | hasattr(obj.__class__, "__slots__")=True
003 | isinstance(obj, SupportsSlots)=False
Shouldn't that be a metaclass?
Yeah, probably. Can you demo?
np, hapy to have an example.
!e ```py
class FooMeta(type):
def instancecheck(cls, instance):
return True
class Foo(metaclass=FooMeta):
pass
print(isinstance(None, Foo))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
True
To save you some time, would I make a metaclass with the __instancecheck__ method, and make the metaclass a metaclass of my SupportsSlots class?
Ah, right. Thanks, I'll take this for a spin.
Hi all - first time posting here.
I’ve published a Python 3.14 type hinting guide built around two goals:
Technical correctness - strict alignment with current Python 3.14 typing semantics, the typing spec, and relevant PEPs, with direct references where possible.
Pedagogical clarity - presenting typing as a coherent static-analysis system (propagation, narrowing, widening, loss of specificity), rather than a list of isolated syntax features.
I’d like this guide to meet the standards of community typing documentation, and I’m looking for help identifying where it should be improved - technically or pedagogically - so it can be shaped into something broadly useful rather than a standalone personal reference.
If you’ve contributed to typing docs, teaching materials, or have strong opinions about how typing should be explained, your input would be especially valuable.
I’m also interested in feedback from people using this as a learning or reference document: where explanations break, ordering fails, or you had to re-read to proceed. And if a section clarified a failure mode you were already fighting or changed how you structured code, I’d like to hear that as well.
Repo:
One question I’d value input on: whether these topics adequately cover the conceptual surface area needed for a general guide to Python typing, and whether anything important is missing that would prevent a reader from forming a correct working mental model of the system.
Top-level TOC
Overview of Python Type Hinting (aka typing)
Python’s Native Typing Support
Core Annotation Syntax
Tuples: Structured Records vs. Uniform Collections
Typing Collections
Type Inference and Resolution
Advanced Narrowing: Promises vs. Proof
CRITICAL USE CASE: Typing Data from the Edge
Typing Functions: Synchronous vs. Asynchronous
Creating Generic Functions with TypeVar
Constraining TypeVar: bound and Variance
Advanced Generic Callables: Higher-Order Functions
Advanced Generic Callables: Wrappers
Additional Typing Module Functionality
Appendices
- Architectural Best Practices
- Anatomy of Type Declarations and Associated Vocabulary
- The Typing Tools Ecosystem
- Major Typing Enhancement Proposals (PEPs)
Glossary of Typing Terminology
very concerning amount of em dashes
Yeah, I hear you. There is a danger in a lack of confidence that some big dollar firms think is fixed with lots of em dashes. But don't read too much into them - the document is well outside the capacity of AI trash. (And I never used an em dash in my life until they were foisted upon me.)
I am a bit confused by the "Numeric Type Promotion" section. It seems to conflate two different things:
- the fact that
intis assignable tofloatand thatfloatis assignable tocomplex. There's no subclass relation between them, it's just special cased and is considered a design mistake by many people (see e.g. https://discuss.python.org/t/54289) - the fact that
int + floatis afloat, which is correct at runtime (regardless or the values) and is likely taken from the typeshed without any special casing
also, what does this mean?
Note: Type checkers treat bool as a distinct type in many contexts even though it is a runtime subclass of int.
So far as I am aware, at runtime a bool is encoded as an int 1 or some such, so a bool is typically interchangable with an int 1. The type system, however, does not match this runtime behavior and type checkers choose whether they will allow a bool to act as a subtype of an int (or other number).
bool is a subclass of int both to type checkers and at runtime. Do you have any examples of special casing perhaps?
This is from the docs:
Boolean Type - bool
Booleans represent truth values. The bool type has exactly two constant instances: True and False.
...
bool is a subclass of int (see Numeric Types — int, float, complex). In many numeric contexts, False and True behave like the integers 0 and 1, respectively. However, relying on this is discouraged; explicitly convert using int() instead.
https://docs.python.org/3/library/stdtypes.html#boolean-type-bool
even x: complex = True is legal
This so far is confirming that bool is a subclass of int, and giving advice to avoid relying on it (because it may hurt readability)
e.g. using sum(1 if foo(x) else 0 for x in xs) instead of sum(map(foo, xs)), which some people might disagree with
I will try to pull a source later, this is the initial PEP on the matter, and it describes bool as inheriting from int - so your position is certainly not wrong, but I am pretty sure I had a reason for trying to allow for inconsistent treatment by type checkers. https://peps.python.org/pep-0285/
This PEP proposes the introduction of a new built-in type, bool, with two constants, False and True. The bool type would be a straightforward subtype (in C) of the int type, and the values False and True would behave like 0 and 1 in most respects (for example, False==0 and True==1 would be true) except repr() and str()...
Without re-creating the wheel just yet, maybe this context gives some substance to my note:
"The annotation int | bool describes exactly the same type as the annotation int. The two types are equivalent. If a type checker treats them differently, that’s a clear indication of a bug in the type checker. In this case, pyright treats the two types as equivalent and mypy incorrectly treats them differently, so this is a bug in mypy. That said, I appreciate that detecting equivalency between types is difficult and costly (prohibitively so in some cases). If you search hard enough, I’m sure you would find cases where pyright’s behavior differs between two equivalent types."
https://discuss.python.org/t/type-hints-for-bool-vs-int-overload/72239/59
There is currently no way in the type system to write an annotation that means “the set of all values that are instances of X but not instances of subclasses of X”. You can effectively do this if X is marked @final, which precludes the possibility of subclassing, but this isn’t possible for non-final classes like int. Your proposed Just p...
Eric is correctly pointing out that treating int | bool and int differently is a bug
You are correct - I think the issue is more that a major type checker inconsistently treats it, so assuming it will work is a known danger case. Perhaps my calling attention to it is more trouble than it is worth - but it could serve as a very "silent" bug in the type system that would be virtually impossible to track from a naive perspective.
I don't think there's anything special about bool here
haven't reread that discussion but I suspect othet subclasses of int would behave the same
Extremely happy to see https://github.com/python/typeshed/pull/12087#event-22240058619
Why can I type hint with an Enum created by subclassing but not with the functional syntax?
from enum import Enum
class A(Enum):
pass
B = Enum()
def a(x: A):
pass
def b(x: B):
pass```
on the typehint B, I get Variable not allowed in type expression
what if you do type B = Enum() or B: TypeAlias for <3.12
the first one won't work, because you won't be able access the enum members
the second doesn't work for the same reason the original code doesn't work
Pyright says it just decided to not support it https://github.com/microsoft/pyright/issues/2401
that's insane
thank you
so much of working with types in python seems to be spending 2 hours trying to get a trivial thing working and discovering you have to just not type it
I suppose I could construct a union of a large number of literals
hi guys
Where can I use Python for real-world applications?
I’m interested in practical use cases such as network scanning or automation. I’m currently using VS Code as my development environment. Do I need additional tools or guidance to get started? If anyone can help or point me in the right direction, I’d really appreciate it.
This doesn't seem to have much to do with typehinting, I would suggest asking in #1035199133436354600 or #cybersecurity , I'd recommend following some kind of online course for Python basics if you aren't already familiar, and then maybe some cybersecurity specific ones
Literal takes variable type args, like Literal[1, "a"]
Because x: A is for instances of type A
And B is a variable, because you assigned it to an instance
Though with enums it's a bit different, with x: A also working for A's members
B = int and B = TypedDict("B", {"x": int}) and B = TypeVar("B") also work, so the question is rather why assigning an enum to a variable isn't recognized as that sort of thing
int not int()
And as of the rest, yeah I guess
Is there a reason for the type of data not being narrowed here, or is it just a limitation of type checker implementation?
from typing import final, Literal
@final
class InvariantContainer[T]:
__match_args__: tuple[Literal["item"]] = ("item",)
def __init__(self, item: T):
self.item: T = item
def set(self, item: T):
self.item = item
def get(self) -> T:
return self.item
@final
class A: ...
@final
class B: ...
def foo(data: int | InvariantContainer[A | B]) -> None:
match data:
case InvariantContainer(A() as item):
reveal_type(data) # Type of "data" is "InvariantContainer[A | B]"
reveal_type(item) # Type of "item" is "A"
Surely since the type of InvariantContainer.item is T, and we know the type of item is A, and A is final, that must mean data overall is InvariantContainer[A], right?
I think in your case this wouldn't be narrowing because InvariantContainer[A] is no a subtype of InvariantContainer[A | B]
Though mypy and pyright don't narrow either if you make it covariant. But I think in general, it's not true that just because it contains an A, it's valid to narrow the generic argument to A
Would any Pydantic gurus be able to answer this question I have on StackOverflow? In a nutshell, I'm trying to write a custom schema to validate/serialize a tuple[SomePydanticModel, *tuple[SomeOtherPydanticModel, ...]]. I know the core library supports it, but the high-level part that reads type annotations doesn't.
:( that makes matches so annoying to use since I have to un and re-wrap the contained item to get the narrowing to work
Does anyone have any thoughts about my guide's general quality? I'd like to share it more broadly, but I want to be sure that there is nothing majorly stupid, distracting, etc. that would prevent people from taking it seriously.
What's the audience of this guide? Is it people familiar with python but without experience with static typing? Or people familiar with some statically typed language but not how Python's static typing works?
And what's the overall goal?
The most likely audience is Python devs who are past beginner stuff but still unsure what typing is really buying them. That includes people who’ve only used Python and see types as IDE noise, and people coming from typed languages who find Python’s typing model confusing or half-baked. No type theory assumed, just basic comfort writing non-trivial code.
The goal is to pull together the scattered “how do I type this” and “what even is typing in Python” advice into a coherent picture of why typing is useful beyond “the linter wants it.” It’s mostly about building intuition for how types help you reason about code, catch mistakes earlier, and make intent clearer, so typing feels like a tool you choose rather than a chore. Ideally, it would sit next to Python’s official typing docs as a conceptual companion to the quick reference.
so it seems like you want to convey information that's in three broad categories:
- what type hints are, literally (to someone who has never seen them before)
- an explanation of how different concrete tools (like generics or type aliases or protocols) work
- high-level strategy and low-level tactics for integrating static analysis into your Python code
is that right?
Yes. Seems fair.
I think what you set out to do is very ambitious and doesn't fit into a single markdown document, it's more like an entire (small) book. So it ends up being very "underextracted": there are not nearly enough examples and elaboration, so it's probably difficult for a new person to fully understand provided information.
So the overview chapter doesn't really introduce type hints to newbies. The concrete tools part is light on examples (and seems a bit chaotic, e.g.: unusual order, sometimes using pre-3.12 generic syntax, variance is a very difficult concept IME and just listing the definition is insufficient). And the strategy part only elaborates on one bit of advice (rigorously turning edge inputs into typed values).
For example, take a look at this article on the typing site that would fit in the "strategy" category: https://typing.python.org/en/latest/guides/libraries.html
It discusses pros and cons of types in libraries; how to actually add them (with links to other detailed articles on stubs); and provides a lot of best practices with examples and links to even more material.
underextracted
As one illustration, in the "Expressive Structural Narrowing with match" section, you define a union of classes whose name all end withCommand. It is a pretty powerful concept and it's a great tool to design your program with static types in mind. But the reader might not be familiar with it: I'm assuming the audience has very few people who know Haskell/F#/Rust. So it's probably worth dedicating an entire chapter to this general idea of carving out your data model with records and unions (something like https://fsharpforfunandprofit.com/series/designing-with-types/)
This section would also benefit from introducingassert_neverand generally having reachability analysis discussed somewhere in the guide (e.g.: why does the type checker complain thatiis potentially unbound infor i in range(69):? And what do you do about it? (good opportunity show that type checkers are sometimes neither smart nor wise so you can silence them using directives))
(Also, pre-3.12 syntax is used for the type alias, even though thetypestatement was already introduced and the guide targets python 3.14. Is this code written by a human?)
What I wanted to say overall is that it would take an enormous amount of work from turning this from a draft into a comprehensive book
I do think that there's a lack of tutorial-level information on type hints. I've been working on a guide myself for a while: https://decorator-factory.github.io/typing-tips/tutorial/0-start-here/. Instead of making a rough overview with the intention of filling stuff in later, I decided to make articles that are feature-complete and have the appropriate level of detail, but limited in scope. It seems like very few people are interested in this though, so I kind of abandoned it
can I get a hand with my AsyncIteratorBytesResource here https://github.com/django/new-features/issues/117
from useful_types import SupportsAnext
class AsyncIteratorBytesResource(Protocol):
"""
all the machinery needed to safely run an AsyncGenerator[Bytes]
(for django-stubs) this allows AsyncGenerator[bytes] but is less strict
so would also allow a anyio MemoryObjectStream[bytes]]
"""
async def __aiter__(self) -> SupportsAnext[bytes]: ...
async def aclose(self) -> object: ...
async def news_and_weather(request: HttpRequest) -> StreamingCmgrHttpResponse:
@contextlib.asynccontextmanager
async def acmgr_gen() -> AsyncGenerator[AsyncIteratorBytesResource[bytes]]:
async def push(ws_url: str, tx: MemoryObjectSendStream) -> None:
async with tx, connect_ws(ws_url) as conn:
async for msg in conn:
await tx.send(msg)
async with anyio.create_task_group() as tg:
tx, rx = anyio.create_memory_object_stream[bytes]()
with tx, rx:
tg.start_soon(push, "ws://example.com/news", tx.clone())
tg.start_soon(push, "ws://example.com/weather", tx.clone())
tx.close()
yield rx # yield inside asynccontextmanager, permitted inside TaskGroup
return StreamingCmgrHttpResponse(acmgr_gen())
Code of Conduct I agree to follow Django's Code of Conduct Feature Description see https://forum.djangoproject.com/t/streamingresponse-driven-by-a-taskgroup/40320/4 I'd like to be able to w...
I want to be able to run something akin to this:
async with response.streaming_acmgr as resource, aclosing(resource) as stream:
async for v in stream:
await send(compressor.compress(v))
await send(compressor.eof())
I'm also not sure if it should be called AsyncIteratorBytesResource or AsyncIterableBytesResource
Thanks for the feedback.
I agree that it is ambitious. The guide is not intended to be short or exhaustive; rather it is meant to be conceptually complete within its scope and sufficiently elaborated to teach/convey the substance. A usage guide, technical manual, etc. is a different beast - I am actively trying to maintain a narrative/conceptual flow (even if trying to make each "section" stand on its own without extensive reading of other sections). It is a hard balance, which is part of the reason I am asking for comments.
I took a look at your guide after you commented the other day. While we are not doing the same thing, I am sure that my project would benefit from your participation.
If the goal is to explain high-level strategy and essentially do marketing for using python's type system, maybe you could assume that the reader is already familiar with the mechanics. Otherwise you end up duplicating already existing documentation on the typing site, or other tutorial material
whether TypeIs/TypeGuard exists or how numbers are handled is probably not important for that aspect
honestly I might be completely missing the idea behind the project
I tried this explanation elsewhere:
The guide largely parallels the typing spec, but it approaches the material from a different perspective and for a different purpose. The intended audience is Python developers who want to understand Python’s typing system as more than just syntax or tooling, regardless of whether they already know how to use it. It’s meant as a bridge from “I can write type annotations” to “I understand why I would choose to use them,” and it tries to build both pieces together rather than assuming either one up front.
A lot of existing material explains how typing works, but spends much less time on why it’s worth the effort in a dynamic language. Saying that typing “permits static analysis” is true, but it doesn’t really explain what that buys you in practice. This guide is focused on making the motivation explicit and showing how typing supports reasoning, design, and long-term code clarity, not just mechanical correctness.
Does @functools.lru_cache() hide the return type for decorated function?
I'm on python 3.10 and mypy 1.19.1 and it doesn't seem to understand the type 🤔
iirc preserves return type but hides arg types
(based on the typeshed stubs, due to some issues around methods. probably something we should special case in mypy though...)
Or rather I guess it is python-lsp-server that doesn't understand the type. Because goto_type doesn't find anything.
pylsp --version = 1.13.1
ClientT = TypeVar('ClientT', bound=Client, covariant=True, default=Client)
class DynamicItem:
async def callback(self, interaction: Interaction[ClientT]) -> Any:
pass
Now "frontend":
class MyItem(DynamicItem):
async def callback(self, interaction: Interaction[Bot]):
...
That causes:
Method "callback" overrides class "DynamicItem" in an incompatible manner
Parameter 2 type mismatch: base parameter is type "Interaction[ClientT@callback]", override parameter is type "Interaction[Bot]"
"Interaction[ClientT@callback]" is not assignable to "Interaction[Bot]"
Type parameter "ClientT@Interaction" is covariant, but "ClientT@callback" is not a subtype of "Bot"
"Client*" is not assignable to "Bot"```
For clarity, `Bot` is a subclass of `AutoShardedBot` that is a subclass of `Client`.
Is there any way to change `ClientT` or whatever that'll make this work?
Suppose that you have this function: py def f(d: DynamicItem) -> None: ... the signature of DynamicItem.callback means that for every T: Client, Interaction[T] will work as the argument.
But MyItem violates this requirement, because it only accepts Interaction[Bot]. Hence the error
One potential solution is making DynamicItem generic over the type of client. For example (in 3.12 syntax for conciseness) ```py
class DynamicItem[C: Client]:
async def callback(self, interaction: Interaction[C]) -> object:
pass
class MyItem(DynamicItem[Bot]):
async def callback(self, interaction: Interaction[Bot]) -> object:
pass
I see
But nah the concept of dynamicitem would have nothing to do with client
As a matter of fact this is a reoccuring problem for every function that takes Interaction
One way of avoiding the issue is just not passing the type arg (and then interaction.client is typed to the default Client)
then you can do bot: Bot = interaction.client
Is it the intention that DynamicItems must be able to work with every client? Or that children of DynamicItem can restrict which clients they work with?
you might need to provide more details
The topic is more around override-able functions that take interaction. Not really about DynamicItem as that was just an example to a class that has such a function. And so, the client isn't related to that class
The Interaction object has a client attribute, which is by default typed to Client.
By passing a custom bot class as a type arg to interaction, it types .client to that bot class, but at the same time complains as I showed in the beginning
From what I understood the only solution is leaving interaction with no type arg and doing bot: Bot = interaction.client
I think I understand it even less now
Fair enough 
If you want users to be able to create DynamicItems that don't work with every possible client, but only with their application-specific client, then the way to do that is to to make DynamicItem generic as I showed
I just don't really get this whole thing of client not being assignable to bot
But Idk much about subtypes or supertypes
For clarity, Bot is a subclass of AutoShardedBot that is a subclass of Client.
Imagine that you have this: py class Animal: def eat[F: Fruit](self, food: F) -> Packaging[F]: # this is a good animal that returns packaging back for recycling ... this signature means that if someone has access to an Animal, they are allowed to provide any fruit to it: py def feed_animal(a: Animal): a.eat(Apple()) a.eat(Banana()) a.eat(Cherry()) so if you wanted to make this: py class Monkey(Animal): def eat(self, food: Banana) -> Packaging[Banana]: ... that would be an "incompatible override", because now passing Monkey to feed_animal would cause type problems, as e.g. Apple() would be assigned to food: Banana
Because Banana is more narrowed yeah
exactly
Hm so then there's no way to support custom bot type
Fair
Though you could do
banana: Banana = animal.fruit```
I think
where?
If you'd have animal.fruit
Was just tryna think how'd something like this would look in your example
that would require typing.cast or a # type: ignore comment
assuming that animal.fruit is just an arbitrary Fruit
or maybe I don't get what you're saying
Welp with bot: Bot = interaction.client it works just fine without cast or type ignore
But nah it's fine 🙏🏻
uh-oh
from dataclasses import dataclass
@dataclass
class A[T = int]:
x: T = 1 # Type "Literal[1]" is not assignable to declared type "T@A"
a = A()
reveal_type(a.x)
from pyright playground. Works with a "normal" class.
and looks like mypy doesn't even support typevar defaults yet?
What is a typevar default?
oh
The warning seems correct to me, otherwise you'd be able to do e.g. ```py
a: A[Literal[2]] = A()
pyright has another custom feature that prevents this (because of the synthesized __init__), but otherwise yeah
How are you doing it with a normal class?
class A[T = int]:
def __init__(self, x: T = 1):
self.x = x
a = A()
reveal_type(a.x)
That works fine
ok I read through what what happening in #python-discussion
No that correctly errors
As I said there, allowing x: T = 1 is a pyright-specific feature (i.e.: mypy or ty won't understand this and will think that it's just a normal optional parameter).
e.g. if you try to do py a = A[bool]() pyright will complain that the x parameter is wrong
So maybe pyright adds some extra checks in a class body in addition to the check on the synthesized __init__ method
Actually yeah, it infers that A() is an A[int] and the complains about the assignment. Even without the typevar default
This actually works without the typevar default ```py
class A[T]:
def init(self, x: T = 1):
self.x = x
a = A()
reveal_type(a) # type of "a" is "A[int]"
it doesn't complain about the assignment in the non-dataclass for me
Basically doing the job of typevar default before it existed
so what you are saying is that pyright is not handling the typevar default correctly, but rather has a specific behaviour that's non spec which just so happens to do the same thing, except in dataclasses?
It's hard to compare stuff because mypy doesn't even support typevar defaults right now (at least in the playground). It just syntax errors.
According to the spec, typevar default does two unrelated things:
- in classes, allows you to omit the type parameter
class Foo[T = int]:
...
def f(x: Foo) -> None: ...
# ^ same as "x: Foo[int]". Otherwise it would be Foo[Any]
This is useful, for example, if you are making an existing library class generic and don't want to add hidden Anys for users
- In functions, guides inference when it would otherwise be impossible to solve a typevar
def first_twice[T = Never](x: list[T]) -> tuple[T, T] | None:
return (x[0], x[0]) if x else None
u = first_twice([])
# u is inferred as `tuple[Never, Never] | None`. Without the default it would be `tuple[Unknown, Unknown] | None`
# bizzarely pyright doesn't eliminate the `tuple` variant...
!e
Actually, it seems like dataclass defaults create a classvar as well... WTF
from dataclasses import dataclass
@dataclass
class Foo:
x: int = 69
y = Foo.x # inferred correctly as int by pyright
print(y)
:white_check_mark: Your 3.14 eval job has completed with return code 0.
69
I think it's related to this
I think I understand it now. If that code was allowed, you'd be able to do this: ```py
@dataclass
class Foo[T]:
x: T = 69
class Bruh(Foo[str]):
pass
y = Bruh.x # y is inferred as str
what a wild programming language
So, to summarize: pyright synthesizes an __init__ that makes use of this pyright-specific feature that allows providing defaults to generic arguments until the function is called ```py
@dataclass
class Foo[T]:
x: T = 69
this is synthesized:
def init(self, x: T = 69) -> None
x = Foo() # x is inferred as Foo[int]
Foostr # error: Literal[69] is not assignable to str
however, `x: T = 69` in the dataclass also creates a class variable, and pyright is complaining about that (because generic class variables are generally dubious)
It there a way to test on member types that the type checker is able to follow? Consider the example:
def fn(a: list[str] | list[int]) -> list[str]:
if len(a) == 0:
return []
if isinstance(a[0], int):
b = [str(x) for x in a]
else:
b = a # b: list[str] | list[int]
return b
Functionally its guaranteed that b is of type list[str] at the return statement (given that all elements are the same), but the type checker is not able to infer that. Is there a way that this can be done?
no, you'll have to silence the type checker
since int and str never overlap, I think there's no way to break this even with evil subclasses of list[str] or list[int]
!e
class Strumber(str, int): pass
: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 | class Strumber(str, int): pass
004 | [1;35mTypeError[0m: [35mmultiple bases have instance lay-out conflict[0m
!e
from collections import UserString
class S(UserString, int): pass
:warning: Your 3.14 eval job has completed with return code 0.
[No output]

Wth is UserString
The typing of many methods on UserString are set to return Self
stdlib/collections/__init__.pyi line 162
class UserString(Sequence[UserString]):```
if you just inherit from str, these methods will return str instead of your custom type.
Oh interesting
Same with UserDict and UserList
that's nice
Yeah, I discovered it when I searched for ways to make a string subclass that returns its own type and not str
Also I'm pretty sure that at an early stage you had to use that if you wanted to make a string/dict/list subclass, but Idk for sure
yes
if TYPE_CHECKING:
# These would be circular imports if it wasn't in TYPE_CHECKING
from my_django_app.models import (
Builder, # noqa: F401
City,
CityLocation, # noqa: F401
FeatureBundle, # noqa: F401
FeatureItem, # noqa: F401
GeoMarket, # noqa: F401
Location,
Marker, # noqa: F401
)
class BuilderQuerySet(OurCustomQuerySet["Builder"]): ...
class CityQuerySet(OurCustomQuerySet["City"]): ...
def some_function(items: "list[City]"): ...
I have some types (Builder, City, etc) that I import in a TYPE_CHECKING block so that I can reference them without creating circular dependences. For many of them I need the # noqa: F401 comment since my type checker (pyright) considers them unused. But some types like City are considered used because they are used in "list[City]".
- Is there a better pattern for dealing with circular types that are only imported to be used as generic type arguments and type annotations?
- Why are some types (like City) considered used and others not used when they are only ever used as type annotations and generic type args?
I'm not quite understanding that second point
Builder and City are used because... you are using them. The other not because you are not
I didn't give you all 2000 lines of our code, I used those two examples to show that one is only used in OurCustomQuerySet["Builder"] and the other is also used in a list[...].
I don't understand what you're getting at
If I were to simply import Builder, it would get removed by the linter because it's not used. It's used as a type arg in quotes, but that is somehow considerd unused. Therefor I added the # noqa: F401 to prevent that.
But City, which is also only ever used inside a quoted type annotation is considered "used" by both the linter (ruff) and the type checker (pyright).
Try using from __future__ import annotations?
i had that same issue using basedpyright type checker in zed but i havent had that problem in vscodium's basedpyright extension
and it was only a zed warning, when i ran the type checker manually i got no errors in the terminal
from __future__ import annotations doesn't seem to change it. I thought it would allow my to remove the quotes, which would then consider all these classes "used", but that fails at runtime since they are only imported in the TYPE_CHECKING block. It seems from __future__ import annotations only allows my to reference types out of order, but not handle types imported inside if TYPE_CHECKING:.
Sounds like it's an issue with your linter
Correction, it's only ruff that considered them "unused". I thought pyright was giving me that warning, but I was wrong. (editor plugins)
Which may help now that I am blaming the right tool..
which editor are you using?
vscode, with pyright (the default microsoft pylance plugin) and ruff extensions.
does the error come up when you run ruff check ? Or is it only visually
but this issue is about ruff now. I was using the editor for realtime ruff feedback, but this issue of removing imports can happen even when pre-commit runs ruff. It's not vscode-related.
yea, all ruff.
I just realized I can write it like this, and not have all the individual comments:
from my_django_app.models import ( # noqa: F401
Builder,
City,
CityLocation,
FeatureBundle,
)
This doesn 't really answer my question about why some are "used" and others not, but it makes me care less since I don't have to keep adding removing the comments on individual lines based on what other code I edit.
okay yeah, im getting the error too, might just be a ruff issue
There was an issue on this: https://github.com/astral-sh/ruff/issues/9298
Seems like they recommend configuring your extended-generics: https://docs.astral.sh/ruff/settings/#lint_pyflakes_extend-generics
not the cleanest way but no other option really (other than supressing it completely)
Yea, this looks like my issue. Thanks.
how does one type a typeddict?
as in valid for Unpack
class Gimme[K: ???]:
def __init__(self, **kwargs: Unpack[K]) -> None:
...
[K: TypeGuard[Annotated[True, is_typeddict]]] would've been cool 
Mapping[str, Any]?
still Expected TypedDict type argument for Unpack PylancereportGeneralTypeIssues
src/pycpio/cpio/archive.py line 168
self.logger.debug(f"Added entry: {c_(entry_name, 'green')}")```
why does mypy only complain about this line??
the loggify decorator adds self.logger
What's zenlib? I can't find it on pypi
pypi won't host it because some person made "zen-lib" 10 years ago and never added code
Wouldn't loggify be better as a Mixin class?
maybe in this case, i have it as that
The loggify decorator doesn't have any types
but this was made after i made the decorator
hmmm mypy is passing on zenlib
and there are tests for the decorator use https://github.com/desultory/zenlib/blob/main/tests/test_logging.py
like ClassLogger?
I might be doing some shenanigans here, but I’m trying to write a decorator that 1. adds a base class (mixin-style) and 2. makes it a dataclass. In this example, I’m using Foo.call as the decorator which adds MyBase. from dataclasses import asdict, dataclass from typing import dataclass_transform, TYPE_CHECKING if TYPE_CHECKING: f...
the decorator is just a bit more powerful than a base class imo, idk i like the metaprogramming aspect
ughh this is tripping it up too: https://github.com/desultory/pycpio/blob/main/src/pycpio/header/cpioheader.py#L43
src/pycpio/header/cpioheader.py line 43
setattr(self, name, value)```
it sets attributes based on the header type you feed it
ah yeah i don't like using the mixin because then i have to make sure to call super in the init
the decorator is just automagic :/
What about using __init_subclass__?
Yeah. You can provide things like the logger name like class MyFoo(LoggerClass, logger_name="foo")
It'll be passed to kwargs
the logger name is passed when the object is made
for lineage
like it reads the parent logger and stuff
yes that's why my decorator setup does it for me lol
i add one line and get full logging
<@&831776746206265384>
!compban 927187476563496981
:incoming_envelope: :ok_hand: applied ban to @proud valve until <t:1770339210:f> (4 days).
Is it possible to have one of the branches in a function as a no return?
```Python
def fun(a: int) -> str:
if a >= 0:
return "+"
else:
critical("ERROR") # This internally raises an exception
# raise # This fixes typing
the critical() function needs to have a return type of Never (typing.Never)
Yeah, this is the way I would like to do it. Though as far as I understand NoReturn is preferable here.
However currently I cannot modify critical and it is typed as None. Guess no other option then.
Never means exactly the same as NoReturn for return type
but it also has other uses
I believe Never is the "modern" way of typing it
Docs do state they are equivalent, but I do remmber seeing somewhere the recommendation to use NoReturn for return types.
NoReturn existed before it was changed to allow Never to be used
type checkers don’t know that critical() never returns unless you tell them.
the problem is that T | Never == T
so typing critical as returning None even if it never does, is valid
because the default return of a function is None, and None | Never == None
at least, from a typechecker checking critical
but the problem arises when you try to use it, as you can see here
Yea, unfortunately I do understand this issue, but cannot modify that one part right now (will be updated later).
So was just wondering if there is "local" option for such a case I might have missed.
yeah basically the problem is that you are allowing fun to return None according to the typechecker, because you do not return in the else
You could add assert False
like assert False, "critical always raises"
If critical does have a bug and doesn't in fact raise an exception, then raise can do something unintentional. E.g. if you are calling fun while handling an exception
Hmmm.... that makes sense actually. Thanks!
put while true
Wouldn't that cause the before mentioned problem? It will effectively raise None if something goes wrong.
That was the point before - if critical does not raise by whatever reason.
No, I expect it to always raise. And I do get what you mean.
```py
def critital2(msg: str) -> Never:
critical(msg)
raise AssertionError("critical did not raise")
Changing typing for that function will be done later.
The wrapper might also be the way, but it will marginally complicate finding usages later.
write a .pyi file for it?
I will definitely have a look at it (as have never did it before). At least to learn how to work with it.
Why is .pyi needed for libraries that run Python code 
Just put the typing in the code directly
sometimes you have to lie about the typing, and it's a lot easier to do so in a typestub
.pyi is for
- Third party typings
- Non-python or compiled libraries
- Python 2 support
Yep that's about it
feel like pyi might also be handy when typing the code itself could make it far less readable
usually type aliases prevent that from being the case
Realized this doesn't work for annotations with type args
I feel like there wouldn't be a way to support that for any class
For example I can use typing.get_args to know a given list should be a list of ints, then I actually can compare that in runtime
But if I'm given a non iterable custom class, the type args of it become meaningless
Might just ignore them totally, or keep support for builtins
Yeah for now I just added expected = typing.get_origin(expected) or expected
I'm getting error while trying this. Perhaps the first question: Is it not possible to inherit TypedDict like this:
from typing import TypedDict, Unpack
class KW(TypedDict, total=False):
a: str
class KW2(KW):
b: str
def fn(**kwargs: Unpack[KW2]):
pass
fn() # mypy: Missing named argument "b" for "fn"
Try adding total=False to KW2
When you merge those two typeddicts, you get ```py
class KW2(TypedDict):
a: NotRequired[str]
b: Required[str]
what's the difference between these? is either preferred?
from typing import Self
class Foo:
def __enter__(self) -> Foo:
...
return self
class Bar:
def __enter__(self) -> Self:
...
return self
does the former not require quotes anymore?
typing.Self is like a Typevar, you could say for Bar, it acts like def __enter__[Self: Bar](self: Self) -> Self
which means that if some class Baz inherits from Bar, then Baz.__enter__ will be Baz -> Baz (see how the return type matches for the subclass)
and confuses people if they didn't actually intend to preserve the specific type
e.g. they write return Bar(...), and expect it to work, because "well, Self is Bar, isnt it?"
Well, in 3.13 it does, or a from __future__ import annotations but I think in 3.14 this doesn't need quotes because types are not evaluated eagerly
it's also arguably nicer to not repeat the specific type
this was a reply to quicknir
it's just another place to change Foo that's not particularly meaningful
Like if you were to rename Foo to Glug then obviously you wouldn't want enter to still return a Foo
So at the simplest level it's just a form of DRY
that's a good point
e.g. Rust has a very similar thing and you will basically always see people write -> Self
and not -> Foo
a form that is only applicable if you return self or do some shenanigans with type(self)(...) or something
otherwise, there is no guarantee that you're constructing an instance of the same class
in rust there's no such problem because there's no subtyping
not quite sure what you mean
if you don't intend it for subtyping then it makes no difference anyway
you might "not intend it", but that is what the typechecker will be checking.
class Foo:
def foo(self) -> Self:
return Foo()
does not typecheck
That's an instance function
you typically use this for static functions that create an instance of the class
and there it will type check
but yeah, in some cases you won't be able to use it
you mean it's a valid typechecking result when you say "does not typecheck"? or it actually doesn't check the type?
unfortunate there's no way to prevent inheritance I guess
i mean that it will produce a type error
there is @typing.final, not sure if it works with that
typing.Self does not only mean self 🤷♂️
i'd rather have it deleted and use an explicit typevar, it confuses people as much as Optional[T] did compared to T | None
and NoReturn...
it would work for a classmethod, because cls: type[Self]
and im not sure if it even should work tbh because the constructor can be overriden
Might be true for people who are very comfortable with typevars, but there's probably a lot of people who benefit from Self, once they understand what it does
from typing import Self
class Foo:
@classmethod
def from_whatever(cls) -> Self:
return cls()
yeah but imagine class Bar(Foo) adds a new required parameter to __init__
Sure, but this function is still correct
Some people get a bit confused and think that it's just a trick to avoid putting the class name in quotes or enabling __future__.annotations. Maybe that deserves a short writeup or something like that
Bar.from_whatever() will error even though it typechecks
Yes, I understand
so it is not correct under parametric polymorphism
ahum that's basically exactly why I was using it 😅
it's correct under whatever typing system python actually has 🙂
So we need a marker to tell the type checker that overridden __init__ functions must be compatible.
python typevars are parametric, so no, it is not correct, but it is what it is
and yet this type checks - not going to argue with you further
I'm not saying it's your fault or anything like that, it's a very common thing
anyway, I was going to say I hate writing classmethod when I don't intend to use the class with inheritance
I'd much rather use staticmethod and keep everything concrete, exactly because of issues like that
no I'm not worried 😄 just trying to understand what's best
so I guess I won't be using Self very much
I remember once writing some code that actually had inheritance and was using a sort of classmethod function as the main method of creating the class
I really didn't like it - just kind of confusing and complex relative to what's actually being accomplished
I just reread the discussion and I think I'm clear on what everyone is saying. Thanks for the in-depth replies!
The -> Self is not a trick/the same as -> Foo in this case?
No, the purpose of Self is so that if you do py class Bar(Foo): pass then Bar.from_whatever() is inferred as Bar, not just Foo
Aha! 👍
is it safe to eval() annotations?
if no, how are we supposed to handle it?
just literally check the string for the type we want to force?
Use typing.get_type_hints()
That will eval it for you
And handle py3.14 annotations
no, because they can contain any valid expression
right
so that's better than https://docs.python.org/3/library/annotationlib.html#annotationlib.get_annotations ?
https://docs.python.org/3/howto/annotations.html#annotations-howto this may be informative
basically telling me to still eval if it's a string
I need to support >= 3.8 here
i'm using inspect.signature currently as I need the default values too
my condolences. Why support EOL versions?
wait my bad
it's >=3.10 https://github.com/Pycord-Development/pycord
does that help with anything?
"Accessing The Annotations Dict Of An Object In Python 3.10 And Newer" I would say so, if you were thinking supporting 3.8
but i'm still getting stringified annotations when using from __future__ import annotations
anything for that?
typing.get_type_hints(obj, globalns=None, localns=None, include_extras=False)```
Return a dictionary containing type hints for a function, method, module or class object.
This is often the same as `obj.__annotations__`, but this function makes the following changes to the annotations dictionary:
I generally recommend against using get_type_hints() in favor of inspect.get_annotations (or annotationlib.get_annotations in 3.14+). get_type_hints() does a bunch of things most of which are questionable; the most generally useful thing is that for a class it also returns annotations defined in base classes.
Depends on what you mean by "safe". User code could have def f() -> "__import__('os').system('rm -rf /')": ... and if you eval() that you're in trouble. But if you imported that code, they could already have done whatever they wanted outside of an annotation.
Even well-behaved annotations could raise exceptions (most likely NameError) if evaled, so if you use eval(), you need to guard for that.
It's a discord api wrapper in this case that needs to map the annotation to an enum
What would you recommend here?
There are like 20 valid types
Optional or T | None needs to be handled too
Couldn't you compare the wanted type to the type of the passed argument
I could but it'll be kinda hard to get it right for all the 20+ types since it's a string
A string of exactly what the user has written
Which could include the module or maybe it's a type alias? Or something
If you can get the annotation as a string then you could compare it to names of the enums (without having to get the actual type as an "object")
But it could get a bit complicated for some cases where you'd probably need to use regex, or get_type_hints etc.
For example:
type Lol = Member
member: Optional[Lol]
# get_annotations(...) -> {"member": "Optional[Lol]"}
Idk how you want me to decipher that without eval
Does get_annotations use straight up eval?
How can I allow something like this
class Foo[T]:
value: T
def is_int_foo(self) -> TypeGuard['Foo[int]']:
return isinstance(self.value, int)```
Pylance giving me User-defined type guard functions and methods must have at least one input parameter
found this, why not?
Don't you want TypeIs there
replacing TypeGuard with TypeIs giving me the exact same error
oh py f = Foo() if isinstance(f.value, int): reveal_type(f) its still unknown at this point
thats unfortunate, i feel like this would be a nice thing to have
This doesn't mean that f is a Foo[int], it could be a Foo[object] or Foo[int | str] or Foo[bool]
yeah i guess, so how would I do something like this?
in my context I have T be pretty general and in all cases its either That general or Very specific so I would liek the API so be .is_specific()
If Foo is covariant and you know for sure that f.value being an int means that f is a Foo[int] (e.g.: Foo is immutable), you could make a free-standing TypeGuard/TypeIs function I think
true but that would make the api a lot worse imo
yes
I wish you could narrow self
!e
from annotationlib import get_annotations
from enum import Enum
import ast
class E1(Enum):
THING = ...
enums = [E1]
def test[**P, R](func: Callable[P, R], /) -> Callable[P, R]:
anns = get_annotations(func, eval_str=False)
parsed: dict[str, ast.Expression | None] = {}
for name, ann in anns.items():
if isinstance(ann, str):
try:
parsed[name] = ast.parse(ann, mode="eval") # "eval" mode does not actually eval()
except SyntaxError:
parsed[name] = None
else:
parsed[name] = ann
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
for name, tree in parsed.items():
if tree is None:
continue
node = tree.body
# Case 1: "E1"
if isinstance(node, ast.Name):
match = next((e for e in enums if node.id == e.__name__), None)
if match is not None:
print(f'"{name}" matches with Enum: "{match.__name__}"')
# Case 2: "E1 | None"
elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
left_id = getattr(node.left, "id", None)
right_id = getattr(node.right, "id", None)
match = next((e for e in enums if e.__name__ in (left_id, right_id)), None)
if match is not None:
print(f'"{name}" matches with Enum: "{match.__name__}" ({match.__name__} | None)')
return func(*args, **kwargs)
return wrapper
@test
def foo(x: "E1", y: "E1 | None"):
pass
foo(E1.THING, None)
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | "x" matches with Enum: "E1"
002 | "y" matches with Enum: "E1" (E1 | None)
You can also support Optional[T]
And for type aliases, you could do func.__globals__.get(node.id).__value__:
# Case 1:
if isinstance(node, ast.Name):
resolved = func.__globals__.get(node.id)
if isinstance(resolved, TypeAliasType):
resolved = resolved.__value__
name_str = resolved.__name__ if isinstance(resolved, type) else node.id
match = next((e for e in enums if name_str == e.__name__), None)
if match is not None:
print(f'"{name}" matches with Enum: "{match.__name__}"')```
Alright (looks fine to me what can I say)
You'd have to use a formatter somehow unless you want to drop this whole idea or use "eval" in some way
I can help with this tomorrow if you're still running into problems
Is it the same or you asked for my review on?
Did you reply to the wrong message lol?
-# this sounds a bit harsh in my head. I don't mean it that way...
Mine is about introspecting annotations using eval: #type-hinting message
I've reverted to eval for now as that works fine
But a proper solution would still be neat
I feel like eval is fine for my use case as it's not exactly user input
or annotationlib.get_annotations if you're on 3.14+
yes, it uses eval internally
So it's fine to use eval for annotations
Well just be aware that running get_annotations on this with eval_str=True will run that code
But atp it's the user's fault for putting something like that there
Like someone can only use that against themselves 😭
Test

<@&831776746206265384> uh yeah
!ban 1470090413611290838 selfbotting
:x: User is already permanently banned (#108378).
:ok_hand: applied ban to @distant lotus permanently.
ty mentioned
is there currently a way to type hint that a dictionary is the kwargs of a function without manually creating the TypedDict?
i wanna do something like
kwargs: ... = {}
# add stuff to kwargs
foo(**kwargs)
no
I've currently got a function with the following signature def read_excel(filepath: Path | str, Table_Format: type) -> Table_Format, where the actual value passed for Table_Format is only ever one of two namedtuple types, and the return value is an instance of that type. Is there any way I can be more specific than type for the type hint of that parameter? Maybe with Generics?
If it could accept any (and only) namedtuple types, that would be ideal, but I'll happily settle for hardcoding the two types it could be.
I think I figured it out on my own: def read_excel[Table_Format: (Format_A, Format_B)](filepath: Path | str, format: type[Table_Format]) -> Table_Format:
Types should be PascalCase btw
-# And parameters should be snake_case
Everything should be snake_case except types which should be PascalCase.
Constants are a variant of snake_case which is SCREAMING_SNAKE_CASE
tho if the codebase is mature and does not use this convention, then the convention of the codebase should be followed
how do you type snake_case in that unusual manner ?
Inline codeblock
surround the text with ` (backtick character)
you can also make a multiline code block with basic python syntax highlighting by starting with ```py and ending with ```
py snake_case
I have a function that returns a set of Foo's and I have type hinted it as -> set["Foo"]
However, sometimes the return value might be the empty set. Do I need to change my type hint? (I'm not using a type checker yet, but at least my IDE is not complaining but I've learned not to trust it too much lately)
Yes, it's correct
thx! I also just found out about mypy playground and basedpyright playground, so in the future i'll check there first before bothering the ppl here. Until I find the courage to integrate mypy in my local environment/workflow, that is 😅
There is also ty, which has a playground here: https://play.ty.dev/
It's relatively new. I don't know how it compares to mypy.
I like how Ty uses webasm and runs type checking locally. Even Pyright which is written in JavaScript uses web requests
Basedpyright playground does run locally, so it's definitely possible
maybe it was easier for microsoft to just make it remote like that, but it is very laggy as a result for me
Can the mypy playground be converted to webasm too?
yeah, with pyodide or something like that
It's much less mature from my experience, but it's also an order of magnitude faster
I have high hopes for it but it's not mature enough for me to integrate it in my workflow
I'm trying out pyrefly and it seems closer to the finish line for now
Only reason I use Pyright specifically is because it comes with Pylance, and I'm fine with it
Are you sure Pyright runs with requests? The playground does but to me it just sounds unlikely it does it on Vscode
Yes, I was talking about the playground compared to ty
Yes to the first question?
Yes
So I can't use it without internet? Damn never thought of that
(It doesn't really happen obviously)
Just the playground. VSCode is fine offline

I wasn't asking about the playground
You replied to my message talking about the playground
Sorry I thought it could be understood in my message 🫡
So then yeah it's local on Vscode
So the reply was unrelated
I mean I wasn't sure whether your statement was talking about the playground or not, my bad
Does staticmethod insinuate that the method is final and shouldn't be overridden? Or does it imply that any subclass overriding the method should also have it as a staticmethod in that case? I ended up with some code like this to avoid lint warnings. Are there any typing notations to help me here?
It doesn't have to subclassed. The issue was solved with:
class Foo:
def base_func(self):
del self # subclasses might override and use self
print("potato")
class Bar(Foo):
def base_func(self):
print("banana", self)
You're mixing things up
staticmethod is for a method that runs independent of an instance (so you can do Foo.bar())
typing.final is to declare the method should not be overriden (this also enforces on runtime)
abc.abstractmethod is to declare the method must be overriden (only works in a class subclassing abc.ABC)
I know all this
My question is, is the del self a good way to mark self as unused in the base class's method?
Well your question made it seem like you don't 😅
Linters would otherwise suggest to mark Foo.base_func as staticmethod.
Nah just use staticmethod
Staticmethod is no good. Then the subclass method will get a lint warning for not having the same argument list as in the base class.
Ah lemme see
Can you give me an example where this happens
You want a method that is a staticmethod by default, but can be an instance method when overriden?
That's how the (really old) code already was (staticmethod in base class and using self in subclass)
I just wanted to quiet the warnings.
I'm not sure/can't remember why self was used in only the subclass.
Suppose that we take an arbitrary subclass SomeFoo of Foo. Does it only make sense to do SomeFoo.base_func()? Or does it make sense to run it as an instance method (e.g. SomeFoo().base_func())? If the latter, then don't make it a static method
I think this is a misguided lint. I'd turn it off
Since it uses self today, I guess it makes sense 😅 yeah, that's what I think as well. The upside with del self is that would silence any linter.
The linter this time was ruff
I assume you mean this rule?
https://docs.astral.sh/ruff/rules/no-self-use/
I'd do a noqa instead of a weird looking del. You're explicitly saying that you're right and the rule is wrong in this case, don't try to work around that to make the linter think it's right, declare it's not.
I don't think the rule is entirely misguided, as many people, especially beginners, don't know what is a staticmethod and do the incorrect thing, but yes, for your purposes, it's wrong here.
Checks for the presence of unused self parameter in methods definitions.
don't think the rule is entirely misguided
consider something like this: ```py
class Fruit(Protocol):
def is_round(self) -> bool: ...
class Banana:
...
def is_round(self) -> bool:
return False # TODO: investigate if there are round banana varieties
class Pepper:
...
def is_round(self):
return self.variety in {"cascabel", "cherry_bomb"}
there are multiple reasons an instance method might ignore `self`:
- It implements some protocol (ruff's rule does ignore `@typing.override`, but it would require explicitly inheriting from a protocol) in a trivial way
- it is not yet implemented fully, or it is made an instance method to future proof (making a static method into an instance method will be a breaking change in a library)
- the method used to look at `self` in a previous version of a library, but now it doesn't (but the meaning of a method hasn't changed, only some library details: e.g. the buffer size used to vary but now it's a constant)
In general, it's about semantics: an instance method tells you something about an instance or does something to the instance, a class/static method represents something about an entire class but not on an instance (I think being able to do `("69").from_bytes(b"0")` is not good, but we can't remove this obviously).
So changing `Banana.is_round` to a static method (even in abscence of `Fruit`) would be misleading and generally a bad change in my opinion.
As an example: int.is_integer always returns True (for compatibility with float), and it wouldn't make sense to make it a class or static method.
TL;DR: i think the lint rule is teaching the wrong lesson about when you should use instance methods vs class/static methods
There's no great tactic for telling mypy/typing systems "this mixin is meant to only be used with subclasses of a certain class" right? Protocols require me to define a whole thing, but adding the class to the mixin leads to awkward MRO conflicts in existing code
Im not sure why you would want to limit your mixin to specific classez. Like ever.
But I suppose you could try to use issubclass() or other type checks in __init_subclass__ of your mixin. But I'm not sure if that alone would work.
Otherwise you could try to use @overload in combination with __new__ and specify a specific signature, with helper classes either containing or not containing that mixin's provided attributes
In any case, your request will likely lead to a solution (if any at all) that feels hacky
You may also be able to inherit both Protocol and the mixin together, which could limit the amount of redefining entire Protocol classes.
-# im just making a guess on this option though. I can't test it myself currently
For stuff like Django model or views a mixin as a way to provide a bit of extra functionality is nice, because you might have mixins A, B, C, D, E but you'll only want B and D in some cases, and if you mashed them all together into one base class suddenly the entire thing is quite messy with config bits etc
you could try self type hints on the methods to avoid full protocols
But IMO most mixins are written with the idea that they are applied to some base class I think
true, usually typing self as the base class works without the mro mess
@eternal trellis so unfortunately that doesn't work on mypy in my experience
class MyMixin:
@classmethod
def some_clsmethod[M: models.Model](cls: type[M], ...):
...
^ the above ends up with an error like "cls needs to be a subtype of MyMixin" which makes sense if you think about it
like M needs to be at least MyMixin right? since it's a class method on MyMixin . But we don't have intersection types so I can't say [M: models.Model & MyMixin].... wonder if I make something subclass models.Modeland Protocol....
yeah a protocol inheriting from both is the standard workaround for now
oh if that just works then I'm actually fine. Will try that
NestedComparison: TypeAlias = dict[str, "NestedComparison" | DefaultComparison]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unsupported operand type(s) for |: 'str' and 'type'
I guess useing Union works good enough, just adds another import from typing
you need to make the whole thing a string
or use the type statement added in python 3.12
type NestedComparison = dict[str, NestedComparison | DefaultComparison]
thanks, i need it to be 3.10 compatible
Question: I think I have found a bug with stubgen in mypy and I'm not sure if this belongs here or in #c-extensions , but I want to run it by at least one other person before I report it as an issue in github:
OTHER_OP_TYPES = Union[complex, int, float]
_OTHER_OP_TYPES = (complex, int, float)
I find that stubgen handles _OTHER_OP_TYPES as a tuple fine but it completely misses OTHER_OP_TYPES? Worst of all, if I make an __all__ that has both of these, then the created stub looks like this:
from typing import OTHER_OP_TYPES as OTHER_OP_TYPES
Which is obviously just crazy wrong... I'm using --include-private and --inspect-mode. Does this seem like a bug worth reporting or am I just doing something weird?
Note: instead of setting __all__, it does the same thing if I set a type annotation of TypeAlias on OTHER_OP_TYPES
This might be totally off base but does mypy do its own sort of name mangling for types with leading single underscore or something?
What's the context here? What issue are you experiencing?
I was replying to @glossy moat
Oh sorry, I thought you were referring to something else. Just to be clear, the one with the underscore is just fine. Its the type alias without the underscore that gets messed up with the typing import
What if you annotated it with TypeAlias?
If I don't annotate with TypeAlias then its missing completely
If I annotate with TypeAlias, then it does from typing import OTHER_OP_TYPES
If I add it to __all__ then it does basically the same thing
It looks to me like it's possible that the one with the underscore is breaking the one without underscore somehow. Just a hunch, I have no actual knowledge of mypy internals. Have you tried giving it a different name yet?
Yeah, I honestly don't think thats it. In that file there is another variable which is also a TypeAlias called OP_TYPES and there is no _OP_TYPES and its also happening with that one.
Here is a minimum file to reproduce the issue:
from typing import Union
__all__ = ['OP_TYPES']
OP_TYPES = Union[int, float]
Running that with stubgen --inspect-mode I get:
from typing import OP_TYPES as OP_TYPES
__all__ = ['OP_TYPES']
# Names in __all__ with no definition:
# OP_TYPES




