#type-hinting
1 messages · Page 45 of 1
any way to type this one too https://images.soheab.com/pcavMTdIUIY.png
t's supposed to always return a sequence
but since users can pass single items too, pages[n] can be one of those too
I assume that's just not possible in python
You can use isinstance with Sequence
if isinstance(page, Sequence) and not isinstance(page, str):
...
and you'll probably need a # pyright: ignore
That's because, theoretically, an object could be both e.g. a sequence and a discord.File (using multiple inheritance)
you gotta be real careful here because if e.g. some of those is a NamedTuple, you are in trouble (because it's also a sequence)
A more robust check would be something like... py if not isinstance(page, (str, discord.ui.WhateverCommonBaseClassThoseHave, discord.File, ...etc)): ... which you could extract into a TypeIs function
This is definitely an issue with "untagged unions". It can be difficult to figure out which of the cases the object is (and what if it matches both?). Programming languages with (safe) unions typically have an explicit "tag" or "case" for each of the options ```rs
pub enum Page<'a>{
Text(&'a str),
File(discord::File<'a>),
Component(&'a discord::UiThing),
Mystery(HashMap<&'a str, &'a dyn Any>),
Nested(&'a[Page<'a>]),
}
and you could do this in Python in various ways, like ```py
type Page = tuple[Literal["text"], str]
| tuple[Literal["file"], discord.File]
| tuple[Literal["nested"], Sequence[Page]]
| tuple[Literal["mystery"], dict[str, Any]]
trying that one currently
but i'm utterly confused.. https://images.soheab.com/s8OK3lRw0gU.png
pylance just refuses to recognize TypeIs?
edit: TLDR: pyright bad
It's new in python 3.13, so if you're using 3.12 then you need to import it from typing_extensions
its highly unpythonic to shoe-horn something other languages have native into painful unions of literals and types - instead of using a alien way to still use a alien object - use a python concepts instead
yeah, you should not do this
that is not idiomatic python
(though types have arguably changed what is considered idiomatic python)
That is on 3.14
I'm using Py 3.10.6. Is there any reason to use the TypeAlias? Everything seems to be working just fine without it, my typechecker doesn't care.
And another question. Is it fine that I use collections.abc for these 2 instead of typing? They work just fine for type hinting from what I can observe. Notice the Py version I use in the message above
TypeAlias is good to indicate intent, and may be necessary in some complicated cases iirc
importing from collections.abc is the preferred way of doing it if supported by the python version you use
See the typing docs. typing.Callable and such are deprecated
Now I'm left with this lol
But I'm pretty sure that for 3.10 there's no alternative
Try this. ```py
from future import annotations
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import TypeVar
!?!?!
that is unlikely to work in a useful way
I guess you could do ```py
if TYPE_CHECKING:
T = TypeVar("T")
else:
class T: pass
if you want to avoid importing typing at all costs
Remembered I already asked myself some of your questions @trim tangle https://github.com/python/typing/issues/1455
every good movie plot starts with
I wasn't aware there were discrepancies between type checkers about this
Btw, somone added even more questions on discourse: https://discuss.python.org/t/pep-718-subscriptable-functions/28457/65
If you allow subscripting arbitrary protocols, even if you do an intersection with functions, the runtime behaviour will be dubious
actually, this isn't quite right. wait a moment
Oh right I figured out what's wrong
this: ```py
class PairIdentity(Protocol):
def call[A, B](self, arg: tuple[A, B], /) -> tuple[A, B]:
...
def my_identity[X, Y](arg: tuple[Y, X], /) -> tuple[Y, X]:
return arg
def call_pair_identity(fn: PairIdentity & FunctionType) -> None:
f = fn[int, str]
call_pair_identity(my_identity)
it's passing the arguments that are in the protocol's order but they're in the wrong order for the runtime function
Maybe a sensible behaviour is to just return the function itself when it gets subscripted? (regardless of how many arguments are passed or whether it's generic at runtime at all)
I finally started working on my generics tutorial. Thoughts on this?
https://decorator-factory.github.io/typing-tips/tutorial/3-generic-functions/
(and any opinions on the TODOs?)
I'm planning to have these articles:
- generic functions
- generic classes (the basics, mostly explaining syntactic features)
- the difficult article where I explain variance (current plan: explain the concepts and the reasoning, and then drop the definitions int he very end; with the goal of building a solid mental model first)
- callable protocols and protocols describing
- typing decorators (also introduces ParamSpec and TypeVarTuple)
- leftover features (typevars with constraints, LiteralString bound, differences between type checkers etc,)
overall i like it, i think that the most important part is the start where you show that some functions can work with different types and "have types of values linked together in some way"
there are a couple places where i might change something:
Type variables support setting a bound, requiring that the solution to the type variable must fit the bound.
i think this is a bit ambiguous if you dont already know what it means, maybe change that the solution type must be a subtype of the bound type
and, as a purist, i would add hashable bounds to places where the typevar is used as a dict key type
# add T: Hashable
def count[T](items: Iterable[T]) -> dict[T, int]:
counter: dict[T, int] = {}
for item in items:
counter[item] = counter.get(item, 0) + 1
return counter
# add K: Hashable
# maybe change keys to Container[K], we dont use len(keys) or iterate over it
def pick[K, V](m: Mapping[K, V], keys: Collection[K]) -> dict[K, V]:
return {k: v for k, v in m.items() if k in keys}
(im not sure why there's no bound on dict's key type to be Hashable)
and,
One common application of generic functions is processing collections of unknown types. When doing so, you should almost always accept a general type like Iterable or Iterator or Mapping instead of concrete classes like list or dict.
this doesnt explain why you should do that, maybe it could link to https://decorator-factory.github.io/typing-tips/tutorial/2-using-protocols/ and have that post explain why 🤷
i think this is a bit ambiguous if you dont already know what it means
Yes, that's definitely hand waving a bit. I haven't introduced the terms "subtype" or "assignable" yet so I didn't want to use them. I was thinking of doing that in the variance article, because that's when it matters the most. But maybe it could be good to explain that in the context of e.g.: why I can't dox: int = foowherefoo: int | str | Banana
this doesnt explain why you should do that
Good point. I originally wanted to postpone that because of variance, but it's probably better to substantiate it in some way at least (like: "that makes the function more flexible and signals that you're note mutating the arguments" or something)
Container is kinda weird. I assumed that it also includes all iterables, because you can do x in iterable. But apparently not
in that case maybe it is appropriate
Is there a problem with just declaring TypeVar? Why do this check
Ah I just saw what you said below
Nah it doesn't matter that much, I'm fine as long as my code works for newer versions of py (I care less about older versions)
And it's not like typing takes much memory space either
Looks great! I've always had trouble with generics, but this article explains every detail perfectly fine and it's easy to understand
i realized i have an issue with my parser library input protocol. i have a __getitem__ that returns Self, but bytes.__get__item may or may not return an int or a bytes
where str.__getitem__ always returns str
indeed
python not having a char type makes this kind of funky
i thought i could make the Input protocol generic and have an overload on __getitem__ but type parameters cant have generic bounds
add an override for slice
i want to be generic over list[T] or str or bytes basically, so i need the protocol to be generic i think right?
or else what do i return from the overloaded method
Why do you you need to be generic over Input? Maybe you just want to be generic over the element type and then accept something likeSequence[ElementType] as the input?
here's some unicode sadness btw
>>> s = "apple\ud8ffbanana"
>>> s
'apple\ud8ffbanana'
>>> print(s)
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
print(s)
~~~~~^^^
UnicodeEncodeError: 'utf-8' codec can't encode character '\ud8ff' in position 5: surrogates not allowed
>>>
now i have an interesting issue being generic over Sequence[I], when i feed it a string, the return type of the parser is Sequence[str] and not str
which is expected i suppose
how do i flatten it back into str
"".join(seq)
how is your generic producing the Sequence[_] in general, but a string for strings? is there an isinstance somewhere? you might need an overload then
the parser is generic over Sequence[I] like this
class Parser[I, O](ABC):
@abstractmethod
def parse(self, input: Sequence[I]) -> tuple[Sequence[I], O]:
pass```
so to feed it in a string it ends up with a Sequence[str]
Maybe you could do ```py
class ParserI, O:
@abstractmethod
def parse(self, input: I) -> tuple[I, O]:
pass
i need something slicable i think at least
where?
here is an example of a core parser
@dataclass
class Take[I](Parser[I, Sequence[I]]):
_n: int
def parse(self, input: Sequence[I]) -> tuple[Sequence[I], Sequence[I]]:
return input[self._n :], input[: self._n]```
or ```python
@dataclass
class TakeWhile[I, O](Parser[I, Sequence[I]]):
_parser: Parser[I, O] | Callable[[I], bool]
def parse(self, input: Sequence[I]) -> tuple[Sequence[I], Sequence[I]]:
match self._parser:
case f if callable(f):
i = 0
while f(input[i]):
i += 1
return input[i:], input[:i]
case Parser():
start = len(input)
rest = input
while (rest, _ := self._parser.parse(rest)):
pass
d = start - len(rest)
return input[d:], input[:d]```
it works by returning rest, consumed
@dataclass
class Take[I](Parser[Sequence[I], Sequence[I]]):
_n: int
def parse(self, input: Sequence[I]) -> tuple[Sequence[I], Sequence[I]]:
return input[self._n :], input[: self._n]
wont that run into the same issue if i use that parser on a str?
Yes, if you use Take[str], it's going to turn str into a generic Sequence[str]
(which is not wrong, a str is a Sequence[str], unfortunately, as there is no character type)
it would be nice if mypy allowed intermixing str with Seq[str] in some cases
[E, I: Sequence[E]](I) -> I could solve this if bounds could contain typevars, i think
that was my original design sorta
What about this ```py
class Slicable(Protocol):
def getitem(self, arg: slice, /) -> Self:
...
@dataclass
class Take[X: Slicable](Parser[X, X]):
_n: int
def parse(self, input: X) -> tuple[X, X]:
return input[self._n :], input[: self._n]
hmmmm I think this won't work
beacuse str has __getitem__(self, arg: slice, /) -> str, not __getitem__(self, arg: slice, /) -> Self
yeah
also that would make parsing bytes weird i think because bytes[0] returns int
i would like to be able to but i guess its not super critical
maybe you're procrastinating on actually parsing some text 🙂
haha
i dont think that makes it "not work", it typechecks on mypy --strict and pyright
hmmmm
from typing import Protocol, Self, Literal
from dataclasses import dataclass
class Slicable(Protocol):
def __getitem__(self, arg: slice, /) -> Self:
...
@dataclass
class Take[X: Slicable]:
_n: int
def parse(self, input: X) -> tuple[X, X]:
return input[self._n :], input[: self._n]
t = Take[Literal["banana"]](2)
banana1, banana2 = t.parse("banana")
``` this passes pyright and pyright thinks that `banana1` and `banana2` are both `Literal["banana"]` 😭
this type system is actually cooked
literal is cooked
its a type that doesnt have to do with types
mypy has the same bug for some reason
I feel using Self in a protocol is the dubious thing here
hmm, the bug is that the __getitem__ signature in the protocol passes for Literal, which it really shouldn't
it shouldn't pass for (non-Literal) str either, str.__getitem__ returns str not Self
so this would also be wrong for a subclass of str
Yes, I agree. This is why I originally thought this wouldn't pass
class Slicable(Protocol):
def __getitem__(self: AnyStr, arg: slice, /) -> AnyStr: ...
this one might be a better choice
so Slicable should be generic, and __getitem__ should return a TypeVar?
if the author just wants str | bytes
that's the same as using Self I think
ah hmm
class Slicable(Protocol):
def __getitem__[S: (str, bytes)](self: S, arg: slice, /) -> S: ...
rare constrained typevar W
(iirc AnyStr is deprecated)
yep
!d typing.AnyStr
typing.AnyStr```
A [constrained type variable](https://docs.python.org/3/library/typing.html#typing-constrained-typevar).
Definition...
one of the definitions of all time
yeah, like that, or making Slicable itself generic
It is theoretically possible to have an object satisfying this. For example: py class UserList: def __getitem__(self, slc: slice, /) -> Self: return type(self)(self.data[slc]) or if the class is @final. So the protocol seems fine to me
Do you think I should open a bug in both mypy and pyright?
There is nothing about it at runtime though: https://github.com/python/cpython/blob/main/Lib/typing.py#L2766
Lib/typing.py line 2766
AnyStr = TypeVar('AnyStr', bytes, str)```
Yeah, unfortunately deprecations don't always manifest at runtime...
runtime warnings will come at 3.16+
Well, usually there should be a comment at least.
I opened a lot of stuff on pyright recently, the maintainers will probably want to murder me
so I'll try mypy first
Yeah the Self part is theoretically achievable, though that also relies on unsound behavior (since __init__ is not LSP checked)
But agree the more direct bug here seems to be that str shouldn't obey that protocol
class UserList:
def __getitem__(self, slc: slice, /) -> Self:
return type(self).from_iterable(self.data[slc])
so not even python -W default $file or 3.14-targeted ruff even suggest something wrong about this 
or a comment in __init__ to not change the signature pretty please
soundness by contract
License: GNU AGPL v3 + __init__ compatibility Clause
I guess that technically contradicts the GPL because it limits the use of the software
sad
btw @plain dock I started making the generics tutorial #type-hinting message
I'm not sure whether I should include constrained type variables. From what I gathered type checkers have quirky thoughts on them
they're weird but they do appear in practice
My sense is the concept was basically invented for the purpose of AnyStr, which was more relevant when we still had to think about Python 2
I'll probably make a separate micro-chapter on them. With re.Pattern as an example
memoryview is also one where it makes sense, it can hold a limited set of fixed types
I think that's a decent use of the feature
ive used them once or twice, and i think others have too, but its hard to know what every param does sometimes
holy hell, mypy has so many open issues
but probably more people know about re.Pattern so it's a better example
eh, is big project. be might weird if it only had a few. 2.7k might be a bit high though
interesting choice of favicon tbh
pyright only has 134 open, and the oldest ones are from march 2025
the oldest open mypy issue is from 2013
Does pyright have stalebot?
IIRC, pyright also tends to close a lot of issues as not planned
no, it just has Eric
yeah, issues are closed as "as designed", even if it's clearly a bug that they just don't plan to fix
ah, i wasn't aware the bane of github had a name. thx
Here are more examples of types that should be assignable to Slicable: ```py
type T0 = Literal[""]
type T1 = Literal["", "a", "b"]
type T2 = Literal["", "xy", "yx", "x", "y"]
Hmmm actually no, this is not right
Because if a type T conforms to a protocol, then a subtype U also must conform to a protocol
Literal["x", "xy"] doesn't conform to the protocol, therefore T2 also cannot conform to the protocol
so only T0 works
made this issue https://github.com/python/mypy/issues/20058
Appropriate that fix error is demanding the fixing of errors.
i think the example here is unsafe and needs to be fixed by a type checker cause that's clearly not meant to match PairIdentity
@overload
def make[*Ts](*xs: *Ts) -> tuple[*Ts]: ...
@overload
def make[T](x: T) -> T: ...
@overload
def make(x: str, y: str) -> tuple[int, int]: ...
reveal_type(make[int](1)) # type is int | tuple[int]```is this meant to be this or should it just be tuple[int]?
wdym?
why does it not match?
because you shouldn't be able to assign my_identity to PairIdentity if you're flipping around typevar order
Then this also needs to be banned? ```py
class Deco(Protocol):
def call[T: type](self, arg: T, /) -> T: ...
def make_deco(name: str) -> Deco & FunctionType:
return lambda klass: klass
@dataclass_transform()
def struct(name: str) -> Deco & FunctionType:
return dataclass(slots=True)
(assuming we get intersections at some point)
I guess to the same topic: what about functions that are only generic in the type stubs? e.g.
https://github.com/more-itertools/more-itertools/blob/master/more_itertools/more.pyi#L737
more_itertools/more.pyi line 737
def repeat_each(iterable: Iterable[_T], n: int = ...) -> Iterator[_T]: ...```
Btw, basedpyright (in the next release) now has this feature: ```py
pyright: enableBasedFeatures=true
from typing import dataclass_transform
@dataclass_transform(skip_replace=True, frozen_default=True)
def frozen[T: type](t: T) -> T:
return dataclass(frozen=True, slots=True)(t)
check that this enables covariance:
@frozen
class Box[T]:
value: T
box1: Box[str] = Box("test")
box2: Box[str | int] = box1
``` you can just disable __replace__ generation if you don't care about it
is there a guide or docs on what do to if you're using cls.__annoations__ and want to support 3.14?
I think it should just work
(if you're already handling normal non-from __future__ import annotations annotations)
!e
def foo(x: CoolNumber, y: str) -> list[CoolNumber | str]:
return [x, y]
CoolNumber = int
print(foo.__annotations__)
:white_check_mark: Your 3.14 eval job has completed with return code 0.
{'x': <class 'int'>, 'y': <class 'str'>, 'return': list[int | str]}
oh? I got an attributeerror on the value being a dict vs what I expected
have yet to check it out lol
can you show the code?
https://github.com/Soheab/somerandomapi.py/blob/main/somerandomapi/models/abc.py#L157-L206 the error was on line 203 on import in a 3.14 env
can you show a traceback?
Btw, you are not supposed to invent custom dunder names. They are reserved in Python
o didn't know that
i dont see how it does?
the protocol describes a function that takes a tuple with two elements and returns a tuple with the same two elements
Here's another example of a function that satisfies this protocol: ```py
def identity[T](arg: T, /) -> T:
return arg
but it doesnt cause it flips them no? i understand typevar checking is not based on name but it is still by id and surely that means that that shouldnt be legal to flip the order and have it be the same function
i dont think anything has to change because the behaviour isnt checked at runtime so it should look fine
is that call meant to return None?
sorry, fixed
Today, I can shuffle type variables inside def func[here](...) -> ... and it does not impact the meaning of that function
i just think thats a bit silly and shouldnt be allowed
Also: type variables defined with the old-style TypeVar do not have a defined order
I don't see a problem with this it looks like FunctionType[[T], T] to me?
I meant that the function doesn't have type parameters at runtime
So this function should also not fit PairIdentity? #type-hinting message
For a micro-survey:
// TypeScript
type PairIdentity = <A, B>(pair: [A, B]) => [A, B]
function identity1<X, Y>(pair: [X, Y]): [X, Y] { return pair }
function identity2<X, Y>(pair: [Y, X]): [Y, X] { return pair }
function identity3<T>(pair: T): T { return pair }
function identity4<A, B>(pair: A | B): A | B { return pair }
const _t0: PairIdentity = identity1
const _t1: PairIdentity = identity2
const _t2: PairIdentity = identity3
const _t3: PairIdentity = identity4
-- Haskell
type PairIdentity = forall a b. (a, b) -> (a, b)
identity1 :: forall a b. (a, b) -> (a, b)
identity1 (a, b) = (a, b)
identity2 :: forall a b. (b, a) -> (b, a)
identity2 (b, a) = (b, a)
identity3 :: forall t. t -> t
identity3 x = x
_ = (identity1 :: PairIdentity)
_ = (identity2 :: PairIdentity)
_ = (identity3 :: PairIdentity)
yeah that should be fine cause they can infer T as whatever it wants
maybe im goofing then idk
Here's another example: py def identity[A, B](thing: A | B) -> A | B: return thing it does fit PairIdentity, and it also has two type variables, but they're used for a completely different meaning
on the other hand, Rust only allows the implementation for Apple here ```rs
trait PairIdentity {
fn foo<A, B>(&self, p: (A, B)) -> (A, B);
}
struct Apple;
impl PairIdentity for Apple {
fn foo<A, B>(&self, p: (A, B)) -> (A, B) {
p }}
struct Banana;
impl PairIdentity for Banana {
fn foo<A, B>(&self, p: (B, A)) -> (B, A) {
p }}
struct Cherry;
impl PairIdentity for Cherry {
fn foo<T>(&self, p: T) -> T {
p }}
There's some information here <https://doc.rust-lang.org/beta/error_codes/E0049.html> but it's rather limited
is there any people using really python for math?
bit of a delay, but got it, lol
(somerandomapi.py) PS C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py> uv run run.py
Traceback (most recent call last):
File "C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py\run.py", line 1, in <module>
import somerandomapi
File "C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py\somerandomapi\__init__.py", line 7, in <module>
from .clients import *
File "C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py\somerandomapi\clients\__init__.py", line 10, in <module>
from .animal import AnimalClient as AnimalClient
File "C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py\somerandomapi\clients\animal.py", line 16, in <module>
from ..models.animal_fact import AnimalImageFact, AnimalImageOrFact
File "C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py\somerandomapi\models\__init__.py", line 7, in <module>
from .animal_fact import *
File "C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py\somerandomapi\models\animal_fact.py", line 9, in <module>
class AnimalImageFact(BaseModel, frozen=True, validate_types=False):
...<9 lines>...
"""The animal image URL."""
File "C:\Users\Sohea\OneDrive\Documents\projects\wrappers\somerandomapi.py\somerandomapi\models\abc.py", line 203, in __new__
if value.init:
^^^^^^^^^^
AttributeError: 'dict' object has no attribute 'init'
Rust is based?
Yeah that link really needs some work lol
Well just the other cases adding
annotations: {'__attributes__': 'dict[str, Attribute]', '__init_attributes__': 'dict[str, Attribute]', '__endpoint__': 'Endpoint', '__reserved_attributes__': 'set[str]', '__possible_options__': 'dict[str, Any]', '__frozen__': 'bool', '__validate_types__': 'bool'}
key, value, _type: __init_attributes__ {} dict[str, Attribute]
looks I forgot to add __init_attributes__ to __reserved_attributes__
weird that it works fine pre 3.14
oh
the annotations of the metaclass BaseModelMeta weren't included before
and only that

if PY_314:
import annotationlib
annotations = annotationlib.get_annotations(self)
else:
annotations = self.__annotations__
Seems to work
can use inspect.get_annotations unconditionally
With Pydantic, I have a class that has a field that is declared with an ABC as a type. Obviously, serialization is not a problem, that is where polymorphism comes to play. However, on validation, I get data, and now what? I cannot instantiate the ABC, but I also don't know what specific derived class to use. How do people solve this? I don't have a list of all available derivatives to duck-try in order, and even if, that would be a horrible way. Should I serialise the qualified name of the class that dumped the data, and then try to use importlib to instantiate it?
Oh yeah that works
How come you don't have a list of all the alternatives? Are you making some sort of generic application with plugins?
Pydantic supports discriminating over several variants with a tag:
https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions
If you truly have some sort of plugin system, you can use https://docs.pydantic.dev/latest/api/functional_validators/#pydantic.functional_validators.PlainValidator for custom validation, and then make some sort of mechanism for registering different classes into a global variable or contextvar.
Directly using the class path&name is not great. It's bug-prone, tightly coupling the data format to your current implementation, and it can present security risks. So it's better to have a more explicit mapping. For example, you could add a concrete class to the list in __init_subclass__, and use some sort of class variable as the discriminator. Or make a registration function that a plugin can call
are there any plans or PEPs currently in progress to allow parameterized bounds? aka class Foo[T: Bound[U]]
yeah…
Here's an example of a security problem: you have some other part of the system save cached user-provided types to a directory named foo_cache, and a malicious user with the ID bob123 uploaded a malicious file. That file got saved as foo_cache/bob123_a0befae698c3fdafcbf.py, and bob got to know this somehow. Now, if you're not careful, bob can provide the import path as foo_cache.bob123_a0befae698c3fdafcbf.MyClass and it's going to execute the malicious code
Or it could be more mundane such as providing builtins.int for the path and the class ending up not matching the ABC
yes, I can see the issue for sure.
Hello, I'm trying to create a metaclass for classes with memoized instances, but I'm struggling to type hint the INSTANCES dictionary in the __init__ method below (I'd like the value type to be cls). I've tried using type variables to make __init__ generic but still cannot get this working. Any advice?
__KEY_PARAMETER: str
@classmethod
def __prepare__(
mcls,
name: str,
bases: tuple[type, ...],
/,
*,
key_parameter: str,
**kargs: Any,
) -> MutableMapping[str, object]:
namespace: MutableMapping[str, Any] = super().__prepare__(
name, bases, **kargs
)
namespace["__KEY_PARAMETER"] = key_parameter
return namespace
def __init__(cls, *pargs: Any, **kargs: Any) -> None:
super().__init__(*pargs, **kargs)
# Ideally change Any in line below to cls
cls.__INSTANCES: WeakValueDictionary[Hashable, Any] = (
WeakValueDictionary()
)
def __call__(cls, *pargs: Any, **kargs: Any) -> Any:
if (key := kargs[cls.__KEY_PARAMETER]) in cls.__INSTANCES:
return cls.__INSTANCES[key]
else:
return super().__call__(*pargs, **kargs)
If I change the type of a field with a field_serializer that has an altering return type, e.g.
from typing import Any
import pydantic
class MyCls(pydantic.BaseModel):
number: int
@pydantic.field_serializer("number", mode="plain")
def _num_to_str(self, value: Any) -> str:
return str(value)
print(MyCls.model_json_schema()["properties"]["number"]["type"])
then it would be reasonable for me to expect that the MyCls.model_json_schema() return should reflect this, right? What could be the reason that this isn't working? The above code prints "integer", when I am expecting "string".
You might want MyCls.model_json_schema(mode="serialization")
Folks, is there any way to typehint this? I don't want to write a Protocol
def get_run_command_func(service) -> Callable[[str, "maybe a bool idk?"], str]:
def run_command(command: str, *, raise_on_fail: bool = False) -> str:
success, result = service.run(command)
if raise_on_fail and not success:
raise Exception(result)
return result
return run_command
that works! THank you. Makes sense!
🤔 what's the point of this
It's a simplified version of a pytest fixture
if it's a fixture, you can leave it unannotated, it's not going to get type-checked anyway
PyCharm does
ah...
Doesn't MyPy? 🤔
well, pytest fixtures require special handling obviously
not sure if there's a mypy plugin
If you remove the return type, does pycharm infer it in a reasonable way?
pyright does
I get "unexpected argument" errors when using the kwarg is the issue I'm having 😄
Wait you mean remove the argument type?
then it seems like you'll have to make a protocol
I meant this ```py
def get_run_command_func(service):
def run_command(command: str, *, raise_on_fail: bool = False) -> str:
success, result = service.run(command)
if raise_on_fail and not success:
raise Exception(result)
return result
return run_command
Ah, right, lemme check
Well, it stops complaining
This is what it infers
You know what, that's good enough, these are only tests anyways 😄
Thanks for the help!
🤔 what is that Any?
The asterisk, I guess
pycharm was vibe coded before we had LLMs
ikr
is there a way to type a dict subclass similar to a typed dict (eg some keys and their types)
(yes, I should just use dataclass and other fancier things. I would if I could)
maybe this:
if TYPE_CHECKING:
keys: Literal["key1", "Key2"] | your_other_types
...
I meant the per key kind
# real runtime subclass of dict that guarantees some keys and their types
class Foo(dict):
key: Type
key2: NotRequired[Type]
from collections.abc import ItemsView
from typing import TYPE_CHECKING, Literal
class Test(dict):
if TYPE_CHECKING:
items: ItemsView[Literal["one", "two"], int]
maybe something along these lines?
default dict type for items its dict_items ie ItemsView from collections.abc. might just need to type hint that for your case. the if TYPE_CHECKING avoids overriding default behaviour.
that seems to be an error?
Incompatible types in assignment (expression has type "ItemsView[Literal['one', 'two'], int]", base class "dict" defined the type as "Callable[[], dict_items[Any, Any]]")
doesn't work either and as far as I can see, this just types .items()
ah as in you want the type hint on the foo["item"]
in that case you might want to type hint the getitem method
might be called __getattr__(self,...)
cant recall the specific name but that is what python used to get the value for the key given
you could have overloads on the getitem function, that accept the various literals and then return a more precise type
Why do you need to make a special hint for the .items method anyways?
but yeah, a hint on .items() is not going to work anyway because you're going to iterate over everything. so you're not going to be able to offer a more specific type
ya i mean i think what you want is TypedDict but you cant use it or something?
you can make it so that x = my_dict["one"] gives you a more specific type but not so that x = next(my_dict.items()) has a more speciifc type, afaik
ya that seems like an awfull specific type hint for a itterator. maybe it could be done but ya seems pretty awfull
This seems to work (if i understood the problem correctly):
just subclass a generic version of dict
actually tbf i type hint dicts like that all the time regularly. dont know why i didnt think of doing that in there like that. dumb
@stiff acorn
from typing import overload, Literal, Any, reveal_type
class Foo(dict):
@overload
def __getitem__(self, s: Literal["Hello"]) -> int:
...
@overload
def __getitem__(self, s: str) -> Any:
...
def __getitem__(self, s): return self.super()[s]
f = Foo()
x = f["Hello"]
y = f["Bye"]
reveal_type(x)
reveal_type(y)
wont that be an recursion eror?
ok, just wondering
since next is mutating but mutating methods cant change the type, this could only work for the first value
when I was writing the code I couldn't remember how to call the base method in python (I don't do that very often
so just put a super there I guess
you mean super()?
super().__getitem__(s), or dict.__getitem__(self, s)
maybe even do __getitem__ = dict.__getitem__ in here? or put the overloads in if TYPE_CHECKING
I can't use []?
iirc not all typecheckers allow setting dunder methods like that though
should be the same thing as super().__getitem__(s), so i'd expect it to work
maybe this is an XY problem. I essentially need a typed dict semantics on a real dict subclass, i.e.,:
foo = MyDictSubclass()
foo["bar"] = None # type error because bar: int
baz = foo["baz"] # correctly inferred type of baz
i guess it really depends a lot on your restrictions
but for me, if I knew I had some guaranteed, typed stuff there, and some additional stuff
would be nice if i could just do
class MyDic(RuntimeTypedDict):
baz: int
bar: str
My first thought would definitely be a dataclass, with one field as a dict to hold additional stuff
but you said you can't use dataclass, so I'm not sure what your exact restrictions are
or maybe even
class Point2D(TypedDict):
x: int
y: int
label: str
``` - TypedDict does inherit from dict as well
(I believe)
perhaps some class RuntimeTypedDict(dict, TypedDict): ... or even simpler py if not TYPE_CHECKING: RuntimeTypedDict = dict else: RuntimeTypedDict = TypedDict would work?
well,if thats the case, just using a TypedDict would probably be the cleanest solution
my restrictions are just that it's a third party code base that predates typing and extensively uses dicts with some known keys
TypedDict subclasses forbid methods, so this would lead to an error if the subclass uses defines custom methods on top of dict
hmm really, that's an interesting restriction
where is that restriction described?
hmm, it just ignores them
Perhaps just make a file like dict_interface.py (or whatever) for the runtime logic of that part only, then a stub with a TypedDict instead of a normal dict subclass, and you'll only have errors in dict_interface.py, which you'll be able to ignore?!
idk, that's getting pretty hacky
I would just do what I had above
it's a little boilerplate-y but it works
Perhaps try this (idk if you have)?
You can do this too but it achieves something different from what TypedDict achieves
well, there is no way to make explicit key: K -> value: V type mappings, but except for that, it might be enough
idk
thisis just saying that the values will either be str, or "a", or "b". But "a" or "b" are already strings themselves, so this doesn't really add much
this approach would be more useful if you know say that all values are either strings or ints, or something like that.
Sorry, i meant to put in before the ,, so the keys might be a or b...
Gotcha, my b
the same argument does apply though
str | Literal["a", "b"] is the same as str, as far as the actual type system is concerned
depends, it might be enough, cause Literal["a", "b", "c"] != str for type checkers, and the Union Literal["a", "b", "c"] | str does not get converted to str, as far as i know
Yes, Literal["a", "b"] != str
but str | Literal["a", "b"] == str
it's not "converted" to str, it will keep the extra annotations around, and it does tell people reading the code something
but in terms of the actual type system and type checking, it's the same
Then, I guess there is no way to make a 100% working solution, a custom TypedDict + type checker support might be required (i've actually had an idea to simplify adding that support for some time now)...
saying that the type of the key is str | Literal["a"] doesn't even mean that "a" must be one of the keys
well, what I wrote above is a 100% solution 🤷♂️
in the sense that it works the same way as TypedDict
you would need to overload setitem too though 🙁
and get...
I hope that's everything 🤔
i was about to ask that, and even if that is done, no immutabillity can be shown (e.g. if a key A can be set once only)
what does immutability have to do with it?
basically, there are a few things that i think are not yet possible to implement in typing 100%
well, a lot of these issues are not python specific. it's just a fundamental limitation of static typing in many cases.
The whole concept of a dynamic collection with heterogenous types over iteration is pretty problematic. Even the Literal stuff is kind of hacky
if we want some key A be able to be set once, but not again. Say this is a API, where we have different types of connections, each as a TypedDict with some keys. The API_TOKEN key should probably be immutable, no?
i don't know, but that wasn't in the original requirements
and python doesn't have anyway to express "set once, but not again"
you could make it so that A must be passed at construction time, and then never changed
The original requirements were pretty broad to begin with
is there a way to type a dict subclass similar to a typed dict (eg some keys and their types)
eh, not really
a subclass of dict with similar behavior to a typed dict. that's what I have above.
though, again, I admit my solution will be a pretty big pain in the ass once you fully implement it
OK, then your solution obviously works, the solution is part of the question already
setitem, getitem, get, setdefault.. oof, 4 repetitions for each field.
you can probably justify not having pop and del, I guess
but it works, and they could probably just generate the boilerplate if its that much (or use AI?!)
yeah, AI is good at that sort of thing. It's pretty bad to maintain, but I guess if this is legacy code that doesnt' change very often, and they're trying to add typing to it, could be worth it
I've had a battle with mypy today writing a decorator that supports both sync and async functions. I think I ran into 3 distinct issues.
One is that inspect.iscoroutinefunction can't use TypeIs. That's cause it won't return true for regular functions that return coroutine objects.
It's a tough problem. First, one may not necessarily care whether it's a coroutine function or some other awaitable. But of course it cannot be known at runtime whether a callable should be awaited without first calling it.
Second, there isn't a more specific type for a coroutine function (i.e. has the CO_COROUTINE flag)
All we have is Callable that returns Coroutine
Just slap # type: ignores on it
I would personally just make two different decorators, or add some explicit option or something like that
yeah, sometimes you gotta lie to checkers, and # type: ignore or cast(...) it. Ive had a function declared to return a string (internal) return an object instead, cause doing x is y is more reliable for objects (users can basically copy the string.
Yeah I ended up casting it. But I did have to add an ignore for one of the other issues, which I believe is a mypy bug with generics and overloads (Pyright is happy). Oh well
Can you show the code?
or the signature at least
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
Oops I didn't use the casted function in wraps() but it doesn't make a difference
def mydec[**P, R](func: Callable[P, R]) -> Callable[P, R]:
if inspect.iscoroutinefunction(func):
@functools.wraps(func)
async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> object:
return await func(*args, **kwargs)
return async_wrapper # type: ignore[return-value]
else:
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return func(*args, **kwargs)
return wrapper
(the overload is redundant in this case)
Why? Is AsyncFunc a subtype of Func?
in Callable[[int], Coroutine[Any, Any, str]], Coroutine[Any, Any, str] is the "R"
OK I get it
Btw, if you do something like this: py @functools.wraps(func) def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> Awaitable[R | SomeError]: if error := some_validation(args, kwargs): fut = asyncio.Future() fut.set_result(error) return fut else: return func(*args, **kwargs) you will need to call inspect.markcoroutinefunction(async_wrapper)
because functools.wraps doesn't copy the coroutine-ness automaticall unless you decorate an async def function
Til about markcoroutinefunction
It's your namefellow
Heh
Hmm it's nice to get rid of the overload but then using R as the return type of async wrapper is incorrect
Something funky is going on here: ```py
def mydec(func: AnyFunc[P, R]) -> AnyFunc[P, R]:
reveal_type(func)
# Revealed type is "def (*P.args, **P.kwargs) -> R-2 | def (*P.args, **P.kwargs) -> typing.Coroutine[Any, Any, R-2]"
if inspect.iscoroutinefunction(func):
reveal_type(func)
# Revealed type is "def (*P.args, **P.kwargs) -> types.CoroutineType[Any, Any, Any]"
That makes me realise iscoroutine function isn't giving a meaningful type to R.
Or maybe R becomes CoroutineType[Any, Any, Any]?
Which would have the same problem as your proposal above I think
In other news, I decided to finally un-draft the tutorial article on generic functions:
https://decorator-factory.github.io/typing-tips/tutorial/3-generic-functions/
Seems to work.
Was wondering, is it possible to implement fallback syntax for versions below 3.12 conditionally? Since for them this would raise SyntaxError
Only if use something like exec().
For type statements you can use typing_extensions.TypeAliasType conditionally on older versions but it's going to be quite verbose
Just upgraded myself to 3.14. Was wondering if I should keep support for old versions xD
Honestly I could just keep the older syntax with TypeVar. It doesn't fail on 3.12+
Hm
that's fine too, it's expected that the new syntax can only be used in codebases that drop support for 3.11
Only reason for me to keep support is for OS's like some versions of Linux (or all versions of it? Dunno) where for some reason 3.11 is the official latest version.
Otherwise I'd say it's the user's problem for not upgrading. Although even on such Linux case you can upgrade manually. Done that recently on my RPI actually
yeah, stable linux distros can sometimes be slow to update python
since ubuntu LTS releases have like a 4 year support window
and once released, they only get bugfixes / security fixes, so they'll never do an update to python there until the next LTS release
people who are on old versions of e.g. Ubuntu can still get a newer version of Python in various ways (custom PPAs, uv, etc.)
definitely, but generally for public libraries, I tend to make sure they support all python versions that aren't EOL
it's up to you to decide what your version support policy is. Some libraries support all versions still supported by CPython itself (currently that means 3.9 and up, soon to be 3.10 and up only). Others, mostly around scientific python, follow https://scientific-python.org/specs/spec-0000/ which I think currently means 3.12+
Description# This SPEC recommends that all projects across the Scientific Python ecosystem adopt a common time-based policy for dropping dependencies. From the perspective of this SPEC, the dependencies in question are core packages as well as older Python versions.
All versions refer to feature releases (i.e., Python 3.8.0, NumPy 1.19.0; not Py...
Nobody uses the thingy I'm working on atm anyway so I'll drop pre 3.12 syntax lol
I'll keep the old syntax commented out
I wouldn't do that
but then again, I just hate "dead code"
I could also do a static check in init and conditionally import different files based on version, no?
but going 3.12+ is perfectly fine
that might work... but at that point, you're just maintaining two files with the same code
so you still need to care about the old syntax
just on top of also having to care about the new one
and keeping compatibility
since new python versions still support the old syntax without any issues, and that's not going to change any time soon, why bother
You mean I should just use old syntax?
yeah
I won't go beyond 3.10 tho xD
that's perfectly fine, especially for new projects, it's not uncommon to start off at a more recent python version
and your versioning policy is fully up to you
do what you feel is best, if you get people that want to use your project with lesser version, you can start caring about that
but especially for things that you don't expect will get a ton of users and are mostly for your own usage, I would maybe even go 3.13+
Back to the old syntax then (: should i add a TypeAlias annotation? it does nothing on 3.10..
I like to, but you don't need to
It's probably for pre 3.10 isnt it
no, it's for pre 3.12
which instead uses the type keyword
it's just a nice way to clearly mark off type aliases and regular variables/constants
you can also get some diagnostics from a type-checker when adding the TypeAlias type-hint, ensuring that the variable does in fact hold a valid type
and you might get different syntax highlighting for type-alias variables
It seemed to do nothing on 3.10.
No runtime problems and my type checker didn't trigger anything whether it's off or on
so i assumed maybe in even older versions type checkers cared about it
idk
so it has functionality? i dont want to use it for no reason
not on runtime, but for type-checkers, it can
like fix error said, when using strings that contain the type annotations, you would have issues with type-checkers understanding that the variable holds a type alias, and not a string
Judging from the docs https://docs.python.org/3/library/typing.html#typing.TypeAlias that is indeed the main use case
e.g.
markdown is surely cool, though theres much more stuff like html tags xD
I still like using it for non-string type aliases too, just to be explicit about what the variable is
recently found out i can do <small></small>
and it can have some effect on language servers, for example look at the syntax-highlighting my editor shows for these:
from typing import TypeAlias
Foo: TypeAlias = "int"
def f(x: Foo) -> None:
y = Foo()
reveal_type(y) # Revealed type is "builtins.int"
# no errors in the file...
``` epic fail...
both in mypy and pyright
yeah, allowing it to be used as a type too is a bit... odd
thats one funny "warning"
What IDE is this 
it's not a warning, it's a debug tool we use with type-checkers, allowing you to see what type the type-checker sees there
No, that is fine and it's the entire point of TypeAlias (if you mean the annotation)
it's a diagnostic at info level
neovim
Ah
though generally, pylance will likely do the same in vscode, it's the same underlying language server (pyright) which provides semantic highlighting hints to the editor
oh, I thought type aliases weren't meant to allow regular usage outside of for annotations
They're fine in annotations, I don't think they're supposed to be usable outside of annotations
So in my mind Foo() would have to be invalid even if you did Foo: TypeAlias = int
exactly
in particular, when you upgrade from the deprecated (!) TypeAlias, you might break code that relied on yourlib.Foo() which type checkers didn't catch
didn't expect pyright/mypy to allow that
I'll try making a pyright issue but I feel like it's going to be as-designed-ed
i wouldnt use type aliases for plain types like just int
unless that was just a barebone example
I'd be surprised if there wasn't one for it already
typealias isn't exactly new
There's this one https://github.com/microsoft/pyright/issues/4413 but it's slightly different
yeah it's just a demo
fasho
hmm, maybe it is worth opening an issue then
(the typing spec doesn't explicitly prohibit Foo() which is why I have this feeling)
pycharm?
pylance/pyright actually
allow me to interject for a moment...
trying to recreate the exact code that triggered it.
couldn't do it
Well, there's at least one person relying on this behaviour
https://discuss.python.org/t/run-time-behaviour-of-typealiastype/43774/87
I having trouble understanding how variance is inferred in types.
from typing import Generic, TypeVar
class Ok[T]:
def __init__(self, value: T):
self.value = value
x: Ok[object] = Ok[int](1)
OK_T = TypeVar("OK_T", covariant=True)
class Ok_CO(Generic[OK_T]):
def __init__(self, value: OK_T):
self.value = value
x_co: Ok_CO[object] = Ok_CO[int](1)
Why is the T in Ok inferred as invariant by default? I looked at the docs on variance inference, but I don't understand what determines when the upper and lower specializations are assignable.
value is a public attribute because it doesn't start with _
This is an extremely frustrating aspect of variance. Type checkers won't tell you why a type is not covariant/contravariant
https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types
Here the underscore prefix for _content is significant. Without an underscore prefix, the class would be invariant, as the attribute would be understood as a public, mutable attribute (a single underscore prefix has no special significance for mypy in most other contexts). By declaring the attribute as Final, the class could still be made covariant:
I also don't undestand why they don't complain when the explicitly specified inference on typevars is wrong... that's like, silly
Huh, I didn’t know that the publicness of a field affects its variance. So with it being a private field, it makes it covariant. At least pyright tells you the variance is part of the issue, but none of them link to info on how to make it right :<
The pyright playground is so painfully slow
so irritating
Even my OG pyright playground wasn't so bad, requests finished within a second or so. And it ran on serverless functions, so it wasn't smart with pre-starting a pool of LSP processes or something like that
pyright doesn't seem to complain for this example
You need strict on I think
nope, doesn't complain about the public attribute assignment
Huh? I get
Type "Ok[int]" is not assignable to declared type "Ok[object]"
"Ok[int]" is not assignable to "Ok[object]"
Type parameter "T@Ok" is invariant, but "int" is not the same as "object" (reportAssignmentType)
I meant that it doesn't complain here:
OK_T = TypeVar("OK_T", covariant=True)
class Ok_CO(Generic[OK_T]):
def __init__(self, value: OK_T):
self.value = value
# ^^^^^^^^^^^^^^^^^^
Ah, you mean because the variance is wrong
yes
it does complain if you use a covariant type variable directly in a parameter type, but not if it's part of a larger type or something? How does this make any sense?
class Ok_CO(Generic[OK_T]):
def __init__(self, value: OK_T):
self.value: OK_T = value
def meth1(self, value: OK_T) -> None: ... # complains here
def meth2(self, value: list[OK_T]) -> None: ... # but not here
Maybe they just didn't bother implementing any thorough correctness checks in the TypeVar days, and when the new syntax and variance inference came, they didn't add checks that the declared inference is the same as the inferred inference
So then what about this? I guess there's something funny happening with dataclasses in here too, since shouldn't this still work as covariant since _value is private?
from dataclasses import dataclass
@dataclass
class OkData[T]:
_value: T
x_data: OkData[object] = OkData[int](1)
That's because of the __replace__ incident: https://discuss.python.org/t/make-replace-stop-interfering-with-variance-inference/96092
mypy doesn't synthesize a __replace__ method, so it doesn't complain
it's been over a year since __replace__ has been in a stable version of Python, but there's still no solution in sight, unfortunately.
Pyright also considers every @dataclass_transform function to add a __replace__ method which it isn't required to do
You can set pyright to pretend you're using Python 3.12 and it's going to "fix" that
Wow that sucks :/
The next version of basedpyright has a... hack to fix this
https://docs.basedpyright.com/dev/benefits-over-pyright/dataclass-transform/
hacks are actually awesome, and it's a shame that pyright doesn't support plugins
so apparently the way you add plugins to pyright is by forking it 🥴
how to type hint that dict can have as value str or list of str?
dict[..., str | list[str]]
... is key
can i do recursive list? like it can be list that also could have list or str inside?
You'll need a recursive type
type StrList = str | list[StrList]
type StrListDict[K] = dict[K, StrList]
I'm finally not forced to use quotes yay
Who will agree that type hinting doesn't really have actual effect, it only helps when documenting a project to help provide actual data type
That sounds like an actual effect to me
Says it has no actual effect -> says its exact purpose
Like yes during runtime it doesn't do anything, that's how typing works
fwiw I think documentation is a secondary purpose. the primary purpose is tooling - both static type checking to catch errors, and making your IDE/editor actually work well and offer auto completion and catching many mistakes instantly.
and that's definitely an "actual" effect
Sad state of types: ```py
used in instanceof, cannot use type statement
JSONPrimitive: TypeAlias = str | int | float | bool | None
recursive, must be quoted or use type statement
type JSONType = JSONPrimitive | list[JSONType] | dict[str, JSONType]
If only we had @runtime_checkable for type aliases
runtime_checkable does what
Ah well no idea what Protocols are either xD
Fair
No idea what's the use case for all these
Ah I can imagine it actually (replacements for hasattr)
Wait in what way can the first one not use type statement..
TypeAliasType cannot be used in instanceof
instanceof?
xD
You can however do isinstance(x, Alias.__value__)
that simple damn
small tip if you define Json recursive aliases it's good to define a mutable one and a read-only one using Mapping and Sequence
for the covariance
well, and for the enforcement of not-mutating stuff
I feel like i'm missing something obvious here but idk
def _is_page[PageT: (BoundPage | BoundV2Page)](page: PageT) -> TypeIs[PageT]:
expected_items = (str, discord.Embed, discord.ui.Item, dict, discord.File, discord.Attachment)
if isinstance(page, expected_items):
return True
if isinstance(page, Sequence) and not isinstance(page, (str, bytes, bytearray)):
return all(isinstance(p, expected_items) for p in page)
return False
class Foo(...):
async def _handle_single_page(
self, page: PageT, /, kwargs: dict[str, Any], items: list[discord.ui.Item[Any]]
) -> tuple[dict[str, Any], list[discord.ui.Item[Any]]]:
if isinstance(page, (int, str, discord.ui.TextDisplay)): ...
elif isinstance(page, discord.Embed): ...
elif isinstance(page, discord.File | discord.Attachment): ...
elif isinstance(page, discord.ui.Item): ...
elif isinstance(page, dict): ...
else:
for _page in page:
if _is_page(_page): # <- not working?
kwargs, items = await self._handle_single_page(_page, kwargs=kwargs, items=items)
# ^^^^^^
return kwargs, items
Argument of type "_BoundPage | _BoundV2Page" cannot be assigned to parameter "page" of type "PageT@BaseClassPaginator" in function "_handle_single_page"
Type "_BoundPage | _BoundV2Page" is not assignable to type "PageT@BaseClassPaginator"PylancereportArgumentType
bound type aliases and PageT: #type-hinting message
-# why do I always bother with typing at late
Did you mean to name the variable here PageT as well?
def _is_page[PageT: (BoundPage | BoundV2Page)](page: PageT) -> TypeIs[PageT]:
because this type guard is kind of a noop
Hey. I'm trying to express that a Callable return type is the same as its argument:
from typing import TypeVar
from collections.abc import Callable
class Base:
...
class Child(Base):
...
T = TypeVar('T', Base)
def apply_func(a: Child, f: Callable[[T], T]) -> Child:
return f(a)
But all type checkers complain at the last line (f(a)) about a type mismatch. Eg https://play.ty.dev/cee488d8-055b-4c66-be9d-bffad85b8bbe
What am I missing?
Can you show some examples of invoking this function?
Do you want the type of f to be: "Expect any type, but at least Base, and return the same type"?
Usage is something like
c1 = Child()
c2 = apply_func(c1, clone)
And yes this is what I want to express on f
Doesn't f need to just work with Child in this case?
def apply_func(a: Child, f: Callable[[Child], Child]) -> Child
In this particular case yes, it is a simplification of the real code. I was hoping type checkers could make use of the type hints here to deduce that f returns a Child
If you want it to work with arbitrary subclasses, you need to do this: ```py
class Func(Protocol):
def call[T: Base](self, arg: T, /) -> T: ...
def apply_func1(a: Child, f: Func) -> Child:
return f(a)
def apply_func2(a: OtherChild, f: Func) -> OtherChild:
return f(a)
The way to express the type of a generic function is a "callable protocol" (a protocol with a `__call__` method)
So what is even the point of TypeVar?
If you are not on python 3.12 yet, then it needs to be this ```py
T = TypeVar("T", bound=Base)
class Func(Protocol):
def call(self, arg: T, /) -> T: ...
The square bracket syntax is new in 3.12. If you are using 3.11 or earlier, you cannot use that and you have to use TypeVar
So what is even the point of TypeVar?
I'm not sure I understand the question
I undestand TypeVar as expressing 'Some type (maybe conforming to constraints), but fixed throughout the usage'. Apparently this is not the case, or not entirely
Here: py def apply_func[T: Base](a: Child, f: Callable[[T], T]) -> Child: return f(a) means that T is a type variable scoped to the apply_func, not inside the f callable. In TypeScript terms, it would be like this: ```ts
function applyFunc<T extends Base>(a: Child, f: (arg: T) => T): Child
// NOT like this:
function applyFunc(a: Child, f: <T extends Base>(arg: T) => T): Child
So it's valid to call `apply_func` with e.g. these functions as `f`:
```py
def f1(b: Base) -> Base: ...
def f2(b: Child) -> Child: ...
def f3(b: OtherChild) -> OtherChild: ...
Thanks, but I don't think the results are different: https://pyright-play.net/?code=GYJw9gtgBALgngBwJYDsDmUkQWEMoAqiApgGoCGIAUKJFAMZgA2Tx9MSYKAzgHTkAjepmy58AYXItBrKlXpNy3blABCS4gC4qUXVF4G5CpSvEALJEwAmACnXdiASm179hqgGJCUALyESFCA2AOQEwQA0UAJgAK4oVj72TnJeVsTAUOQICExwAPrAcfQ25JpQ5pZWkcBlktICrADajQQAupFtjlAAtAB85RbW2l6uIMQwMSAoUMAljnJpGVk5%2BYUo9C1lSa0ltYNVM7VSig3EzW0drV19A5UuemMTUzNzQA
That's not my suggestion, that's just your example using the newer syntax
it has exactly the same meaning as ```py
T = TypeVar("T", bound=Base)
def apply_func(a: Child, f: Callable[[T], T]) -> Child:
return f(a)
I'm explaining that f: Callable[[T], T] is not defining a requirement that f must be a generic function
I understand, I was wondering what is the point of TypeVar (in either old or new generic syntax), if type checkers cannot deduce that the return type is identical to the received argument type
A TypeVar is resolved to some specific type when you call a function.
Here's an example of correctly using type variables:
T = TypeVar("T")
def run_func(argument: T, func: Callable[[T], int]) -> int:
return func(argument)
def does_contain(element: T, items: list[T]) -> bool:
for item in items:
if item == element:
return True
return False
x = run_func("aaa", len) # x is an `int`, T is resolved as `str`
y = run_func("bbb", len) # y is an `int`, T is resolved as `str`
z = run_func(42, len) # T is resolved as `int`, but `len` doesn't accept `int`, so this is not allowed
xs: list[int] = [1, 2, 3]
t0 = does_contain(42, xs) # T is resolved as `int`, ok
t1 = does_contain("ccc", xs) # error, cannot resolve `T` such that "ccc" is a T and xs is a list[T]
I think I understand. Thank you!
unfortunately callable protocols like this are not explained in detail anywhere, so I can understand the frustration
Indeed.
on the typing of async generators: is every AsyncGenerator annotation expected to contain None in its send type?
(the second type argument)
because otherwise I don't see how await agen.asend(None) would be legal from the typing perspective
and this is required to start any async generator (you have to send it None as the first item regardless of how you use it)
I asked AI and it gave me the answer I expected: this is an incoherent, unfixable mess
did you ask it about just this, or typing in general? 😛
I asked it that specific question
with a follow-up
it pointed me to a post on discuss: https://discuss.python.org/t/ambivalent-async-generator-function-signature/71439
TL;DR - async def f() -> AsyncGenerator is incoherent compared to other generator/function type annotations in Python. After getting back to Python after almost fifteen years (last time it was Python 2!), I have quite happily adopted its type hints. I find the system, despite being a bolt-on feature, quite coherent and integrates well with Pyt...
in my case I was actually able to figure out a workable solution:
elif item_to_send is None:
item_from_agen = await self._asyncgen.__anext__()
else:
item_from_agen = await self._asyncgen.asend(item_to_send)
I'd just do py item_from_agen = await self._asyncgen.asend(item_to_send) and then a # type: ignore[...]
that's what I did at first but I strongly dislike type ignore comments
Well, __anext__ existing on every AsyncGenerator is a similar kind of soundness hole
no argument there
but yes, # type: ignore can hide errors you didn't intend to hide
You could do something like this, though I assume it only works in CPython ```py
def start_gen[T](gen: Generator[Any, T, Any]) -> T:
assert not (gen.gi_suspended or gen.gi_running)
return next(gen)
basically, ensure that the generator isn't started yet and only then start it
Wdym?
That shouldn't interfere with anything?
Well, this type guard is pretty useless ```py
def is_foo[Foo](x: Foo) -> TypeIs[Foo]
If you're using typevar constraints instead of bounds, I'd be a bit more trigger-happy about typing.cast or # type: ignore because that feature is a bit janky
I guess I misunderstood what you meant here #type-hinting message
The type PageT is scoped to the type guard function, it's unrelated to the PageT in your class
you can rename PageT in it to Foo and it will have the same effect
Wait what
Does it not uh "pass" that when calling it?
Wait
I'm so confused lmfao
So what I thought it was supposed to do is
page = BoundPage | BoundV2Page
the function makes sure of that
-> return it's 100% BoundPage | BoundV2Page
PageT = (BoundPage, BoundV2Page) so it's fine
I see the misunderstanding from me here, but no idea how to fix it
So you suggest just ignoring it because it's janky as you've already explained before
That would be spelled as ```py
def is_page(page: object) -> TypeIs[BoundPage | BoundV2Page]:
...
though in your case, that probably won't help, since you need to check whether it's specifically the same type as your bound variable
so yeah I'd just use typing.cast and move on to more interesting tasks
fair, thanks again!
anyone (cough cough @trim tangle) ever had the idea to try doing some kind of unit type using vectors or is that too out of the reach of possibility right now?
jesus, wear a mask
I think you could do some nasty stuff with TypeVarTuple for it maybe
You mean something like foo: Kilograms
generally, if your question is Is this fun and useful thing possible with TypeVarTuple, the answer is probably no
well yes but Kilograms would be an alias to something like Units[0, 0, 1, 0, 0, 0, ...] https://en.wikipedia.org/wiki/International_System_of_Units#SI_base_units I'm mainly interested in the dimensional analysis side of it
The International System of Units, internationally known by the abbreviation SI (from French Système international d'unités), is the modern form of the metric system and the world's most widely used system of measurement. It is the only system of measurement with official status in nearly every country in the world, employed in science, techno...
and it can also be fractional
oh yeah guess its impossible cause no literal floats
that's fine, we can use a tuple to represent a fraction
we can't do arbitrary addition or subtraction in the type system though
it would need some kind of vector multiplication at type time
but i remember seeing you do some wacky stuff with typevartuple
alas maybe one day
typing.Literal with no way to manipulate literal values in the typesystem is a mistake
its not like the typecheckers dont already effectively (re)implement python, it wouldnt be hard to add computation of compile-time values
We need to work on adding a lispy microlanguage into basedpyright
inside every sufficiently complex static analysis tool there is a partial implementation of the thing its analysing waiting to burst out
found a super interesting case with structural types as constraints.
this doesn't pass type checking in pyright & mypy and only passes in ty:
from typing import Generic, Protocol, TypeVar
class Fooable(Protocol):
def foo(self) -> None:
...
class Barable(Protocol):
def bar(self) -> None:
...
class Both(Protocol):
def foo(self) -> None:
...
def bar(self) -> None:
...
T = TypeVar("T", Fooable, Barable)
class G(Generic[T]):
z: T
g = G[Both]()
reveal_type(g) # ty: G[Both], pyright & mypy: G[Fooable]
g.z.bar() # ty: ok, pyright & mypy: error
because both pyright and mypy reveal G[Fooable], because it was the first that matched!
now if i change the order of constraints:
-T = TypeVar("T", Fooable, Barable)
+T = TypeVar("T", Barable, Fooable)
pyright and mypy allow this because they reveal G[Barable]!
i'm not happy with how any of type checkers handles this.
i believe that it shouldn't be possible to bind T to Both, nor that Both should be interpreted as the first matching type from the constraints.
@oblique urchin wdyt?
this is very theoretical, but just exploring.
yeah this is a very hard edge case
arguably ty is wrong here because T can only solve to Fooable and Barable and nothing else
but then there's no way to prefer either over the other so what can you do
not sure if ty made a principled choice here or this is accidental behavior
I think G[Both] should be illegal. Only G[Fooable] and G[Barable] should be legal
allowing this ```py
T = TypeVar("T", str, int)
class G(Generic[T]):
z: T
x = GLiteral["foo"]
and "fixing it up" as `G[str]` will just confuse people who don't understand the difference between typevars with a constraint and a bound
(which is common)
Is there a reason why Literals cant be floats, but ints are accepted? In PEP 586, it says Floats: e.g. Literal[3.14]. Representing Literals of infinity or NaN in a clean way is tricky; real-world APIs are unlikely to vary their behavior based on a float parameter., but there are no sources to this in any way. Some API's could perhaps say that if a timeout < 0.0, we return Never (pretty obvious, but type-checkers won't raise errors i guess)?!
Well first Python doesn't have float literals for infinity or NaN, so we'd either have to special case math.nan/inf, allow float("inf")' inside Literal`, or something else. Your example shows the second part - you'd need a comparison, not a literal value. Unlike integers you can't really list all the values. Though, I do agree it would be nice to have something for this, even just for nan/infinity.
Oh also the problem of negative infinity.
Accident I think. There's lots of sharp edges in our current generics solver that we haven't put much work into fixing, because we're attempting a comprehensive rewrite of the whole thing
Well, if we ignore special cases like nan, +/-inf and such, floats should be possible to be added, right?
Well that's the trouble really, what floats, how much precision?
Since you're almost always doing comparisons, not equality checks.
wrote a stub generator with Literal consts for the errno module, output: https://github.com/bswck/generate-errno-stub/actions/runs/18749599178/job/53485540693#step:4:5.
this however should be run on other platforms than linux, windows & macos (errno.pyi has some fragments specific to other platforms)
wondering if we could apply this workflow (after tweaks) to some other typeshed stubs as well
Contribute to bswck/generate-errno-stub development by creating an account on GitHub.
this assumes that an errno that is present in linux, windows, macos with identical values (literal ints) will exist in all supported platforms with these exact values (literal ints)
cc @oblique urchin
nice! this could be used periodically to check stubs for validity, not sure it's worth the complexity to run it in typeshed CI
would you be interested in literal consts in stubs?
perhaps only those specialized
yes, I think we've been adding Literals for a bunch of these
or maybe we decided that we wanted ESOMETHING: Final = 42, can't recall
does it have any nuanced difference to ESOMETHING: Final[Literal[42]]?
(in general)
it may depend on which type checker? Unless mypy treats Final differently, ESOMETHING would be of type int in one case and Literal[42] in the other
in pyright it will be Literal[42] in both cases I believe
yes, type inference like that is not specified, so for type stubs or library exports you may need to annotate explicitly
Thats not what i wanted to do, i wanted to make var: Literal[...] = ... with some float, so i can use the var in comparision later on
i perhaps didnt explain the issue i have good enough, but im basically just asking, why Literal[<some_float>] is invalid, when we could say its valid for everything thats not nan, +inf, -inf
The pep that introduces Literal says: https://peps.python.org/pep-0586/#illegal-parameters-for-literal-at-type-check-time
The following are provisionally disallowed for simplicity. We can consider allowing them in future extensions of this PEP.
- Floats: e.g. Literal[3.14]. Representing Literals of infinity or NaN in a clean way is tricky; real-world APIs are unlikely to vary their behavior based on a float parameter.
do you have some use case for this?
Yes, thats what i already noted im my original question, but i'm asking why that decision was made that way, instead of saying 'floats are fine, unless they are nan, inf, neg_inf'
In an API im making right now, i have TIMEOUT defaults (as Final Literals), which are floats (e.g.: DEFAULT_TIMEOUT: Final[Literal[0.0]])
Why does it have to be a literal?
python3 -c 'print("helloworld")'
what's wrong in the script?
this is the wrong channel, see #❓|how-to-get-help and #1035199133436354600
it does print helloworld if you have a python3 executable
i hope you do realize that its a shell command rather than python code though
well, the thing after -c is python code
How are Checkers gonna know the value (0.0) else? Some might check the code itself (e.g. if i do DEFAULT_TIMEOUT: ... = 0.0), and infer that something like if DEFAULT_TIMEOUT < 0.0: ... is unreachable, but most checkers will not go that far. If i were to make it an int however, e.g. DEFAULT_NUMBER: Final[Literal[0]], then checkers should be able to know that similar code is unreachable (idk how advanced all checkers are. Apart from that, users that only have access to the stubs (e.g. if i decide to write parts of the API in C), will be able to see that the default value is 0.0, without me having to create any comments or such.
Can you show the code maybe? I don't think I follow
Ah, I see. You want reachability analysis when directly using the value
from typing import Final, Literal
__all__ = [
"DEFAULT_PORT",
"DEFAULT_DATABASE_PATH",
"DEFAULT_TIMEOUT",
"DEFAULT_METHOD",
]
DEFAULT_PORT: Final[Literal[8000]]
DEFAULT_TIMEOUT: Final[float] = 0.0
DEFAULT_DATABASE_PATH: Final[Literal["project.db"]]
DEFAULT_METHOD: Final[Literal["get"]]``` See how the only value, where i have `= ...` is `DEFAULT_TIMEOUT`
Please move this to #1035199133436354600
Is there a type safe way to take in a tuple[T, Sequence[T]] and flatten it into a Sequence[T]? My intuition is telling me no because of types like str that are sequences of themselves, but that really sucks since this would be very nice functionality to have.
do you mean T | Sequence[T]? tuple[T, Sequence[T]] seems fine
Oops, yeah I meant tuple[T | Sequence[T], ...] phone hard
yes, that's inherently difficult to type safely
you can do the obvious thing (def f[T](x: Iterable[T | Iterable[T]]) -> Sequence[T]: ... or similar and most types will probably be inferred the way you want in practice
The definition works fine at least in concept, but (aside from wrangling the sea of generic soup in my actual code) I'm struggling to make the implementation work. Trying the first thing that comes to mind:
from collections.abc import Sequence
def flatten[T](seq: tuple[T | Sequence[T], ...]) -> Sequence[T]:
output: list[T] = []
for item in seq:
if isinstance(item, Sequence):
output.extend(item)
else:
output.append(item)
return output
basedpyright rightfully complains that output.extend(item) doesn't work, since item could either be the desired Sequence[T@flatten], or T itself could be a Sequence, resulting in the Sequence[Unknown], and I'm not sure how this would be solvable.
you would need negative types, right?
typescript has Exclude<T, U> for this
def flatten[T: ~Sequence[Any]](): ...
Unrelated question since I’ve recently been thinking about this, why Sequence[Any] instead of Sequence[object]? Since Sequence is covariant, shouldn’t object be better?
or well, I guess you can still accept sequences inside of the sequence... and they aren't flattened
yep, should work as well
Any would not work here (assuming a sound interpretation of negation types)
I wonder if T & ~Sequence[Any] would mean
T which is not a sequence of some specific, but unstated, static type
or
T which, for any static typeX, is not aSequence[X]
A ~Sequence[Any] could still be some kind of Sequence
(which means I think it should mean @trim tangle's first option)
yeah you probably need to type ignore this. It's a true type error
I think the way the spec talks about Any is very esoteric and probably not how Python developers think about Any
you want to accept strings as sequences btw?
and there's also the caveat that it introduces unstated type variables with dependencies on runtime values or something like that
At least for my use cases strings being sequences of themselves doesn’t really matter, it would work fine either way
the spec doesn't define Any in terms of typevars
you need to make a distinction between "doesn't error" and "works as intended"
though some of the typing literature does
Maybe I'm misremembering, but I asked some time ago about this: py def set_attribute(obj: object, key: str, value: Any) -> None: setattr(obj, key, value) it doesn't make sense to say that Any refers to some specific, but unstated, static type. Since the type varies depending on what key is
But I am also find out that this idea was probably a bust from the start, since I think actually typing the type of this in a useful way with my current implementation would require higher kinded type bounds (which also don’t exist yet)
def flatten(seq: Iterable[Any]) -> list[Any]:
keep in mind that Sequences are not mutable
My implementation is actually a class that passes a self instance into another class that then is returned and does the processing (generic soup), and I don’t think with all my genetics I can actually express the self bound
But I can just split it up into four less powerful methods that handle the four len 2 tuple cases, which should be enough to get the job done
or use overloads in the typing
Am I doing this correctly? I just recently found out about TypeVarTuples, and was excited to use them to make some code less spaghetti, but it looks like BasedPyright is not happy. Is this a bug in BasedPyright, or am I doing it wrong? playground
All the other type checkers also experience errors in completely different places (ty doesn't have support yet, mypy accepts this but dies on a more complex example, pyrefly gives a completely different error at Ok(func(*self._value)) ), so is this just something that's not fully supported / too spaghetti for current type checkers? Or am I just doing something wrong?
Will report tomorrow but I think I found a bug where if you merge an unknown type with a literal string you get a literal string with pyright
normal pyright is fine with it I believe
On normal, yes, on strict, no playground
I am using strict with pylance LSP
the issue is with the lambda of unknown type, no?
Pylance isn't necessarily the latest pyright version
When I do that, I get the error
that has nothing to do with type var tuples
^
At least in theory, the type var tuple should allow for the lambda type to be infered
And it also works fine in just the Ok case, so type inference does work to some extent, but adding in the Err case breaks it
playground No errors if I switch the function to just Ok instead of Result
ok well that is because in Err.star_map_ok(), you did not establish a relationship between the class and the type var tuple
so there is nothing to determine it
you only reference *Ts once there, in the args of _func, so when you pass in an untyped function, it doesn't know
@bleak imp ?
So is there any way to fix that? I'm trying thing now to see if I can
the best way would be to just not have that method on Err?
actually I may have misunderstood the design
It's trying to model Rust's Result type, so having the method on both Ok and Err is the entire point, since you'd only use this on a combined Result.
I see
(Not shown in the example is the equivalent function star_map_err, which does the same thing reversed: Map the error value if it's Err, and does nothing on Ok)
I'm trying to figure out how to accept a lambda when the type doesn't matter, but it likes to complain that the type is not known
because that's the real issue
here's an MRE
mypy (after adding a return type to f) is fine with it on strict
A couple things:
I also just came to the same conclusion, since the same issue happens with map_ok, so the type var tuple isn't at fault.
And I found this solution, which seems to work, though I'm not sure if it's type safe: playground
you have another issue btw
Final name declared in class body cannot depend on type variables
That one's mypy exclusive so I'm pretending it doesn't exist
yeah not sure why it exists
I'm still not convinced thowing nevers at it is the correct solution, but it seems to work for the star case as well: playground
So then I guess the next question is: Is there something I can do instead of ```py
type SuperNeverCallable = Callable[[Never], Never] | Callable[[Never, Never], Never] # And then put like a billion more versions here
? Since the obvious-seeming `Callable[..., Never]` doesn't work
the problem is that ... is not Never, but rather, Any (or maybe object)
I remember reading this somewhere, Callable[..., T] is the same as def foo(*args: Any, **kwargs: Any) -> T
Oooh that's smart, but doesn't work :(
I wonder why it doesn't though, these error messages are super confusing:
BasedPyright:
Argument of type "(x: Unknown, y: Unknown) -> Unknown" cannot be assigned to parameter "_func" of type "SuperNeverCallable" in function "star_map_ok"
Type "(x: Unknown, y: Unknown) -> Unknown" is not assignable to type "(*args: Never, **kwds: Never) -> Never"
Parameter "*args" has no corresponding parameter
Parameter "**kwds" has no corresponding parameter (reportArgumentType)
Mypy:
main.py:27: error: Argument 1 to "star_map_ok" of "Err" has incompatible type "Callable[[Any, Any], int]"; expected "SuperNeverCallable" [arg-type]
main.py:27: note: "SuperNeverCallable.__call__" has type "Callable[[VarArg(Never), KwArg(Never)], Never]"
Maybe it has to do with the variance of args in callables? But then it doesn't make sense why the union method works
I think for that to work the args would have to be covariant, but I remember reading that function args are contravariant instead (but that still doesn't explain why the unions work. Special typing inside the type checker?)
awesome
it doesn't work because pyright thinks that FunctionType.__call__ is a Callable[..., Any]
That just might be display jankery, I think the protocol callable definition has to work normally
Another fun thing is that mypy doesn't accept the union method, so I guess it's a pyright specific special case?
versus without __call__
Hm, now I'm even more confused. Mypy does accept this code, despite the fact that it rejects the other usage:
from typing import Callable, Never
def foo(_x: Callable[[Never], object]):
...
def bar(_x: int): ...
foo(bar)
you aren't passing a lambda
The types should be incompatible though
should they?
it means that you can't pass an arg to _x when you use it inside of foo (or well, call it at all then)
Or wait, yeah that makes sense, since it's with respect to the type in the function
And yes, this works ```py
class SuperNeverCallable(Protocol):
def call(self, *args: Never, **kwds: Never) -> object: ...
Contravariant in the inputs, covariant in the outputs, so Never for the inputs, and object for the output.
I forgot to put self
still doesn't work in pyright, no?
It does! playground
you didn't turn strict on
all is more strict than strict
BasedPyright adds more new rules
ah ok found it... you didn't use SuperNeverCallable
you still have a Callable[...] in your Err class
;-;
I have too many playgrounds open and got confused
to me it just seems like a bug in how FunctionType.__call__() is handled
Hm, ty also agrees with this https://play.ty.dev/a497c7d9-2377-41e1-a36c-2e781fee0466
from ty_extensions import static_assert, is_assignable_to
from typing import Any, Callable, Final, Never, Protocol
class SuperNeverCallable(Protocol):
def __call__(self, *args: Never, **kwds: Never) -> object: ...
# Static assertion error: argument of type `ty_extensions.ConstraintSet[never]` is statically known to be falsy
static_assert(is_assignable_to(Callable[[int], None], SuperNeverCallable))
So I wonder what we're missing, since all the checkers agree on it
did you get the order of the arguments right in the in_assignable_to?
(just wanna check)
Yep, someone finally added docstrings and better arg names just this last release to all the ty_extensions functions
aight
I kinda feel like all these typecheckers are wrong on this point
but I'm sure Jelle could tell me why they are right
Or I guess the more correct one would be is_subtype_of, since we are dealing with fully static types here: https://play.ty.dev/d0af2466-e44b-47e9-8462-f7deeac0b3bc
from ty_extensions import static_assert, is_assignable_to, is_subtype_of
from typing import Any, Callable, Final, Never, Protocol
class SuperNeverCallable(Protocol):
def __call__(self, *args: Never, **kwds: Never) -> object: ...
# Static assertion error: argument of type `ty_extensions.ConstraintSet[never]` is statically known to be falsy
static_assert(is_subtype_of(Callable[[int], None], SuperNeverCallable))
But still doesn't work
mmk
I wonder if there's some existing typing discussion around functions with Never *args and **kwargs, since I remember the Any case having some special casing, but that probably doesn't apply to Never
Yep, I should go to sleep, so I'm hoping one of the typing wizards sees this later and can help understand where we went wrong
Why do you guys use typing.Callable? Its been deprecated (for checkers), no?
It still works technically
But checkers should error if they see it (atleast the newest versions)
Mine doesn't 
Which checker(s) do you use?
The one Pylance has, I belive it's Pyright
Yes it is
do you have some strict mode enabled?
I'm on standard, maybe that's why it doesn't care
This is what i get:
Lemme see if it changes when I'm on strict
should, perhaps your version is outdated though?
Nope
I'd assume it updates when Pylance does 
Hm
Yeah, turn that on
i forgot i have like 300 lines of custom config for every checker i use, and just assume thats standart lol
(tbh, i just turn everything to the most strict setting there is, and if my code has no errors, its truly good (or has a few type: ignore comments xd))
thats why you should hint stuff very deeply (like just a plain x: dict is already too little imo, everything that ís generic should be used as such)
Here, i'd suggest a dataclass though, that should work best

I don't see how that's pairable with the dict
OK, that makes no sense lol
@dataclass(...)
class JSON:
id: int
type: int
content: str
lobby_id: int
channel_id: int
author: dict[...]
metadata: dict[...]
flags: int
...
something like that should work
And where does it actually get them from the data
That's just static
you mean the attirbute access or what?
data[...] or data.get(...)
I can also make JsonDict a typeddict
there are multiple ways to make a dataclass/typeddict thing
iirc you can create dataclass instances from dicts
Well, sadly hinting always adds a ton of 'unnecessary' stuff
Question, can I subclass a dataclass
iirc yes
You mean a normal class inheriting from it, or a dataclass that inherits from another dataclass?
actually i think i have a way to solve the first problems
nice
I can keep it a normal class with __init__ if I follow this logic
But it only works if I type-wrap each thing.
Due to that, we still have an issue when it's a dict
Because I can't wrap it

No, make the attribute access be different i guess
In what way tho
For me, this works: ```py
from dataclasses import dataclass
@dataclass
class c:
a: int
b: str
print(data := c(a=100, b="hi"))
print(data.a)
and due to kwarg logic being allowed at initiation, i think c(**orig_data) should work
Hmm should I just make all of these be dataclasses
Not even that, just copying the attribute names and their hints (and possible defaults) should work
Like I did here?
I can also use typing.cast
Though im not a fan of all these tricks in simple objects
Yeah, i try to avoid casting as much as possible too, its kinda like cheating
the only way to fix this without cheating would prob be dataclass for each object
How many dict-like objects do you have?
Objects that are a dict
But tysm for this
Oh type checker on strict triggers when you use _privates
That's cool
It says unecessary instance calls which is true but I literally made that check for users without typecheckers..
Should I:
- Add type ignore
- Remove and let users eventually realize theyre doing something wrong
Just # type: ignore[unnecessaryIsinstance] ngl
or make it into a function, like ```py
def is_valid(event_type: Any) -> bool:
return not (isinstance(event_type, type) and issubclass(event_type, events.AnyEvent))
nah no need
does passing things in these [] actually do anything
or is it for clarity
yeah, it specifies that the type: ignore is only for the errorcode unnecessaryIsinstance (or whatever its called, i forgot), so any other error will be raised like normal
so now if you pass some invalid event_type, or something, it tells you
ah so i gotta spell it the exact way, in that case it's reportUnnecessaryIsInstance ig
can i pass multiple of those to the []
hrmmmm
yeah, seperate them with , (the whitespace is bc thats cleaner lol)
seems to not exactly work
if i change type to pyright it does work lol
ah yeah, forgot that, sadly there is no exact standart all checkers use
(would be cool tho)
But tbh this is kinda pointless when that line is always static xD
might as well just put type ignore normally
but its good for clarity
Bruh (using 3.12+ type hinting fixes this)
just not sure i wanna migrate to the newer typing, idk who might use the code
Well, if HandlerFunc is generic, just use it as a generic piece of code. If you still want a "placeholder" value, parameterize it with Any, or perhaps make the AnyEvent = TypeVar("...", default=events.AnyEvent)
the default should then automatically be applied everywhere, unless you do HandlerFunc[...]
what's the diff between bound and default
bound means i cant use it in any way but x?
Bound means that the typevar can only be of that type's bound, yes
but default means, that if you dont pass anything for the typevar, it "replaces" itself with the dafault
like type G[T] = list[T], where T = TypeVar("T", default=int) means that G == G[int]
Alrighty thanks
np
would it be better to rename it to AnyEventT
Yeah, usually thats some NamingScheme (or T_AnyEvent or _<name>, if you don't plan on 'exporting' it)
if you're targeting 3.12+ (which seems reasonable for a new library), just don't use TypeVar
TypeVars are useful, even for 3.12+, some stuff like co/contra, bound, defaults cannot be expressed otherwise (or not as easily)
Not for my case
If I can safely assume enough people migrated to post 3.12 then i should migrate my typing
The only thing missing is default (which you need typing_extensions for before 3.13) but yes.
bound is: def f[X: YourBound], and variance is inferred automatically (type checkers also don't point out when you manually select the incorrect variance...)
Wait, variance is automatic, and i never noticed? Lmao
yes, variance is inferred automatically
and typing_extensions.TypeVar now has an infer_variance parameter
vsc doesnt show that, and im too lazy to print all that, so i never noticed lol
🤔 that's from pyright, so you should get this when you hover over T
type HandlerFunc[AnyEvent: _AnyEvent] = Callable[[AnyEvent, datetime], Coroutine[None, None, None]]
type HandlerFunc = Callable[[_AnyEvent, datetime], Coroutine[None, None, None]]
Will both work the same way
Well second seems to work just fine
Some setting im missing perhaps?
maybe, not sure
You can always test it in a crude way like ```py
def f(x: c[int | str]):
t0: c[int | str | None] = x # if this fails, x is not covariant
t1: c[int] = x # if this fails, x is not contravariant
works in the pyright playground: https://pyright-play.net/?code=MYGwhgzhAEBCD2APAwvAbmATgSzAOwBcBtAFQF0AuAKGlugBMBTAM2gH03s9sCOAKCIxDMANNAwgArowrQSASmgBaAHzQAcvDwyadPYOEA6NhOnQAvOLBTGVXbSasA5owIChzRarnU9ezK6SmHjQBszGprZUoJAwCIgAkngYOPjE5L50juyc3Lxs7sJikbIKymqa2pl%2BoR6GkRZWNnZ62S5uYV5qJNV%2BAQRBIWH11tJAA
def on_event[AnyEvent: _AnyEvent](self, event_type: type[AnyEvent], /) -> HandlerFuncDecorator:```
```py
@app.on_event(events.ApplicationAuthorized)
async def foo(event: events.ApplicationAuthorized, time: datetime):
...
I get "_AnyEvent" is not assignable to "ApplicationAuthorized" (ApplicationAuthorized subclasses _AnyEvent)
HandlerFuncDecorator needs to be geneirc in AnyEvent
This is how it is rn:
So Decorator should take what as generic?
Should they both be generic?
Both being generic: works (oof so much repeating syntax), except for one thing:
self._event_handlers: dict[str, HandlerFunc[_AnyEvent]] = {} it's outside of the function that takes the generic "AnyEvent"
And then it causes a problem with this line, that is in the function that takes the generic "AnyEvent":
self._event_handlers[reversed_map[event_type]] = func
The error:
Type "_AnyEvent" is not assignable to type "AnyEvent@on_event"
Might just make event_handlers's type hint take Any (or ... ?)
And regardless, there's another problem:
self._EVENTS_MAP: dict[str, type[_AnyEvent]] = {
"APPLICATION_AUTHORIZED": events.ApplicationAuthorized,
...
}```
```py
event_cls = self._EVENTS_MAP[event_type]
event = event_cls(event_data)```
"Expected 0 positional arguments" - also caused by the type hint of EVENTS_MAP. Idk what to change here
Should I just use Any in those places
type Handler[E] = Callable[[E, datetime], Awaitable[object]]
def on_event[E: _AnyEvent](self, event_type: type[E], /) -> Callable[[Handler[E]], Handler[E]]:
What does _AnyEvent.__init__ look like?
class _AnyEvent: ...
?
Its only purpose is for the generic handling
If you want the __init__ of event classes to have a certain shape, you need to define that
Or you could make a classmethod, especially if event_data is some raw untyped and unvalidated data
The classes that subclass it do have
Should _AnyEvent also have?
Yes
What'd i put there lol
Well, what do you want the interface of an _AnyEvent's __init__ to be?
Seems like you want it to accept a single argument of some specific type
yep (but in runtime when i do event = event_cls(event_data) im pretty sure it goes directly to the specific object that subclasses _AnyEvent)
🤔
class _AnyEvent:
def __init__(self, event_data: object, /) -> None:
# override this in a subclass
pass
Sqlalchemy has a func namespace which allows you to call functions defined in your database (e.g., func.count() for COUNT). Sqlalchemy is typed and func is as typed as it can be since it obviously can't know what built in functions every database offers so it uses __getattr__ for these. Is there anyway to tell typecheckers that this specific object has X, Y, and Z methods because I know my database has those?
Maybe assert isinstance(var, inst) ?
TLDR: supplementary type information for a third party object?
uh ig
🫡
It's for https://github.com/TheServerit/webhook_events , been working on it for a while
(Hope this does not count as self promo. Will delete if told)
@fiery canyon i'd suggest you make _AnyEvent an ABC, and delete it too in events.py. I've send a PR for that.
I'm not too familiar with the concept of ABCs, what exactly should I do
Or whats its purpose
I've already send you the PR, ABC's make that kind of subclassing of empty classes cleaner, and are recognized by Type-Checkers
Shouldnt del _AnyEvent as handler.py uses it
Unless thats not how it works
Ok, i'll fix that, didn't see that, sorry
Dw 🙏🏻
Ok, should be done now
I see, like the changes, really appreciate it :D
Merging
cool
Shouldn't you use a Protocol over abc in modern Python?
both have their uses
Wouldn't protocol only be useful when it is not empty
true
NUH_UH
Great condition name 😆
protocols are not really for runtime (unless you do @runtime_checkable), but they are designed for checkers, whilst ABC's are not as easy to define, but they work at runtime (and they can inherit from non-abc's, which is great imo, protos should have that too))
Though ABCs also add a metaclass, which isn't always wanted.
yeah, and you need another import (protos would only need typing, which should be pretty common in hinted files)
You have to do extra work to use them with a custom meta. ```py
import abc
class MyABCMeta(abc.ABCMeta): pass
class MyClass(metaclass=MyABCMeta): pass
iirc thats pretty uncommon though
also can you even define a custom meta for protos? Like they have to subclass other protos, so i'd guess there are some rules for that too
Protocol doesn't have a meta
wdym? Something like this (although dumb ofc), doesnt fail for me ```py
from typing import Protocol
class Proto(Protocol, metaclass=type):
...
metaclass=type is the default
well it wokrs tho
And this too
from typing import Protocol
class meta(type):
@staticmethod
def meth(a: int, b: int) -> str: ...
class Proto(Protocol, metaclass=meta):
pass
Proto.meth(1, 2)```
(altho again that would be pretty stoopid to make, its just an example)
Protocol itself doesn't
neither do any type checkers I run over it.
Is there a way to tell that a var inherits the type from another? In this example fakeprint is something that behaves like print().
def fakeprint(*args, **kwargs):
return None
myprint = print if use_real else fakeprint
# ^ How to ensure `myprint` ony use the signature of print
You can simply copy the annotations of print, which are found in builtins.pyi (typeshed)
How?
Copy the lines here into a .pyi for yourself: https://github.com/python/typeshed/blob/main/stdlib/builtins.pyi#L1801C1-L1812C15
stdlib/builtins.pyi line 1801
@overload```
Isnt this supposed to be all the code, not just line 1?
stdlib/builtins.pyi lines 1801 to 1812
@overload
def print(
*values: object,
sep: str | None = " ",
end: str | None = "\n",
file: SupportsWrite[str] | None = None,
flush: Literal[False] = False,
) -> None: ...
@overload
def print(
*values: object, sep: str | None = " ", end: str | None = "\n", file: _SupportsWriteAndFlush[str] | None = None, flush: bool
) -> None: ...```
yeah, saw that in #bot-commands
should i try and fix that?
that sounds useful, yes
k
I think I know how @overload is used (at least the most basic use), but I'm not sure how to apply that to my fakeprint function. But I can search around to see how it might be done. Thanks.

