#type-hinting
1 messages · Page 71 of 1
Yeah perhaps TypeVars should limit themselfs to types only, and not terms promoted to types, although maybe there is a better way to reconcile this?
Also, yeah from the direction it is going, literals will be specially useful when working with arrays where you want the type to contain information about the dimensions of the arrays
Does that not makes functions with Flags more cumbersome to use though?
Regardless of their merits
I mean the pep focuses on examples such as open
You would still have to have a concept of a literal if you used enums, as annotations would have to accept members of enums to properly fill the functionality that literals try to solve
re is a pretty old module and it uses an enum for flags
open is tricky because the return type depends on the concrete mode
But it could accept something like TextMode.read() and BinaryMode.write().and_read()
I think what Python is kinda lacking is discriminated unions. You can replicate them in various ways, but they are kinda not really there
Maybe I just didn't have enough experience, but exhaustive pattern-matching is not easy to pull off.
But then arent you back to having literals?
or literal like concept
No, there are just two types -- TextMode and BinaryMode. You use methods on them to construct a mode.
Or just have two Enums
Oh I see what you mean
If you also want to encode whether the file is writable or readable, you could have some generic monster like Mode[_CanRead, _CanWrite, _IsText]
I mean at the end of the day, the more expressive you can be with the type system, theoretically the better no? You could express some stuff by just making concrete types for everything but curtain things do line up better with just using values imo. The only downside I do agree is the part about complicating the type system for type checkers
I think complicating the type system is a considerable disadvantage.
Not just for type checkers, but also for humans
Enums are also less error-prone for people who don't use a type checker, I would say.
Literals are also not as flexible:
i am big -1 on literals except as compatibility for existing apis that rely on magic strings. source: personal experience working with literals
and believe me, i love the idea. i want refinement types in python
If you wanted to encode something like this with literals:
a) you'd have a combinatorial explosion of overloads
b) you'd need to implement a complex Template Literal Type system from TypeScript
I guess one theoretically valid application of Literal is when you want to return a subset of an Enum. I've seen it a few times but can't think of an example now
I just had a horrific idea for doing Starts/EndsWith (in ts) in python
LiteralString.add and radd
I would say that Literals are a low hanging fruit when it comes to that, I think that teaching about literals types as they currently are is a lot easier when compared to other things in the type system. And yes I do agree they are not as flexible, but I see that as a reason to make them better, perhaps by adding template literals
interestingly, in TS you can even convert camelCase to snake_case and vice versa on the type level
While generally that is true, I think there are some valid exceptions, although right now I can only think numpy
I actually have missed the string type templates at some point, I think it was in an instance where I had to reduce the number of options a curtain function could take
and having multiple enums would mean a lot of code repetition
Umm
... can you elaborate with a code example?
def foo(x: "this_" + LiteralString):
if not x.startswith("this_"):
reveal_type(x) # Never
@covert dagger for something like that, you need to be able to lift any integer into a type, which isn't really what Literal is, but maybe I'm misunderstanding
this is also related to the other point being made: re the enums and different types being returned from read, there's really two separate issues
there's Literal vs enums, and there's lifting values, which are considered a "dynamic" thing generally, into the static world of types
In say C++, you can inject values into the static world by accepting them as non type templat eparameters
so in C++ you could do things like
reader r = open<Mode::Read>(filename);
if you want to return different types from read based on what the flags are then you need to ensure that the values passed in are known statically
not really, even in C there's been enum sets since forever
So I was storing some common typehints in a seperate file, some of them are from typing_extension (which is not installed but available during type checking), and I defined some type aliases however during runtime TYPE_CHECKING resolves to false and these aren't defined.
So I was replacing these definitions with "something else", what should this "something else" ideally be?
from typing import TYPE_CHECKING, TypeAlias, Any
__all__: tuple[str, ...] = ("Self", "Unpack")
if TYPE_CHECKING:
from typing_extensions import Self, Unpack
else:
# Should we assign this to anything else?
Self: TypeAlias = Any
Unpack: TypeAlias = Any
it doesn't matter to the type checker
Ah, do you not have typing_extensions during runtime?
I've seen people do like if sys.version_info checks even when they should be fine importing from typing_extensions - which is unnecessary unless you only require typing_extensions for older versions
how would i type hint a coroutine would it be
Coroutine[arguments, ReturnType]
like Callable?
usually Callable[[arg1, arg2], Awaitable[T]]
and what goes inside of Awaitable?
the return type
Coroutine is the type of the in-progress coroutine itself, it's what's returned by the function.
got it thanks!
Note for async def, Coroutine is implied, just specify the return type like a sync function.
The docs often use "coroutine" to mean "coroutine function" (i.e. an async def function) which I think is very bad.
The discord.py docs have the same sin
They just copy asyncio's
I've asked this before and did not get a straight answer, so here goes one more time: does numba (not numpy) compile faster if there are type-hints (it's compilation times are very slow in general, although the tool is otherwise fantastic)? Does it properly error out if the type-hints are incorrect?
hi everyone, i'm having trouble with hinting line 11 here
Pyright is complaining that args[0] is an object, not a class instance so I can't do that, I'm just disabling it for now
Is there a better way to do this?
My purpose:
- to use
selfin the wrapper - to type hint all the stuff
I tried def wrapper(self: DummyTest, *args, **kwargs): (< 3.10) but it won't type hint args/kwargs
import logging
from typing import Callable, TypeVar, ParamSpec
from functools import wraps
P = ParamSpec("P")
Ret = TypeVar("Ret")
def authenticated(method: Callable[P, Ret]) -> Callable[P, Ret]:
@wraps(method)
# pylint: disable=used-before-assignment
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Ret:
self: DummyTest = args[0]
if self.auth.valid is False:
self.logger.info("Not logged in, logging in...")
self.auth.authenticate()
return method(*args, **kwargs)
return wrapper
class DummyAuth:
def __init__(self, key:str):
self.key = key
self.valid = False
def authenticate(self):
# check valid
if self.key == "abc":
self.valid = True
class DummyTest:
def __init__(self, key:str):
self.logger = logging.getLogger(__name__)
self.auth = DummyAuth(key=key)
@authenticated
def req_api(self):
# do api call here
self.logger.info("API CALL")
I think this is the case where you need Concatentate https://docs.python.org/3/library/typing.html#typing.Concatenate
something like this
def authenticated(method: Callable[Concatenate["DummyTest", P], Ret]) -> Callable[Concatenate["DummyTest", P], Ret]:
@wraps(method)
# pylint: disable=used-before-assignment
def wrapper(self: DummyTest, *args: P.args, **kwargs: P.kwargs) -> Ret:
if self.auth.valid is False:
self.logger.info("Not logged in, logging in...")
self.auth.authenticate()
return method(self, *args, **kwargs)
return wrapper
wow looks great, I skimmed PEP612 and missed a lot about Concatenate and just kept diving into ParamSpec, wrong move
thanks a lot!
Just one thing you might wanna do instead is T = TypeVar('T', bound="DummyTest") and use the T instead
that way you can subclass DummyTest with less problems
you mean the Ret that I used right?
No, I mean def authenticated(method: Callable[Concatenate[T, P], Ret]) -> Callable[Concatenate[T, P], Ret]:
def wrapper(self: T, *args: P.args, **kwargs: P.kwargs) -> Ret:
I recommend you going to #c-extensions, but have you seen this? https://numba.pydata.org/numba-doc/0.12.2/tutorial_types.html
I don't think it mentions type annotations though.
I'm having some trouble when it comes to Typeddict
for requirement, required_amount in requirements.items():
if count[requirement] < required_amount:
return False
requirements and count are both of the same typeddict type. Running the above still gives me error: TypedDict key must be a string literal; expected one of ("examplekey1", "examplekey2", "examplekey3", "examplekey4")
Yeah TypedDicts aren't great for this, also when you grab the values it just returns object so the < op is also disallowed
Not sure what can be done here other then # type: ignore that doesnt involve changing much
x)
What type is count?
count = cast(PasswordRequirements, collections.Counter())
and
class PasswordRequirements(TypedDict):
example1: int
exampl2: int
example3: int
example4: int
Is it always 4 items?
Yeah
I'd probably use a dataclass and unroll the loop
count[arbitrary key] is of type object because TypedDicts may have arbitrary additional keys due to subtyping
Eg write all the comparisons out
True, I might as well. I've spent a bit too much time on this one 😅
Thoughts on using Hashable for dictionary keys? I have objects that basically hash a field of theirs, so I want to be able to use both - which I've found to be the easiest to do with Hashable
It is still type-safe, although yeah it may not catch as many issues where you accidentally lookup the wrong thing
It would be nice if bound was hashable but mypy doesn't support it
yes we tried that in typeshed recently and got trouble
Maybe you could refactor the code so that you have a list of (str, int) pairs (or maybe Requirement objects)?
The Validation applicative functor!
what's that in non-haskell?
You can have a collection of validators that validate data and accumulate errors
Eg if a password needs 3 numeric digits and 2 special symbols
right so an applicative lets you sequence the operations while accumulating errors
Validationis an example of, "An applicative functor that is not a monad."
Is it a better practice/idea to use typing.Tuple instead of just tuple to have backward compatibility or would it be just better to use tuple (convenience aside)?
If you want to be compatible with Python<3.9, then use typing.Tuple
Are you making a library or an application?
application
half of the existing code uses Tuple
but meanwhile the new code uses tuple
An application usually targets a specific version of Python, not a range of version. So backwards compatibility is usually not a concern.
so I was wondering which way to drift to have consistency across the codebase
typing.Tuple and friends are actually deprecated (see PEP 585), and tuple is definitely more convenient for both reading and writing. So I would drift in that direction
as a minor plug, I have a flake8 plugin that finds these imports, so you don't have to manually search for them
https://github.com/decorator-factory/flake8-pep585
What is this file for ?
https://github.com/decorator-factory/flake8-pep585/blob/master/flake8_pep585/py.typed
I saw an another project with a similar file as well 🤔
I heard it was for typing metadata or something but didn't really catch it
It signals that the library has in-code type annotations, and that type checkers should use them for type information:
https://peps.python.org/pep-0561/#packaging-type-information
so if it's not present, the type checker basically says: "I have no idea what's going on in this library, I won't even bother" unless you supply stubs
probably not needed in a flake8 plugin, but I don't see any harm in it (you can technically still import stuff from it)
oh the stubs here mean .pyi files which have no implementations but only annotations (and are generally generated automatically)
yeah
how do I even know if my project needs a py.typed ?
Not sure about being automatically generated though, if your project already has type annotations you don't need a stub
https://www.innoq.com/en/blog/validate-your-domain-in-scala/ the Scala tutorial is a bit more readable
if your project is a library and it has type annotations in the code (*.py files or *.pyi files), you need it
otherwise, you don't
yes, I think so
Don't you also need it for pyi files?
hmmm I don't rember
Note that for stub-only packages adding a py.typed marker is not needed since the name *-stubs is enough to indicate it is a source of typing information.
Exception proves the rule?
you're right then, thanks
a while ago we had someone contribute stubs for a bunch of flake8 plugins to typeshed. not sure what their use case was
more typehints for the typehint gods
my function either returns Tuple[bytes, bytes] or throws an Exception, what do I put as the return type so it doesn't complain about the Exception path?
nothing special, type checkers don't care about exceptions
thanks!
how about if I have def foo(p: Path): ... but want to call foo with a str but inside foo I certainly want a Path.. is the only way def foo(p: Union[Path, str]): p = Path(p); .... ?
yes, that's the most reasonable way
if you really want to accept the most general type, use os.PathLike[str] instead of path
def foo(path: str | os.PathLike[str]): ...
btw, here is how you can use @dataclass and type annotation to represent multimodal data https://docarray.jina.ai/fundamentals/dataclass/
(most probably interesting to data scientists and ML engineers
DocArray’s dataclass is a high-level API for representing a multimodal document using nested Document structure. It follows the design and idiom of the standard Python dataclass, allowing users to represent a complicated multimodal document intuitively and process it easily via DocArray Document/...
kind of unfortunate that they used the same name
unless it's strictly a superset and drop-in replacement, i think that's kind of an arrogant thing to do by the devs
that said, i never saw this library before and it looks very interesting
I've been looking at how type inference works and I'm curious if there is a way to infer a class attribute from another?
class MyThing:
output: object # <-- can you hint this should be return type of output_function?
output_function: Callable
def __init__(self):
self.output = self.output_function()
def my_func() -> SomeOtherClass:
return SomeOtherClass()
class SubThing(MyThing):
output_function = my_func
subthing = SubThing()
reveal_type(subthing.output) # if possible this would infer `SomeOtherClass`
Or is that just too much magic for the type checker?
You could make it Generic, something like this: ```
class MyThing(Generic[T]):
output: T
output_function: Callable[[], T]
you beat me to it
but you'd have to explicitly inherit from MyThing[SomeType]
although Callable attributes like this will anger mypy
Should it not be able to resolve T?
I think I am confusing it with something else because I have seen patterns where it can resolve the other TypeVar when inheriting
Ok, to complicate it more, the baseclass already has a generic inheritance.
class MyThing(SomeBase[T]):
Is the proper way to add another Generic like this?
class MyThing(SomeBase[T], Generic[T, U]):
yes
in theory maybe, but pretty sure current type checkers don't
SomeType (without generic) implies SomeType[Unknown] in other contexts, that might be surprising
but idk
theres no way to make this work is there?```py
from future import annotations
import types
from typing import *
from typing_extensions import Self
P = ParamSpec("P")
T = TypeVar("T")
InstanceT = TypeVar("InstanceT")
class FunctionType(Generic[P, T]):
def call(self, *args: P.args, **kwargs: P.kwargs) -> T: ...
@overload
def get(self, instance: None, owner: Any) -> Self: ...
@overload
def get(self, instance: InstanceT, owner: type[InstanceT]) -> MethodType[InstanceT, P, T]: ...
def get(self, instance: Any, owner: Any) -> Any:
...
class MethodType(Generic[InstanceT, P, T]):
self: InstanceT
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
...
class This:
f: FunctionType[[Self, int, str], Any] = FunctionType()
reveal_type(This.f) # Type of "This.f" is "FunctionType[(This, int, str), Any]"
reveal_type(This().f) # Type of "This().f" is "MethodType[This, (This, int, str), Any]"
this should be MethodType[This, (int, str), Any]
I'm either doing something wrong or this doesn't produce the inferred type.
could you show your (new) code?
T = TypeVar("T")
Z = TypeVar("Z")
class Matcher(Generic[T]):
...
class BaseMatcher(Matcher[T]):
...
class BaseResolution(BaseMatcher[T], Generic[T, Z]):
output: Z
output_function: Callable[..., Z]
def __init__(self, *args: object, **kwargs: object) -> None:
cls = self.__class__
if args and kwargs:
self.output = cls.output_function(*args, **kwargs)
elif args:
self.output = cls.output_function(*args)
elif kwargs:
self.output = cls.output_function(**kwargs)
else:
self.output = cls.output_function()
class SomeMatcher(BaseMatcher[str]):
def __init__(self, thing: str):
self.thing = thing
def my_func(thing: str) -> Matcher[str]:
return SomeMatcher(thing)
class Resolution(BaseResolution):
# output: SomeMatcher
output_function = my_func
subthing = Resolution()
reveal_type(subthing.output) # if possible this would infer `SomeMatcher`
This is more aligned with my actual code. I'm afraid my first example was boiled down a little too much.
reveal_type is "Any". If I can make this work it should infer SomeMatcher
Doesn't look like it is, that is using the exact format of [[Self, int, str], Any], You would need to be able to either use Concatenate in the generic signature or somehow break the P into K and P[1:]
Even then what would you bind the K in the concatenation 🤔
it seems like it's only unifying in one direction
I'm afraid I don't know what you mean by that.
you're expecting it to realize that my_func returns Matcher[str], so it should infer that output is also Matcher[str] because they are both Z, right?
Ahh.. Yeah. I tried this too:
def my_func(thing: str) -> SomeMatcher:
When inheriting from BaseResolution, you're actually inheriting from BaseResolution[Any, Any]. So you need to add generic parameters to it
but no, this unfortunately cannot be inferred
That's what I have been figuring.
Maybe you can avoid creating subclasses and just instantiate it with a function? ```py
subthing = Resolution(my_func)
or declare the type of the variable, but that's kind of a workaround
My best option, I think, is to just annotate each of the Resolution classes.
class Resolution(BaseResolution):
output: SomeMatcher
output_function = my_func
Feels like that's the price to pay with that much abstraction
yeah, that would have been preferred. not my choice of design unfortunately.
class Foo(Generic[T]):
y: type[T]
class SubFoo(Foo[Any]):
y = 42
No complaints from Pyright
But if you change T to Any it complains
I wonder what is going on internally in trying to resolve T x)
mypy compains about that code
Yup
I'll be frank, I dont understand what's supposed to be going on with using type[T] here
is that equivalent to typing.Type ?
yes
and if so, doesn't that mean we're saying y should be the function int ?
Any instance of type will do
or more accurately, the uninstantiated object of whatever T is
that should be an error
Yes, but but maybe it has been fixed already? I exclusively use fix errors pyright website
oh derp -- I let the y = 42 blind me. if I understand correctly, y shouldn't be set to an actual int. To be correct it should be something like y = int.
Which iirc wasnt always up to date
Yeah
still groking type hinting
repros on latest pyright/pylance
So does/should the Any here have any expected affect on y?
class SubFoo(Foo[Any]):
y = int
I would look at that and say Any should not be used here. While not wrong.. it's not helping
right?
depends what you're trying to achieve I guess. Foo[int] would be more precise
fair.. I'll ask in a different way. When would it make sense to use SubFoo(Foo[Any]) ?
It doesn't I think, it is always implied if left empty, I just added it there to see if it perhaps would help Pyright, since if I did y: type[Any] it would correctly report an error
oh.. of course... class Sub(Base) ==> class Sub(Base[Any]) : while not specifically always useful, it's more explicit. 💡
I exclusively use fix errors pyright website
please don't use it for bug repros, it's massively out of date 😄
So I did remember correctly xD, I was looking for my venv that had pyright installed to double check but Jelle had already reproduced it
pyright in vscode is really nice
def bet(amount: int):
if amount == 'max':
amount = # all of users money
else:
pass
How would I accomplish something like this
I'm getting amount from a discord message, so I have to type hint it to avoid putting int(amount) every time
But I want to check once to see if they want to bet max
amount: int | typing.Literal['max'] ?
You'll need to validate it's either an int or max before passing it in though
If it is a discordpy like library command maybe it already validates it, they makes weird uses of typehints
Yeah I was about to say it supports Literal as a type hint
What does that mean?
So if you put what a5rocks sent in your command signature(assuming you are on 3.10+) it should just work™️
Although you'll probably need to change it to use
!d typing.Union
typing.Union```
Union type; `Union[X, Y]` is equivalent to `X | Y` and means either X or Y.
To define a union, use e.g. `Union[int, str]` or the shorthand `int | str`. Using that shorthand is recommended. Details...
do I need to import typing
Perfect, thank you!
How would I check if this
typing.Literal['max'] is lowercase
!d typing.get_args
typing.get_args(tp)```
Use this to get the tuple of arguments passed
I have this decorator that auto-wraps asyncio.run when needed. How should I annotate it?
def maybe_sync(f):
"""Wraps a function and turns it into a sync call if it is not called from
inside an coroutine.
"""
@functools.wraps(f)
def decorator(*args, **kwargs):
caller = inspect.stack()[1].function
if inspect.iscoroutinefunction(caller):
return f(*args, **kwargs)
return asyncio.run(f(*args, **kwargs))
return decorator
Why do you need such a function?
These functions are pretty much the same but will behave very differently under your decorator: ```py
async def f1():
return await g()
def f2():
return g()
Not possible without an intermediate class
Both functions in my example have the same type
Well, I mean something that would do foo() -> T or await foo() -> T
You can't know at runtime if a function always returns an awaitable object
As in the best you can do is say that it will either return an awaitable[T], or T, but then you cant safely use await
Doesnt an async function always returns an awaitable?
def f2():
return g2()
does this function return an awaitable or not?
if g2 returns an awaitable, f2 returns an awaitable
yes, but not with the hacks above
the thing about checking if the caller is an async caller or sync caller
right, according to maybe_sync it is a sync function
you can call an async function in a sync context and a sync function in an async context
well, let's wait for the OP maybe 🙂
xD
I ended up getting rid of it.
Too much complexity, not enough type support.
Anyway, I'm doing a new thing now. ```py
P = ParamSpec("P")
R = TypeVar("R")
def copy_param_spec(_: Callable[P, Any]) -> Callable[[Callable[P, R]], Callable[P, R]]:
def decorator(func):
return func
return decorator
class MyClient:
...
@copy_param_spec(httpx.AsyncClient.request)
async def _request(self, *args: P.args, **kwargs: P.kwargs):
r = await self.client.request(*args, **kwargs)
It seems to work out alright.
it appears to copy the __doc__ too (pylance)
Well, you can do this:
T = TypeVar("T")
P = ParamSpec("P")
@overload
def syncify(f: Callable[P, Awaitable[T]]) -> T: ...
@overload
def syncify(f: Callable[P, T]) -> T: ...
def syncify(f):
...
but it will lead to some sticky situations like: ```py
def foo(t: Callable[[int, str], T]):
return syncify(t)
reveal_type(foo) # (t: (int, str) -> T@foo) -> T@foo
^ which is unsound, because if `t` is an async function, then the return value is not `T`
I think the best solution would be to have a separate module for wrapped async methods.
Oddly, when I hover over a usage of self._request() it shows the full documentation for httpx.AsyncClient.request. But when I Ctrl+Space it, it shows undefined
Quick question regarding this snippet of code:
@t.overload
def ab(
a: int,
b: str,
) -> int:
...
@t.overload
def ab(
a: str,
b: str,
) -> str:
...
def ab(
a: t.Any,
b: str,
) -> t.Any:
...
```Is it intentional/expected that pylance is unable to infer the correct overload here? (Note that the snippet is just a dumbed down version of something I do actually need overloads for, but this was just the shortest way I could reproduce it.)
It should it just shows you both when you're typing
I think it doesn't display the "current" overload in this window. It just displays all of them so you can scroll through them
I guess in theory it has enough information to figure out what the right overload is here
If you press the down key you should see the second overload
ah alright, for whatever reason I was convinced it would show the 'correct' overload
it will scroll through depending on the number of arguments, though
mhm, alright, thanks ^^
s
hy code is hideously ugly with type annotations
(defn ^None write-file
[^(| str PathLikeStr) filename-or-path
^(| str bytes) content
^bool [text True]
^bool [append False]]
(setv mode (if append "a" "w"))
(when (isinstance content bytes)
(setv mode (+ mode "b")))
(with [fp (open filename-or-path mode)]
(.write fp content)))
whats the difference between typing.Awaitable and typing.Coroutine ?
Awaitable is more general
you can define __await__ on anything, which makes it an Awaitable
whereas Coroutine is more specific
ic thank you!
why does it say that, its clearly a tuple of embeds
tuple[Embed] means a tuple of exactly one Embed
oh
your return annotation should be tuple[Embed, ...]
ok lol
thanks
so if i have a dict {'a': 'b', 'c': 'd'} i would annotate it like dict[str, str, ...]?
No, dict[str, str], where dict[key_type, value_type]
k
the ... thing is just for tuples. Tuples are treated specially because it's common to use them for heterogeneous data
Would be nice to have heterogeneous support for more than tuples eg dict[k,v,k2,v2] q.q
is there a setting to not infer things as Literal values in pyright? this passes in mypy without the type hint in queue, but not pyright because pyright thinks it's List[Tuple[Optional[TreeNode], Literal[0]]]
def verticalOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
ans = defaultdict(list)
queue: List[Tuple[Optional[TreeNode], int]] = [(root, 0)]
for node, k in queue:
if node:
ans[k].append(node.val)
queue.append((node.left, k - 1))
queue.append((node.right, k + 1))
return [ans[k] for k in sorted(ans)]
I think they mean without the type hint
Can you show the error then?
Without it pyright assumes Literal[0] so you can no longer append just any number
I would assume this:
Argument of type "tuple[TreeNode, int]" cannot be assigned to parameter "__object" of type "tuple[TreeNode | None, Literal[0]]" in function "append"
Tuple entry 2 is incorrect type
"int" cannot be assigned to type "Literal[0]"
But lets see what OP says x)
yes that's the error message for both the queue.append lines
the type hint tells pyright to infer the general int case, but that's kind of annoying to have to tell it
(if I understand it correctly)
Yeah, with tuples pyright infers it down to literals, I think it is due to their immutability so it feels confident to do so, but then when inferring the list, it just does list[whatever_this_was], which in this case is tuple[TreeNode | None, Literal[0]] . I had a look at pyright configs and there doesnt seem to be a way to change that
How can I typehint an instance that is created with with?
For example
async with pool.acquire() as con:
con.execute(...)```
`con` is not typehinted by default
async with pool.acquire() as con:
con: asyncpg.Connection
con.execute(...)
also yes, asyncpg's types are not very good 😔
Alright thanks
Ikr
TypedFrozenDict please
id like a TypedMapping
name is a bit weird cause TypedDict is already a mapping but TypedDict would just be defined as```py
class TypedDict(TypedMapping):
pass
Hello all, how should i write type hinting for example below:
def func(param: int) -> int | str:
if param > 50:
return 100
else:
return 'poop'
param = 100
if param > 50:
result: int = func(param)
With current code I have the following error.
Incompatible types in assignment (expression has type "Union[int, str]", variable has type "int")mypy(error)
Is there a way to limit expected type to specific types while using Optional[] ?
Mypy isn't smart enough to see that func would return an int in this case
I think you could use cast to tell mypy that it will be an int:
result: int = typing.cast(int, func(param))```
I have tried using it for my original issue, unfortunately this time I receive a different mypy error: ```Invalid type comment or annotation
```py
def test_successfulUserQuery(self, instantiateRepo: TransactionRepo):
"""Check if query return values are correctly cast to User class
Args:
instantiateRepo (TransactionRepo): fixture instantiating repo
"""
# Arrange
expected = instantiateRepo.cur.fetchall.return_value = (
1,
"ZaiZu",
"password",
"John",
"Smith",
)
# Act
user: typing.cast(User, instantiateRepo.userQuery('ZaiZu')) = instantiateRepo.userQuery('ZaiZu') # mypy error
In this userQuery call I'm mocking my DB response, so it will always return a User
def userQuery(self, username: str) -> User | None:
"""Query the DB with username to check if corresponding user exists
Args:
username (str): username
Returns:
User | None: User instance if user exists, None if not
"""
I tried to simplify the example, here's my real issue
the type of user isnt typing.cast(User, instantiateRepo.userQuery('ZaiZu')), its just User
user: User = typing.cast(User, instantiateRepo.userQuery('ZaiZu'))
tbf the annotation is kinda redundant here, just user = typing.cast(User, instantiateRepo.userQuery('ZaiZu')) is good enough imo
It's a new syntax to me, does it mean that typing.cast works as a wrapper to allow mypy to understand what type is passed?
Yup, my bad. That's a stupid syntactic mistake
though it may be better if you explicitly check if user is None
Typing.cast just let's the type checker know that you know best
If you're having to use it, it's usually a bad sign
so is output (Optional[object, None]) of functions/methods like mentioned above userQuery inherently bad? userQuery is called in other methods and the User object it returns is processed, but only after explicit check that the return value is not None. Unfortunately this forces me to use typing.cast in any such function.
@onyx kayak If you want to check specifically for None, you can do assert user is not None
or maybe raise an error as Bluenix suggested
or have two methods, like get_user(name: str) -> User (raises error) for cases when you're mostly sure the user exists; and try_get_user(name: str) -> User | None when the two outcomes are equally likely
Yes, I can see now that handling it with exceptions sounds better. Thank you all for help!
def foo(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
...
t: tuple[int, int] = (1, 1)
foo(*t) # error
def bar(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
return foo(*args) # fine
I don't know the reasoning behind it but you're only allowed to unpack args if the args itself is also a variadic arg
thats dumb
yeah I have no idea why ¯_(ツ)_/¯
!pep 646
Also, that file has 24000 lines 😳
i cant find anything when i just do a search for *args but i still i dont think have read all of 646
I don't think it is in there, sounds like something that was discussed afterwords and a conclusion was reached that it would either cause problems or wasnt worth it x)
Or maybe it is an implicit consequence of one of the "Not allowed"s in the pep
Maybe it would've just complicated the code too much and it wasn't explicitly supported in the PEP
apparently typechecker isn't happer assigning int(bool(...)) with Literal[0, 1]
is there anything that I am doing wrong here?
async def exists(self, key) -> Literal[0, 1]:
async with await self.__lock__():
return int(key in self.__destinations__()) # 1 or 0
Expression of type "int" cannot be assigned to return type "Literal[0, 1]"
Type "int" cannot be assigned to type "Literal[0, 1]"
"int" cannot be assigned to type "Literal[0]"
"int" cannot be assigned to type "Literal[1]"
- pyright
Since construct int from a bool will always be either a 0 or a 1
That information is lost when you do int(...)
def foo(a: Literal[0]): ...
foo(int()) # Argument of type "int" cannot be assigned...
I don't think it's worth including such special cases in typeshed.
Why not just return bool?
(also, IIRC making your own dunder methods is discouraged, since these attributes can get added for internal use later)
Indeed, we must content ourselves with the limitations of the real world
reveal_type(1 ** 26) # Reveal type Any 🥲
Who is more correct here?
from typing import Protocol
class Foo(Protocol):
x: int
y: str
class Bar(Protocol):
x: bool
y: str
z: float
def f(a: Foo): ...
def g() -> Bar: ...
x = g()
f(x)
Mypy:
error: Argument 1 to "f" has incompatible type "Bar"; expected "Foo"
note: Following member(s) of "Bar" have conflicts:
note: x: expected "int", got "bool"
Pyright:
# No errors
Is there any reason that structural subtyping should not include subtyping of members?
yea, pyright is wrong here i think
You could add the frozen annotation
dataclass frozen that is
And see if that matters
Alright I would personally do it with a custom class
Oh right, so yeah this should be invariant
Not with int and bool
Because that's a horror that mypy for example could choose to ignore
Right but nothing would break if you would set an I see nowint field to a bool?
Setting an int to a bool field
I mean regardless of what python says I think it's awful both ways
Hopefully static type checkers complain about both
Looks like mypy doesn't complain
Sad
I wonder if it would be nice to have immutable types, that means that stuff like this could be marked.. although it would probably still not pass even by Mypy because it has probably not implemented it 😂
Well you can have a frozen dataclass
That will probably suffice
It's not really about immutable types
Recursive = dict[K, SomeTypedDict | "Recursive"]? There's not really enough info to give a better solution
I think pyright supports recursive types
https://github.com/microsoft/pyright/commit/9a4052b4330efc3f9e64ec550866bcdc6c5e009e Already fixed, how is he so fast 😂
Hi, is there a quicker way to do things depending on a bool quicker than py value = db.query(models....) if not value: raise HTTPException(status_code=404, detail="not found") return value I would imagine something like this but it doesnt work: ```py
return db.query(models....) | raise HTTPException(status_code=404, detail="not found")
return db.query(models...) or SomethingElse, but you can't raise as an expression, raise is a separate statement
however you can use the assignment expression to make the if not form more succinct:
if not (value := db.query(models....)):
raise HTTPException(status_code=404, detail="not found")
return raise:
idk if that's better though. the first form would be idiomatic and preferred imo:
value = db.query(models....)
if not value:
raise HTTPException(status_code=404, detail="not found")
return value
(this isn't really a #type-hinting question, unless i misunderstand the question)
mypy and pyright both don't know how to handle descriptors:
from __future__ import annotations
from typing import Generic, NoReturn, TypeVar
T = TypeVar('T')
S = TypeVar('S')
class Constant(Generic[S, T]):
def __init__(self, value: S) -> None:
self.value = value
def __get__(self, obj: T | None, typ: type[T]) -> S:
return self.value
def __set__(self, obj: T, val: S) -> NoReturn:
raise TypeError('Constant attribute cannot be set.')
def __del__(self, obj: T) -> NoReturn:
raise TypeError('Constant attribute cannot be set.')
class Thing:
x: int = Constant(1) # ERROR
is this feature on the roadmap for either of the two type checkers? or one of the other ones (pyre?)
try x = Constant[int, int](1)?
wouldn't it be ```python
class Thing:
x: int = Constantint, 'Thing'
?
it still doesn't like that though, because Thing.x "is" an int
mypy and pyright both have some descriptor support
mypy says this
class Thing:
x = Constant(1)
Thing().x + 5
main.py:27: error: Need type annotation for "x"
oh wow! it works if i do
class Thing:
x = Constant[int, 'Thing'](1)
Thing().x + 5
that's because you didn't give type arguments
yes that's what I was suggesting
i see... but then won't the types be all wonky in help() etc?
ah, reveal_type actually says int for Thing().x
it'd be nice if Protocol supported optional methods
then you could have a Descriptor protocol for such annotations, so you can write an explicit annotation if you wanted
that or just a special typing.Descriptor
using the proposal from https://mail.python.org/archives/list/typing-sig@python.org/thread/P6FCGRJUJHEVKHHJI737PHIKVNHFIJKR/
import typing
from typing import Protocol
S = TypeVar('S')
T = TypeVar('T')
@typing.runtime_checkable
class Descriptor(Protocol[S]):
@typing.not_required
def __get__(self, obj: T | None, typ: type[T]) -> S: ...
@typing.not_required
def __set__(self, obj: T, val: S) -> None: ...
@typing.not_required
def __del__(self, obj: T) -> None: ...
hey! can someone please help me fix this
def arange(self, start: float, stop: float, step: Union[float, None]) -> None:
"""Fill in 'values' with range of values."""
assert step != 0
while start < stop:
self.values.append(start)
if isinstance(step, float):
start += step
if isinstance(step, None):
start += 1```
it says that "none" cannot be assigned to a parameter
It can be a bit misleading but type checkers only allowed you to use None in the same way as you do say int, float for convenience
None is not a type, if you try to do isinstance(step, None) at runtime that will always throw an exception
You want to do if step is None
Nope, NoneType is, and None is its only instance
Technically you could do if isinstance(step, NoneType)
But for checking None, it is not the normal way of doing it
uhh.. so then what's the difference between NoneType and None?
i had no idea NoneType was a thing
if you do type(None) you will see "NoneType", you can also find it in the types module from types import NoneType.
uhh.. so then what's the difference between NoneType and None?
Think of the typebool, True and False are it's only instances, but there is a difference betweenTrueandbool.Noneis the same, just instead of having two instances, it only has one, so it is a singleton
oooh i see
okay so
that makes more sense
that's the error i have
i changed it to an else. instead of isinstance(step, None)
step: Union[float, None], you need to make that a default argument if you dont want to have to do positive.arange(1.0, 5.0, None)
..., step: Union[float, None] = None)
this is what i ve got
def arange(self, start: float, stop: float, step: Union[float, None]) -> None:
"""Fill in 'values' with range of values."""
assert step != 0
while start < stop:
self.values.append(start)
if isinstance(step, float):
start += step
else:
start += 1```
No, you have step: Union[float, None]
def arange(self, start: float, stop: float, step: Union[float, None] = None) -> None:
"""Fill in 'values' with range of values."""
assert step != 0
while start < stop:
self.values.append(start)
if isinstance(step, float):
start += step
else:
start += 1
You want this
why would i default it to none?
why does this happen 🤔
You define 3 positional arguments, and then you ask why the ide is unhappy when you call it with only 2 :P
oh that makes sense XD
yeah b/c it requires them
they're arguments
_< thank you!
stdlib/builtins.pyi lines 198 to 199
_PositiveInteger = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
_NegativeInteger = Literal[-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18, -19, -20]```
stdlib/builtins.pyi lines 246 to 258
def __pow__(self, __x: int, __modulo: Literal[0]) -> NoReturn: ...
@overload
def __pow__(self, __x: int, __modulo: int) -> int: ...
@overload
def __pow__(self, __x: Literal[0], __modulo: None = ...) -> Literal[1]: ...
@overload
def __pow__(self, __x: _PositiveInteger, __modulo: None = ...) -> int: ...
@overload
def __pow__(self, __x: _NegativeInteger, __modulo: None = ...) -> float: ...
# positive x -> int; negative x -> float
# return type must be Any as `int | float` causes too many false-positive errors
@overload
def __pow__(self, __x: int, __modulo: None = ...) -> Any: ...```
Yeah, we would need some predicates for this to work
Although I guess it could return an union as a fallback
interesting how other languages handle this
rust's pow for ints accepts only an unsigned int for the exponent
java accepts both args as doubles and returns a double
haskell's ^ just errors out for a negative exponent
Haskell doesn't let you raise an int no?
Call unsigned "Nat" and maybe pythonistas would be fine with that xD
Prelude> 5 ^ 2
25
Prelude> 5 ^ (-2)
*** Exception: Negative exponent
Prelude> 5 ** (-2)
4.000000000000001e-2
Oh right, ^
Jesus it has 3 exponentiation operators
(^) :: (Num a, Integral b) => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: Floating a => a -> a -> a
oof
typing.Union[str, int] == str | int?
yes
Just that unions is for before python 3.10?
yeah
Right ok 👍
it kind of makes sense to have 3 different operations for different use cases!
well a lot of things in haskell are ad-hoc
so many name collision workarounds
friendly reminder that idris allows overloading as long as the definitions are in 2 separate namespaces 🙂
Actually i never understood from future import annotations does this mean I can use int | None in place of Union [int,None] in python3.9
Is that even overloading at that point?
yeah because you can use them all in the same namespace
Data.Nat.Exponentiation.pow : Nat -> Nat -> Nat
Totality: total
Visibility: public export
Prelude.pow : Double -> Double -> Double
Totality: total
Visibility: public export
they don't really have the "numerical tower" fleshed out well though. e.g. i don't think there's a generic Integral+Fractional exponent operator like haskell has (you'd have to write your own probably, unless i just didn't find it in the source/docs)
idris 2 is v0.5.x still, lots of room for improvement (and prs welcome :P)
Humm, interesting, idris 2 is one of the languages I have on my todo, but because my only interest was on typing I was debating learning agda instead
yeah sorry that wasn't clear. you can do this for example
module Silly
namespace ForDouble
addOne : Double -> Double
addOne x = x + 1.0
namespace ForInteger
addOne : Integer -> Integer
addOne = (+ 1)
yeah the agda community is more oriented around meticulously proving program behavior, sort of the gray area between programming language and proof assistant (the joke is that agda programmers don't ever run their programs). idris 2 is still kind of a research language, but its explicit goal is practical dependent types, so there are lots of purity vs usability tradeoffs involved
also imo idris is a "better haskell" when you look at its standard libraries and prelude. and it has compiler backends for chez scheme, racket, and javascript, so you can actually do web dev in idris 2 (and there are some community members who are focused specifically on web dev in idris 2)
there's also a python backend taking shape!
there is also a reference-counting C compiler target, but imo it's great that it compiles to all these "high level" targets which makes it a lot easier to use in a lot of cases i think (and chez in particular is ridiculously fast and will sometimes beat c anyway)
there was a lua target (which could be run with the also-impressive luajit) but i don't think it's maintained and has fallen badly out of date
Oh wow, I wasnt aware that idris could compile to so many languages, and yeah I have also heard that sentiment from other people when talking about Haskell and more abstract typing features, that is had become too successful to change too much about itself
yeah, see all the functions added for applicatives that already had names defined for monads
yes as long as thats in an annotation (you need to wrap it in ""s otherwise) as future.annotations tells the compiler that annotations should be made strings at runtime
Thanks man
I mean makes sense considering haskell is kinda old
exactly. apparently people have the same feeling about purescript, its prelude and standard library were a chance for a "fresh start" and it is a lot more friendly to use
i think there are alternative haskell preludes but they never caught on as far as i know
you need to use overloads
Interesting, pyright does not like that sum(values, start=start)
one overload like that, another that takes start: Literal[0] = ... and returns Callable[..., Literal[0]]
You would do this
@overload
def wrapped_summation() -> Callable[..., int]: ...
@overload
def wrapped_summation(start: T) -> Callable[..., T]: ...
def wrapped_summation(start=0):
"""
Wrapped summation function.
"""
def inner_func(*values):
return sum(values, start=start)
return inner_func
But because you had inner func annotated, I would assume you would also want the Callable[..., T] annotated with receiving arguments of type T, but that is not possible in Mypy right now, for that matter I am not sure you can in other type checkers as well
from typing import Protocol, TypeVar
S_contra = TypeVar('S_contra', contravariant=True)
T_co = TypeVar('T_co', covariant=True)
class VariadicCallable(Protocol[S_contra, T_co]):
def __call__(self, *args: S_contra, T_co) -> T_co: ...
def wrapped_summation(start: S_contra = 0) -> VariadicCallable[S_contra, S_contra]: ...
reveal_type(wrapped_summation(1))
reveal_type(wrapped_summation(0.0))
reveal_type(wrapped_summation(""))
other than this error, it works:
main.py:9: error: Incompatible default for argument "start" (default has type "int", argument has type "S_contra")
i'm not sure what it's actually complaining about. wouldn't int just be the inferred type of start when no arg is passed?
pyright gets it right
is that error a bug in mypy?
Humm, I dont think so, because you arent suppose to be able to use defaults like that in generic functions I think
If you do wrapped_summation()(1,2,3) pyright complains
error: Argument missing for parameter "T_co" (reportGeneralTypeIssues)
that's not the error i expected
Wasnt it supposed to be def __call__(self, *args: S_contra) -> T_co: ...
oh, yes
does this work differently if i use a separate type stub?
like would that work?
error: Argument of type "Literal[1]" cannot be assigned to parameter "args" of
type "S_contra@wrapped_summation" in function "__call__"
is that because it's contravariant?
x: list[int] = [1, 2, 3]
wrapped_summation()(*x)
error: Argument of type "int" cannot be assigned to parameter "args" of type "
S_contra@wrapped_summation" in function "__call__"
huh, i wonder why that's not allowed?
I think it has to do with the fact that when you call the VariadicCallable, it has not resolved S_contra to anything
It doesnt understand (start: T = 0) -> VariadicCallable[T, T] as (start: T = 0) -> VariadicCallable[int, int] when called with no arguments
As for this, you should be able to do it in pyright, but it seems to be broken
def wrapped_summation(start: T) -> Callable[[Unpack[tuple[T, ...]]], T]: ...
But it ends up allowing any type regardless
but that's what doesn't make sense to me
why isn't start() equivalent to start(0)?
Yes mypy doesn't do that. I'm not entirely sure why. I think pyright does support this behavior, but Eric Traut has been grumbling about how this feature is tricky to get right.
interesting, it seems like it should be straightforward, but i trust the opinion of people who actually do these things
Well I did implement this behavior in pyanalyze and it was pretty straightforward 🙂 Maybe I missed something though
Maybe it just has to do pyright's current way of resolving things, the moment when typevars are "stored" and default arguments's types are inferred are at different stages? One of the current pyright issues has the same "seems straightforward" but goes against the current architecture
Doen't pyanalyze work differently though? It works by semi running the code right?
Idk how much of a difference that makes anyway xD
true but I don't think that makes a difference here
Sweet beard man.
this is usually the case for "seems straightforward" things that turn out not to be straightforward. something turned out to be insufficient in the software architecture that makes the small feature require a big change
hi, anyone aware of a easy way to have "computed protocols" - i have a "sans" api for creating http requests/parsing the resulting response, and i want to create typesafe sync/async wrappers around it using sync/async http libs
i would love the ability to derive a protocol from a concrete class, if that's what you're asking. i am not aware of that feature in any type checker, though
'''
in_types_1 = [str, int]
in_types_2 = float
def something(input: Union[in_types_1 , in_types_2])
'''
how to do the above?
even though those values are constants its hard to type this without repeating yourself
theres an issue open about this in python/typing i cant find it currently
but it would definitely be a welcome addition
would be nice if you could write Union[*in_types_1, in_types_2]
You can technically make this work by doing SomethingType = Union[str, int, float]
in_types_1 = SomethingType.args[:-1]
in_types_2 = SomethingType.args[-1]
def something(input: SomethingType):
But I wouldn't advise that, you might just be better repeating yourself
Although maybe you can if you use a tuple and unpack it using a type checker that supports pep 646
What would that do?
It's a list of types not a Union?
programmatically* and ahh, you did, I didnt see it was just a list xD
works with pyanalyze 😄 (because it just looks at the runtime objects)
Yay for data class transforms!
wait it was accepted?
I don't think so but the PEP was announced (meaning that it's moving forward)
ah yes, I guess we missed that before
I am looking at the typing_extensions implementation, and I take it that it will be the reference implementation:
https://github.com/python/typing/blob/master/typing_extensions/src/typing_extensions.py#L1793-L1802
Why doesn't it accept **kwargs? The PEP clearly outlines plans to add more and more keyword arguments and I feel like maintaining compatibility when you want to use newer features won't work out.
typing_extensions/src/typing_extensions.py lines 1793 to 1802
def dataclass_transform(
*,
eq_default: bool = True,
order_default: bool = False,
kw_only_default: bool = False,
field_descriptors: typing.Tuple[
typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
...
] = (),
) -> typing.Callable[[T], T]:```
Afterall, Python doesn't fail because you defined an unknown dunder.
Hasn't been updated yet?
Well thank you for your efforts 😁
was there ever a time where List.__origin__ == List?
I can't go back to 3.6 to check if it was unfortunately
in 3.6 List.__origin__ is None
I'm on 3.6.10
https://github.com/danielgtaylor/python-betterproto/blob/master/src/betterproto/__init__.py#L891-L894 so this would have never been True?
src/betterproto/__init__.py lines 891 to 894
if hasattr(t, "__origin__"):
if t.__origin__ in (dict, Dict):
# This is some kind of map (dict in Python).
return dict```
or could you do typing.Dict[str, int]()?
TypeError: Type Dict cannot be instantiated; use dict() instead
Dict[str, int].__origin__ is Dict
what a mess
yes, 3.6 typing had some oddities. It's also possible this changed between 3.6.0 and later versions
can you do typing.List[int]() I'd guess not?
no
is Literal[x, y] different from Literal[x] | Literal[y]?
No, it's a convenient shorthand.
pyright sometimes doesn't like it for some reason and will flag this with an error 🤔
just pyright being bad?
formats: Sequence[Literal['png', 'jpg', 'webp', 'gif']] = ('png', 'jpg', 'webp', 'gif') if banner.is_animated() else ('png', 'jpg', 'webp',)
what error do you get?
Sometimes it shows an error and sometimes it doesn't and don't think it's something I've done since it only shows maybe 1/5 the time?
https://cdn.discordapp.com/attachments/905603112004362301/963275969525854279/unknown.png
' | '.join(str(asset.with_format(f)) for f in formats)
nvmstr(asset.with_format(f)) is no longer a Literal
asset.with_format expects literal['webp', 'gif', 'png', 'jpeg', 'jpg']
f should be from what's defined above but it sometimes complains that it's a str
This channel is about type annotations. If you have a general Python question, you should see #❓|how-to-get-help and claim a help channel
my bad
I have a pair of overloads that look like this: ```python
@overload
async def auth(self, username: Union[str, bytes], password: Union[str, bytes]) -> str:
...
@overload
async def auth(self, password: Union[str, bytes]) -> str:
...
How would I write the implementation?
I originally had this... ```python
async def auth(self, username: Union[str, bytes], password: Union[str, bytes, None] = None) -> str:
# If password is None the username is treated as the password
...but as you can tell, if someone calls it with keyword arguments it'll fail
I can always use *args and **kwargs and handle things, but I would prefer not to
When I had something similar I used *args **kwargs verified by an inspect.Signature
I would just make both arguments default to None and raise the type error myself
If you pass no arguments the type checker will complain anyway as there is no overload implementation for no arguments
Rising a nice/proper error yourself can get annoying
inspect.Signature? I haven't used that for anything except introspection
it would be easier if you made them kw-only
then you can do username: Union[str, bytes, None] = None
don't see any harm in this
auth("foo", "bar") looks like f(true, true, false, null, null, "yes") to me 🙂
I got the signatures from the overload functions and then try to bind the arguments to that
Then how do I differentiate between calling with one argument and username=?
If it succeeds you can access them by name
Haha, yeah
Yeah I am considering that right now
you could also make them positional only
There shouldn't be a difference though, in both cases username would no longer be None
Doesn't just calling with username become password
Also you could just rely on the type checker to verify.
can you make a mypy-play.net?
This is one of the cases where I think it's a bit better to provide some "hand holding" instead of failing in some weird way if you try to call it in an incompatible way
from typing import overload
@overload
def auth(*, username: str | bytes, password: str | bytes) -> str:
...
@overload
def auth(*, password: str | bytes) -> str:
...
@overload
def auth(first: str | bytes, second: str | bytes, /) -> str:
...
@overload
def auth(first: str | bytes, /) -> str:
...
def auth(
first: str | bytes | None = None,
second: str | bytes | None = None,
/,
*,
username: str | bytes | None = None,
password: str | bytes | None = None,
) -> str:
print(f"{first=} {second=} {username=} {password=}")
match (first, second, username, password):
case (None, None, None, None):
raise TypeError("bad")
case (first, None, None, None):
return _clean_auth(username=first, password=first)
case (None, second, None, None):
raise TypeError("bad")
case (first, second, None, None):
return _clean_auth(username=first, password=second)
case (None, None, username, None):
raise TypeError("bad")
case (None, None, None, password):
return _clean_auth(username=password, password=password)
case (None, None, username, password):
return _clean_auth(username=username, password=password)
raise TypeError("bad")
def _clean_auth(username: str | bytes, password: str | bytes) -> str:
print(f"{username=} {password=}")
return ""
might want to handle case (first, None, None, password): too
with:
@overload
def auth(first: str | bytes, /, *, password: str | bytes) -> str:
...
That was my first idea but I am more inclined to make them keyword-only. Generally, I'd rather make the calling more explicit than implicit I think.
What a mouthful 😅
def auth(*, username: str | None = None, password: str) -> str:
...
``` simple and idiomatic 🙂
if someone has a bytestring, they can decode it as they wish
What if there is no appropriate encoding?
but in that case why accept a string at all?
ok well i see why
it's really frustrating that the "combined" types can't be inferred from the overloads
What does your function do in that case?
It's sent to Redis, so it works like usual.
why would you use a non-unicode username and password?
is it not possible to use Literal with an enum member?
from enum import Enum
from typing import Literal
class Sentinel(Enum):
Empty = object()
Empty = Sentinel.Empty
x = Weird
reveal_type(x)
y: Sentinel = Empty
z: Literal[Empty] = Empty
main.py:10: note: Revealed type is "__main__.Sentinel"
main.py:14: error: Parameter 1 of Literal[...] is invalid
main.py:14: error: Variable "__main__.Weird" is not valid as a type
main.py:14: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
Found 2 errors in 1 file (checked 1 source file)
you may have to write Literal[Sentinel.Empty]
pyright gets it right of course 😛
No configuration file found.
No pyproject.toml file found.
Assuming Python platform Linux
Searching for source files
Found 1 source file
/home/runner/PyrightPlayground/e2403359.py
/home/runner/PyrightPlayground/e2403359.py:10:13 - info: Type of "x" is "Literal[Sentinel.Empty]"
0 errors, 0 warnings, 1 info
Completed in 3.185sec
from enum import Enum
from typing import Literal
class Sentinel(Enum):
Empty = object()
Empty = Sentinel.Empty
v = Empty
reveal_type(v)
x: Sentinel = Empty
y: Literal[Sentinel.Empty] = Empty
z: Literal[Sentinel.Empty] = Sentinel.Empty
main.py:10: note: Revealed type is "__main__.Sentinel"
main.py:12: error: Incompatible types in assignment (expression has type "Sentinel", variable has type "Literal[Sentinel.Empty]")
Found 1 error in 1 file (checked 1 source file)
Who am I to judge 😅... but yeah
How can I type a lambda? 🤔
dumbpw/engine.py:102:15: error: Cannot infer type of lambda [misc]
validator=lambda charset, length: len("".join(charset)) > 0,
^
dumbpw/engine.py:110:15: error: Cannot infer type of lambda [misc]
validator=lambda charset, length, result: all(char in "".join(charset) for char in result),
^
dumbpw/engine.py:110:15: error: Argument "validator" to "ensure" has incompatible type "Callable[[Any, Any, Any], bool]"; expected
"Callable[[NamedArg(str, 'charset'), NamedArg(int, 'length'), str], Union[bool, str]]" [arg-type]
validator=lambda charset, length, result: all(char in "".join(charset) for char in result),
^
Found 3 errors in 1 file (checked 1 source file)
If you have to type hint lambda, you're doing something wrong. Just write regular function def instead
Simple, you use mypy and write a plugin xD
lambda doesn't support type hinting
not out of the box anyway
quick-and-dirty doesn't really play well with typing
so is there any solution besides # type: ignore ?
wild
You need to make the args for the lambda have the same names don't you?
Yeah I think that's the only reasonably verifiable part, the typehints should be treated as unannotated and implicitly Any.
I'd like lambda x: x.y.z().k to get a fancy automatically generated
class _HasY(Protocol, Generic[T]):
@property
def y(self) -> T: ...
class _HasZ(Protocol, Generic[T]):
def z(self) -> T: ...
class _HasK(Protocol, Generic[T]):
@property
def k(self) -> T: ...
Callable[_HasY[HasZ[_HasK[T]]], T]
They do? Don't they? wat
maybe you just need a callback protocol and mypy doesnt support NamedArg anymore? I can't see anything else wrong with it
Hello!, What would be the best way to create attributes in a python class, I always have this question.
Currently I use the self.name_attribute = 'something'
self.attribute_name2 = None
class Foo:
attribute_name2: str | None
def __init__(self, attr2: str | None) -> None:
self.attribute_name2 = attr2
Or just like this: ```py
class Foo:
def init(self, attr2: str | None) -> None:
self.attribute_name2 = attr2
the difference is that type checkers will have to infer the type of attribute_name2 in the second example
where in the first one you are being explciit
if you're not setting the attribute directly from an argument you can do something like this: ```py
class Foo:
def init(self) -> None:
self.attribute_name2: str | None = None
I'm trying to come up with a protocol that will match a class that has descriptors for its properties because it seems that read-only descriptors don't match read-only properties on protocols... the problem is that I'm hitting issues with my protocol... here's what I have:
_T_co = TypeVar('_T_co', covariant=True)
_D = TypeVar('_D', bound='Descriptor')
class Descriptor(Protocol[_T_co]):
@overload
def __get__(self: _D, instance: None, owner: Any) -> _D:
...
@overload
def __get__(self, instance: object, owner: Any) -> _T_co:
...
class DescriptorRecord(Protocol):
name: ClassVar[Descriptor[str]]
However, when I use this protocol with a class that has a class-level descriptor, it won't match:
# from sqlalchemy-stubs:
class TypeEngine(Generic[_T_co]):
@property
def python_type(self) -> type[_T_co]:
...
class String(TypeEngine[str]):
@property
def python_type(self) -> type[str]:
return str
class Column(Generic[_T]):
type: TypeEngine[_T]
def __init__(self, type_: TypeEngine[_T]) -> None:
self.type = type_
@overload
def __get__(self, instance: None, owner: Any) -> Column[_T]:
...
@overload
def __get__(self, instance: object, owner: Any) -> _T:
...
def __get__(self, instance: Any, owner: Any) -> Column[_T] | _T:
if instance is None:
return self
# some black magic happens here
return cast(_T, {})
# end from sqlalchemy-stubs
class SARecord:
__tablename__ = 'things'
name: ClassVar[Column[str]] = Column(String())
def process_record(record: DescriptorRecord) -> None:
...
process_record(SARecord()) # error
(for the record, I'm typing this descriptor because read-only descriptors don't seem to match read-only properties on protocols, either)
Thanks very much @hasty hull
What is the correct way of type hinting this? This: python class MyClass: def __init__(self, arg_1: int, arg_2: float) -> None: self.arg_1 = arg_1 self.arg_2 = arg_2 or python class MyClass: def __init__(self, arg_1: int, arg_2: float) -> None: self.arg_1: int = arg_1 self.arg_2: float = arg_2?
@long moss the latter
Ok thanks!
I dont think you can do what you want with protocols
class A(Protocol):
a: float
class B(Protocol):
a: A
class Foo:
a: float
class Bar:
a: Foo
def foo(x: B): ...
x: Bar
foo(x) # Error
uhhh... that seems... odd
I am not sure what is the harm here... It has to do with the fact that because those fields are mutable they should be invariant, but I am not sure how that works when the field itself is structurally typed
ultimately, I'm trying to figure out how to accept an object that can have the operation obj.prop done on it and return a string
it doesn't matter if it's a descriptor or a read-only attribute or a mutable attribute
class A(Protocol):
@proptery
def prop(self) -> str:
...
This works for most objects except descriptors
I've asked a similar question in the python/typing discussions, but haven't gotten any responses: https://github.com/python/typing/discussions/1123
Your example seems to indicate that structural typing is only good on the surface and can’t be used deeper than attributes
Yeah, the deeper issue here seems invariance on the attributes, seems that the @property route was just a 'hack' so you could have classes that had attributes of subclasses of that, but it is probably special cased, I think it is a blind spot on typechkers
I think that might be related but I am not sure
seems close
and I notice that @oblique urchin marked it... I wonder what his thoughts are on all of this
(sorry for pinging you... also, hi... we used to talk on the typing gitter)
I probably just searched for "property" in the tracker and marked everything 🙂
hah
Don't have any interesting thoughts, I'd need to look at this more deeply. Maybe we need a more principled way to indicate "read-only attribute" in a protocol
hi 🙂 While we're here, I was curious: You always seem to report rather complicated issues to the pyright tracker. What is the project you're working on that makes you run into all those issues?
and helping with discord.py
my day job is working with TypeScript, so sometimes I try to make Python's type system do too much
oh interesting, does discord.py do runtime introspection of type hints?
discord/ext/commands/core.py line 450
self.params: Dict[str, Parameter] = get_signature_parameters(function, globalns)```
Actually about that, why do protocols respect that aspect but normal classes don't? Is this just a case of convenience vs correctness or is there something that makes the protocol case worse
@dataclass
class A:
a: int
@dataclass
class B(A):
a: bool
def foo(x: A):
x.a = 3
b: B = B(True)
foo(b)
reveal_type(b.a) # Revealed type is "builtins.bool"
b.a # int at runtime
for the record, it seems that in nevermind, I hadn't saved my filemypy, that example works fine... pyright gives an error
I'm still floored that it doesn't work
I think It doesnt work because of this
class A(Protocol):
a: float
class B(Protocol):
b: A
class Foo:
a: float = 0.0
c: int = 42
class Baz:
a: float = 3.14
class Bar:
b: Foo
def foo(x: B):
x.b = Baz() # This respects that B.b should be subtype of A
x: Bar = Bar()
foo(x) # Maybe fine
x.b.c # Runtime Error
hmm
I wonder if typescript just ignores this kind of error, one of their non goals is
Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.
Yup, this would be less annoying if python's type system was more expressive, but for now you can't really do much about that other then ignore the errors, eg proper read-only attributes
this wouldn't be an issue if descriptors would match read-only protocol properties 😂
If they had gone with Jelle's idea of overloading the meaning of Final for this context or a variant of this, we probably wouldn't be here xD
meh... I'd like to see ReadOnly[]
it would match TypeScript's readonly 😄
class User(Protocol):
email: ReadOnly[str]
There is now precedence of adding new symbols to typing.py for specific constrictions such as NotRequired for TypedDict so I think adding a new one like ReadOnly could be possible
right
I'd also like to see something like TypeOf[] to match TypeScript's typeof
I think someone pointed me to a proposal the other day for something similar
@hearty shell btw, the example above you gave helped me understand why your initial example is disallowed
thank you
if only Generic property
?
well if you could do ```py
class User(Protocol):
email: property[str]
hmm
I don't disagree... but then someone (cough) would argue that descriptors still don't match properties
That would still often be more precise than you want
I just want a way to say, "pass in an object that can have obj.prop run on it to get a value out of it"
and it doesn't matter if prop is a property or descriptor or some other mechanism
for now, I guess I'll use # type: ignore 😦
can someone help me how to create notification desktop
This isn't the correct channel for that, you probably want #❓|how-to-get-help or #tools-and-devops
what's the min python version for builtin subscripting like list[int] and tuple[thing]?
3.9
thank
Would something like this work?
from typing import NoReturn, TypeVar, Generic, Protocol
T = TypeVar("T")
class Readonly(Generic[T], Protocol):
def __get__(self, obj, objtype=None) -> T:
...
def __set__(self, obj, value) -> NoReturn:
...
class A:
x: Readonly[int]
def __init__(self):
self.x = 7```
Given what I’ve experienced with descriptors and how type checkers handle them, I don’t think that would work
Don't know if this is the right spot. But I want to add to my pydantic model a model where I define the key and the value of dict with pydantic:
class i18n(BaseModel):
lang: str
translation: str
class element(BaseModel):
title: str
translation: Dict[i18n.lang, i18n.translation]
so the translation value in element would be something like: `{ en: "english version", jp: "japanese version"}
translation: Dict[str, str]
ok sorry. One part is missing I want to add a validation for lang to be a iso conform lang code.
ValidLangs = Literal["en", "jp", ...]
...
lang: ValidLangs
Incompatible types in assignment (expression has type "Union[CollectConfigBase, ProcessConfigBase, ForwardConfigBase]", variable has type "RegexMaskProcessConfig") [assignment]
RegexMaskProcessConfig is a subclass of ForwardConfigBase.
So, how do I properly type hint this?
Could you show where the error happens
async def forward(
*,
ctx: PipelineRunContext,
event: CollectedEvent,
) -> None:
"""
Method called to forward the event.
"""
indent: Optional[int] = None
config: DiskConfig = ctx.config
config: DiskConfig = ctx.config also throws the same error
class PipelineRunContext(BaseModel):
"""
Class representing a pipeline run context.
"""
config: Union[CollectConfigBase, ProcessConfigBase, ForwardConfigBase]
cache: Dict[str, Any] = Field(default_factory=dict)
shared_cache: Dict[str, Any] = Field(default_factory=dict)
fyi, BaseModel is a pydantic base model
if that makes a difference
class DiskConfig(ForwardConfigBase):
"""
Configuration schema for the disk forward plugin.
"""
path: pathlib.Path
filename: Optional[str] = None
pretty_print: bool = False
@hearty shell does the above bring enough context?
Not sure about the first issue yet, but when you do config: DiskConfig = ctx.config DiskConfig might be a subclass of ForwardConfigBase, but is it also a subclass of either CollectConfigBase or ProcessConfigBase
Also either way, you are telling it should be DiskConfig, that is not compatible with ForwardConfigBase
hmm, CollectConfigBase , ProcessConfigBase and ForwardConfigBase share the same base class
so, how do I define PipelineRunContext.config as subclasses of the three mentioned above?
and not those
Also, if it's a subclass of ForwardConfigBase, it will not be a subclass of CollectConfigBase or ProcessConfigBase, or, are you telling me that mypy can't know that?
class ForwardConfigBase: ...
class DiskConfig(DiskConfig):
a: int
class PipelineRunContext:
config: ForwardConfigBase
async def forward(ctx: PipelineRunContext):
config: DiskConfig = ctx.config
config.a
Here, you are telling config is of type DiskConfig
But ctx.config is of type ForwardConfigBase
You are trying to assign the super class to a subclass annotation
yes, makes sense.
Is there a way to tell mypy that PipelineRunContext.x is a subclass of ForwardConfigBase but not ForwardConfigBase?
looks like that's what I need?
Not sure that is what you need, the problem here is where do you want to sacrifice, you have an object PipelineRunContext that has a config parameter that could be an Union of any of these "Base" types of configs CollectConfigBase, ProcessConfigBase, ForwardConfigBase.
Then you have a function that accepts this very broad object, you access it's config and you then want to it to be DiskConfig
Maybe you want the function to take a more precise object that already has a DiskConfig as one of its parameters
dam, no, I do want that broad object :/
So how are you sure that it is in fact a DiskConfig
and not any other subclass of ForwardConfigBase?
Yup
well, I know, because it's what I'm passing in the code, but that's me, not mypy 🙂
I accept that mypy needs help in some cases, just haven't figured out how to help it, besides cast() :/
If you only call that specific function with a PipelineRunContext that always has a config compatible with DiskConfig, the your annotation for that function is too broad. But maybe that is more work then its worth x)
There are other ways of narrowing types, like using assert or checking the type inside the function
yeah, I'm doing something like
if TYPE_CHECKING:
assert isinstance(ctx.config, DiskConfig)
which seems to appease mypy
was this what you were referring to?
yes
it's kind of a plugin system, so each module needs to define one of these three
the configs, yes
Ah, then you life could get a little easier with Generics, although I have never use Pydantic so I dont know how that interact
seems that pydantic has its own type of generics
pydantic is supposed to support generics
But then what you could have is something like this
T_co = TypeVar('T_co', covariant=True, bould=ConfigBase)
class PipelineRunContext(BaseModel, Generic[T_co]):
"""
Class representing a pipeline run context.
"""
config: T_co
async def forward(
*,
ctx: PipelineRunContext[ForwardConfigBase],
event: CollectedEvent,
) -> None: ...
BaseModel, Generic[T_co] not this exactly, but something like that
Np! 😄
Btw, why is it important that it's a read only object?
It doesnt have to, it is just that if you want PipelineRunContext[DiskConfig] to be a subtype of PipelineRunContext[ForwardConfigBase] you need PipelineRunContext to be covariant on the type variable, and if that attribute can be also settable you can run into problems
It is the same type of problem you have whenever you tried to give a list of subtype to a function that expected a list of the supertype
I say "It doesnt have to" because even though a settable attribute should not be covariant, you can still make it so if that makes your life easier
Thanks for the explanation
Hmm, I have code like this: ```python
CommandT = TypeVar('CommandT', bound=CommandMiddlewareMixin)
_MiddlewareDecorator = Callable[[CommandT], CommandT]
MiddlewareDecorator = _MiddlewareDecorator[CommandMiddlewareMixin]
Is there any other way of doing it? Originally I only had the second line, but then realized it became `Callable[[Any], Any]` as opposed to a shortcut for writing `Callable[[CommandT], CommandT]`
doesnt that just become MiddlewareDecorator = Callable[[CommandMiddlewareMixin], CommandMiddlewareMixin] 🤔
i'm likely missing something
hmm
I don't think you're missing anything
It does, yes, but in the case of subclasses that have generics those are kept
wdym?
class Command(CommandMiddlewareMixin, CommandCallback[P, RT]):
...
Without that typevar, what will happen is that a Command is inputted but the output is CommandMiddlewareMixin
hmm I don't see how MiddlewareDecorator isn't exactly the same as Callable[[CommandMiddlewareMixin], CommandMiddlewareMixin]
def deco(command: CommandMiddlewareMixin) -> CommandMiddlewareMixin:
...
class Command(CommandMiddlewareMixin):
...
x: Command = ... # type: ignore
reveal_type(deco(x)) # CommandMiddlewareMixin
..no?
no, it doesn't start acting like a typevar
unless I misunderstand what you mean
can you post a snippet that works on mypy-play or pyright playground?
Ah shit, so these aren't equivalent? ```python
def factory() -> Callable[[CommandT], CommandT]: ...
Shortcut = Callable[[CommandT], CommandT]
def factory() -> Shortcut: ...
no they arent equivalent
to make them youd need to do ```py
def factory() -> Shortcut[CommandT]: ...
mypy thinks differently though
main.py:10: note: Revealed type is "def [T] (T`-1) -> T`-1"
main.py:11: note: Revealed type is "builtins.int*"
main.py:18: note: Revealed type is "def (Any) -> Any"
main.py:19: note: Revealed type is "Any"
which... I would expect
You can make links on the pyright playground too 
I would expect using Shortcut in a standalone way to mean Shortcut[Any], as with any other generic. Basically what @soft matrix said
yeah it's been possible for 5 months, there just isn't a UI control for it 😛
also, if someone non-lazy could contribute a UI control or something like that, it'd be awesome
Can I do that in python? 
@blazing nest I wonder if this is a bug in pyright
I cba to read the typing peps
OooOooh
if you want the type of a generic function, you'd need to make a protocol:
MyT = TypeVar("MyT", bound=Foo)
class MyDeco(Protocol):
def __call__(self, arg: MyT, /) -> MyT:
...
I don't know, no. It should mean Shortcut[Unknown] which is Shortcut[Any].. which Pyright complained about in --verifytypes
I would start a thread on https://github.com/python/typing/discussions
or I can start it if you don't want to
Feel free
c:\Users\%username%\Projects\wumpy\library\wumpy-interactions\wumpy\interactions\commands\checks.py:error: Return type is partially unknown
Return type is "MiddlewareDecorator[Unknown]"
Type argument 1 for type alias "MiddlewareDecorator" has unknown type
Hmm, to follow pyright I am doing Command[..., object] now which introduces some complications: ```python
def command(
self,
callback: Optional[Callback[P, RT]] = None,
*,
name: Optional[str] = None,
description: Optional[str] = None,
) -> Union[Command[P, RT], Callable[[Callback[P, RT]], Command[P, RT]]]:
def decorator(func: Callback[P, RT]) -> Command[P, RT]:
subcommand = Command(func, name=name, description=description)
self.add_command(subcommand)
return subcommand
if callback is not None:
return decorator(callback)
return decorator
Argument of type "Command[P@command, RT@command]" cannot be assigned to parameter "command" of type "SubcommandGroup | Command[(...), object]" in function "add_command"
Type "Command[P@command, RT@command]" cannot be assigned to type "SubcommandGroup | Command[(...), object]"
"Command[P@command, RT@command]" is incompatible with "SubcommandGroup"
TypeVar "RT@Command" is invariant
Type "RT@command" cannot be assigned to type "object"
The other (somewhat unrelated) issue is that my tests on Python 3.10 > is failing: ```
E TypeError: Parameters to generic types must be types. Got Ellipsis.
What do I do? typing_extensions doesn't have a Generic backport from what I can find
quote it? We could probably add a backport for that behavior to typing-extensions too
Oh right, yeah I'll quote it
Is there any reason that it doesn't just include all of typing?
I sometimes forget which I am supposed to import from 😅
Would be quite nice to change typing to typing_extensions on say a project that only supports 3.9 and then get compatibility with previous versions. Nice marketing value: drop-in replacement 😅
It only includes things that weren't in typing in 3.5.0 I suppose. We could re-export the rest too, would just be a bit of work.
Are you saying that as in "insignificant work"?
Usually you'd hear it on the tone
Doesn't seem that bad. In typing-extensions, add a bunch of from typing import X, add to __all__, add some tests, add documentation. Similar thing in typeshed. Update type checkers to recognize typing_extensions.X is typing.X for more values of X.
@blazing nest it is indeed a bug https://github.com/microsoft/pyright/issues/3346
also to be clear I'm not sure I'd support this, there's some risk of confusion too
class Event:
NAME: ClassVar[str]
class TypingEvent(Event):
NAME = "TYPING_START"
/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/site-packages/wumpy/bot/events/channel.py:error: Module "wumpy.bot.events.channel" is partially unknown
wumpy.bot.events.channel.TypingEvent.NAME
/opt/hostedtoolcache/Python/3.7.12/x64/lib/python3.7/site-packages/wumpy/bot/events/channel.py:22:5 - error: Ambiguous base class override
Type declared in base class is "str"
Type inferred in child class is "Literal['TYPING_START']"
Any way for me to remove this warning or whatever it is bringing down my type-completeness score?
what if you reannotate it?
I could, I believe (sadly I don't get this locally when I verify the types), but if possible I want to keep the type Literal["TYPING_START"] if possible while simultaneously requiring subclasses of Event to define a NAME
If type narrowing can't be done on attributes then I might scrap it (and turn it into ClassVar[str]) - it was the primary motive but I haven't really played around with it much apart from with TypedDicts.
Yeah, rip Literal 😅
Where does Embed|None come from? self.embed_attr is dict[str, Any] and x is supposed to Any?
Because you're calling .get() which returns None if the key isn't found. If you just want the type of the key then you need to use self.embed_attr['timestamp']
Yes but why is it complaining that embed.timestamp is incompatible with Embed|None when x should be Any?
Ah sorry I see, I thought you were talking about why x is Any | None
Can someone explain me why typehinting here is a problem? The code works, but mypy has a problem with hints.
Why is tuple not treated as a iterable and why mypy expects Optional[int] for .extend() or += operation?
def filterRepo(
self,
user: User,
*,
amount: tuple[float, float] = None
) -> list[Transaction]:
filterValues = []
# *snipped*
filterValues += amount
# Argument 1 to "__iadd__" of "list" has incompatible type "Tuple[float, float]"; expected "Iterable[Optional[int]]"mypy(error)
filterValues.extend(amount)
# Argument 1 to "extend" of "list" has incompatible type "Tuple[float, float]"; expected "Iterable[Optional[int]]"mypy(error)
Where does the Iterable[Optional[int]] hint come from?
Do you do anything to filterValues prior to filterValues += amount
Crap, yes, ive appended it with a different object. So mypy assumes this is the type which will be stored in it
Thanks! Is there a typehint which will tell mypy to take in any type? object?
Yes, Any, but the issue here is just you tried to append floats to a list that mypy has guessed takes ints, you could just annotated the list to be of list[float | None]
filterValues: list[float | None] = []
Yes, but in my case the list will hold multiple different variable types. That's why i didn't type hint it during filterValues creation. I just forgot that the first .append will work as a type hint for mypy
list[Any] would be the type hint in that case
Use object not Any
Or even better the full union of all the things you'll append to it
Using object as an annotation for your list gets really annoying though, if you dont care about correctly typing a list, any is usually less of a hassle then object
Or even better make the list in one expression
don't know how to word this but is it possible to make variadic generic but one could provide it by providing it in square bracks, sort of like the first argument in Callable like,
foo: MyClass[int, [str, hex, bytes]] = MyClass(...) # How to implement this generic?
How to do this?
from typing import TypeVar, TypeVarTuple
DType = TypeVar('DType')
Shape = TypeVarTuple('Shape')
class Array(Generic[DType, *Shape]):
...
That gets you Array[T, U, V, ...]
But you can't do Array[T, [U, V]]
But there's no point in using Any when you can just use the actual type annotation
Yes, I agree with you that using a Union of all types would be better, I am just saying that if someone asks for "A list of whatever type, I dont care what type it is", telling them to use list[object] might do that but endup being more restrictive then they want, maybe they know that the last item of the list is always of a curtain type for example and then in that case they probably don't want a "Can't access member ... for type 'object', ..."
not an answer to your question, But I would like to know how you set it to show the type warnings inline
can we add annotation in the variable side? eg: prev_ranks: "that would be ID and rank" = dict() , which both ID and rank are integer
i didnt get the question
that is a dictionary that element1 would be ID , element2 would be rank, both of them are integer
Just noticed there isn't a SupportsStr Protocol (https://docs.python.org/3/library/typing.html#protocols), is there any reason for this? I've just implemented it myself:
class SupportsStr(typing.Protocol):
def __str__(self) -> str: ...
everything is a SupportsStr
It's actually kinda creepy if you think about it
the error lens vscode extension
Thx
Seems like a bug, I reported it here https://github.com/microsoft/pyright/issues/3354
🙏
What is the industry standard for using typing_extensions? something like this?
install_requires =
requests
typing_extensions ; python_version < '3.8'
or should I expect it to be installed
or something else
depends on what you need from it
I feel like it's fine to just always install it
but I'm targeting 3.6+
well, maybe tomorrow you want to use Self which is only in the stdlib in 3.11
setup.py line 199
'typing_extensions>=3.10',```
okay thanks
though in Black we do version-bound it: https://github.com/psf/black/blob/main/setup.py#L105
setup.py line 105
"typing_extensions>=3.10.0.0; python_version < '3.10'",```
Installing it always is probably simplest, considering it already does version checks to avoid redefining things already implemented in typing. It'll just be a bunch of aliases.
it's regularly a top 10 most downloaded package on pypi, so i'm sure lots of people install it by default
@rustic gull
Yup, otherwise you'll see a bunch of annoying version checks in the codebase
This appears to be a mypy bug
Pyright is happy with it
In the meantime you can rewrite it like this so that mypy is happy with it: ```py
from typing import Any, Optional, Callable
def test8():
def wow(
x: Any,
filter_func: Optional[Callable[[Any], bool]] = None,
) -> bool:
if filter_func is not None:
return filter_func(x)
return bool(x)
print(wow(1))
print(wow(2, lambda x: x % 2 == 0))
print(wow(2, lambda x: x % 2 == 1))
if name == 'main':
test8()
Fair enough, you could also switch to pyright ;)
is there a way that overloads can be copied directly from the super?
a method is being modified to add different functionality but it does not have any overloads changed
how can i use the super's overloads for type checkers
https://github.com/HypothesisWorks/hypothesis/issues/3296
looks like a mypy bug to me, especially since pyright is happy with it.
I'll try to assemble a minimal repro later, but thought I'd post here first in case anyone has suggestions.
I'm using a Protocol that defines a single attribute, and a class implementing that Protocol implements that attribute as a read-only property which causes invariance due to mutability
The thing here is, if I don't care about the attribute being read-only or not, how would I type it?
Sounds like you should make it read-only on the Protocol
@property on the protocol
Or define a setter that throws a runtime error on your implementation with NoReturn
thanks! read-only on the protocol worked
I have another (kind-of) similar problem with mutability
Another protocol defines a Mapping attribute that two classes implement
the first one uses a plain dict since the attributes aren't known
the second one uses TypedDict as the attributes are received from an API and can benefit from typed attributes
however, since dict and Mapping aren't compatible, how would I go about making the protocol satisfy both cases?
You can downcast it to a Mapping
downcast?
v: Mapping[str, object] = td
Or you could use another protocol instead of Mapping depending on what your consumers use
eg just the __getitem__(self, k: K)-> V: ... method
thanks! that worked :D
first time using mypy and i got an error with aiohttp
http_client.py:1: error: Cannot find implementation or library stub for module named "aiohttp"
http_client.py:1: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
Try using mypy --install-types
I don't know if aiohttp is untyped, provide types inline or has a stubs package but when you see that error that's how you can generally fix it
it gives me an unrecognized argument error
What mypy version are you on?
0.942
he means --install-types, but that won't help here because aiohttp is not in typeshed
if I recall correctly aiohttp has inline types, so you may just have to point mypy to it
oh
how would i point mypy to it then
oh nevermind
i just now saw the link i'm dumb
actually how would you point mypy to aiohttp
because i'm still confused even after looking at the docs
oops sorry for that typo, yeah I just checked and aiohttp does have inlined types, are you on an old version of aiohttp?
make sure it's installed in the same env as mypy
shit
my aiohttp version is 3.8.1
yeah like Jelle said that'll be an env mismatch then
how would i make sure
are you using pipx?
pipx?
OK nvm .. it's a tool to install Python tools in isolated environments
If you aren't using it it's not relevant, I just have to deal with mypy being isolated from my deps because I use pipx
oh i don't know how to check if mypy is installed in the same env
How are you running mypy?
mypy file.py
Try running python -m mypy
oh it worked
is that the default way of running mypy in a env?
yeah as far as I'm aware, mypy resolves your imports by looking at your current env
Hello, how should I proceed so mypy doesn't have a problem with rootObj being Optional[ET.Element]? This is a return value from XML parsing library method. Obviously using if statement won't work here, but I wanted to visualize my point.
def parseAmount(rootObj: ET.Element, XPath: str) -> tuple[Any, Any]:
"""Parse tuple (Amount, Currency) from Equabank XML"""
try:
if isinstance(rootObj.find(XPath, namespace), Element):
return (
float(rootObj.find(XPath, namespace).text),
# Item "None" of "Optional[Element]" has no attribute "text"mypy(error)
rootObj.find(XPath, namespace).get("Ccy")
)
except (TypeError, ValueError):
return (None, None)
Generally I'm curious how can you make mypy 'work' when you have a method returning combos like int | None and you process the returned value only in case of it not being None. When I implemented such mechanism, I was told to rewrite it and instead of None, handle it with Exceptions. Here I'm using external library so I cannot do much.
you should assign the rootObj.find(XPath, namespace) to a intermediary variable so you can perform the narrowing on that
Yes. That's also an optimization because currently you're calling .find() twice. Not sure if the perf difference matters in practice though.
Would this look like that?
def parseAmount(rootObj: ET.Element, XPath: str) -> tuple[float | None, str | None]:
"""Parse tuple (Amount, Currency) from Equabank XML"""
try:
foundElement = rootObj.find(XPath, namespace)
if isinstance(foundElement, Element):
return (
float(foundElement.text),
foundElement.get("Ccy")
)
except (TypeError, ValueError):
return (None, None)
mypy is happy when it comes to the previous issue, altho now I receive ```Missing return statementmypy(error)
yeah thats it
Hmm, I am having some issues with object - isn't all objects convertable to object (like Any except that you can't do anything with object)?
from typing import Callable, Generic, ParamSpec, TypeVar
P = ParamSpec('P')
RT = TypeVar('RT')
class WrappedCallback(Generic[P, RT]):
...
def wrap(func: Callable[P, RT]) -> WrappedCallback[P, RT]:
...
def accept(callback: 'WrappedCallback[..., object]') -> None:
...
@wrap
def example() -> None:
...
accept(example)
Argument of type "WrappedCallback[(), None]" cannot be assigned to parameter "callback" of type "WrappedCallback[(...), object]" in function "accept"
TypeVar "RT@WrappedCallback" is invariant
Type "None" cannot be assigned to type "object"
I want to use object as opposed to Any so that if my code decides to call the wrapped callback, no assumptions can be done about the return type - but this is causing issues for me 🤔
RT = TypeVar('RT', covariant=True) I think? since callables should be covariant in the return type
Isn't that the default?
the default is invariant
Type variables may be marked covariant or contravariant by passing covariant=True or contravariant=True. See PEP 484 for more details. By default, type variables are invariant.
Oh, huh, but typehints by default are covariant?
class A: ...
class B(A): ...
def accept(arg: A) -> bool: ...
accept(B())
This is covariance yeah?
no, this is because T is covariant in T
that's a very obscure way to say it I guess...
but the logic is like ```py
b: B = B()
a: A = b # this is ok because B is a subtype of A
accept(A)
How do you guys remember the difference between Iterator and Generator?
same way as you remember the difference between iterable and iterator? I don't think there's much to it apart from knowing the methods it expects
Fair
I just have to google it every time or let mypy tell me I messed up but I just don't use them very often
well if you google it enough you'll memorize it I'd guess
though for most uses I'd say you want Iterator
Generator is basically a concrete type, only use it when you actually write a function with yields
I think technically you can make your own, but I've never seen that in practice
also if I have a class that implements __iter__ and/or __next__, is it still proper to subclass something from typing?
you don't need to thanks to Protocols
Nice
Though not all typing classes are Protocols (e.g., Mapping isn't), so if you want to write your own Mapping, you should subclass explicitly
And if that generator function does something apart from yielding values, otherwise I'd usually use Iterator unless it needs to be a generator for other reasons
subclassing the abcs from collections can also give you some default behaviour and make it easier to spot if you miss something
see https://github.com/python/typeshed/pull/7430 for some related discussion
well that falls under the needs to be a generator for other reasons
but I'd say the vast majority of generators are just iterators written with yield
This is a much more diffcult difference though 😅
An iterator you can loop through only once, an iterable is a general collection of some kind that you can loop over repeatedly. A generator is a specific kind of iterator which also has send() and throw().
hello
i ran into another problem with mypy
currently whenever i check my file with mypy it doesn't throw an issue when i'm obviously giving the wrong type to it
i'm actually on that right now
in addition to the above... show the code! and use --strict
can you reproduce it on https://mypy-play.net/ ?
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 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()
currently this is my code and i'm passing a str to _request
and _request returns a dict
resp.json() probably returns Any?
okay but that doesn't explain why it doesn't error when i give _request an string
(side note, you should write _BASEURL: typing.ClassVar[str])