#type-hinting
1 messages Β· Page 6 of 1
I have modified enum class's new function to do that
Not always, but many times. Sometimes I only need the EventBase.value member which is detected as Any, so from type checker pov its no error
ok maybe you want to use some kind of fake field function here or Annotated
Fake field function?
it might be too hacky actually thinking about it
I have a decorator that can be applied to a property. Can I type hint my decorator to only accept properties with a particular return type?
currently, properties aren't generic, so you can't do that unless you'd instead use some custom generic property implementation and work with that in your code
as an example, you could do this: ```py
from typing import Generic, TypeVar, Optional, Callable, Any
T = TypeVar("T")
class Property(property, Generic[T]):
"""Generic implementation of a property.
Allows us to provide type annotations on top of builtin properties.
"""
def __init__(
self,
fget: Optional[Callable[[Any], Any]] = None,
fset: Optional[Callable[[Any, Any], None]] = None,
fdel: Optional[Callable[[Any], None]] = None,
doc: Optional[str] = None,
) -> None:
super().__init__(fget, fset, fdel, doc)
def __get__(self, obj: Any, objtype=None) -> T:
return super().__get__(obj, objtype)
def __set__(self, obj: Any, value: T) -> None:
super().__set__(obj, value)
def __delete__(self, obj: Any) -> None:
super().__delete__(obj)
You can remove implementation of all methods and move class body into if TYPE_CHECKING: to get rid of overhead
Thank you! :)
And big thanks for the example code, that looks exactly like what I'm looking forπ
yes, that's right
yes, __init__ doesn't return anything
although in many cases, you can just not type-hint the return type
type-checkers will infer it
you generally shouldn't type-hint it
you could do ```py
class Foo:
def init(self: Foo):
...
Well, actually there's Self
Beat me to it haha
Don't beat yourSelf up
lol
Only python 11 though, which is currently in the release candidate stage
Self ban time
yeah, Self is a thing and it's quite nice, but nevertheless, you generally won't need to type-hint self at all, type-checkers will infer it
you can use typing_extensions
beat me to it
Oh I thought it was only added to typing_extensions in 11 π
it's a module you can install which allows you to use some typing features that aren't yet in typing, basically a bunch of backports
!pypi typing_extensions
Thanks, I use it for NotRequired :)
it was added to typing in 3.11, typing_extensions is an external module, that's only here to give you a way to use some typing features that aren't yet in your python version. This works because most type-checkers have special handling for it
I see, makes sense π
if something gets added to typing_extensions, you'll be able to use it all the way down from 3.7
What is reveal_type(powers)?
If I have a Generic[T] type, and a method of the form (self: MyType[T], fun: Callable[[T], V]) -> MyType[V], I can annotate this as:
def map(self: "MyClass[T]", fun: Callable[[T], V]) -> "MyClass[V]":
But what if I also want, for subclasses of MyClass, this method to return the specific subtype self is, and not MyClass? Something like (self: Self[T], ...) -> Self[V] - except that Self isn't subscriptable.
what you want are higher kinded type-vars, which aren't (yet) supported in python - https://github.com/python/typing/issues/548
why does that always happen whenever I'm trying to type something in python
Thanks!
that doesnt look unknown
it's partially unknown because of the Any, I guess
Can I make a generic alias of sorts, something like Predicate[T] = Callable[[T], bool] except, well, syntactically correct?
oh,
Predicate: TypeAlias = Callable[[T],bool]
works
yep
as an exercise, try making a type alias with the arguments flipped π₯΄
like, py Foo = Callable[[A, B], B] but Foo[int, str] meaning Callalbe[[str, int], int]
i thought pyright would have had that show up as Unknown if it was unknown
that's interesting, how would you do it?
i have no idea
I suppose one could make a callable protocol
you cant do it with a typealias but you can do it with an actual subclass cant you?
yeah, that's what I was thinking too
but that's cheating
and, well, callable is just an example, you could have a different type alias
hmmm, is it possible to make a type alias with two parameters that ignores the first one?
interesting
from random import choice
from typing_extensions import Never
def f(x: tuple[Never, int, str]):
p = choice([0, 1, 2])
if isinstance(x, tuple):
reveal_type(x[p])
I'd hope that tuple[Never, int, str] would be reduced to Never π

Is there any way to represent something like Rust's
fn sum(nums: Vec<T>, start: V) -> T
where T: Add<T, Output=T>, V:Add<T, Output=T>
?
The problem here is requiring the bound of V to contain T, an unrelated typevar. I'm trying this:
SumDefaultT = TypeVar("SumDefaultT", bound = SupportsAdd[SupportsSumWithDefaultT,SupportsSumWithDefaultT])
and getting "TypeVar bound type cannot be generic" from pylance.
not 100% sure what the rust version says, but it seems equivalent to just writing SupportsAdd[..] in the function signature
you can also create a type alias SumDefaultT: TypeAlias = SupportsAdd[...]
or maybe you need to create a typevar just with bound=SupportsAdd and parameterize the typevar
you cant parameterise a typevar
pythons add operator is really hard to type
i remember theres a gist somewhere here that does it most of the way
you would have a similar problem trying to type a Mapping i think
how to say a function returns str or None?
def f() -> ?:
pass
This?
from typing import Protocol, overload
T_contra = TypeVar('T_contra', contravariant=True)
T_co = TypeVar('T_co', covariant=True)
class SupportsAdd(Protocol[T_contra, T_co]):
def __add__(self, x: T_contra) -> T_co: ...
class SupportsRAdd(Protocol[T_contra, T_co]):
def __radd__(self, x: T_contra) -> T_co: ...
@overload
def add(x: SupportsAdd[T_contra, T_co], y: T_contra) -> T_co: ...
@overload
def add(x: T_contra, y: SupportsRAdd[T_contra, T_co]) -> T_co: ...
def add(x, y):
return x + y
maybe
Does anyone know of a type hint i can use to accept any type that supports iteration and len? Basically the intersection of Sized and Iterable
class X(Sized, Iterable, Protocol): ...
haha, good point :P Many thanks!
not very soon, we'll be able to do SizedIterable = Iterable & Sized
can we type hint an optional argument as just None in a typing.overload?
or remove it entirely?
you can remove it
Is there a type hint for "supports comparison operators"? (e.g. Ord)
In my argmin function, mypy requests Union[SupportsDunderLT, SupportsDunderGT], but I can't find SupportsDunderLT, SupportsDunderGT or Ord in the typing library or collections.abc library.
It's probably from _typeshed
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from _typeshed import SupportsRichComparison
def sortit(lst: list, *, key: "SupportsRichComparison" | None = None): ...
stdlib/_typeshed/__init__.pyi line 69
SupportsRichComparison: TypeAlias = SupportsDunderLT[Any] | SupportsDunderGT[Any]```
Huh. So I have to pip install typeshed to get that to work properly? Shame.
I found a workaround by just doing Ord = int which is incorrect but hopefully communicates what I meant to any human who might read it (unlikely to be anybody since this is a personal project)
depends on whether it's optional (as in, can be missing when calling), or it's Optional (as in, can be None).
the two possibilities have an intersection π
No you don't. mypy/pyright knows about typeshed, and with if TYPE_CHECKING: you'll only import it when using mypy/pyright
in fact, you can't pip install typeshed. You can only get it bundled with your typechecker
I assume mypy clones the git repo when bundling?
it just has a copy of the stubs cause the _typeshed.pyi file is included in typeshed
that gets tricky real fast π
What kinds of errors does it catch that type checkers wouldn't?
although, I guess, it lets you annotate parameters with extra checks
def hex2rgb(hex: str) -> tuple[float, float, float]:
rgb = hex[1:]
if len(rgb) == 6:
red, green, blue = rgb[0:2], rgb[2:4], rgb[4:6]
elif len(rgb) == 3:
red, green, blue = rgb[0] * 2, rgb[1] * 2, rgb[2] * 2
else:
raise ValueError(f"Invalid value {hex} provided for rgb color.")
return tuple(float(int(v, 16)) / 255 for v in (red, green, blue))
pyright is smart enough, but mypy? meh
Incompatible return value type (got "Tuple[float, ...]", expected "Tuple[float, float, float]")
its right in front of mypy that the return value will have 3 floats
why is it so dumb
Well, this might be a workaround xD
r, g, b = (float(int(v, 16)) / 255 for v in (red, green, blue))
return r, g, b
seriously π€¨
but yeah in general I find that pyright is much better at type inference than mypy
mypy should die a horrible death for its sins
is there a SupportsStr protocol?
actually pyright just doesn't check this. It also gives no errors if you use a 4-tuple
no everything supports str
Eric would probably say he doesn't want to special-case the tuple() constructor
whose eric
eric is one of the primary maintainers of pyright
hmm
On one hand, it sucks. On the other hands, there's enough clutches and special cases π
is Type[Union[A, B]] equivalent to Union[Type[A], Type[B]]?
Type[Union[A, B]] is not meaningful. Type[] is only supposed to work with concrete types
That's what I thought, but why does this work? ```py
from typing import TypeVar, Type, Union
class A:
x = 1
class B:
y = 2
OBJ = TypeVar("OBJ", bound=Union[A, B])
def get(obj: Type[OBJ]) -> OBJ:
return obj()
get(A).x # Pass
get(B).y # Pass
get(B).x # Fail
TypeVars are explicitly allowed in Type[]
So if I want to recreate Type[Union[...]] I can just use a typevar?
Because I have a union of 10-15 types that I would really like to shorten ```py
Union[Type[A] | Type[B] | Type[C] | Type[D] | Type[E] | Type[F] | Type[G] | Type[H] | Type[I] | Type[J]]
K = TypeVar("K", bound=Union[A, B, C, D, E, F, G, H, I, J])
Type[K]
in python 3.10+, what is from __future__ import annotations used for?
from __future__ import annotations was previously scheduled to become mandatory in Python 3.10, but the Python Steering Council twice decided to delay the change (announcement for Python 3.10; announcement for Python 3.11). No final decision has been made yet. See also PEP 563 and PEP 649.
delayed evaluation of type annotations
I know it encases typehints in quotes, but how is that useful for 3.10
you can declare a class used in type annotation anywhere (above or below). that's not possible without delayed eval
def a(k: Klass): ... # Used above
class Klass: ... # Defined below
True, hm, thanks
is there any active proposal to add type hints for a "const" variable and/or attribute? something like this:
x: Const[float] = 3.5
class Thing:
y: Const[ClassVar[int]] = 12
z: Const[str]
def __init__():
z = "hello" # Can be assigned exactly once, in __init__
thing = Thing()
x = 4.5 # Error
Thing.y = 11 # Error
thing.z = "bye" # Error
this doesnt that look that different from Final
I think only the "assign once in init" is different
The dataclass InitVar can be repurposed for this purpose. Its similar to C# readonly
This is close
i thought Final was specifically meant to be used for class inheritance. is this an "off-label" use?
oh hey it works with mypy too
i'm ok with it not checking inside instance methods
preventing assignment from outside is good enough
thanks for the tip!
it would be interesting if there was something like a module-global attribute you could set that would enable Final by default for all attributes not annotated as Mutable or something like that
hmm tbh I'm not seeing how that's related
because these are both of the same type, Base
you are assigning a dict[str, Base] to a dict[str, Base | something]
dict is invariant in its value type
so you get an error
gotcha
I as the programmer know that the first one is a subset of the second one, is there a way to indicate this to pylance?
this is my current setup
cleaners.py: ```py
class Base:
pass
class Dictionary(Base):
def init(self, structure: dict[typing.Any, dict | Base]):
pass
other_file.py ```py
from . import cleaners
class Options:
def __init__(self, url: dict[str, cleaners.Base] | cleaners.Base | None):
if isinstance(url, dict):
x = cleaners.Dictionary(url)```
the typing of the arguments is correct in both of these places, I don't want to change the argument type itself
use Mapping instead of dict, it's covariant in the value type
invariance 
thanks that worked
Hmmm how would that work? I mean, for arbitrary objects
Is it possible to configure pyright so that it produces an error when a public instance variable is not declared at the top level of the class? I'm looking for something stricter than reportUninitializedInstanceVariable, which still allows instance variables to be "declared" inside __init__ via assignment
Is it possible to set custom rules for pyright?
wdym by custom rules?
integrate some set of rules into static linter so I could do some of my own ast checking and make the pyright result pass or fail.
For example, I want to make it so that no variables have a name that starts with a, so I would make a node visitor that visits Name blocks with Store context and appropriately raise problems, is there a way to integrate this to existing solutions such as pylance?
I think this can be done in the case of mypy, but I specifically wanted to do this with pylance
sounds like a flake8 plugin
but no, pyright doesn't support plugins
How can I get this working?
@overload
def subdict(self, select: Callable[[AnyEvent], bool], repeat: Literal[1]) -> EventDict: ...
@overload
def subdict(self, select: Callable[[AnyEvent], bool | None], repeat: int) -> Iterator[EventDict]: ...
ok so this worked
@overload
def subdict(self, select: Callable[[AnyEvent], bool], repeat: Literal["dont"]) -> EventDict: ...
@overload
def subdict(self, select: Callable[[AnyEvent], bool | None], repeat: int) -> Iterator[EventDict]: ...
but how do I avoid re-annotating the impl function again?
Why not make two different methods?
can you show the implementation?
what is the method supposed to do?
@overload
def subdict(
self, select: Callable[[AnyEvent], bool], repeat: Literal["dont"]
) -> EventDict:
...
@overload
def subdict(
self, select: Callable[[AnyEvent], bool | None], repeat: int
) -> Iterator[EventDict]:
...
def subdict(self, select: Any, repeat: int | Literal["dont"] = "dont"):
"""Yields EventDict(s) till ``select`` and ``repeat`` are satisfied.
Args:
select: Called for every event in this dictionary by iterating over
a chained, sorted list. Returns True if event must be included.
Once it returns False, rest of them are ignored and resulting
EventDict is returned. Return None to skip an event.
repeat: Use -1 for infinite iterations. Defaults to "dont".
"""
el: list[IndexedEvent] = []
if repeat == "dont":
for ie in chain.from_iterable(self.dct.values()):
if select(ie.e):
el.append(ie)
return EventDict(self, el)
for ie in chain.from_iterable(self.dct.values()):
if not repeat:
return
result = select(ie.e)
if result:
el.append(ie)
elif result is False:
yield EventDict(self, el)
el = []
repeat -= 1
I am also getting type annotation errors for the return type of impl function
well that's not correct, the function returns an iterator in any case
huh
import random
def foo():
if random.random() > 0.5:
return 42
else:
yield from [1, 2, 3]
hmm = foo()
print(hmm)
what do you expect in the output?
Do you know what return does in a generator function?
Like
def foo():
yield 1
return 2
because yield returns an Iterator which requires next to get the next value
now i am confused
so how will i get [1, 2, 3]? by repeatedly calling foo() (considering the else branch is always occured?
or do I need to call next(foo())
If you don't I recommend seeing this:
https://stackoverflow.com/questions/16780002/return-in-generator-together-with-yield
A function is a generator function if it has a yield in it. It doesn't conditionally return a generator or a non-generator
So this is a generator function which yields zero elements:
def empty():
if False:
yield "delicious"
back to your question: why not make two methods?
one searches for a single event, another one searches for multiple events
i am confused about what I name them π
can you explain what it does?
that's why I made one
Well, you can avoid function naming entirely if your program consists of a single main() function π
well behavior of the method is decided by repeat
it returns a "dictview". hard to explain, would you like to see the impl?
sure
ahh well forgot to include IndexedEvent
@dataclass(order=True)
class IndexedEvent:
r: int
"""Root index of occurence of :attr:`e`."""
e: AnyEvent = field(compare=False)
"""The indexed event."""
I don't see how it preserves insertion order
the r attribute of IndexedEvent preserves insertion order
so when all the dict values are chained together they can be sorted by using r as the key
ahh god
pls don't make me explain that again π₯
it will take a good 30 mins π₯
well, there may be a tool that saves you from explaining everything every time
I think it's called "documentation" π
but tldr - I want a dict-like "lookup" with list-like "insertion / deletion" with a hierarchy such that whenever a child dict is modified it modifies all its parents
well i did my best in the docstrings
can you show an example maybe?
You documented the low-level details, which are frankly already clear from the names and such. But you left out the high-level explanation.
Ok, so lets begin with my ted talk π
Maybe you want a collections.OrderedDict?
If there's a list like this:
lst = [Event(A, 1), Event(B, 1), Event(A, 2)]
and I create an EventDict, say ed out of it whose attr dct looks like this:
dct = {A: (0, [Event(A, 1)), (2, Event(A, 2))], 2: [(1, Event(B, 1)]}
where the tuple represents an IndexedEvent.
Observe that the keys are nothing but "event IDs" (i.e. the first arg of Event).
and first member of tuple (a.k.a. root index) represents index of that event in lst.
If I modify the dict like append or delete (its a dict but by looking the keys are just for lookup, so its actually as good as a list)
ed.insert(0, Event(B, 2))
I use the root index to keep the "real" order. Once I want to reconstruct the dict into a list I just chain all values of the dict and sort by the root index.
lst = [Event(B, 2), Event(A, 1), Event(B, 1), Event(A, 2)]
@trim tangle
its like a tree, list, and dict all in one whatever idc π
please reply now, that really takes time to type
have you seen my suggestion about multidict?
it sounds exactly like what you want
it's used by HTTP libraries to store HTTP headers, which are ordered and indexed by header name
can i reconstruct the list with the order?
multidict doesn't allow for mutable views (MultiDict.getall returns a list, which unfortunately doesn't mirror back the changes like additions or removals into the parent dict)
so no
filter_one, filter_many?
I just named them subdict and subdicts now
what's the difference between tuple[int] and tuple[int, int]?
(42,) is a tuple[int], and (42, 69) is a tuple[int, int]
ohh
can we use those for checks like let's say
def check(bar: tuple[int, int]):
# instead of doing this
if isinstance(bar, tuple) and len(bar) == 2:
...
# do (something like) this
if type(bar) == tuple[int, int]:
...
the second one definitely not. Some type checkers may accept the first
ah i see
yeah runtime checks aren't really a thing
how to type hint a very generic decorator so that it doesn't erase the type annotation of the function it decorates?
type hint it in such a way that input and output type annotations should be same
You could use a function typevar
Func = TypeVar("Func", bound=Callable[..., Any])
def foo(func: Func) -> Func:
def decorator(*args, **kwargs):
return foo(*args, **kwargs)
return decorator
otherwise there's ```py
P = ParamSpec("P")
R = TypeVar("R")
def foo(func: Callable[P, R]) -> Callable[P, R]:
...
If I use exec to make some functions type checkers are in the dark even if there's type hints right?
yes
well pyanalyze might be able to handle it
but i dont think anything else could
yeah, pyanalyze could handle it for global functions
but generally doing so isnt recommended anyway
well, dataclasses does it π
but agree it's not something you should frequently do
dataclasses have an actual typing representation now tbf
what do you mean by this?
I just decided to print them and copy paste instead of exec thanks
How would you know if a method is "mutating" or not?
oh, i was thinking that any and all assignments to a Const instance attribute would be prohibited, outside of exactly one assignment in __init__ as a hard-coded special case
!e
from __future__ import annotations
from typing import TypeVar
import ctypes
flags_offset = 21 * ctypes.sizeof(ctypes.c_size_t)
py_tpflags_basetype = 1 << 10
CT = TypeVar('CT', bound=type)
def final(cls: CT) -> CT:
ctypes.c_size_t.from_address(id(cls) + flags_offset).value ^= py_tpflags_basetype
return cls
@final
class X: ...
class Y(X): ...
@tranquil turtle :x: Your 3.11 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 18, in <module>
003 | TypeError: type 'X' is not an acceptable base type
ohh you mean that
I thought you meant, like, disallowing append on a const list
oh yeah no
i think javascript has the same "hole" in its const annotation
the variable is "constant", not the value therein
yeah i was just about to say isnt this something you can still do in js
same with Java's final
because, well, it's generally undecidable
simplest case: the attribute is of an abstract type -- you literally don't know if a given method is mutating it
Though typescript has as const for literals. It's not enforced at runtime, but the type checker does
I think the benefits of true consts only really apply to compiled languages
Is a constant in Python (variable which is never changed) inlined everywhere its used?
It's not about speed, more about reasoning about your program
Well, we're talking about a different thing I think
Oh I didn't see this channel!
I've compromised with a different solution but would like to do what I discussed here:
#help-burrito message
Would appreciate it if someone wanted to take a look
Speed has become a real problem for me in past few days using python π
My test suit nosedived into taking 6 seconds for what took 0.8-1 π
You can use annotations without assignment, and then evaluate annotations to get subclass of BaseConfig
class MyClass(BaseClass):
config: SomeConfig
def __init__(self):
self.config = typing.get_annotations(self.__class__)['config'].from_env()
Yeah thatβs a different solution for my compromise but how would I do this with config_class
class BaseClass:
def __init_subclass__(cls):
cls._config_cls = typing.get_annotations()['config']
class MyClass(BaseClass):
config: SomeConfig
# _config_cls = SomeConfig # will be assigned at MyClass class creation time
def __init__(self):
self.config = self._config_cls.from_env()
I dont think it is possible now
For typescript enum const, yes
how do I fix this??
im trying to add a [str, str] key value pair to dict[str, Any]
i think params is a dict[str, dict[str, Any]]
aha, i c the problem, thanks :D
question: what is the general type-hint for AST nodes?
browsing the source for ast apparently they all subclass ast.AST but pycharm still complains
ast.AST is right
thanks, so i'm guessing it's the .pyi stubs that are wrongly generated
possibly, or there's something wrong in your code or in pycharm
pycharm's type checker is spotty sometimes
If it works in mypy, maybe open a bug report on youtrack
Any idea what may cause mypy to complain about the same types?
>>> "Incompatible return value type (got "Optional[List[StatementLine]]", expected "Optional[List[StatementLine]]")mypy(error)"
Couldn't replicate this with simpler objects.
are there multiple classes named StatementLine?
no, just this one (it's a NamedTuple)
can you replicate the error on https://mypy-play.net?
not really. I've tried quite a few combinations to replicate this, but no success, I was wondering if there was a known setup that would cause this type of behavior
now thinking about it again, my best bet is that this is related to the cache, since this is an intermittent issue, and hard to replicate
can you show the code maybe?
at least the imports
oh wait, intermittent
yeah that's what you get when you solve a declarative task in a very imperative way π
Similar issue: #type-hinting message
That's different
Is parsing Annotated types slow?
that question isn't easy to answer. There's nothing extremely slow in the Annotated implementation, but I'm sure there's things you can do with it that could be slow
wow mypy v0.981 is a huuuge improvement
finally going through the release blog post
really exciting stuff in here
callable attributes, recursive type aliases, typeddict kwargs
also "implicit" abstract methods in protocols is a feature that i didn't even know i wanted until right now
Have a link?
Weβve just uploaded mypy 0.981 to the Python Package Index ( PyPI ). Mypy is a static type checker for Python. This release includes ne...
i've lately started putting a link to the changelog for every dependency in my projects
The callable instance attributes are big
I can finally remove the majority of my type ignores
Yea totally, its on the runtime use
Is mypy really this dumb
well, technically those signatures are incompatible
the top one will accept name=42 while the bottom one won't
what's the use of Concatenate?
It's for ParamSpec
https://peps.python.org/pep-0612/
oh, so it's like used to "append" any other additional types to a Callable?
prepend only, but yes
ah, I see, thank you!
Why does a TypeVar need us to type a name twice? Can't object.__set_name__ be used by it?
that only works for object attributes
Does it?
I think
It takes an instance and a name arg
!e ```py
am I doing this right?
class a:
def set_name(self, obj, n):
print(n)
x = a()
class b:
c = a()
@rare scarab :white_check_mark: Your 3.11 eval job has completed with return code 0.
c
There is a PEP that proposes to add new syntax for type variables
It should work for modules too ig since modules are nothing but objects after all
it's owner, not instance.
yes
Can we allow overriding methods to have different arg names if we prefix the arg names with two underscores?
Have seen that quite a lot in typeshed.pyi
the double underscore thing means the argument is positional-only
it's a hack for parsers that don't support the / syntax from PEP 570
!pep 570
3.8
It was * before instead of / wasn't it?
- is for kwonly args
It just came like itself, like how / is just a single symbol
* makes things after it keywords only, / makes things before it positional only.
Like (a, *, b)
Don't they have the same effect ultimately 
Oh positional only means you can't pass them by kwargs at all?
Yeah.
def func(a, /, *, b): ...
func("a", b="b")
Which is useful because def func(spam, /, **kwargs) can accept func("eggs", eggs=4, kwargs=6) - since it's positional, that's not ambiguous.
What is the advantage of * and /
Something like dict.update() needs that to ensure all possible keywords are still valid.
Caller should be smart enough
example with dict.update. d.update(d2, x=y) is not valid (I think). You can make sure this doesn't happen by using * and / (though it's technically kwargs)
* is useful to force passing by keywords, to avoid being able to call a function like func("first", "second", 2, -1, True, False) - it's very unobvious what all those parameters mean. If you have to pass by keyword it's more clear.
Yeaa right
It sometimes happens to me as well, I get confused whether I should pass by kwarg or not
Enforcing it can be a good thing
But is this a type hint feature or python feature?
Python feature.
Python. Was added in 3.0
Hm
As seen above / was added in 3.8.
can you clarify what you want exactly?
I want to use TypedDict for ctor kwargs, making it a typevar will help to avoid casting it from a plain dict everytime
!pep 692
Because in child classes I can't change the type and even if I do it still gets detected as Any
anyone know how to use param spec with pyre?
i actually cant figure out how to make the revealed type show anything useful
i also cant figure out why this doesnt work
even with pyre_extensions
does it not support ParamSpec?
isnt it Callable[P, int] not Callable[[P], int, maybe thats why it expects P to be a type
hah, normally gobot is correcting me on my misuses of paramspec
Maybe trying to do this stuff at 1 o'clock isn't a good idea
I am trying to build dataclasses like classes for binary structs. (PEP593 inspired)
which look like this:
@dataclasses.dataclass # Get all the dataclass goodies bundled
class MyStruct(Struct):
int1: u8
bool1: b00l
Impl: https://paste.pythondiscord.com/ixorizawar
However only basic types work, I need to work on the Enum (adapter / converter) for example.
I don't know what type hinting stuff I should use to make it work like this (I think __class_getitem__ might be useful):
class MyEnum(enum.IntEnum):
A = 1
@dataclasses.dataclass # Get all the dataclass goodies bundled
class MyStruct(Struct):
int1: u8
bool1: b00l
enum_u8: Enum[u8, MyEnum]
such that enum_u8 will get parsed as u8 and returned as MyEnum?
Should I make use of the attrs package for the conversion part (dunno how, but should I?)
I am even thinking if I could just use an int like an annotated type int[bytes=1, endian="little"] so that, the types like u8 etc aren't needed and it looks more Pythonic?
also if there's by any chance a maintained library already doing what I am trying to do, please let me know
that's invalid syntax
you could make your own generic Enum class to support this, but it won't do conversions for you
for that maybe add another decorator that wraps or stacks on top of dataclass
Yea I kinda did:
class Enum(Generic[T, E]):
def __class_getitem__(cls, item: tuple[T, E]):
...
but I am not sure what code, if any I should put there (or if even this is valid)
you don't need an explicit __class_getitem__, Generic does that for you
oh
yea that makes sense
how to detect annotated types by isinstance, the docs mention something about it, but i think it checks forT of Annotated[T, x]
Generic enum breaks the behaviour
You can't then access members by name dynamically using getitem
any way i can do this?
T = TypeVar("T")
U = TypeVar("U")
V = Union[T, U]
class Vec(Generic[V]):
def __new__(cls, *args, **kwargs):
# how can i know which variant the Vec is?
...
vec = Vec[T]()
i dont understand the intent behind
T = TypeVar("T")
U = TypeVar("U")
V = Union[T, U]
class Vec(Generic[V]):
unless you want to use byte code inspection you cant in __new__ unless you implement your own generic alias subclass and alter the signature of Vec.__new__ to take the subclassed generic alias as a parameter
one day π€youll be able to just do ```py
class Vec[V]:
def new(cls, *args, **kwargs) -> Self:
type_param: type[V] = V.value
or something
to show that Vec can be generic against T or U in typing ofc
Can you give an example of what that would mean?
vec1 = Vec[T]()
vec2 = Vec[U]()
What's the difference between the two?
and, well, Vec[T]() isn't valid on its own because T doesn't mean anything
vec1's inner buffer stores smth else
hm?
T typevar
from __future__ import annotations
from array import array
from typing import Union
from typing import Generic
from typing import TypeVar
b = TypeVar("b", bound=int) # 'b' signed integer 1
B = TypeVar("B", bound=int) # 'B' unsigned integer 1
u = TypeVar("u", bound=int) # 'u' Unicode character 2 (see note)
h = TypeVar("h", bound=int) # 'h' signed integer 2
H = TypeVar("H", bound=int) # 'H' unsigned integer 2
i = TypeVar("i", bound=int) # 'i' signed integer 2
I = TypeVar("I", bound=int) # 'I' unsigned integer 2
l = TypeVar("l", bound=int) # 'l' signed integer 4
L = TypeVar("L", bound=int) # 'L' unsigned integer 4
q = TypeVar("q", bound=int) # 'q' signed integer 8 (see note)
Q = TypeVar("Q", bound=int) # 'Q' unsigned integer 8 (see note)
f = TypeVar("f", bound=float) # 'f' floating point 4
d = TypeVar("d", bound=float) # 'd' floating point 8
T = Union[b, B, u, h, H, i, I, l, L, q, Q, f, d]
class Vec(Generic[T]):
def __new__(cls, *args, **kwargs) -> Vec[T]:
return super().__new__(cls)
def __init__(self, len_: int) -> None:
self.__buffer = array()
self.__len = 0
@classmethod
def new(cls) -> Vec[T]:
return cls()
v = Vec[]
cursed ik
That's now how typevars are supposed to be used
A typevar is for linking two types together
Type variables are used for polymorphism, which in this case means that b could stand for any type that's "at least an integer", rather than a specific signed integer type
You probably want something like
@dataclass(frozen=True)
class ElementType(Generic[T]):
code: str
size: int
python_type: type[T]
u8 = ElementType("b", 1, int)
u16 = ElementType("H", 1, int)
``` and then you can call `Vec(42, u8)`
ooooo
maybe you could show how you'd actually use the class?
i like that yeah yeah
have no ideas yet
just fcking around, finding out stuff
experrimenting
Would this be possible? I always forget how much Enums "let" you do
@dataclass(frozen=True)
class ElementType(Generic[T]):
code: str
size: int
python_type: type[T]
class Dtype(Enum):
b = ElementType("b", 1, int)
H = ElementType("H", 1, int)
...
class Vec:
@classmethod
def new(cls, dtype: Dtype): ...
v = Vec.new(Dtype.H)
i dont see why not
though ideally you'd want it to be more like
class Vec(Generic[T]):
@classmethod
def new(cls, dtype: Dtype & ElementType[T]): ...
if you want to be able to handle inputs
like telling vec.append(1) apart from vec.append(1.2)
you cant get exact type without overloading __class_getitem__, or overloading metaclass.__getitem__ or looking at __orig_class__ or explicitly passing this type to constuctor
alr thanks
in some place i have this code:
@classmethod
@functools.cache
def __class_getitem__(cls: TT, key: T) -> TT:
assert not isinstance(key, tuple), 'tuple parametrization is not implemented'
if isinstance(key, TypeVar):
return cls
# return types.GenericAlias(cls, key) # type: ignore[return-value]
key_name = key.__name__ if hasattr(key, '__name__') else ''.join(filter(str.isalnum, repr(key))) # type: ignore[attr-defined]
return types.new_class( # type: ignore[return-value]
f'{cls.__name__}_{key_name}',
(cls,),
None,
lambda ns: ns.update({'__generic_type__': key}),
)
it creates subclass with class variable __generic_type__, and i can get it from instance if i want
in this case i have almost no overhead on instance creating time
# pseudocode:
>>> X[int]
<class 'X_int'>
>>> x = X[int]()
<'X_int' instance>
>>> x.__generic_type__
<class 'int'>
i love this
ah
it does not make sense in my case, so i didnt implemented it
It kind of reminds me of a java TypeToken
i love __slots__, so i have no space for __orig_class__ attr
Tried this? ```py
def class_getitem(cls, key):
class X(cls):
generic_type = key
X.name += "_" + keyname
return X
it does the same thing as types.new_class
but it uses familiar syntax
i dont care, it is implementation detail
why do we need that when we can just type("A", (), {})
it is almost the same as class statement and types.new_class
i just chose one of three options
ah i see
you could also do
key_name = getattr(key, "__name__", "".join(filter(str.isalnum, repr(key))))
cool
def __class_getitem__(cls, key):
key_name = getattr(key, "__name__", "".join(filter(str.isalnum, repr(key))))
return type(f"{cls.__name__}_{key_name}", (cls,), {"__generic_type__": key})
this is good
it shouldnt create new class, if you are parametrizing it with typevars for type-hinting
i will keep that in mind
T = TypeVar('T')
TT = TypeVar('TT', bound=type)
class GenericMixin(Generic[T]):
__slots__ = ()
__generic_type__: type[T]
# __generic_type__: ClassVar[type[T]] # mypy is angry about this
@classmethod
@functools.cache
def __class_getitem__(cls: TT, key: T) -> TT:
assert not isinstance(key, tuple), 'tuples not implemented'
if isinstance(key, TypeVar):
return cls # should i return types.GenericAlias instead?
# return types.GenericAlias(cls, key) # type: ignore[return-value]
key_name = getattr(key, '__name__', ''.join(filter(str.isalnum, repr(key))))
return types.new_class( # type: ignore[return-value]
f'{cls.__name__}_{key_name}',
(cls,),
None,
lambda ns: ns.update(
{
'__generic_type__': key,
'__module__': cls.__module__,
}
),
)
``` complete code
orig_class is hardly that big right?
it is hard to access generic attr from __orig_class__
you should do self.__orig_class__.__origin__.__args__[0]
im my case i can do self.__generic_type__
nice bonus: it is faster and dont require extra space in __slots__
I'm confused you know you aren't setting slots here
bruh, my bad π
why ClassVar[...] cannot contain typevars?
PEP 526:
Note that a
ClassVarparameter cannot include any type variables, regardless of the level of nesting:ClassVar[T]andClassVar[List[Set[T]]]are both invalid ifTis a type variable.
it is not true, sometimes it does make sense and it is useful
It only makes sense if you have a generic base class that's subclassed in a concrete form
which yes, might be useful, but currently there's no way to restrict it to that
or maybe you have something different in mind?
there was talk of removing that in the last typing meeting
no, i mean exactly what you said
Hi! I'd like to make the Result enum from rust in python. And I have I problem: how can I type hint value?
class Result(Generic[T, E]):
success: bool
value: T if success else E
(please ping me)
value: T | E
Is there any better solution? T | E ignores success
class Result(Generic[T, E]):
value: T | E
class Ok(Result[T, Any]):
value: T
class Err(Result[Any, E]):
value: E
``` in this case you dont need `success` at all
it is (almost) impossible to check using static analysis, so you should do it in different way
@dataclass(frozen=True)
class Ok(Generic[T]):
value: T
@dataclass(frozen=True)
class Err(Generic[E]):
error: E
Result = Ok[T] | Err[E]
def read_file(path: str) -> Result[str, IOError]:
...
This is better than a single class because it plays nicely with match
Ok. Thanks!
why not subclass Ok and Err from common base class?
Why would you?
why
Python doesn't have sealed classes so that's not very useful
How do I hint
{1: [[2000, 981], [2060, 981], [2120, 981], [2180, 981]],
2: [[2000, 1037], [2060, 1037], [2120, 1037], [2180, 1037]],
3: [[2000, 1093], [2060, 1093], [2120, 1093], [2180, 1093]],
4: [[2000, 1149], [2060, 1149], [2120, 1149], [2180, 1149]],
5: [[2000, 1205], [2060, 1205], [2120, 1205], [2180, 1205]],
6: [[2000, 1261], [2060, 1261], [2120, 1261], [2180, 1261]],
7: [[2000, 1317], [2060, 1317], [2120, 1317], [2180, 1317]]}
I'm trying to do this, but it's highlighting that there's a problem.
dict[int:list[[int,int]]]
nvm...
dict[int:list[list[int,int]]]
you need to use a , not :
oh?
dict[str, list[tuple[int, int]]]
you'll have to use tuples instead of lists though
although that looks like a strange structure. what does it represent?
It would probably be better if you introduced some kind of dataclass, because a 2-tuple doesn't mean anything
@trim tangle it's representing row and columns of pixels.
whats the default type for an unknown TypeVarTuple? i was under the impression it was *tuple[Any, ...] but im being told by eric its *tuple[()] but i cant see either mentioned in pep 646
wait it is mentioned https://peps.python.org/pep-0646/#behaviour-when-type-parameters-are-not-specified
Python Enhancement Proposals (PEPs)
I am beginning to think, what Annotated tries to achieve can already be done by descriptors, why was it created when descriptors exist?
Anyways the metadata of Annotated is used at runtime, so descriptors can do stuff like "ValueRange" etc
@something
def receive_thing(foo: Annotated[int, U8]) -> None:
...
I think that can be used with discord.py for example to specify a converter for each argument
Why is that required 
Could might as well be int
Or a custom class called U8 deriving from int?
Or just a NewType?
There might be more options. Like Annotated[int, {"size": 8, "endian": "big}]
Just wanted someone to double check in case I am going mad but when updating an old existing type hint of the below to 3.10
def _parse_args(
self, to_parse: str, /
) -> tuple[
Dict[str, Dict[str, Union[int, str, List[str], None, Dict[str, Union[int, str]], Dict[str, int | list[str]]]]],
Dict[str, str],
]:
It would simply become
tuple[dict[str, dict[str, int | str | list[str] | None | dict[str, int | str | list[str]]]], dict[str, str]]:
Or am I missing something?
Sounds about right but it's an extremely cursed type hint
You should probably introduce some dataclasses at least
I think you need some type aliases
I don't think it's going to make it better
Wow yeah this is-
you should probably use a TypedDict at the very least if possible
Thanks, I don't entirely hate it
Hey, sorry to bother but with 3.11 arriving, do you have a rough estimation for when mypy will support typing.Self? I've been loosely following https://github.com/python/mypy/pull/13133 for the past couples months and I was just wondering if I should expect it to be ready days/weeks/months from now.
FWIW if the PR is (at least mostly) working atm I would be fine with installing against your fork until it's merged.
@soft matrix are there any TypedDict available for type.__new__'s ns (namespace) arg?
You're correct in saying it's mostly working I'm just working on tests and I'm currently just a little busy with studies and am trying to chip away at the failing tests. I'll say it will hopefully be done within a few weeks
This kinda goes against the whole point of dynamically creating a type at runtime, this could almost certainly be solved with an intersection type at some point
How to get the enclosing class's (analogous to owner class in descriptors) type hint in __class_getitem__ or should I use a descriptor for that?
What would the enclosing class be in this scenario? A[B[C]]
I am a bit confused by that term overall
Say I have an union like SomeGeneric[Literal["a"], int] | SomeGeneric[Literal["b"], str], is there any way to have a function take the literal string and return the appropriate type that's paired in the generic?
I don't think so, that would require intersection types
Can you provide more context?
I'm working with a lib that does something like this
class PipelineAction:
name: str
def chain(self, other_action): 0
# chains another action with its own name
def run(self): 0
# run all chained actions and map their values to names specified by name
def get_by_name(self, name: str): 0
# get a value by name from all the chained actions
def create_value(self) -> SomeType: 0
# called by run to create the value
but currently the types from create_value just get lost when get_by_name is used to extract the value
Would you mind providing a link to this lib?
This is such a strange way to write defaults
What is up with the 0 return type?
I also thought that it's return type at first but look closer: it's the body
... or pass would be better suited for that.
Actually no. Docstrings would be perfect
I've been working in typescript, which uses : as the return type indicator
Because he has a comment for each one function anyways, he can use docstrings for the body
unless it's a function type declaration, then it's =>
does pyright not understand @no_type_check
no its a typing function
@typing.no_type_check```
Decorator to indicate that annotations are not type hints.
This works as class or function [decorator](https://docs.python.org/3/glossary.html#term-decorator). With a class, it applies recursively to all methods defined in that class (but not to methods defined in its superclasses or subclasses).
This mutates the function(s) in place.
I intentionally don't support no_type_check. There are many ways to address type errors, and all of them are better than disabling type checking for entire functions.
Consider using typing.Annotation instead?
Ok so I have this dependency which provides type hints (of a class) only for __new__ and not __init__, atleast as of latest release
This is a mistake dev fixed probably in latest commit, but till then I have to opt out of type checks
then use # type: ignore
It doesn't stop pyright from complaining where the class is used
What's the dependency?
!pip construct-typing
yes
last commit to the file Adapter is in was May last year
construct-stubs/core.pyi line 187
class Adapter(```
Oh, I was looking in the wrong folder
You could pip install the repo
pip install git+https://github.com/timrid/construct-typing
i generally hesitate installing ultra bleeding edge versions like that
then you'll have to deal with pyright errors until an update comes
construct-stubs/core.pyi lines 190 to 192
def __init__(
self, subcon: Construct[SubconParsedType, SubconBuildTypes]
) -> None: ...```
That doesn't allow for more args
So this doesn't cover your issue? https://github.com/timrid/construct-typing/issues/13
with overridden, yes, as long as you can call the original (though that's optional)
only temporarially (:p)
i did the pip install it didn't install in the venv
add -U
hangon
Collecting git+https://github.com/timrid/construct-typing
Cloning https://github.com/timrid/construct-typing to c:\users\\appdata\local\temp\pip-req-build-ix8lxspx
Running command git clone --filter=blob:none --quiet https://github.com/timrid/construct-typing 'C:\Users\\AppData\Local\Temp\pip-req-build-ix8lxspx'
Resolved https://github.com/timrid/construct-typing to commit e10938f30838a13f27cae3c0ff5865ee4606b847
Preparing metadata (setup.py) ... done
Requirement already satisfied: construct==2.10.67 in f:\prog\python\pyflp\venv\lib\site-packages (from construct-typing==0.5.2) (2.10.67)
@rare scarab
add -f
What even is type-hinting
Hinting types of objects
m nice
Mainly for those who read the code and type checkers
see #type-hinting message and the links there, as well as the rest of the pins
You also get attribute suggestions if your editor has intellisence
def foo(s): ...
``` what type is s?
LOL thats funny, i have seen people use this and kinda followed it but dident even know what it was.
( i turned off ping)
Depends what are you taking from s...
would VsCode count?
LAMO
If you have the right plugin then yes
I want to make a CLI wrapper for pyright to give --quiet output like pytest. Anyone know which libraries would help? Rich and an option parser? Is there something already made for doing this?
Wouldn't a bash script work
Why there is no Sized in bases?
it's not necessary because Sized is a Protocol
though I don't understand that comment, there's actually a PR right now to change it
stdlib/typing.pyi line 328
class Sized(Protocol, metaclass=ABCMeta):```
`stdlib/typing.pyi` line 456
```pyi
# Implement Sized (but don't have it as a base class).```
isnt is easier to just add Sized to bases? What was the reason to not add it to bases?
Is there anything in the git blame?
https://github.com/python/typeshed/commit/de50614957f3f599a6abe403d5508e9c53f8ad21
https://github.com/python/typeshed/issues/2655
Instead define abstract len in affected classes.
Fixes #2655 without breaking
https://github.com/rominf/ordered-set-stubs/issues/1
Hey guys. How can I correctly type hint things in my project when I use mysql.connect which does not seem to use type hints in its own code. My current approach for the connection and cursor objects looks like this
from mysql.connector import MySQLConnection
from mysql.connector.cursor import CursorBase
db: MySQLConnection = MySQLConnection(**db_config)
cursor: CursorBase = db.cursor()
Whereas for the other objects, mostly just results of the fetch*() methods, I inspected what I would get as a result and just set a type hint for my variable based on that whilst also adding a # type: ignore so that my linter wouldn't keep bothering me because all it knows is that the fetch*() methods all return None, which they don't always do, e.g.
cursor.execute("SHOW TABLES FROM table")
tables: list[tuple[str]] = cursor.fetchall() #type: ignore
But especially this part feels very prone to errors. Is there any better way? Thanks in advance.
that seems difficult, since the type system can't really know how many columns a specific table would have. the type of the return differs based on a string, so the type system wouldn't be able to do anything, i think. as far as manually doing it, i have no idea
there's only so much you can do here. the best thing you can do in the short term is tell mypy to ignore imports from that module (there's a setting for it). you can work around this by explicitly annotating results like tables: list[tuple[str, ...]] as you did here.
longer-term, you can write your own type stubs for the relevant subset of the library that you use.
you might need to create a "shell" around the untyped library using typing.cast, assert, isinstance, and other "type-narrowing" functionality
How can I correctly type new attributes that I add to a log record using get/setLogRecordFactory?
I tried this but it fails
is it possible to provide types for generic methods when subclassing? ```py
E = TypeVar('E')
T = TypeVar('T')
S_co = TypeVar('S_co', bound='PageSource', covariant=True)
V_contra = TypeVar('V_contra', bound='PaginatorView', contravariant=True)
class PageOption(Generic[S_co]): ...
class PageSourceProto(Protocol[T, S_co, V_contra]):
def get_page(self, index: int) -> T: ...
def get_page_options(self, view: V_contra, page: T) -> list[PageOption[S_co]]: ...
def format_page(self, view: V_contra, page: T) -> str: ...
class PageSource(PageSourceProto[T, S_co, V_contra], Generic[T, S_co, V_contra]): ...
class ListPageSource(PageSource[list[E], S_co, V_contra], Generic[E, S_co, V_contra]): ...
Intended usage:
class MyPageSource(ListPageSource[str, PageSource, PaginatorView]):
def format_page(self, view, page):
# view should be inferred as PaginatorView,
# and page inferred as list[str]
return '\n'.join(page)``` The idea is that PageSource and ListPageSource are base classes with partial implementations of PageSourceProto. Users are meant to subclass one of them and provide the types in inheritance so users don't need to manually typehint the protocol methods. Is this possible? Or should I not be doing this at all?
can you provide a little more context?
I added these two new attrs to log record and wanted to type th to prevent mypy from screaming at me and get better ide support, I accomplished it by inheriting into a new class from both logrecord and mixin
Yes, I could've directly made the class with added annotations but I chose what I did
the union means one or the either, not "both"
so yeah i think your best option would be to actually subclass LogRecord here
or write a protocol that covers both LogRecord and your mixin
i've always wanted a way to construct a protocol from an existing concrete class
Same
I think intersection types solve this problem better than a method to do that
I'm not a big fan of adding special casing to the type system
intersection types?
my use case is: someone writes a custom class, i want to use "any class that is compatible with that class", but without re-writing their entire class
x: Protocol[SomeClass] = OtherClass()
# or
x: Implements[SomeClass] = OtherClass()
if someone's code has isinstance(x, X) or type(x) is X checks, it wont work
When you parametrize the class, that should update the method's signature with the typevar. That appears to be all lost when you override it, since that is now override.
type(x) is X checks that x is instance of exact X, not of subclass of X
issubclass(type(x), X) checks that type(x) is subclass of X (note: X is subclass of X)
isinstance(x, X) checks that x.__class__ is subclass of X
usually type(x) and x.__class__ are the same, but in very rare case, if you define __class__ property in your class, x.__class__ wont be equal type(x)
Til that type(x) is X isn't the same as isinstance
Well it makes sense
How is X a subclass of X?
Its the same clas
!e bool is subclass of int
x = True
print(type(x) is int) # False
print(isinstance(x, int)) # True
print()
print(issubclass(bool, int)) # True
print(issubclass(int, int)) # True, because issubclass(X, X) is always true
@tranquil turtle :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | False
002 | True
003 |
004 | True
005 | True
its convenient
very often you need instance of some type, so it is ok to have that exact type or its subclass
so, isinstance(x, X) is true, iff x is instance of exact X or of subclass of X
otherwise, you should do these checks: isinstace(x, X) or type(x) is X or issubclass(cls, X) or cls is X
i think we should go to #offtop channel
after some testing i couldnt get the unannotated method to have parameter types other than Any, but i was at least able to have mypy react to incorrect typehints
not what i hoped for but at least i know my typevar/generic stuff isnt messed up
I'm confused as to what the comments about variance are referring to here
its in context to the subclass
Oh ic
It's similar to TypeScript I guess, where a lot of things can be inferred by having the library do most of the typing work
Do we have inference in cases like these in Python? ```python
def add_callback(func: Callable[[int], object]) -> None:
...
add_callback(lambda var: var + 1)
with some testing it seems mypy can infer it for inline lambdas but not for functions or lambdas assigned to a variable
Pyright seems to do it
what does it say about mp.Pool?
fun fact mp.Pool is a function
or a bound method to be precise
while mp.pool.Pool is indeed a type, https://github.com/python/typeshed/blob/fd75bc21fca7d62235bdd2063ce65cdddbd40a1a/stdlib/multiprocessing/pool.pyi#L71
stdlib/multiprocessing/pool.pyi line 71
class Pool:```
so is it ```py
def Pool(*args, **kwargs):
return pool.Pool(*args, **kwargs)
Ah, it's from a context var
TIL on that
How can I bound a ParamSpec so that Callable[P, Any] only accepts functions that only have keyword arguments?
I'm trying to write a decorator that only operates on such functions, but I also want to type hint that the signature of the output function is the same as that of the input function
you cannot
maybe you can bind a TypeVar (not a ParamSpec) to a callable protocol that accept **kwargs: Any
Haven't thought of this solution, I'll try that, thanks!
That didn't work, Nvm, it was just me not type hinting the return types correctly**kwargs only matches functions that accept any keyword arguments, but won't match those that have specific keyword arguments
Is there a reason why the typevars of stdlib collections that need washables aren't bound to Hashable?
because there is no Hashable type actually
bad things happened when we tried, https://github.com/python/typeshed/pull/6244
I wonder what happens when we set dicts key type parameter to be bound to Hashable.
I think there's an issue where mypy thinks type[object] is not Hashable, so this will depend on that.
python/...
seems like the main thing was that mypy thought types weren't Hashable
how to override base class instance method arg type in child class?
could you provide some details maybe?
or an example
wouldn't the things that fail with bound typevars be cases that'd fail with normal use of those types?
if type checkers have no bugs, yes
class Base:
def ins(self, a: int):
...
class Child:
def ins(self, a: str): # type errors!
...
I am beginning to believe that inheritance is actually bad
Composition requires way less thinking than inheritance
it will break this function ```py
def foo(base: Base) -> None:
base.ins(42)
no it wont
it will
they arent subclasses
well, if Child inherits from Base it does
don't take the type hints literally
uhh
in my case they are different enum subclasses
but I have a huge hierarchy of classes using my custom enum class already
and there's an another enum class which is not a base class of my custom enum class
hence the incompatibility
cant you annotate the superclass as taking the superclass of the 2 enums?
and hence make it safe for lsp?
something like this?
class Foo:
pass
class Bar(Foo): pass
class Baz(Foo): pass
class Base:
def quack(self, hmm: Foo) -> None: ...
class Child(Base):
def quack(self, hmm: Bar) -> None: ...
I can but I just think its not worth the effort and wrote another class, it will do too many changes, break a few things
that should be safe π€
not like that
what is it then?
class MyEnumMeta(enum.EnumMeta):
...
class MyEnum(int, enum.Enum, metaclass=MyEnumMeta):
...
class AnotherEnum(enum.IntEnum):
...
class Base:
def ins(self, id: MyEnum):
...
class SomeDistantChild(Base):
def ins(self, id: AnotherEnum):
...
Well, MyEnum and AnotherEnum are not related at all
so it is like this
composition isnt gonna save you from this, this is still unsafe
I made SomeDistantChild not base off of Base
well the classes were actually descriptors, I thought since the logic is very close. I might use an instance of Base in SomeDistantChild rather than subclassing it
but it proved to be a wrong idea anyway
and now I have 2 copies of almost same classes
Hello, I'm wondering if python has a library or a future spec that could read types from the available docstring.
I think the docstrings are more verbose that basic type annotations and could provide both documentation and type hinting.
stubgen (mypy's stub generation tool) can extract some type info from docstrings
however, docstrings come in many formats. the value of type annotations is that they're definitely machine-readable
for example, type checkers would not necessarily know what "any" means in your example
good point there.. makes sense not to slow down compilation. I guess docstrings could include typehints without having to define them in both areas
I'd advise against including types in docstrings since they're redundant with the annotations
yeah, they are guaranteed to de-synchronize π
Right, IDE or python could read the types and feed it into the docstring auto magically.
i have more type hints than docstrings. But when looking to document stuff for others it'd be nice to access type info. like someObject.__annotations__ only shows the return type
It shouldn't only show the return type if you annotate the other parameters
thank you
Can someone help me to understand why the type narrowing not working?
Can you give an example where you think it's not working?
class TSessionParams(TypedDict):
type: Literal["snmp"]
host: "str"
port: int
class TSnmpSessionParamsV1V2c(TSessionParams):
version: Literal["v1"] | Literal["v2c"]
community: str
class TSnmpSessionParamsV3Base(TSessionParams):
version: Literal["v3"]
username: str
class TSnmpSessionParamsV3NoAuthNoPriv(TSnmpSessionParamsV3Base):
security: Literal["noAuthNoPriv"]
class TSnmpSessionParamsV3AuthNoPriv(TSnmpSessionParamsV3Base):
security: Literal["authNoPriv"]
auth_protocol: Literal["md5"] | Literal["sha"]
auth_key: str
class TSnmpSessionParamsV3AuthPriv(TSnmpSessionParamsV3Base):
security: Literal["authPriv"]
auth_protocol: Literal["md5"] | Literal["sha"]
auth_key: str
priv_protocol: Literal["des"] | Literal["aes"]
priv_key: str
TSnmpSessionParamsV3 = (
TSnmpSessionParamsV3NoAuthNoPriv
| TSnmpSessionParamsV3AuthNoPriv
| TSnmpSessionParamsV3AuthPriv
)
TSnmpSessionParams = TSnmpSessionParamsV1V2c | TSnmpSessionParamsV3
This is the definition
class SnmpSession(Session):
def __init__(self, parameters: "TSnmpSessionParams") -> None:
super().__init__(parameters)
credentials: V1 | V2C | V3
match parameters["version"]:
case SnmpVersion.V1:
credentials = V1(parameters["community"])
case SnmpVersion.V2C:
credentials = V2C(parameters["community"])
case SnmpVersion.V3:
credentials = V3(username=parameters["username"])
match parameters["security"]:
case V3Security.NO_AUTH_NO_PRIV:
...
case V3Security.AUTH_NO_PRIV:
credentials.auth = Auth(
key=parameters["auth_key"].encode(),
method=parameters["auth_protocol"],
)
case V3Security.AUTH_PRIV:
credentials.auth = Auth(
key=parameters["auth_key"].encode(),
method=parameters["auth_protocol"],
)
credentials.priv = Priv(
key=parameters["priv_key"].encode(),
method=parameters["priv_protocol"],
)
when i am using match pattern, it does not narrow it, but when i am using if statement it does
src/connections/sessions/snmp_session.py:47: note: Revealed type is "TypedDict('connections._types.TSnmpSessionParamsV1V2c', {'type': Literal['snmp'], 'host': builtins.str, 'port': builtins.int, 'version': Union[Literal['v1'], Literal['v2c']], 'community': builtins.str})"
src/connections/sessions/snmp_session.py:50: note: Revealed type is "Union[TypedDict('connections._types.TSnmpSessionParamsV1V2c', {'type': Literal['snmp'], 'host': builtins.str, 'port': builtins.int, 'version': Union[Literal['v1'], Literal['v2c']], 'community': builtins.str}), TypedDict('connections._types.TSnmpSessionParamsV3NoAuthNoPriv', {'type': Literal['snmp'], 'host': builtins.str, 'port': builtins.int, 'version': Literal['v3'], 'username': builtins.str, 'security': Literal['noAuthNoPriv']}), TypedDict('connections._types.TSnmpSessionParamsV3AuthNoPriv', {'type': Literal['snmp'], 'host': builtins.str, 'port': builtins.int, 'version': Literal['v3'], 'username': builtins.str, 'security': Literal['authNoPriv'], 'auth_protocol': Union[Literal['md5'], Literal['sha']], 'auth_key': builtins.str}), TypedDict('connections._types.TSnmpSessionParamsV3AuthPriv', {'type': Literal['snmp'], 'host': builtins.str, 'port': builtins.int, 'version': Literal['v3'], 'username': builtins.str, 'security': Literal['authPriv'], 'auth_protocol': Union[Literal['md5'], Literal['sha']], 'auth_key': builtins.str, 'priv_protocol': Union[Literal['des'], Literal['aes']], 'priv_key': builtins.str})]"
this is what reveal_type shows
I am missing something?
yeah kinda weird
If that is a Google style docstring, then it is not necessary to add type hints to your docstring if your code already uses type annotations
flake8-docstrings doesn't help with that either
How can I remove the self cast of pl_event here?
pl_event = None
if ...:
pl_event = ...
for ... in ...:
items = []
for i, item in enumerate(pl_event or []):
pl_event = cast(PlaylistEvent, pl_event)
isn't it obvious that the loop won't happen at all if a [] is enumerated?
you can add assert pl_event or put entire for-loop or its body into if pl_event:
there already is quite a bit of nesting going on
the code above is simplified
pl_event = None
if ...:
pl_event = ...
for ... in ...:
items = []
for i, item in enumerate(pl_event or []):
assert pl_event # <--- add this
# pl_event = cast(PlaylistEvent, pl_event) # cast no longer needed
i never add any assert in production code, maybe I can just use
if pl_event is None:
...
# rest of the code...
but why is this not automatically inferred?
type checkers would need to know quite a bit about runtime behavior to infer this
I don't quite understand what I want to say or suggest, but here is some idea:
# there is a lot of important parts missing
class Falsy(Protocol):
def __bool__(self) -> Literal[False]: ...
class Truthy(Protocol):
def __bool__(self) -> Literal[True]: ...
TruthyIterable = Truthy & Iterable # or class TruthyIterable(Truthy, Iterable, Protocol): ...
FalsyIterable = Falsy & Iterable
T = TypeVar('T', bound=TruthyIterable | FalsyIterable)
G = TypeVar('G')
# then special-case iteration over FalsyIterable in type-checker such that code inside it become unreachable
for i in FalsyIterable():
# unreachable, not type-checked
# iterables are narrowed inside for-loop body
it = SomeIt() # maybe empty
for i in it:
reveal_type(it) # TruthyIterable & SomeIt
def enumerate(it: T) -> T: ...
def map(f: ..., it: T) -> T: ...
@overload
def range(n: Literal[0]) -> FalsyIterable: ...
def reversed(it: T) -> T: ...
@overload
def sorted(it: FalsyIterable, ...) -> list[Any] & Falsy: ...
@overload
def sum(it: FalsyIterable, start: G = ...) -> G: ...
@overload
def max(it: FalsyIterable, default: G, key=...) -> G: ...
@overload
def len(it: FalsyIterable) -> Literal[0]: ...
@overload
def any(it: FalsyIterable) -> Literal[False]: ...
@overload
def all(it: FalsyIterable) -> Literal[True]: ...
class tuple:
@overload
def __bool__(self: tuple[()]) -> Falsy: ...
class str:
@overload
def __bool__(self: Literal['']) -> Falsy: ...
class bytes:
@overload
def __bool__(self: Literal[b'']) -> Falsy: ...
@overload
def filter(f: ..., it: FalsyIterable) -> FalsyIterable: ...
@overload
def filter(f: Callable[[Any], Falsy], it: Iterable) -> FalsyIterable: ...
@overload
def filter(f: ..., it: Iterable) -> Iterable: ...
has incompatible type "TEndpointParams"; expected "Dict[str, Any]"
class TEndpointParams(TypedDict):
host: "str"
port: int
mypy you're crazy
I am sending typedict to function who get dict[str,Any]
i dont see how typedict and dict is not the same
so should i use Mapping instead?
Mapping seems to solve it
yeah TypedDict is compatible with Mapping
def foo(x: dict[str, Any]):
x.pop("host")
x["port"] = "poop"
x["unrelated"] = 42
foo(endpoint_params)
subtyping is sometimes mind-bending π
yeah kinda hard
I cant understand why type narrowing not wokring for a union of types
yeah same issue as before
using vscode with pylance (he is using pyright behind the scenes to me knowledge)
this looks like a pyright bug if it works with if but not match
but mypy complains
I am wondering is anyone know about a package who can make Json-Schema as typing?
You'd need a mypy plug-in.
Though there's probably one that turns it into typeddicts
!pip json-schema-codegen
Should I use the LiteralString type hint over str?
in general, no
do it if the code you use it with is vulnerable to an injection attack, like an sql query or shell command
am i expecting too much from the type hints here?
from typing import Callable, TypeVar
from typing_extensions import reveal_type
A = TypeVar("A")
B = TypeVar("B")
C = TypeVar("C")
D = TypeVar("D")
def bifunc(
left: Callable[[A], B], right: Callable[[C], D]
) -> Callable[[tuple[A, C]], tuple[B, D]]:
return lambda args: (left(args[0]), right(args[1]))
def left(left: Callable[[A], B]) -> Callable[[tuple[A, C]], tuple[B, C]]:
return bifunc(left, lambda x: x)
def right(right: Callable[[C], D]) -> Callable[[tuple[A, C]], tuple[A, D]]:
return bifunc(lambda x: x, right)
if __name__ == "__main__":
some_tuple = (3, "k")
other_tuple = left(lambda x: x + 3)(some_tuple)
reveal_type(other_tuple) # Pyright: Type of "other_tuple" is "tuple[Unknown, Literal['k']]"
I feel like it should be able to infer the type of the left item in the tuple
since the constraints on it should come from the argument applied to the function returned from left
even if i directly annotate the type of the function returned by left, pyright is not able to figure out the lambda argument types
if __name__ == "__main__":
some_tuple = (3, "k")
# Pyright: Operator "+" not supported for types "A@left" and "Literal[1]"
# Β Β Operator "+" not supported for types "object*" and "Literal[1]" when expected type is "int"
# Pyright: Return type of lambda is unknown
l_fn: Callable[[tuple[int, str]], tuple[int, str]] = left(lambda x: x + 1)
other_tuple = l_fn(some_tuple)
reveal_type(other_tuple) # Pyright: Type of "other_tuple" is "tuple[int, str]"
left returns a function over pairs that applies the first item in the pair to the passed function and returns a pair with the result and the other element of the pair
@rustic lagoon under mypy, it can't figure out the type of the lambda
it's annoying how this does not type check 
if __name__ == "__main__":
def double(x: int) -> int:
return x * 2
words = ["foo", "bar", "baz"]
double_word_zip = map(left(double), zip(range(10), words))
print(list(double_word_zip)) # [(0, 'foo'), (2, 'bar'), (4, 'baz')]
https://mypy-play.net/?mypy=latest&python=3.10&flags=show-error-codes%2Cstrict&gist=05823fe430106af502ef35f66aaca761 if you fix the type of the function, it works
idk about map, that might have other issues
the function name and parameter name being the same might get really funky
i didn't even consider that python would allow that
i assume the parameter name takes precedence
yeah, but that can just get changed
https://mypy-play.net/?mypy=latest&python=3.10&flags=show-error-codes%2Cstrict&gist=b345b1d855a78df5d64f8bdee9e46ac7
i'd avoid using lambdas here anyway
i obviously can't expect python to be haskell, but those types should be possible to infer
module Main where
bifunc :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
bifunc fl fr = \(l, r) -> (fl l, fr r)
left :: (a -> b) -> (a, c) -> (b, c)
left lf = bifunc lf id
right :: (a -> b) -> (c, a) -> (c, b)
right rf = bifunc id rf
main :: IO ()
main =
let someTuple = (3, "k")
otherTuple = left (\x -> x + 3) someTuple -- otherTuple :: (Integer, String)
words = ["foo", "bar", "baz"]
doubleWordZip = map (left (\x -> x * 2)) $ zip [0 ..] words -- doubleWordZip :: [(Integer, String)]
in print otherTuple >> print doubleWordZip
this is obvrioulsy not idiomatic nor good haskell, but i just wanted it to shadow the python code as closely as possible
well yeah, hindley-milner vs. whatever the fuck python is
any ideas why this occurs?
.host is clearly a string
so why does pylance flag it
Can the parent object be None?
ahh it could be
interesting
thank you
im not really sure what circumstance would result in a None address but still it could be None π₯΄
might edit the starlette module code to remove this error since ive never encountered a None address before
hence my confusion
it works!
surely that's the return type for a reason?
probably but i'll cross my fingers and hope that the situation never occurs
this is what i changed it too
@property
def client(self) -> Address:
# client is a 2 item tuple of (host, port), None or missing
host_port = self.scope.get("client")
return Address(*host_port)
but i dont see a scenario where the address is None, if i ever encounter it then of course ill change it back to the original code
this is what it was before:
@property
def client(self) -> typing.Optional[Address]:
# client is a 2 item tuple of (host, port), None or missing
host_port = self.scope.get("client")
if host_port is not None:
return Address(*host_port)
return None
Consider asserting that it is not None if you do not want to deal with the possibility.
Is anyone aware of a way of checking if dict[Enum, T] contains elements for every member of Enum?
Pretty sure it would need a plugin.
Typedicts only take strings as keys no?
But the best I could come up with was instead of writing the dicts as literals, use a metaclass to inject a classmethod map(cls, member1: T, member2: T, ...) -> Dict[Enum T] into every enum and write a plugin to type check that.
which worked
but i'm not very familiar with mypy plugins, and if there's a simpler way of doing it.
I am curious how this is used.
The match-case statement might be of use here.
Not sure id like to rewrite all of my simple dicts as function-wrapped match statements.
Would have quite a overhead as well.
Only skimmed the article, but it explains the situation well, I think.
I still kinda dont understand what the ellipisis is for in those parameters in overloaded functions/methods.
from typing import overload
@overload
def foo(x: int = ...) -> None: ...
# whatever comes next
it means "there is a default value for this parameter". so it means foo() is a valid call, as is foo(123)
with overloads in particular, it can help disambiguate what happens when nothing is passed in:
@overload
def foo(x: int = ...) -> int: ...
@overload
def foo(x: str) -> str: ...
^ here type checkers know that foo() will return an int
but if you have:
@overload
def foo(x: int) -> int: ...
@overload
def foo(x: str = ...) -> str: ...
then type checkers will know that foo() returns a str
is there a way to make this typing work?
class A:
async def test(self) -> None:
return None
class B(A):
...
class TClassMapper(TypedDict):
a: NotRequired['A']
b: NotRequired['B']
g: TClassMapper = {}
async def test_typing():
for x in g.values():
await g[x].test() #type is object
if 'a' in g:
await g['a'].test() #type is correct
when iterating over the TypeDict instance the type of the value is object
you could make it final
what to make final?
TClassMapper
final is not supported with TypeDict
@final cannot be used with TypedDict [misc]mypy(error)
according to the error i get
any idea how to setup pyright with vscode?
should be installed if you install the python extension
you just need to set the typechecking mode to basic
vscode still complains about the type being object
Oh thanks, so foo() in your last example means
You leave it default but it expects a string and will return a string right?
might be a pyright bug lemme check
Been a while since I have used it. What does values() do?
Yesterday i did say typeddict are partially implemented (by typecheckers) π
return list of values of the dict
Not a list
well more correctly View
Yes
So it contains value if we did d[key] = value?
was this what you were referring to?
I assume d[value] does not usually make sense?
Oh. I'm slow. We are trying to get mypy to error on it?
no pyright shows that the type of .values() is dict_values[str, object] not dict_values[str, A | B] as it should be
when decorated with final
I will use cast for now
Ah. We want ValueType to automatically be a Union of the given value types?
yeah
oh ive figured it out, https://github.com/microsoft/pyright/issues/3526
this isnt supported so dict_values[str, union] wont be
I have a question now,
from typing import overload
@overload
def foo() -> None: ...
@overload
def foo(x: int = ...) -> int: ...
``` this is wrong right?
Because if i foo() it'd check the first overloaded function
None is annotated in the return
But then would it approve the second one too?
If we pass nothing, means that x is set by default
Which would annotate return to int
typecheckers must use the first matching overload
so
foo() is None
foo(1) is int
foo('') is error
also, if you are not in stubs, you should provide actual implementation for function (without @overload)
we can discuss pydantic here right? I have a websocket connection, different kinds of JSON messages can be sent through and they can be distinguished by the type field like so:
{
"type": "some-type-here",
"data": {}
}
how can i use pydantic to automatically serialise the message based on the type
this is what i have but... it sucks basically (it works but i dont think its the best way)
class EventType(str, Enum):
CONNECT = "connect"
DISCONNECT = "disconnect"
class EventData(BaseModel):
pass
class ConnectData(EventData):
username: str
class DisconnectData(EventData):
username: str
class EventRequest(BaseModel):
type: EventType
data: EventData
@validator("data", pre=True)
def valid_data(cls, value, values):
match values["type"]:
case EventType.CONNECT:
value = ConnectData(**value)
case EventType.DISCONNECT:
value = DisconnectData(**value)
return value
then i can just parse the recieved message to an EventRequest
i know this Connect and Disconnect models are the same here, this is just an example. is this really the best way to do this?
i heard about https://pydantic-docs.helpmanual.io/usage/types/#discriminated-unions-aka-tagged-unions but im not sure i should be using it instead
Data validation and settings management using Python type hints
yes, like denball says, when multiple overloads can match, type checkers typically just use the first one. In this example, I would just remove the default value β¦
Is there a simple way to type this? ```py
async def compose(payload, *funcs):
for func in funcs:
payload = await func(payload)
return payload
payload should be the first function's arg, the last function should be the return type
Hm.. can I not have multiple overloads where the difference is the length of the *args unpack? ```py
@overload
async def compose(
payload: T1,
*funcs: Unpack[
tuple[
Callable[[T1], Awaitable[T2]],
Callable[[T2], Awaitable[T3]],
Callable[[T3], Awaitable[T4]],
Callable[[T4], Awaitable[T5]],
]
],
) -> T5:
...
That's what I get for trying to be fancy.
you can, just spell it out payload, func1: ..., func2: ... etc: https://github.com/python/typeshed/blob/276a4d7d696c457a0daddbec234ba3c76bd79362/stdlib/builtins.pyi#L1700
stdlib/builtins.pyi line 1700
def __new__(```
Yeah, I ended up doing that
you'll get the best effect with overloads anyway, but for the general case you can use something like *funcs: Unpack[tuple[*tuple[Callable[[Any], Awaitable[Any]], ...], Callable[[Any], Awaitable[Return]]]] to make sure the return type is at least typed properly
Does mypy fully support dataclass_transform yet?
I probably went overboard to force sqlalchemy to behave with mypy/pyright (mypy is having issues with the dataclass transform)
https://paste.pythondiscord.com/inekuqasux
https://paste.pythondiscord.com/aroxuvodob
Once sqlalchemy is at 2.0, I'll be happy
not at all
is using type hints from the typing module bad/deprecated?
should we be using types or collections instead?
you can use if TYPE_CHECKING to simulate some use cases of dataclass_transform
if youβre using python 3.9 and newer, iβd prefer using the types in collections.abc
3.11 for me :)
mypy is really going wacky over my codebase
its really slow
detects wrong types, and is quite slow
codebase isn't very big, 200kb combined
that's with all the comments, docstrings and license headers
Maybe it's scanning some other directories with large amount of files
my lib depends on construct-typing
which is quite big ig
What are your settings for mypy?
pyproject.toml lines 62 to 66
[tool.mypy]
python_version = "3.7"
enable_incomplete_features = true
ignore_missing_imports = true
warn_no_return = false```
pyproject.toml lines 98 to 103
[tool.pyright]
ignore = ["./venv"]
reportPrivateUsage = false
reportMissingTypeStubs = false
venvPath = "."
venv = "venv"```
I have a pretty small project with 3k LOC, it takes 7 seconds for mypy to check it, that's without cache
oh well its pylance - pyright that hangs as well\
type checkers should use rust coz blazing fast
It's easier to use python, plus iirc some parts of mypy use cython or c, I don't remember and I might be completely wrong
doctor@main MINGW64 /c/dev/help/PyFLP (master)
$ pdm run where mypy
C:\dev\help\PyFLP\.venv\Scripts\mypy.exe
doctor@main MINGW64 /c/dev/help/PyFLP (master)
$ pdm run mypy .
Success: no issues found in 24 source files
@slender timber π€·ββοΈ
?
It runs fine
It's pretty fast too
the "issues" it shows are due to glitchy behavious
A couple of seconds
yea but it hangs in vscode a lot
especially for channel.py
Wdym by "in vscode"? π€
and i have enabled pyright and mypy both, maybe thats conflicting?
I usually just run mypy from time to time manually
its possible to enable mypy and get errors from lsp
from python extension
like how pyright works through pylance
Idk, as I said I just run it manually and in CI
that is slow
?
the ide analysis
and at times the lsp server just crashes
need to reopen vscode
you can restart the lsp from within vsc
but why does this happen
it also happens when i use the lib as a dependancy
does using too many type hints cause this?
Im using pyright lsp in sublime text. And sometimes it crashes and i should restart sublime to fix it. It happens on startup time of sublime or randomly later.
all i can suggest is using the pre-release version after switching i dont think ive had any issues with it
pyright
ok
pycharm's typechecker being wonky as usual, i think
does this error too
def foo(col_name: str) -> bool:
return "index" not in col_name
... = pd.read_excel(..., usecols=foo)
is there no warning with mypy / other IDEs?
Try this:
# vvv pos-only arg
def foo(col_name: str, /) -> bool: ...
how to completely exclude a library from mypy and pyright
import untyped_lib # type: ignore
this work?
mypy has a configuration option to ignore imports from specific packages
pyright?
Pyright bug? (It should be an AsyncGenerator)
I'm a bit oblivious in the art of type hinting. But how would i go about hinting an iterable containing 2 iterables. With both 5 elements.
How detailed should i even go, should i just settle with defining it as a list or.
Depends on the context, as always π
so tell more about what you're doing
tuple is the only iterable type that supports heterogeneous types
Well, I suppose you could do some trickery with ParamSpec. Like how you could make asyncio.gather typed with
foo, bar, baz = await (gather + a_foo() + a_bar() + a_baz()).join()
or like Gather().add(a_foo()).add(a_bar()).add(a_baz()).join()
So basically copying the javascript promise api
hm?
well, the only advantage here is to allow for ParamSpec
class Gather(Generic[Unpack[P]]):
...
def add(self, other: Awaitable[T]) -> Gather[Unpack[P], T]: ...
In JavaScript you have Promise.all which is well-typed in TS π
a promise api actually would be useful for the compose function I made yesterday.
and it is the only iterable type which length is known statically
def __init__(self, board: pygame.Surface, pieces: list[list[pygame.Surface]]): Basically board is a surface, and pieces contain all the surfaces for all the ches pieces for both colors so [[5 surfaces for white], [5 for black]]