#type-hinting
1 messages ยท Page 72 of 1
from aiohttp import ClientSession
class HTTPClient:
_BASEURL: str = "https://random-d.uk/api/v2/"
async def _request(self, endpoint: int) -> int:
async with ClientSession().get(f'{self._BASEURL}{endpoint}') as resp:
return await resp.json()
client = HTTPClient()
client._request('hi')
with --strict --no-missing-imports --show-error-codes
main.py:9: error: Returning Any from function declared to return "int" [no-any-return]
main.py:13: error: Argument 1 to "_request" of "HTTPClient" has incompatible type "str"; expected "int" [arg-type]
Found 2 errors in 1 file (checked 1 source file)
-
are you actually using mypy, or pycharm? pycharm has its own built-in (and somewhat buggy) type checker
-
try using
--strict, just in case (you should anyway) -
there must be something else wrong with your code
Show us complete code samples that show the problem you're seeing. Otherwise we can only guess
- it's possible that the thing you thought was
stractually has typeAny. unless it's a string literal, this is a possibility. try usingreveal_typeon whatever you pass to_request
i'm using mypy
my ide is vscode
class HTTPClient:
_BASEURL: ClassVar[str] = "https://random-d.uk/api/v2/"
async def _request(self, endpoint: int) -> int:
async with ClientSession().get(f'{self._BASEURL}{endpoint}') as resp:
return await resp.json()
a: HTTPClient = HTTPClient()
async def main():
print(await a._request("random"))
asyncio.run(main())
do you also want my imports
main() is unannotated so mypy doesn't check it
fuck
and this is why you should use --strict!
there's basically no reason not to use it unless you're working on a legacy codebase
no, you're starting to hate loosey-goosey type hinting
might aswell since i'm here
what are generics
i think i know what generics are but still not sure
think about a list. a list can hold any type. the list type itself has no knowledge of the stuff that it holds. a list knows that it can hold stuff, and that you can iterate over it, slice it, append to it, etc. but the actual stuff inside is unknown to the list and irrelevant to the behavior that makes the list act like a list.
so list is a generic type. it has one type parameter, indicating the type of the list elements.
okay
in python, you can define your own generics by inheriting from Generic
for which you also need TypeVar. a "type variable" is just a placeholder: it says "there will be a type here, but i don't know what it is yet"
okay
consider a custom maplist function:
from collections.abc import Callable, Iterable
A = TypeVar('A')
B = TypeVar('B')
def maplist(func: Callable[[A], B], xs: Iterable[A]) -> list[B]:
return [func(x) for x in xs]
it doesn't matter what A or B actually are, as long as every A placeholder gets filled with the same type, and every B placeholder gets filled with the same type
so you can pass arguments Callable[[int], float], list[int] but not Callable[[int], float], list[str]
do you see how that works?
yes
oh i did know what generics are
that was my understanding of what generics are
yeah, you'd say that maplist is a "generic function", and list is a "generic type"
also i have another question what does this mean ```
Unsupported dynamic base class "Generic"
oh wait nevermind
is that what happens if you inherit from Generic instead of Generic[T]?
i just now realized i did parenthesis instead of brackets
why does .json() return any?
The type checker can't know what it returns
hmm so i can't use dict for it i guess
also can you not use Generic[T] if the class doesn't take in anything?
cause it's just been screaming at me
i think you can, but it won't have any effect
or maybe you can't
from typing import Generic, TypeVar
T = TypeVar('T')
class Thing(Generic[T]):
pass
this is valid according to mypy
it's cause i get an error ```
error: Missing type parameters for generic type "HTTPClient"
as always, show your code
T = TypeVar("T")
V = TypeVar("V")
class HTTPClient(Generic[T, V]):
_BASEURL: ClassVar[str] = "https://random-d.uk/api/v2/"
async def _request(self, endpoint: str) -> dict[T, V]:
async with ClientSession().get(f'{self._BASEURL}{endpoint}') as resp:
return await resp.json()
a: HTTPClient = HTTPClient()
async def main() -> None:
print(await a._request("random"))
asyncio.run(main())
i'll change dict to any later
the problem is a: HTTPClient
you need to say what the T and V are now
(json keys are always str)
normally i annotate json objects as dict[str, object] and then use TypeGuard or typing.cast as needed to take the dicts apart
that, or i use attrs or pydantic to deserialize to something more structured
in the typehint of a?
like HTTPClient(str, str)?
nevermind figured it out
I don't comprehend this error, I'm trying to make a Protocol that can engulf both TypedDict and dict for reading
Here's what InfoDict is:
K = TypeVar('K', contravariant=True)
V = TypeVar('V', covariant=True)
DV = TypeVar('DV')
class InfoDict(Protocol, Generic[K, V]):
def __getitem__(self, key: K, /) -> V: ...
@overload
def get(self, key: K, /) -> Optional[V]: ...
@overload
def get(self, key: K, default: DV, /) -> V | DV: ...
def get(self, key: K, default: DV = None, /) -> V | DV: ...
Can you show the code the error is talking about?
there's practically none, just using this to bring up the error
oh god nevermind this was XYZ
Mapping worked, I just needed to make it a property on another protocol attribute
thanks
hi, im getting slightly conflicting errors and i have no idea how co/contravariance works
class GlobalWaitForCheck(Protocol[EventNameT]):
def __call__(self, event: EventNameT, *args: Any) -> Awaitable[bool] | bool:
...
class WaitForTuple(Protocol[EventNameT]):
@overload
def __getitem__(self, item: Literal[0]) -> EventNameT:
...
@overload
def __getitem__(self, item: int) -> Any:
...
def __getitem__(self, item: int) -> EventNameT | Any:
...
``` ```py
Type variable "EventNameT" used in generic protocol "GlobalEventCallback" should be contravariantPylancereportInvalidTypeVarUse
``` ```py
Type variable "EventNameT" used in generic protocol "WaitForTuple" should be covariantPylancereportInvalidTypeVarUse
``` ```py
EventNameT = TypeVar("EventNameT", bound=Hashable)
``` so like, `GlobalWaitForCheck` is a protocol to accept `str` if the generic is `str`, `WaitForTuple` returns a `str` for the first element if the generic too is `str`
Can we create mapping (input model to output model) using pydantic ? If not, any good tools for this ?
WaitForTuple returns a str for the first element if the generic too is str
What do you mean?
Also, just use different TypeVars, not sure what you are doing but seems like you could just have EventNameT_contra = TypeVar("EventNameT_contra", bound=Hashable, contravariant=True) and EventNameT_co = TypeVar("EventNameT_co", bound=Hashable, covariant=True)
I have a tutorial series on generics, it covers variance briefly
https://decorator-factory.github.io/typing-tips/tutorials/generics/
yeah ive read that, every explaination is just so wordy to me
like i get the slightest idea but they get so complex
Open any typing playground and start messing with them with very simple functions and classes, I found that to be the best way to understand them
This situation is what helped me understand covariance: https://pyright-playground.decorator-factory.su/?gist_id=dce6fb019ab637e6aea82b02fb286165&filename=covariant_example.py
I needed to write a function to take an instance of a generic object but that function has to be agnostic about the type that was given to the generic object
i'm surprised that pyright is requiring covariance in that situation
it makes sense that they'd warn you
or is it semantically invalid to have an invariant function parameter typevar?
i never bother to set up co/contra-variance ๐ too lazy
and what happens if you have a typevar that is uses as both a return type and a parameter type in the same definition?
how to type hint a generator? shoud i just say int if its yields int?
Either collections.abc.Iterator[int], or typing.Generator[int, None, None].
Generator lets you specify the parameters for send() and throw().
oh ok, thanks
i understand Generator[YieldType, SendType, ReturnType] YieldType and ReturnType but what is SendType??
yield is an expression, not a statement - you can do blah = (yield x), and then the code doing the iterating can send a value back into the generator with the send() method.
ah ok
can you typehint a already typehinted variable when assigning a new value to the variable?
You should just make a new name for the variable
is it possible to type-hint a return type of a function as an overloaded callable?
@overload
def my_fun(a: int, convert: Literal[True]) -> float:
...
@overload
def my_fun(a: int, convert: Literal[False]) -> int:
...
def my_fun(a: int, convert: bool) -> Union[int, float]:
...
def foo() -> my_fun:
...
I was thinking of doing something like Union[Callable[[int, Literal[True]], float], Callable[[int, Literal[False]], int]], but that didn't work as I'd expect, instead, it tried to match both of the union types, so when I passed True, it complained that it wasn't compatible with Literal[False], and vice-versa
Use Protocols
class my_fun(Protocol):
@overload
def __call__(self, a: int, convert: Literal[True]) -> float:
...
@overload
def __call__(self, a: int, convert: Literal[False]) -> int:
...
def __call__(self, a: int, convert: bool) -> Union[int, float]:
...
oh, of course, that makes sense
yeah, that should do it
though I still think it's pretty weird that it acted like this
doesn't Union mean either of
why did it try to match both
Because the return type of foo is a Union, so the callable that gets returned is either the one that takes a literal true or a literal false
hm, I'm not taking self though, should I make them static methods? I'm not sure this would actually match the signature of my_func
You can't be sure which one it is
So it is not safe to call it with either literal true or literal false
ah, I see
It'd be neat if there was something like typing.Overald taking variadic of callables
The annotations could get very overwhelming though, usually when you get a "x does not match Overload[]" the error messages are huge
this would kind of work, but I'd have to cast the returned function as this proto, since pure callable isn't compatible with this
ig that would depend on the amount of overloads but yeah, nevertheless it's a pretty useful thing and there's basically no good alternative
using protocol with overloaded __call__ isn't really the same as Callable
and doesn't match type-wise
I mean, this works and basically does what I'd need ```py
def foo() -> MyFunProto:
return cast(MyFunProto, my_fun)
but it's not ideal
it's also very annoying to have to type out all of the overloads again, when my_fun already defines them
It should be compatible, as in no cast should be required, that is if you apply repetition
which is annoying I agree
apparently it's not though
Can you show the code?
class MyFunc(Protocol):
@overload
def __call__(a: int, convert: Literal[True]) -> float:
...
@overload
def __call__(a: int, convert: Literal[False]) -> int:
...
@staticmethod
def __call__(a: int, convert: bool) -> Union[int, float]:
...
def foo() -> MyFunc:
return my_fun
my_fun is defined as I've posted initially
it may be the staticmethod
You are not supposed to omit self
class MyFun(Protocol):
@overload
def __call__(self, a: int, convert: Literal[True]) -> float:
...
@overload
def __call__(self, a: int, convert: Literal[False]) -> int:
...
but my_fun doesn't take self
This is good enough for the protocol
It's treated as a bound method
I think you can conceptually think it does
ah, that explains it
a function is just an instance of FunctionType
self is not passed to the callable but it's needed in the protocol cause functions technically take self
apparently, this works too: ```py
class MyFunc(Protocol):
@overload
@staticmethod
def call(a: int, convert: Literal[True]) -> float:
...
@overload
@staticmethod
def __call__(a: int, convert: Literal[False]) -> int:
...
Yeah you can also do that
alright, cool, thanks!
I still do think that an Overload type would be neat though
or just allowing to do def foo() -> some_function
Yup, you can see it in their error messages
Although you can't instantiate it from python
Overload[(a: int, convert: Literal[True]) -> float, (a: int, convert: Literal[False]) -> int, (a: int, convert: bool) -> (int | float)]
They also use the sadly caput pep for concise function annotation
rip
Oh yeah, that is another issue
overloads are kind of limited when you can only refence positional arguments
without names and such
which without that pep, callables are not capable of doing, and neither would a hypothetical Overald annotation
https://github.com/python/typing/issues/566 implementing this would also kind of address the issue, since AnyOf would act like Any and just adapt to what it needs to be, but would only work for specific types. If this was implemented, it could basically be used as the Union I was thinking of initially
though I didn't see any issue that even brings up implementing typing.Overload (I may just be blind though)
Would be nice if you could use TypeVars for that imo
Something like FunT = TypeVar('FunT', Callable[[int, Literal[True]], float], Callable[[int, Literal[False]], int])
that's probably not going to happen, but yeah
My bad, not even conceptually, it does actually take a self argument lol x)
I don't think so, it's more that the functions in the protocol are wrapped and the self is automatically passed, so it's removed from the signature and therfore meets the same signature of my_fun
i.e. the methods defined in that class are essentially partials, with self already passed, which is done by making them "bound method" objects
on the class itself, they're essentially descriptors, and on instances, they're just auto-passed self
but my_fun is defined globally as a simple function, it doesn't take self and there's no hidden mechanism there making it take it
class MyFoo:
def __call__(self, a):
return a + 42
f = MyFoo()
f(0)
This is just as hidden as what you are talking about, you can still access the actual function via f.__class__.__call__ and the same goes with functions
def foo(a): return a + 42
foo.__class__.__call__()
doing that will complain it is missing two arguments
__call__ doesn't have anything to do with self
what you shown is just there to be compatible with objects etc.
but I'm pretty sure internally, this is just handled differently and there's really no self there
that's also why you can't just access it like you can with regular instances
Not sure what you mean, a method taking in self just means that it uses an instance of that class in the function
Trying to do foo.__class__.__call__() will raise an error, saying it is missing an instance of FunctionType (self) and the actual argument a
just like it would with a normal calss
huh
that's very interesting, I thought this worked a bit differently
can you actually instantiate FunctionType yourself?
You can even pass a completely different function
def foo(): return 3
def bar(): return 9
foo.__class__.__call__(bar)
this will return 9
Yes you can, but it is a bit hard because you need to pass a CodeObject
ah, yeah, that makes sense then
So
How exactly am I supposed to work mypy into my workflow?
And I supposed to run it over my code as an analysis tool periodically (several times a day???) to make sure everything is up to snuff?
Honestly, anything less than live analysis just seems... wrong to me. But I know PyCharm in the least has a rather pitiful type checker
Not sure about the other popular IDES, but it should go without saying that some IDEs will have more, or less, complete checking
If I have a function with two parameters one and two and one is a Literal, is there a simpler way to change the type of two based on the value of one than a massive series of overloads?
class AAAData(TypedDict):
param1: str
param2: int
class BBBData(TypedDict):
param1: bool
param2: dict[str, str]
@overload
def func(one: Literal["aaa"], two: AAAData) -> None:
...
@overload
def func(one: Literal["bbb"], two: BBBData) -> None:
...
def func(one: Literal["aaa", "bbb"], two: dict[str, Any]) -> None:
...
(The case I have has like a dozen separate options for the literal)
pre-commit? But I could see that being annoying
same thing with CI but a bit less obstructive while being more annoying to deal with
maybe embed the tag into the dict?
like ```py
class AAAData(TypedDict):
tag: Literal["aaa"]
param1: str
param2: int
class BBBData(TypedDict):
tag: Literal["bbb"]
param1: bool
param2: dict[str, str]
def func(some: AAAData | BBBData) -> None:
...
VsCode has a pretty good pyright (a better alternative to mypy imo) integration
The pyright integration is awesome
It is, works well with error lens (I guess most linters would)
I did have an issue with it scanning folders it should not be when using pdm, but just had to mess around with the config a bit
Run it on every commit?
Best case is of course live linting in your editor, but I would also explicitly run it (be it by an automated script, or just by you directly) in some part of your workflow
Hmmmm
What with all the work Python as a language and as a community has put into getting type hinting up and running, and drilling the concept home
It feels pretty weak that the various IDEs don't consider their live linters to be of greater importance
My issue is this: what do I do if I need to write middleware that can't be understood very well by most editors?
As much as I'd love to be able to expect user's of my code to use this IDE or else incorporate that type checker into their workflow, that seems unrealistic
As a rule of thumb, most users will be using mypy, and if mypy supports it pyright has supported it for longer (in my experience)
I should be typing my projects to be properly understood by mypy
And its up to my user to consume the project in a sensical way
it does depend on the type of project tho:
if you are making a library that other will be using, you would want to support mypy.
but if the project is something else were the only time people will be working with the code is when developing for that project, requiring the use of a specific type checker is less of an issue in my opinion, similar to how you might mandate the use of flake8/black
As my teacher says, writing code to be used by other people is a whole other animal from writing what you might call frontend code
exactly
Here's my problem
I'm writing a library which would massively benefit from a custom generic descriptor class
Easy enough to type in mypy, but not supported by live linting wrt to most IDEs
yhe I have heard pycharms type checker is a bit limited
And since the project is designed for use by beginners, I can't expect them to have the wherewithall to be able to use mypy
VSCode is supposed to be better ๐
it is much better in my experience
at least when using Pylance (the vscode extension that wraps pyright)
So how should I proceed? I meant, I guess so long as I put the work in to write the descriptors properly, I could make the case to myself that its the IDEs fault for being limited and no my fault for not being smart enough to write the tool properly
I would focus on it working with mypy, and if it does most editors will have extensions/plugins that add mypy support.
you might include something in the form of this in your description:
I recommend using a type checker with this project
to
I recommend using a type checker with this project, for example mypy: [links to extensions for common editors]
or anything in between
or maybe include a warning for PyCharm user specifically that the inbuild type checker is not supported by your project
Seems reasonable. If that's the best that can be done, then that's the best that can be done
Thank you for the frank assessment. I've been having a hard time getting straight answers
np ๐
@overload
def listen(self, event_name: EventNameT) -> Callable[[EventCallback], EventCallback]:
...
@overload
def listen(self, event_name: None) -> Callable[[GlobalEventCallback[EventNameT]], GlobalEventCallback[EventNameT]]:
...
def listen(
self, event_name: EventNameT | None = None
) -> Callable[[EventCallback | GlobalEventCallback[EventNameT]], EventCallback | GlobalEventCallback[EventNameT]]:
...
Overloaded implementation is not consistent with signature of overload 1
Function return type "(EventCallback) -> EventCallback" is incompatible with type "(EventCallback | GlobalEventCallback[EventNameT@Dispatcher]) -> (EventCallback | GlobalEventCallback[EventNameT@Dispatcher])"
Type "(EventCallback) -> EventCallback" cannot be assigned to type "(EventCallback | GlobalEventCallback[EventNameT@Dispatcher]) -> (EventCallback | GlobalEventCallback[EventNameT@Dispatcher])"
Parameter 1: type "EventCallback | GlobalEventCallback[EventNameT@Dispatcher]" cannot be assigned to type "EventCallback"
Type "EventCallback | GlobalEventCallback[EventNameT@Dispatcher]" cannot be assigned to type "EventCallback"
Type "GlobalEventCallback[EventNameT@Dispatcher]" cannot be assigned to type "EventCallback"PylancereportGeneralTypeIssues
quick question, how should I type hint a tuple with the first item of type A and one or more items of type B?
Tuple[A, B]
or
Tuple[A, B, ...]
or smth else
from typing_extensions import Unpack
x: tuple[int, Unpack[tuple[str, ...]]] = (1, '', '')
note that typing_extensions isn't in the stdlib
hmm, I see
Although, in this case, both A and B are two IntEnum subclasses, so it seems Tuple[int, ...] works
this would be acceptable, right?
generally its good to be as specific with types you export
but at the end of the day its up to you
I've been using the unpack version personally
I only just found out you can unpack the tuple into the tuple to do this
Does anyone know about this though
Don't you want to do this? ```python
Callable[[EventCallback], EventCallback] | Callable[[GlobalEventCallback[EventNameT]], GlobalEventCallback[EventNameT]]
ended up typing the non-overload with ... as the overloads show possibilities and the inner function knows what it gets too
as if i do that, the inner decorator has complaints as its types arent typevar's
Hello me again
async def get_list(self, *, GIF: bool=False, JPG: bool=False, HTTP: bool=False) -> dict[str, list[str] | int] | list[str]:
data: dict[str, list[str] | int] = await self._request("list")
if GIF:
gif_data: list[str] = data['gifs']
gif_data.sort(key=lambda v: int(v.split(".")[0]))
return gif_data
elif JPG:
jpg_data: list[str] = data['images']
jpg_data.sort(key=lambda v: int(v.split(".")[0]))
return jpg_data
elif HTTP:
http_data: list[str] = data['http']
http_data.sort(key=lambda v: int(v.split(".")[0]))
return http_data
return data
so this is my code
i get alot of errors with mypy
nevermind i'm dumb
actually i do need help
how can i make gif_data, jpg_data, and http_data typehint into a list and not make mypy scream at me
because data is dict[str, list[str] | int]
the values are either a list of strings or an int
and these variables that i typehinted as list are list
but since i typehinted data earlier as dict[str, list[str] | int]
it's making me do those variables as list[str] | int
they will never be ints though
i've been told to use TypedDict
but i feel like returning TypedDict and having my variables typehinted as TypedDict would be too vague
or is it just me
whatsup bastards java is a divine language , pythonners smoks grass, when i code on java, God himself blesses me to ascend to heaven, cthulhu favors me! the codes on java make the world reen, and the codes on your disgusting python only make the light bulbs fade, since all the gods of the gods of the sphere of worlds are disgusted by you. when people see python coders, they takes a pythfork in theirb hands and start a righteous war
<@&831776746206265384>
hello sebastian
Thanks for that copypasta/monologue, @static saddle. It's not on topic for this channel, though.
You don't use TypedDict directly, you inherit from it to define the types of each key, then treat that as a distinct type.
Could someone help me? I want to try type-hint (I think, idk the terms) an argument so it will only accept certain values, I don't really know how to explain it, so I'll try show and example of what I'm trying to do:```py
def some_function(dictionary: in source_dictionaries):
source_dictionaries = ['all', 'ahd-5', 'century', 'wiktionary', 'webster', 'wordnet']
You want Literal["all", "ahd-5", "century", ...].
ok, thank you :D
Note that Literal only accepts literal values, so regular str is not allowed.
I am inheriting it
Yes that is what i did
I just feel like it's too vague
Or am i just wrong
got it, thank you for the help :)
That is a common thing to do, define your types elsewhere.
Ideally you'd swap it with a proper class, perhaps a dataclass, but if you have to use dicts it's a good option.
You could define a type alias for each key in the dict, so you don't need to re-declare the typesyou expect...
I was also told to use a dataclass
an enum would probably be a better fit here
def some_function(dictionary: Literal['all', 'ahd-5', 'century', 'wiktionary', 'webster', 'wordnet']):
...
oh, you already got that answer... sorry for the duplicate
nws :)
I apparently can't use TypeVar(bound=TypedDict) with Unpack in python 3.11?
It works with a normal TypedDict class
Technically though, I'm using python 3.10 with typing_extensions
are you getting a runtime error? We can change that
No.
Expected TypedDict type argument for Unpack (Pylance reportGeneralTypeIssues)
FYI I'm trying to make a basic interface for partial to see what it can do.
from functools import partial as _partial
from typing import Callable, TypeVar
from typing_extensions import Concatenate, ParamSpec, TypedDict, TypeVarTuple, Unpack
TArgs = TypeVarTuple("TArgs")
TKwargs = TypeVar("TKwargs", bound=TypedDict)
R = TypeVar("R")
P = ParamSpec("P")
def partial(
func: Callable[Concatenate[Unpack[TArgs], Unpack[TKwargs], P], R],
*args: Unpack[TArgs],
**kwargs: Unpack[TKwargs],
) -> Callable[P, R]:
return _partial(func, *args, **kwargs) # type: ignore
def foobar(a: str, /, b: list[str], *, c: int) -> str:
...
three = partial(foobar, c=3)
ok I can't change that ๐ Feel free to report a bug to Pyright.
I don't think bound=TypedDict is acceptable in general though, TypedDict isn't itself a type
also, that looks like you're reinventing ParamSpec
maybe.
I'm trying to extract things from ParamSpec and exclude it from the returned callable
does Paramspec support slices?
no it doesn't
you are aware most type checkers already have builtin functools.partial support
I can't concat 2 paramspecs either?
So I have to actually call the partial to get its param spec
otherwise it's just partial[T]
they do? I don't think mypy special cases it
interesting, Eric doesn't like special casing things ๐
At the same time:
maybe typeshed adds types to it?
no it's there ```packages/pyright-internal/src/analyzer/constructorTransform.ts:// Applies a transform for the functools.partial class constructor.
packages/pyright-internal/src/analyzer/constructorTransform.ts: // We assume that the normal return result is a functools.partial class instance.
packages/pyright-internal/src/analyzer/constructorTransform.ts: if (!isClassInstance(result.returnType) || result.returnType.details.fullName !== 'functools.partial') {
packages/pyright-internal/src/analyzer/constructorTransform.ts: // the "partial" mechanism, mark it has having a default value.
packages/pyright-internal/src/analyzer/constructorTransform.ts: // Create a new copy of the functools.partial class that overrides the call method.
(from git grep partial\\b in pyright repo)
oh yeah
i was only searching the first page of releases
sure would be nice though if pep 612 made partial mostly typeable
Special casing is no fun
I just realized why *args: *TArgs doesn't work with from __future__ import annotations. It really doesn't like the syntax.
yes you need Unpack. *args: *T is new syntax in 3.11
I'm aware ๐
dataclass_transform is just a big special case though, and he proposed it xD
but it's a standardized special case ๐
True
so he doesn't have to write special special cases for each library
instead it's one general special case
It's a special case to end all special cases
pyright is being uncooperative with this new typing
I figured it out.
It is not really meant for that yeah x)
This is what it wanted.```py
Args: TypeAlias = tuple[str, int]
well pyright isn't complaining
Oh I think it might just be TypeVarTuple that you cant use with kwargs then
When I put a non Unpack[TypedDict] in kwargs, it says
Expected TypedDict type argument for Unpack
They should add NamedTuple as a possible unpack value for *args
Pyright accepts this as an experimental feature. There's a PEP for it in preparation
pyanalyze accepts it too
I see, interesting
I have a package which uses python 3.10's union type hinting using the | symbol, however I want users who are using python3.9 to be able to use the package as well, i've added typing extensions but it doesn't quite seem to work, what i might be doing wrong? this is what i have in my pyproject.toml:
[tool.poetry.dependencies]
typing-extensions = {version = "^4.1.1", python = "3.10"}
// other dependencies here
typing-extensions can't support the | syntax because it's implemented in the core. You can use from __future__ import annotations to not evaluate annotations, which will allow |
Gotcha, thanks!
Any way I could implement the 3.10 union syntax during annotation resolution? I feel it'd be cool to still support it when doing reflection stuff but I don't wanna affect anything outside of the resolution
Like yeah I could overwrite the bitwise or for types but I'm not sure how reliable that is and it might maybe break something
You could implement a custom codec which rewrites the token stream before parsing, that's the "usual" black magic people use to implement non-standard syntax.
That's sufficiently cursed that I've never seen it in a production library though.
sadru you better not do that >:(
I don't think anything less would work though, overriding bitwise or for types is not going to work reliably.
I would strongly recommend just giving up on this feature, as I have for Hypothesis (which is not shy about black magic)
fair
Most people should've upgraded to 3.10 anyways at this point
I've wanted to do this for months but all my attempts failed and the previous convo here just reminded me of it
If you can, sure, but eg pytorch doesn't support 3.10 yet, and the latest PyPy is 3.8 - there are good as well as bad reasons not to be there yet.
"If you want support for older versions we can discuss my consulting rate" is a very valid position though, as an OSS dev the ease-of-dev/ease-of-deploy tradeoff is up to you ๐
Yeah I doubt most people are ok with 3.10+
well actually, pypy 3.9 is out
https://www.pypy.org/download.html
but yeah pypy 3.10 is not coming out soon
Do you know how I'd typehint the return type of a class decorator which returns another class. E.g:
T = TypeVar("T", bound=type)
def decorator(cls: T) -> ?:
class NewClass(Mixin, cls):
pass
return NewClass```
Not possible to typehint the return of a function with a class that is exclusively defined inside it's scope. But you can make a protocol for NewClass in the outer scope and return that instead
Also given you have a type variable type[T], if you are tying to add properties of type T, you cant do that, you would need an intersection type
which is sadly not supported yet
(why do you want a decorator instead of just having cls inherit from Mixin when it is created)
well because it's cooler. Also dataclasses do the same, but they probably have extra status
From a typechecker perspective
Thank you for your answer. Tho a protocol wouldn't be generic right?
you can have generic protocols, but that is not powerful enough to cover multiple inheritance like you have
Yup you are correct, they are special cased, although dataclass_transform is a feature that is in the works (draft pep) that can help you make decorators that behave like @dataclass deco. That still wont help you if you are adding arbitrary new arguments and functions to the class, (again you would need intersection for that)
yeah, py class Proto(Protocol, T): ... isn't a thing, rather ```py
class Proto(Protocol[T]):
...
sadly
but if your decorator is really purely for adding a mixin to the class, I'd suggest not using a decorator and just doing it manually
interesection type would be really complicated to implement and you shouldn't expect it any time soon
unless it would be in some limited form
Also I'm not even sure if my example was right. Is it
T = TypeVar("T")
def decorator(cls: type[T]): ...```
or
```py
T = TypeVar("T", bound=type)
def decorator(cls: T): ...```
Yeah that would've been my alternative
it's type[T]
pretty sure a typevar bound to type would just cover everything
actually, both would probably work
interestingly enough, even though object is a subclass of type, type-wise passing instances of objects wouldn't meet being bound to type
it is the other way around iirc
!e py print(type.__bases__) print(object.__class__)
@boreal ingot :white_check_mark: Your eval job has completed with return code 0.
001 | (<class 'object'>,)
002 | <class 'type'>
oh, that's interesting, I though type would be in the MRO of every object
Yeah, type is a subclass of object (like everything else) and object class is an instance of type
object is an instance of type, but object is not a subclass of type
ah
it makes sense when you think about it, but it is a bit confusing
For example, an integer (like 42) is not a type.
But it's an object.
object cannot be a subclass of type because type is a subclass of object
yeah, that does make sense, but it really is pretty confusing
Probably less then python 2 x)
glad I got to skip that one
I only came to python from like 3.6, and while people were still using 2 and making guides for it etc. I never actually used it since 3 was a thing
but I've seen some quirks of it
So the 2nd one is right?
I didn't quite keep up
Either, but since the best you can do is just return the same class, you dont even need the bound
T = TypeVar('T')
def decorator(cls: T) -> T: ...
The difference is that with type[T] you have access to T as an instance as well
Ok
For example, if your decorator returned an instance, that could only be done with type[T]
T = TypeVar('T')
def factory(cls: type[T]) -> T: ...
But I'd still do the bound, because otherwise this would be valid:
decorator("Hi")```(for the first version)
ah right
would it really be so complicated?
class C(A, B):
pass
x: Intersection[A, B] = C() # would be valid```
then in my case I could annotate the return type as Intersection[Mixin, T]
mypy and pyright already have intersection types, you just cant make them from python yet
How do you create them then? And do you know if there's already a pep for that or an issue,...?
you don't, it's internal to type checkers
Lol 2014
It's also in pep 483
the second linked issue (213) is a bit more recent and still open
though still not that much more recent
someone mentions in it that they are working on a pep for it, but that was December last year so I don't know what happened to that
well, say you defined an intersection of str and dict, except they have completely different structures and aren't really compatible like that, for example __iter__ in one refers to individual characters, while in the other it refers to stored keys. What happens in an intersection? Do both get added as overloads? if so, would that really make sense? Not to mention things like handling variables named the same but with different types, how would you combine this? ```py
class Foo:
def init(self, x: int):
self.x = x
class Bar:
def init(self, x: str):
self.x = x
A limited intersection may be possible, where there aren't any conflicting cases like these, but it's too complex to implement this fully since resolving cases like these in a way that suits everyone just won't happen
I understand your concerns, but maybe we aren't talking bout the same thing. Also maybe intersection is not the right name. What I wanted to express with it is that the args to it behave exactly like a multiple inheritance class, like in my example. Then there would no longer be a problem as to which classes attribute is taken, simply the first that has it is taken.
The type script approach (I think, I dont use it) would be that an intersection of two incompatible types is possible, it is just that since you cant actually make a type that for example is both an int and a str, you will never be able to satisfy x: Intersection[int, str]
Yeah I'd agree with this it should just be typing.Never
that would be fine, yeah
yes, string & number resolves to never in typescript
what do you mean exactly by intersection? I thought about it like this:
class C(int, str):
pass
x: Intersection[int, str] = C()```
Yes that would be a form of intersection
I suppose intersections would be more useful with Protocols, Callables and TypedDicts
More broadly an intersection of two types is a type that simultaneously satisfies the semantics of both "intersectioned" types, whether Nominal or Structural (Protocols)
So Intersection[Nominal1, Nominal2] is a type that inherits from both Nominal1 and Nominal2, Intersection[Nominal1, Structural1] is a type that inherits from Nominal1 and also has the properties of Structural1, and Intersection[Structural1, Structural2] is a type that has the properties of both Structural1 and Structural2 (with no inheritance constraints)
Intersection[Nominal1, Nominal2] is a type that inherits from both Nominal1 and Nominal2
Actually I am not so sure about that one
Because class Foo(A, B): ... is not the same as class Foo(B, A): .... So Idk what the right definition would be
It should just be an error if the order of the inheritance matters right? Because that would suggest that the inherited classes overwrite eachothers methods
it could also be implemented like set intersection
x: Intersection[A, B] = y is correct iff x: A = y is correct and x: B = y is correct
so for example if A had (x: int) -> str and B had (x: int) -> int, intersection would be an empty set
if they were the same though, it would be present
or would it? ๐ค
maybe it's (x: int) -> Never
That would not allow for nominal intersection
why?
Well, so this would be the case right?
It already is, so yeah that stands
you can't inherit from two classes with incompatible method signatures
because that would break LSP
Yeah, I forgot that was already disallowed, so not really a concern for an intersection type of two nominal classes
Wait it probably shouldn't be called intersection
Why not?
Because multiple inheritance is more like union
class C(A, B): pass```has methods of A and B
Not only methods which both have
so more like A | B not A & B
or am I completely wrong
When we say Union and Intersection, we are talking about the Union of terms and intersection of terms, not the set themselves. So the elements of the Union of type A and type B are all the elements that are either a term of type A or a term of type B. The Intersection of A and B, are all the terms/elements that are both an element of type A and type B
Oh ok
I dont think you are wrong, you are just taking a different interpretation that is not really useful in the typing context
Im really looking forward to that addition to python. What also would be nice is typechecker support for typing.Annotated.
What do you mean by "support"?
Well first some types like Range, Length which can be used in annotations like this: x: Annotated[int, Range[0, 10]] or y: Annotated[list, Length[10]] and then also validation for those types
That is orders of magnitude more difficult to implement then an intersection type though
yeah that's true
it requries running the code though
unless it's a literal directly
you can't expect a type checker to know what an int value in a variable will be, after manipulations
Not necessarily, you can use abstract interpretation and constraint solving for stuff like that
LiquidHaskell is an example of that and it uses SMT
yeah, sometimes, but that could take ages depending on complexity of that algorithm, you could face infinite loops, and so many other things
some things can be abstracted, sometimes, but that'd be pretty limited
not to mention things like reading values from files/sockets/stdin
for literals, this could get implemented, but it's mostly a waste of time since you usually won't use literals, and it would be a very incomplete implementation
is this what dependent types are?
No, this is called refinement types
You take a type and refine it via a predicate, eg: "Larger then X", "In between X and Y", "With the form ..."
Actually it might also be dependent types, but I think that is in the "not interesting" side of dependent types, since a lot of things that are in theory dependent types are not actually called that
re: intersection type
Java and typescript do this using the & operator.
java
public class Foo<T extends IFoo & IBar> {
}
typescript
type IFoo = {foo: string;}
type IBar = {bar: string;}
type Foo = IFoo & IBar;
The same would probably be done for python, with Intersection[] for backwards compatibility
Just like Union
Both inspired by the set methods
One thing I wish I could do in python is something like typescript's typeof operator.
what does typeof do?
Question: is there a way to correctly annotate a return type for this func?
T1 = TypeVar("T1")
T2 = TypeVar("T2")
def add(a: T1, b: T2):
return a + b```
It feels like there is still some work to do to make pythons type annotation system truly generic
"correctly" in what sense?
Without type errors and as precise as possible. So not just object etc.
would it not be this?
class SupportsAdd(Protocol):
def __add__(self, __x): ...
T = TypeVar('T', bound=SupportsAdd)
def add(a: T, b: T) -> T:
return a + b
But there's no guarantee that __add__ returns the same type
Also a and b should be able to be of different types
i dont think theres a way to do that currently, youd need to feed the type of b into a and a into b
:(
I mean, they can be different types they just have to be broadly compatible
Which I think is the norm when it comes to these operations
I don't think using one typevar for both allows different types
def add(a: T1, b: T2) -> operator.add[T1, T2]:
return a + b```doesn't work unfortunately, but could be a solution (?)
It does, it will just take the "common denominator" all the way up to the bound of the typevar
so you can give it a float and a int for example
That indeed does not work though
And isnt a planned feature as far as I know
yeah was just dreaming
That's cool
what about something like
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
Re intersection: I think it would also be really nice to write them as A & B
what does co- and contravariant mean again?
something to do with subclasses I honestly don't know either ๐
can't remember which one is which
Well, idk, just tried add(3, 4) and it is already complaining xD
whaaa, it worked for me last time I checked
Oh wait, it works in mypy, just not in an outdated version of pyright
Ah, the problem was this
edited, now it should work for on the playground
yeah tbh mypy is the weird one here
Interesting, yeah this is an intense specification of an add function ๐
Why is add not defined like this in typeshed? ๐ค
you mean operator.add?
stdlib/_operator.pyi line 52
def add(__a: Any, __b: Any) -> Any: ...```
Yeah I was referring to that, I was thinking that maybe this wouldn't work for some scenarios but I think whatever the case is @brisk heart specification should work
please don't use it
or at least drop the radd stuff
feels pretty overcomplicated with it
Have you looked at typeshed?
xD
So many hacks, and some many functions with a million overloads
This is pretty good in comparison
most of the overloads I've seen should be fixed with Unpack
can't think of anything weird that's not caused by variadic arguments tbh
look at pow()
Yeah that is the one I was thinking of when I said hacks ๐
oh I remember seeing y'all talk about that a while ago actually
Hm, I kind of asked something similar before but what is the exact variance of a callable's return type?
covariant
arguments are contravariant
Should I define my ParamSpec as contravariant then?
Ah, for ParamSpec yeah
The docs seems to suggest otherwise although I am not sure how it applies
ParamSpec allows covariant=True at runtime but its behavior is not specified
Parameter specification variables created with covariant=True or contravariant=True can be used to declare covariant or contravariant generic types.
Not sure how that works x)
it currently does nothing
What variance would I apply for a typevar I use like Callable[[T], T]? It has a bound class.. which I think I want covariant?
Not sure we should have made those flags exist at all. For TypeVarTuple we just don't accept them.
likely invariant, because you need both co- and contravariance
Ah yeah because the arguments were contravariant, and the return type covariant
I mean that pep leaves an open door for it, so shoudnt it be allowed so that in the future no changes are required to the runtime?
Sure, that's an option. But if it's sufficiently unlikely that we'll add semantics in the future, it'll just end up confusing people.
why doesnt TypeVarTuple support variance?
cause it seems like it'd be easy enough to specify
bounds would be nice too
There was an example recently here where It seemed like a natural use
had to do with overloads
Another thing I've already asked about, but ended up with a bug report that worked against me ๐ ...
I have made a utils for users to use: ```python
CommandT = TypeVar('CommandT', bound=CommandMiddlewareMixin)
MiddlewareDecorator = Callable[[CommandT], CommandT]
```python
def func() -> MiddlewareDecorator:
...
return check(...)
Thing is, MiddlewareDecorator is currently just Callable[[Any], Any]. If I give it a type (which I don't want the user to need to do; I would just like to be the CommandMiddlewareMixin because that's the "bottom type") then it becomes invariant right?
def test(command: SubcommandGroup) -> SubcommandGroup:
return command
def x() -> MiddlewareDecorator[CommandMiddlewareMixin]:
return test
Expression of type "(command: SubcommandGroup) -> SubcommandGroup" cannot be assigned to return type "MiddlewareDecorator[CommandMiddlewareMixin]"
Type "(command: SubcommandGroup) -> SubcommandGroup" cannot be assigned to type "MiddlewareDecorator[CommandMiddlewareMixin]"
Parameter 1: type "CommandMiddlewareMixin" cannot be assigned to type "SubcommandGroup"
"CommandMiddlewareMixin" is incompatible with "SubcommandGroup"
I want a way to say:
A callable that takes in an argument that is a subclass of
CommandMiddlewareMixinand returns the same type
๐ฉ
Here is a reproduceable: https://mypy-play.net/?mypy=latest&python=3.10&gist=48ec2e9622f67cc35bdca59b95ca4985
You can copy the same code and run it with Pyright and it complains similarly
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
I think I showed you the way the other day ```py
CommandT = TypeVar('CommandT', bound=CommandMiddlewareMixin)
class MiddlewareDecorator(Protocol):
def call(self, cmd: CommandT, /) -> CommandT:
...
if you want to specify the bound in square brackets, that's not possible.
I am only doing [Mixin] because I need to; I don't want to.
I am looking for a solution where I don't need to...
But that already raises an error?
I don't ๐
Or you saying you wanted a solution that didnt involve that and still made the error
If I don't then it becomes Any
...which of course passes ๐
you want PEP 646 to be more complicated? ๐
Oh so you want it to error
ideally i'd rather not leave stuff like this to be implemented later
Of the 3 things, bound would be the easiest least hard to add right?
Ah sorry you may have, this still doesn't work? https://mypy-play.net/?mypy=latest&python=3.10&gist=ab9dfe9ad8d215b5fcf913c1e350a2db
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
A very different error though, so I am not sure if that's something wrong with how I added it there?
I want it to typecheck without being Any/object/untyped
Actually this is not the whole story
oh noes
This is also a bug in pyright but
class A:
def __add__(self, __other) -> int: return 42
class B(A):
def __radd__(self, __other) -> str: return "foo"
reveal_type(A() + B()) # Type of "A() + B()" is "int"
This is actually "foo" at runtime
TIL ๐
I know, I had no idea, but it is documented
that's only if the RHS is a subclass of the LHS I think
Note If the right operandโs type is a subclass of the left operandโs type and that subclass provides a different implementation of the reflected method for the operation, this method will be called before the left operandโs non-reflected method. This behavior allows subclasses to override their ancestorsโ operations.
mypy gets it right, I wonder how that is implemented
Hummm, how does self variance work again?
Specifically when it comes to protocols
Mypy wants self: T with T being covariant, while pyright wants it contravariant
This is fine by mypy, but pyright doesnt like it
from typing import Protocol, overload, TypeVar, Any
T = TypeVar('T')
T_co = TypeVar('T_co', covariant=True)
T_contra = TypeVar('T_contra', contravariant=True)
Self = TypeVar('Self', covariant=True)
class SupportsAdd(Protocol[Self, T_contra, T_co]):
def __add__(self: Self, __other: T_contra) -> T_co: ...
class SupportsRAdd(Protocol[Self, T_contra, T_co]):
def __radd__(self: Self, __other: T_contra) -> T_co: ...
class SupportsLRAdd(Protocol[Self, T_co]):
def __add__(self: Self, __other: T_contra) -> T_co: ...
def __radd__(self, __other: Any) -> Any: ...
@overload
def add(x: SupportsLRAdd[T, T_co], y: T) -> T_co: ...
@overload
def add(x: SupportsAdd[T, Any, Any], y: SupportsRAdd[T, Any, T_co]) -> T_co: ...
@overload
def add(x: SupportsAdd[Any, T_contra, T_co], y: T_contra) -> T_co: ...
@overload
def add(x: T_contra, y: SupportsRAdd[Any, T_contra, T_co]) -> T_co: ...
def add(x, y):
return x + y
class A:
def __add__(self: Any, __other: Any) -> int: return 42
class B(A):
def __radd__(self: Any, __other: Any) -> str: return "foo"
class C:
def __radd__(self: Any, __other: Any) -> bool: return True
class D:
def __add__(self, __other) -> int: return 42
def __radd__(self, __other) -> str: return "foo"
reveal_type(add(A(), B())) # Revealed type is "builtins.str*"
reveal_type(add(A(), C())) # Revealed type is "builtins.bool*"
reveal_type(add(D(), D())) # Revealed type is "builtins.int*"
i think thats a pyright bug covariance there makes sense to me
I see, I wonder how it works with typing_extensions.Self, I havent tested that yet
it wouldnt do anything for you here
Trying to find to see if this has been an issue in the past but Eric disagreed will be hard, Self protocol didnt really narrow it down
Do you think there is a better way of doing this?
Not even sure this covers everything
idk this seems alright to me
Nvm you still have the same issue since you could just subclass D
class D:
def __add__(self, __other) -> int: return 42
def __radd__(self, __other) -> str: return "foo"
class E(D):
def __radd__(self, __other) -> str: return "foo"
D() + E() would say it returns int while it should return str
I think this can might be possible with bounded type vars, but then if one wanted to do this to all operators, you would need a million different type var, each one bounded by a specific protocol
... it also makes add(2.3, 2) reveal as int
Omg I didnt even think about that fact that float + int should return int according to the notion that int is a subclass of float since it redefines __radd__ but then of course at runtime it would return a float
So the idea really is
class Foo:
def __add__(self, __other) -> A: ...
def __radd__(self, __other) -> B: ...
class SubFoo(Foo):
def __radd__(self, __other) -> SubB: ...
reveal_type(add(Foo(), Foo())) # should reveal A
reveal_type(add(Foo(), SubFoo())) # should reveal B, not SubB
Ah, I misunderstood what you meant
I think I don't get it
I know every static checker is different
But what if I'm in this situation...
class MyDescriptor(Generic[T_Type, T_Owner]):
def __init__(self, type: Type[T_Type]) -> None:
...
def __set_name__(self, owner: Type[T_Owner], name: str) -> None:
...
Is it possible to infer two different defining typevars across the instantiation of the object?
I have a system where the user returns a decorator. This decorator can be placed on any CommandMiddlewareMixin instance.
To aid the user, I want to provide an alias they can use so that when they make their code they can annotate the return type with this. Here is what I am imagining: ```python
def some_user_defined_whatever() -> LibraryProvidedTypeAlias:
def inner():
return 'bananas'
return library_code(inner) # returns (<instance of CommandMiddlewareMixin>) -> <same instance of CommandMiddlewareMixin>]
Note that I also use this annotation a lot internally, so I would really like to write an alias for it because it is repeated so much
Hi, which one is correct?:
class Foo:
pass
dictionary:dict(Foo) = {}
dictionary:dict[Foo] = {}
The latter
-but also.. neither
dict takes two arguments. The key and the value
oh ok, then how should we type hint for a dictionary that will store instances of a class?
dictionary: dict[str, Foo] = {} for example
so we are saying key is of type string, and value is of type Foo class?
Got it thank you!!
He is not happy about it x)
So if the rules followed by the runtime are as convoluted as it appears they are, there's no way for a static type checker to determine the correct answer statically. I would therefore advise against using a pattern where you have a class hierarchy where the parent implements
__add__and the child implements__radd__or vice versa.
If this could be ignored making the operators in the operators module generic would be much easier
returning an Union might be the best that can be done while still maintaining parity with the runtime behaviour
It probably can be. Any subclass that provides an incompatible override violates LSP anyway.
The problem is that all you have to do is have the function declared, so it doesnt really violate LSP I think, but it definitely violates principle of least astonishment
class A:
def __add__(self, __other) -> int: return 42
def __radd__(self, __other) -> str: return "foo"
class B(A):
def __radd__(self, __other) -> str: return "foo"
A() + A() # 42 at runtime
A() + B() # "foo" at runtime
Doesn't mypy yell at you if your __add__ and __radd__ are incompatible?
Only partially I think, but that is probably implementation stuff
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
class A:
def __add__(self, __other: 'A') -> int: return 42
def __radd__(self, __other: int) -> str: return "foo"
class B(A):
def __radd__(self, __other: object) -> str: return "foo"
reveal_type(A() + A())
reveal_type(A() + B())
this on the other hand works just fine
Found it
It is nice there is a step by step xD
mypy/checkexpr.py lines 2585 to 2588
# STEP 2a:
# We figure out in which order Python will call the operator methods. As it
# turns out, it's not as simple as just trying to call __op__ first and
# __rop__ second.```
Hi! I see a lot of these if TYPE_CHECKING imports. Why are they used? Why is TYPE_CHECKING needed here? Why don't they just simply import it without the if?
# Something like this
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from somewhere import something
def func(arg: "something"):
...
(ping me if you respond)
TYPE_CHECKING resolves to False at runtime, so the import doesn't execute (and doesn't cause import cycles, or unnecessary imports)
then the "something" annotation is used so python doesn't try to use the non-existent name when you run the code @near mural
Ok. Thanks!
class ChildClass(ParentClass):
child_attribute = 'something'
def my_function(argument: MyClass[ChildClass]):
return argument.child_class.child_attribute # cannot access member "child_attribute" for type "ParentClass" Member "child_attribute" is unknown
``` How do I make my type checker know that `MyClass.child_class` will be the one inside the `[]` on the annotation and not just any class inheriting `ParentClass`?
What does myclass look like?
jank with __class_getitem__ that returns an instance of itself, but uh this has to do with discord.py converters and how they work
Oh no
yes its bad
You should probably be using commands.param if you can
oh right that's a thing
class WithFlags:
def __init__(self, result: str, flags: FlagConverter):
self.result = result
self.flags = flags
def __class_getitem__(cls, item: Type[FlagConverter]) -> ActualConverter:
return ActualConverter(item)

i'm aware it's shit, but the type checker was mad at me calling something inside the type hint
if you wanted runtime introspection without interfering with type checkers, you can always make MyClass an alias for Annotated
at if TYPE_CHECKING
Annotated support was also recently added to v2 so that would also be a good solution
Actually iirc it is a bit ugly, last time I tried that I had to do from typing import Annotated as ...
Yeah
hmmm
I find annotated ugly, or at least like Annotated[FlagResult, WithFlags[SQLFlags]]
i guess it's my best option though, lol
No, I mean
does annotated allow you to not pass the 2nd arg?
MyClass[ChildClass]
Like
if TYPE_CHECKING:
from typing import Annotated as MyClass
else:
class MyClass:
...
def __class_getitem__(cls, item):
return cls
lol, that also is ugly, but i think that would do it
ill just drop it in a helper file and pretend it doesn't exist
wait no that doesn't fix anything lmao
Uhhhhh that's disnake only I believe
Is that what you wanted?
I added it to slash commands in disnake after dpy got shut down
two things, dpy isnt shutdown and you're talking to the man who PRed commands.parameter into official d.py lmao
When I saw the abbreviation I assumed differently mb
you're good lol dw
And I am aware it isn't abandoned anymore
Anyways fuck dpy and fuck the forks, hikari for life
I see your ๐ค, @hearty shell. so...
x: WithFlags[Thing] means that x.thing would be a Thing instance. x would still be an instance of WithFlags
and uh I just can't find a way to do that. I don't even know if it's possible lmao. But I feel i've seen this behaviour before. but i have no clue where i saw it
No, dpy added that recently
Ah i didn't see Leo's message
I haven't been keeping with dpy at all, it's absolutely mb
And I feel hikari-tanchi does this signature parsing stuff much better than dpy & forks anyways
ooo how is it?
Converters with Converter[func] which is Converter(func) on runtime and the return type of func during type-checking
its not cause i literally added it ๐
Yeah mb
I shouldn't be correcting peeps about dpy when I haven't used it in ages, I apologize for that
wait, how so?
like if it was Annotated? hmm
Through __getitem__ of a metaclass, doesn't work with mypy but I doubt anyone in their right mind uses mypy over pyright
If anyone is then explicit Annotated is recommended
ah, that sounds like what I tried to do for a thing and failed
TargetVerifier[Member/Role/Channel/uhh others]
class MyClass(Generic[T]):
thing: T
def __class_getitem__(cls, item):
return ...
injections are defined with Injected = Annotated[T, InjectionTypes.TYPE] but that's probs the only one
Something like this?
let me try that!
__class_getitem__ is unfavorable with pyright in my experience because it always assumes it's only ever used for custom impls of Generic stuff and not for returning a custom type
i thought that was mypy
idk what the use case here is though so probs not a problem
mypy is generally unable to handle special behavior of both metaclass getitem and class getitem
Pyright just doesn't deal well with class getitem
But I though the point was that they dont deal with that no?
The only way I could ever get anything like it to work was with metaclass getitem to add some special behavior
It is perfect, as we can use that to lie and do runtime introspection while keeping the ugly internal
For clarity I'm doing stuff like this https://github.com/thesadru/tanchi/blob/master/tanchi/types.py#L105-L127
Purely because using __class_getitem__ never worked properly
PEP 560 defines class_getitem as a way to enable indexing a class (to define custom generic types for instance), but mypy does not seem to recognize this. To Reproduce $ cat test.py class A: de...
worked, thank you!
I remember seeing some suggestion that there should be a way to tell what type-checker is being used to be able to define different annotations for them and I think that'd help a lot of my projects
for example in the example I sent I could just tell mypy that it's just an alias typing.Annotated since it can be used with an explicit type as Converted[runtime_type, converter_function] which is the exact same as typing.Annotated
there is already something like that? ๐ณ
Well, it came before TYPE_CHECKING
if MYPY: works like if TYPE_CHECKING: but only for mypy
So Idk what happend to it afterwords
you just have to put MYPY = False in your code so it doesn't execute at runtime
oh that's cool actually
pyright doesn't respect if MYPY?
it's treated like any other boolean outside of mypy
so declare it as False
pyright can then infer it to be False under most circumstances
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
class SupportsAdd(Protocol[_T_co, _T_contra, _R_co]):
def __add__(self: _T_co, __other: _T_contra) -> _R_co: ...
class SupportsLRAdd(Protocol[_T_co, _T_contra, _T2_contra, _R_co, _R2_co]):
def __add__(self: _T_co, __other: _T_contra) -> _R_co: ...
def __radd__(self: _T_co, __other: _T2_contra) -> _R2_co: ...
def add(x: SupportsAdd[_T, _T1, _T2], y: SupportsLRAdd[_T, _T1, _T, _T2, _T3]) -> _T2 | _T3: ...
class A:
def __add__(self, __other: Any) -> A: ...
class SubA(A):
def __radd__(self, __other: SubA) -> bool: ...
t: SupportsLRAdd[A, Any, A, A, bool] = SubA() # Correctly erros
reveal_type(add(A(), SubA())) # Fine
Does anyone know why add(A(), SubA()) is fine here?
SupportsLRAdd[A, Any, A, A, bool] is just my best estimate as to what those variables resolve to when doing the above
Hey there. Why does pyright say
Function declaration "callback" is obscured by a declaration of the same name
with this code
def _generate_callback(cls: Union[Type[_Command], CB], fake: bool = False) -> CB:
if inspect.isclass(cls) and issubclass(cls, _Command):
# Context menu callback relies on the annotation
if fake:
async def callback(interaction: Interaction):
pass
elif cls.__discord_app_commands_type__ is AppCommandType.user:
async def callback(interaction: Interaction, target: Union[Member, User]) -> None:
cls.__discord_app_commands_id__ = int(interaction.data['id']) # type: ignore # This will always be present
inst = cls()
inst.interaction = interaction
inst.target = target # type: ignore # Runtime attribute assignment
await inst.callback()
elif cls.__discord_app_commands_type__ is AppCommandType.message:
async def callback(interaction: Interaction, target: Message) -> None:
cls.__discord_app_commands_id__ = int(interaction.data['id']) # type: ignore # This will always be present
inst = cls()
inst.interaction = interaction
inst.target = target # type: ignore # Runtime attribute assignment
await inst.callback()
else:
async def callback(interaction: Interaction, **params) -> None:
cls.__discord_app_commands_id__ = int(interaction.data['id']) # type: ignore # This will always be present
inst = cls()
inst.interaction = interaction
inst.__dict__.update(params)
await inst.callback()
return callback # type: ignore
return cls # type: ignore
I just don't get it. How is callback getting defined more than once?
Because the functions have different signatures
Doesnt matter if only one of then gets defined at runtime, when static typing there is no way for the type checker to know which one it is going to be
ah
Pyright has like a work-around where you give them different names and then do callback = ...
From the sniped you posted though, there seems to be a way to tell what callback it is going to be statically
I think
(@loud bloom)
hmm
Can __discord_app_commands_type__ be known statically?
kinda...
but i just ended up doing this
Alright, well, I mean the solution I was talking about was just making overloads of _generate_callback, but this looks like a private method so might not be worth it x)
I still haven't figured this one out but I think this is might be a bug
It was a bug all along ๐ฅฒ
class ProtoFoo(Protocol[_T_co]):
def foo(self: _T_co) -> None: ...
class Foo:
def foo(self) -> None: ...
class SubFoo(Foo): ...
a: ProtoFoo[SubFoo] = Foo() # Fine
are you saying that's a bug?
oh wait yes
it's generic over the self type of the method
Yeah, I think it is because it makes the comparison after the method bounding, but given that mypy explicitly complains if I type self as contravariant, there must be something under the hood that was meant to support this
regardless it is not of much use until pyright is convinced that self should be typed covariant, which it doesnt atm
regardless it is not of much use until pyright is convinced
My attempt: https://github.com/microsoft/pyright/discussions/3373
x)
How do I annotate a hashable sequence? Do I have to create a class which inherits both of these?
That wouldn't work, because Sequence is an abstract base class (nominal), not a protocol (structural)
I'm afraid you'll have to just make a protocol with all the methods
Hello, how can mypy still treat parsedSum as Optional[str]?
if isinstance(parsedSum, str):
dir = parseRecord(root, ".//nms:TtlNtries/nms:TtlNetNtry/nms:CdtDbtInd")
if isinstance(dir, str) and dir == "DBIT":
parsedSum = -float(parsedSum) #Incompatible types in assignment (expression has type "float", variable has type "Optional[str]")mypy(error)
What do you mean? The problem here isnt the Optional part, it is trying to assign float to a str
Variables only have one type in analysis, so you shouldn't use one variable for multiple things
Therefore if I parse a string from some file and want to cast it to float, I should create a new variable?
Yes
Makes sense, thanks
It's a bit confusing to have a str value in something named parsedSum anyways haha
any ideas why would map(asyncio.create_task, coros) not work type-wise while (asyncio.create_task(coro) for coro in coros) would?
If you remove the first one does it work?
no
Well map is a class different from generator so idk what's up if it's not that
I've tried that, the error did seem like it had problems assigning to tasks but commenting it out didn't work
huh
but renaming tasks to something else solved it
so it's probably a pyright bug

What does it resolve to when you rename it?
Mypy seems to have the same problem
hm, actually it doesn't work with other names either, it was just pyright-playground being slow lol
Yeah I think the problem is how map is resolved
so it's a typeshed issue?
if mypy has the same issue, it's probably just badly defined types in python itself
_T = TypeVar("_T")
_S = TypeVar("_S")
class map(Iterator[_S], Generic[_S]):
def __init__(self, __func: Callable[[_T1], _S], __iter1: Iterable[_T1]) -> None: ...
def __iter__(self: _T) -> _T: ...
def __next__(self) -> _S: ...
class Task(Generic[_T]): ...
create_task: Callable[[Coroutine[Any, Any, _T]], Task[_T]]
coros: Iterable[Coroutine[Any, Any, int]]
tasks = map(create_task, coros)
reveal_type(tasks) # Revealed type is "__main__.map[__main__.Task*[_T`-1]]"
I am just speculating but it looks like the problem is trying to give a generic function when initialising map
Resolving _S requires resolving Callable[[_T1], _S] which in this case is Callable[[Coroutine[Any, Any, _T]], Task[_T]], but to resolve _T you need to resolve __iter1: Iterable[_T1]
isn't coros just a map of None?
no, it's map[Coroutine[Any, Any, Iterable[int]]]
Wait how placeholder returns None
it doesn't
placeholder returns Iterable[int] and it's an async func, so it's actually returning a coroutine that returns that
Oh I didn't see the async
I think this is a base example of the problem
class Foo(Generic[_T]): ...
def foo(a: Callable[[_T], _S], b: Foo[_T]) -> _S: ...
bar: Callable[[_T], _T]
baz: Foo[int]
reveal_type(foo(bar, baz)) # Argument 1 to "foo" has incompatible type "Callable[[_T], _T]"; expected "Callable[[int], _T]"
I think there are some mypy issues about this, it's pretty hard to get right
Literally from pyright
from typing import *
class Parent: ...
class ChildA(Parent):
def __add__(self: 'ChildA', __other: int) -> int: ...
class ChildB(Parent):
def __add__(self: 'ChildB', __other: bool) -> int: ...
class ProtoFoo(Protocol):
__add__: Callable[[Parent, int], int]
a: ProtoFoo = ChildA() # Fine
a: ProtoFoo = ChildB() # Error
So much for "nonsensical", that this is literally how it is internally handled, just not exposed normally
That makes sense to me
well, kind of, if you didn't return NotImplemented, pyright would be correct here
Yes, but not to Eric
No, pyright is definitely correct here, not because of that, but because self should be covaraint
now try doing the same thing but instead of doing Callable use normal function syntax
oh nvm, I missunderstood the issue here, yeah
Yeah this issue stems from me trying to define a Protocol as generic covariant on the first parameter of one of its methods, the self parameter, and then having pyright complain
saying that it should be contravariant
I'm doing something super cursed with metaclasses and I'm trying to figure out how to make it play well with Pyright
I have two classes, Foo & Bar
Foo is meant to be subclassed in order to be used (and not instantiated); Bar is not subclassed, it's an instance made for every Foo subclass
Foo has a metaclass with a __new__ that looks something like this:
def __new__(
cls,
classname: str,
bases: tuple,
attrs: Dict[str, Any],
):
if not bases: # This metaclass should only operate on subclasses
return super().__new__(cls, classname, bases, attrs)
sub_cls = super().__new__(cls, classname, bases, attrs)
# Do stuff here
return Bar(...)
However, with an example like this:
class Baz(Foo):
pass
Baz becomes an instance of Bar, but the type checker thinks it's Type[Baz], a subclass of Foo... Is there a way to let Pyright know Baz is an instance of Bar?
anyone know why mypy would give this error for a library that is typed and built with a py.types marker?```error: Skipping analyzing "growstocks": module is installed, but missing library stubs or py.typed marker [import]
I think py.typed is not in the built package
why would it not be?
Could you clarify with a bit more code
Not sure what is going on here, you are returning an instance of Bar for a class creation?
Actually, pretty sure there is no way, but who knows
Is there a way to let Pyright know Baz is an instance of Bar?
still real cursed but you could useif TYPE_CHECKING: Foo = Bar
Type[Baz] should also be recognised as a Bar too, you should see if you can define special methods etc. in general though metaclasses are tricky for type checkers, they should support basic operations.
๐ thanks idk how i missed that
Oh right type_checking ๐
But the fact that this doesnt work tells me that you cant actually do anything else:
class MetaMetaFoo(type):
def __call__(self, *args, **kwargs) -> type[int]: ...
class MetaFooA(type, metaclass=MetaMetaFoo):
def __new__(cls, *args, **kwargs) -> type[int]: ...
class MetaFooB(type):
def __new__(cls, *args, **kwargs) -> type[int]: ...
class FooA(metaclass=MetaFooA): ...
class FooB(metaclass=MetaFooB): ...
a: int = FooA() # Error
b: int = FooB() # Error
maybe using some kind of descriptor would work?
While I'm here...
I have a type: ignore[no-untyped-def]at the top of one of my files to ignore all occurrences of that code, and it successfully works to disable that code, but at the same time it also gives Unused "type: ignore" comment... Any idea why?
i thought of that but the classes have very different attributes and since the class is designed to be subclassed i want the typing to be correct when you're subclassing
yup
for a subclass creation
so if i do
class Baz(Foo):
pass
Baz is actually a Bar instance, not a subclass
oh and in this case it would be Type[Bar] instead of Bar
ah
a solution that partially works is doing if TYPE_CHECKING: Foo = Any
from typing import *
class Bar: ...
class Foo: ...
def deco(cls) -> Bar:
return cls # type: ignore
@deco
class Baz(Foo): ...
reveal_type(Baz) # Type of "Baz" is "Bar"
@loud bloom Does this work?
Well
This would also work
from typing import *
if TYPE_CHECKING:
class Bar(type): ...
class Foo(metaclass=Bar): ...
else:
class Bar: ...
class Foo: ...
class Baz(Foo): ...
a: Bar = Baz
unless I'm misunderstanding it, that doesn't work
is it a error?
yeah, Expression of type Type[Baz] can't be assigned to declared type Bar
can you show the code? This snippet seems to work on both mypy and pyright
codebase is quite huge, ill try to trim it down a little
Alright, for all methods and functions just strip the implementation
from typing import TYPE_CHECKING
if TYPE_CHECKING:
base = type
else:
base = object
class Bar(base): ...
class Meta(type):
def __new__(cls, classname, bases, attrs):
if not bases: # This metaclass should only operate on subclasses
return super().__new__(cls, classname, bases, attrs)
return Bar(...)
if TYPE_CHECKING:
meta = Bar
else:
meta = Meta
class Foo(metaclass=meta): ...
class Baz(Foo): ...
a: Bar = Baz # Error
That's the gist of it
Humm, seems like the problem is having the metaclass be a variable and not the it directly
if TYPE_CHECKING:
class Foo(metaclass=Bar): ...
else:
class Foo(metaclass=Meta): ...
would have to be like this
You haven't type hinted the return correctly?
hi, i got a question re. typing.
consider this snippet of code
from typing import TypeVar
from pydantic import BaseModel, Field
T = TypeVar("T")
class Thing(BaseModel):
a: int = Field()
@classmethod
def query(cls: T) -> T:
return cls(
a=42,
b=42,
)
class ExtendedThing(Thing):
b: int = Field()
here, def query(cls: T) -> T: is wrong because the actual return type is an instance of T (a type/class), not the actual type/class T, how do i specify this? (basically looking for opposite of typing.Type)
def query(cls: type[T]) -> T:
also @hallow fractal, if you already depend on typing_extensions you can just use Self
that's great info, thanks!
bump
I've seen that before too ยฏ_(ใ)_/ยฏ
Apparently it's been discussed here: https://github.com/python/mypy/issues/10304
and in more detail here: https://github.com/python/mypy/issues/9440
Hi, is there any way to get func param type with Optional by annotation?
import inspect
from dataclasses import dataclass
from typing import Optional
@dataclass
class T:
a: str
def foo(bar: T):
print(bar.a)
def fooo(bar: Optional[T] = None):
if bar:
print(bar.a)
# without Optional, the annotation is the type obj
func_sign = inspect.signature(foo)
param = func_sign.parameters['bar']
p_type = param.annotation
print(p_type)
t = p_type(a=2)
print(t)
# with Optional, how to get the runtime type?
func_sign = inspect.signature(fooo)
param = func_sign.parameters['bar']
p_type = param.annotation
print(p_type)
# how to make a t?
# t = p_type(a=2)
# print(t)
typing.get_args(Union[T1, T2]) == (T1, T2)
You probably want typing.get_type_hints() for this, to handle rarer cases like Annotated and forward references for you.
Then typing.get_args(), as sadru says.
!e ```python
from typing import Generic, ParamSpec, Union, TypeVar
P = ParamSpec('P')
RT = TypeVar('RT')
class Test(Generic[P, RT]): ...
class Subclass(Generic[P, RT]): ...
Alias = Union[Test[P, RT], Subclass[P, RT]]
Alias['...', object]
@blazing nest :x: Your eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 10, in <module>
003 | File "/usr/local/lib/python3.10/typing.py", line 311, in inner
004 | return func(*args, **kwds)
005 | File "/usr/local/lib/python3.10/typing.py", line 1061, in __getitem__
006 | arg = arg[subargs]
007 | File "/usr/local/lib/python3.10/typing.py", line 311, in inner
008 | return func(*args, **kwds)
009 | File "/usr/local/lib/python3.10/typing.py", line 1053, in __getitem__
010 | raise TypeError(f"Expected a list of types, an ellipsis, "
011 | TypeError: Expected a list of types, an ellipsis, ParamSpec, or Concatenate. Got ForwardRef('...')
anyone aware of a easy way to type annotate the kwargs to a function based on the fields/parameter of a different function?
i have types like
class MyModel:
id: uuid
name: str
hostname: str
and i want to declare helpers like
def wait_for_update(model_or_id: Model|uuid, **kw: Magic) -> Model):
where i would like kw to be a non total TypedDict thats the fields of MyModel without id
wouldn't it be simpler to just have (..., *, name: str, hostname: str, **kw)?
i dont want to keep the signature in sync with the model, the real model has dozens of more fields
plus i have more models and cases where i have similar patterns - repeating all the arguments all over the place seems very error prone and tedious
looks like typing_extensions has it https://github.com/python/mypy/issues/4441#issuecomment-1078173329
model_or_id does that take a model instance?
model or id takes either the uuid or a model instance
(this is a wrapper for some painfull openapi based stuff)
how would one imply the partial typed dict from a typed class tho?
the most annoying thing about python typing is that one cant compute or "lift" them :/
hmm
basically i cant just "unpack" the Model" i have to go from "Model" which has a id, to a TypedDict that does NOT have the id and additionally is total=False, and even thats just a basic half measure
if i want to type helper methods that mirror a subset of the ctor arguments of a model, then i suddenly have to suddenly do something else and painfull
@brisk heart @stark shuttle thanks, it works
import typing
f_hint = typing.get_type_hints(fooo)
p_hint = f_hint['bar']
types = typing.get_args(p_hint)
print(types)
p_real_type = next(_type for _type in types if _type is not None)
t = p_real_type(a=3)
print(t)
if type(_type) != type(None)
You can, with mypy plugins || ๐ฅฒ ||
On a serious note, yeah, type-level programming is very cool
Have you tried TypeScript?
so aware of any mypy plugin that will actually let you programm those lifts in your code/library? else its just very hard and frustrating
i mean i'm also looking for stuff that takes a set of classes that are an interface for some sans web api description and lifts async/sync wrappers for them around it
aka ```python
class MyApi:
def myMethod(id: UUID) -> RequestReturning[MyModel]:
magic...
apiclient = RequestsCLient(MyApi)
apiclient.myMethod(someuuid) # returns MyModel
async_api_client = HttpxAsyncCLient(MyApi)
await async_api_client.mymethod(someuuid2) # yield MyModel
I don't know about mypy plugins, but my pyanalyze (https://github.com/quora/pyanalyze) can definitely do this because it evaluates the annotations at runtime, so you can do whatever you want to generate the annotations as long as the end result is something it recognizes. (Caveats apply: maybe you don't want it to import everything; maybe you're missing some other mypy feature.)
looks interesting, but possibly not enough
Is there really no way to take a ParamSpec from one function and give it to another as optional? ๐ค
also its a pain that we cat jsut use signature objects to declare callable interfaces
uh
Is there a way I can inherit typing.Annotated?
Basically, I'd like to write a class which, to the static checkers of the world, types exactly like some wrapped class
Except it carries with it some extra annotational data and methods
not currently
Well damn
That sucks XD
from types import GenericAlias
from typing import Annotated
from typing import Generic
from typing import TypeVar
T_Annotated = TypeVar('T_Annotated')
class _ReadOnly(object):
def __getitem__(prototype, annotated: T_Annotated) -> T_Annotated:
return Annotated[annotated, 'ReadOnly']
ReadOnly = _ReadOnly()
a : ReadOnly[int]
print(__annotations__)
Does the job, it seems ๐
I'd have preferred an actual wrapper around the modified type, since then I could have implemented some operators
Beartyping has a validator system which uses bitwise notation to combine with other annotations or regular types
im hoping to at some point make a pep that generalises things like Annotated, TypedDict and LiteralStr so that they are less rigid to use
but idk when ill have the time to do all that
I mean... ๐
At the core of this, I suppose, is the fact that __getitem__ is technically lying around what it returns
Which is fine, since functionally there is no different between Annotated[int, ...] and int and that's pretty much the point
So I guess instead of returning Annotated[annotated, 'ReadOnly'], I could just as easily return an instance of _ReadOnly... maybe...?
Wait really?
Doesnt look like it ๐ค, although for stuff like this I think it is usually best to lie to the typecheker using if type checking and make your own Annotated
if you don't mind the type not being able to have instances you could do ReadOnly = typing.Annotated[T, "ReadOnly"]
not sure about mypy but it definitely works for pyright
yeah, the PEP explicitly calls that out as supported https://peps.python.org/pep-0593/#aliases-concerns-over-verbosity
Python Enhancement Proposals (PEPs)
are you expected to use TypeAlias for that?
sure
Can anyone assist me with type hinting Mixin classes that live in other modules? I have a class let's say:
# foo.py
from ._mixins import AMixin
class Foo(AMixin):
....
# _mixins.py
class AMixin:
def do_something(self: Foo) -> Foo:
# circular ref on imports now ?
# typing.TYPE_CHECKING won't work
the mixin interface is fluent so it returns self
I can use a protocol? but the Foo class will be substancial in size
I was under the impression that the point of a mixin is that it can be used with different classes
You are type hinting the parent class with a subclass?
...otherwise just put the mixin code in the main class
You might want to use typing_extensions.Self, or the equivalent solution with a TypeVar
ideally yeah, i'm writing an assertions library and the Foo class would be colossal so I was trying to move things out into mixins
i.e StringMixin, RegexMixin etc
Ahh, I see, then typevars/Self should be your solution yeah
class AMixin:
def do_something(self: Foo) -> Foo:
this wouldnt typecheck even if they were in the same module you eliminated circular references
from typing_extension import Self
class AMixin:
def do_something(self) -> Self:
T = TypeVar('T', bound='AMixin')
class AMixin:
def do_something(self: T) -> T:
I need autocomplete for my users (its vital), maybe I should just move back to composition but the number of 'delegating' methods in the main class would be quite substancial, I'm just not happy with either approach, need to bang my head on the wall some more!
well, if a class has 200 methods that's some complexity for ya
if you split it into mixins, now the user has more indirection when reading the code.
"which mixin is this method defined in again? let me search all of them one by one..."
Yeah it's a bit of a rock and a hard place, when all mixin types are typically a 'possibility' for pretty much any value
much easier if i care less about types/autocomplete, but that's something I really want
perhaps i shall move over to software-design to assistance on that front, thanks for the help here tho - been very useful
al
My new favourite way of getting an Unknown:
[][0]
Interestingly, mypy and pyright react differently to {}[0]
Have any of you ever benefited from the fact that type[SubA] is compatible with type[A]?
I guess orms would use it
But the constructor isnt type checked, so is that annotation really that useful when it comes to orms?
In fact that only way it would seem to be useful is if the constructor was checked for compatibility across subclasses, as stated in the documentation
I was thinking like select(Model) if its defined like class Model(ormlib.BaseModel)
however it has been years since type was introduced and both mypy and pyright dont check for that
What would select do here?
start a select query
row = await select(Model).where(Model.a == 1).one()
I'm not sure if any orm actually works like this because I haven't used one in ages
Humm, I think usually something like select would be a method or classmethod on the models, but yeah in that case there would some value for annotating the select function. But the usefulness stops there I think, you can't really have any idea what fields would be returned if you annotated it as
def select(model: type[BaseModel]) -> SelectModel: ...
I guess where it be more useful for this case indirectly is using as a type bound for a type variable
Yes
like
T = TypeVar('T', bound=type[BaseModel])
def select(model: type[T]) -> SelectModel[T]: ...
I do runtime type checks of the instructor anyway
If I have code like that, I usually annotate a parent class (like a mixin or simply a "marker" class) I want users to subclass
Humm, so for your use case, would it be more typesafe if the constructors were actually checked for compatibility or would that just get in the way?
Wait mb, that is not how you annotate this, you would do T = TypeVar('T', bound=BaseModel), so in that case there is also no need for type[SubX] to be compatible with type[X]
Anyway, this is some interesting problems that come with having that be the case
https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&gist=04e6dfea2765778e64a6afca5cbe5ea2
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
granted the constructor is the only "real" problem, which the docs do say that type checkers should check
Am I doing something wrong? Why is mypy complaining? I'm also having a similar issue in PyCharm. https://mypy-play.net/?mypy=latest&python=3.10&gist=8054339843ed9410fec6d1fe7844d1e4
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
I am not quite sure. I guess the answer would be yes it would be better.. although I wouldn't want this change
TIL you can have a class property
I would file a bug on mypy
Apparently there's already an issue open https://github.com/python/mypy/issues/11619
Yeah, I wonder why this hasnt been implemented yet. It would be nice to have an invariant version of type though
They are new in 3.9 iirc
How do I make this work? I'm not very experienced with this sort of stuff https://mypy-play.net/?mypy=latest&python=3.10&gist=9294d28aeb431e3c4e172044c7772170
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
The typevar/generic thing was just my attempt to make it work
Yeah here is a simpler version with the same problem https://mypy-play.net/?mypy=latest&python=3.10&gist=f0b485f7c7f0c8f34f7a51021a152f0a but ultimately I do want some sort of mixin rather than having to repeat it for every abstract class.
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
Would I just have to cast it in this case?
Not possible I think, you are trying to "dynamically" map literals to types, so you would need a way to alter the supported keys of a typeddict throughout "compilation", or a way to add overloads to a function in a similar way
both of which are not currently possible as far as I know Both of which are definitely not supported, and I think for similar reason __subclasshook__/ABCs is also not supported
Thanks. I started to suspect it wasn't possible the more I thought about it. That's why I mentioned casting at the end.
Looks like there are strong chances of them being deprecated
Yeah, I saw that mentioned but couldn't really follow the discussion.
Is it a bug that pyright doesn't narrow the type of the attribute in the second case? Not sure if I should be filing an issue. https://paste.pythondiscord.com/ewowicagez
The gist of it was
- class properties are not actual properties as they remain instances of the
classmethodclass, and not thepropertyclass, this breaks stdlib and third party assumptions about properties - It does not allow for setter semantics you would expect with a normal
property staticmethodandabstractmethoddo not compose well withproperty- Reconciliating all these factors would evolve braking changes and or a lot of special casing
I see. Indeed I have experienced the woes of trying to make abstract class properties
Regarding my other question, I think it is a bug since it works fine with normal classes https://paste.pythondiscord.com/zocenufeye
Well, I found https://github.com/microsoft/pyright/issues/2970#issuecomment-1028160209 but not sure if it applies to my case
Pyright doesn't currently support type narrowing in the negative (not matched) case for class patterns that have arguments or for generic classes.
Strangely, the reason that works is actually because the type is Unknown
Are there any use-cases where strict-typing is not "better"?
Oh right, the reason it is unknown is because that actually doesnt work with normal classes
because you have not implemented the __match_args__
I think
Oooh yeah good point
let me try to add that
Actually I'll just use kwargs in the pattern
Yup then I get the same issue as with a dataclass
I suppose one case is when you need to annotate something that is not supported (or is very tedious) by the type system or type checker. Then it's a headache :^)
Unrelated, another thing that's been bothering is no type narrowing for something like ```py
a = A(1)
match a:
case A(str()):
B(a.value)
I have to do `str() as value` and use that instead to get the type narrowed, even though (at least for a human) it's possible to infer that `a.value` must be a `str` at that point.
Oh, I just thought of an actual problem I've been facing.
map: dict[str, Any] = {}
if map.get(1) is not None:
# do something ...
Pyright complains that using an int 1 as a key is not valid, even though get() can accept any type and will just return the default value if it cannot find it.
But map.get(int()) would never be anything else other then None
How would allowing that be useful?
Here's a better example to show my situation. I think it makes more sense in this case:
class Parent: ...
class Child(Parent): ...
map: dict[Child, Any] = {}
def func(thing: Parent):
if x := map.get(thing):
...
Humm, touche
But the same can be said about all other function, it is just that get does not raise an exception
def foo() -> object: ...
def bar(x: bool): ...
bar(foo())
There are at least 2 cases where this function call would be fine
Why does this fail https://paste.pythondiscord.com/texotiloro but it works when the slice overload for getitem is removed https://paste.pythondiscord.com/idicimubug ?
(using pyright)
got an error message?
Expression of type "tuple[C, ...]" cannot be assigned to declared type "HashableSequence[B]"
"tuple[C, ...]" is incompatible with protocol "HashableSequence[B]"
Type "(__x: SupportsIndex, /) -> C" cannot be assigned to type "(s: slice, /) -> HashableSequence[T@HashableSequence]"
Parameter 1: type "slice" cannot be assigned to type "SupportsIndex"
"slice" is incompatible with protocol "SupportsIndex"
"__index__" is not present
Function return type "C" is incompatible with type "HashableSequence[T@HashableSequence]"
"C" is incompatible with protocol "HashableSequence[T@HashableSequence]"
"__reversed__" is not present
...
๐คทโโ๏ธ
I looked at the stubs for tuple and it has the slice overload so I'm not sure why it doesn't want it here