#type-hinting
1 messages · Page 22 of 1
3.12 is in the version dropdown menu at the top of the playground page- i selected it but still got the error about needing to use 3.12
gave it a go with a local 3.12 and the error message is more accurate (error: PEP 695 type aliases are not yet supported [valid-type]) so i guess there's an issue with the playground version selector, or it's converting the message into this
perfect, TY
pretty sure the version selector is about the --python-version option to mypy, not about the version mypy itself is invoked with
ohhh ok i see
can i inform the type checker that a function parameter should not be mutated?
i'm not finding much from my searches
the python type system doesn’t really keep track of mutability. you can write your own protocol that only describe the immutable aspects of your object. for instance, the way typing.Sequence describes only the immutable parts of list
unfortunate
And then there's Mapping and defaultdict 💀
T = TypeVar("T", covariant=True)
class A(Generic[T]):
def foo(self, arg: list[T]) -> None:
...
class B(A[int]):
def foo(self, arg: list[int]) -> None:
...
a: list[A[object]] = [B()]
a[0].foo([""])
Isn't this problematic ? both mypy and pyright don't report any errors
that does seem like it should error, yes
yep
Actually the problem here is the foo method
If T is covariant, then you can't have a method like that
https://pypi.org/project/typing-extensions/4.8.0rc1/ is out, please test it!
@trim tangle rejoice
confusion
class A:
a: int
class B(A):
a: bool
def foo(a: A) -> None:
a.a = 1
B().a = 1 # not ok
foo(B()) # ok
my ci's now just broken in a different place yay!
class A:
a: int
class B(A):
a: bool
``` this  is incorrect
`a: int` means that you get `int` when you read this and you can write any other `int` to this attr
`a: bool` still returns `int` on read (`bool` actually), but it disallows assignment of arbitrary ints to this attr
I'm confused does that mean I shouldn't do a: bool or the typing system/type checkers are flawed
Type checkers generally allow this for pragmatic reasons
except in protocols, where got an opportunity to be stricter 🙂
Does auto-complete also suggest the new thing?
Don't think so
This may be disallowed in the future without this instead being generic over that type with a bound of int and all uses of it filling the generic, it's come up as part of the discussion of formalization and the consequences of such interacting with other issues like replacement with Never.
Hello, does this list[str, str, ...] mean a list of at least 2 strings? or is it garbage?
Oh, so there's no way to say a list of 2 strings? I should use tuple instead?
Tuples yes
and ... is equivalent to typing.Any?
Ok thanks!
Hey guys, how do I type hint an exact return dict? Example:
{
"url": "my_aws_url",
"fields": {
"key": "MANUAL_FILE/test.csv",
"AWSAccessKeyId": "my_key",
"policy": "my_policy",
"signature": "my_signature"
}
}
!d typing.TypedDict
class typing.TypedDict(dict)```
Special construct to add type hints to a dictionary. At runtime it is a plain [`dict`](https://docs.python.org/3/library/stdtypes.html#dict).
`TypedDict` declares a dictionary type that expects all of its instances to have a certain set of keys, where each key is associated with a value of a consistent type. This expectation is not checked at runtime but is only enforced by type checkers. Usage...
Great, thank you!:)
Whats the difference between var: Tuple and var: tuple with a lowercase t?
Tuple is deprecated
Before 3.9, tuple wasn't generic
The patent must have expired
That took me a second.
Not the first line, but I am happy to take some tuples in me
Is it possible to add a more strict type restriction on a single method of a class? For example:
class Foo(Generic[T, U]):
def foo(self) -> T:
...
def bar(self) -> U: # restrict this method
...
The Foo.bar method only run when Foo's T is another specific type or generic type. That is, for example, when Foo is Foo[int, U] then we can use bar, and when Foo's T is not int, then bar is not available.
I dont need runtime check, but static check for maybe IDE's to use.
maybe try def bar(self: Foo[int, U]) -> U: ...
i'm working with a library that has a bunch of cdef classes with names like _Foo. how should i write type stubs for this? should i use the exact names from the pyx file, or should i do something like class Foo(Protocol)? or should i just write class Foo:?
(specifically the library is lupa if anyone is interested or knows of existing type stubs)
no protocol then?
Unless you want to use it to represent several unrelated types
makes sense
I get a pylance error when defining the following paramter:
format: Type[Record | dict] = dict
Expected type arguments for generic class "dict"
Is there any way to resolve these issues without disabling type checking?
the format parameter expects a literal type such as dict which shouldn't require type arguments right?
Type[Record] | Type[dict]?
It still expects type arguments for generic class "dict"
Perhaps this is an issue with pylance's comprehension because Type[dict] should not result in an error
yeah with pyright this is working https://pyright-playground.decorator-factory.su/?gzip=H4sIAMjD_WQC_8vMLcgvKlEoqSzIzEtXSCxWKOHiqrBSKNELqSxIjU7JTC6JBQAhesLHIwAAAA%3D%3D
damn, thanks
No it's correct, you need type[dict[Any, Any]] if you want any dictionary
though you can ignore this error through the config
but I don't want a dictionary, I want a class (the in-build dict class in this case)
I shouldn't need to declare the type of the dict because I'm not asking for an instance of a dict right?
You could have something like ```py
class Foo(dict[str, int]):
...
in general, if you have a generic type, you must fill in the parameters
Why do you need to accept a class as a parameter though?
99% of the time what you really want to accept is a function
I have a method that returns a list of objects, the type of object depends on the format parameter. It isn't very pythonic but heres what it looks like:
def select(self, columns: tuple[str, ...] = (), /, *, format: Type[dict | Record] = dict, where: Callable[[Record], bool] = lambda record: True) -> None:
# other stuff here ("rows" is currently a list of dictionaries)
return rows if format == dict else [format(row) for row in rows]
But the format parameter is used to specify the return type of the objects
I would call it like
select(format=dict, ...)
select(format=Record, ...)
as I said, you should accept a function instead of a type
format: Callable[<YourParams>, dict] = lambda x: x
(or whatever default is appropriate)
or don't include this argument at all, keep the function interface more simple. The caller can call the function on their own
If it's some kind of database library, the factory is usually specified on the connection/connection pool
or just make Record good enough so that people don't have to fiddle with record factories like in sqlite3
This shouldn't be allowed right ? Type checkers don't complain tho.
class A:
def __lt__(self, other: Self) -> bool:
...
class B(A):
# the new __lt__ is incompatible with the old one
def __lt__(self, other: Self) -> bool:
...
I wonder what is the best way to do this.
Why is it incompatible?
ah hmmmmmm maybe
def foo(a: A) -> bool:
return a < A() # ok
foo(B())
B() < A() # error
Yep
Using Self in parameter types is useless anyways. You can't guarantee that self and the parameter are of the same type. So might as well do (self, other: A)
thats just not true. you can't guarantee it, but it's how you indicate to a consumer and their tooling (static analysis) the intended use.
The way to reconcile this fully is overloads, you're right that this Shouldnt be allowed, the problem is that it has been for so long now....
(edited, wrong link on hand, may not find the right related issue right now)
Actually, there's an interpretation where it's fine as well, but it requires:
def __lt__(self: Self, other: Self) -> bool:
...
(pyright handles missing self annotation for self impliticly, dont remember if mypy does as well, if it does, your original form is fine (unless you're using an even less used option that also does not))
Self didn't always exist.
thanks to typing_extensions, it now always exists
typing_extensions, time travelling since 2021
But they can also assume that self and other are of the same type and then some unintended behaviour go unnoticed because type checkers don't report any errors.
There is no difference between self and self: Self using pyright.
The main motivation to why Self was added in the first place is to use it in the return type. The same problem exists with TypeVar I think the method should behave like the function and allow all subclasses of A otherwise it doesn't follow the Liskov Substitution Principle.
T=TypeVar("T", bound="A")
class A:
def foo(self: T, _: T): ...
def foo(a: T, _: T): ...
from PEP 673:
class Shape:
def set_scale(self, scale: float) -> Shape:
self.scale = scale
return self
Shape().set_scale(0.5) # => Shape``` However, when we call `set_scale` on a subclass of `Shape`, the type checker still infers the return type to be `Shape`.
...
We propose a more intuitive and succinct way of expressing the above intention. We introduce a special form `Self` that stands for a type variable bound to the encapsulating class.
perhaps Self isnt the correct thing to be using there in the first place? If you typehint it as def __lt__(self, other: "A") -> bool: ..., the contravariance of other means subclasses have to support A if they want to comply with LSP
Yes I know that I ended up using Generics. Just sharing the fact that type checkers allow it although they shouldn't.
ah right, im not sure how you used generics but as for the "best way", typehinting it with the least specialized class makes the most sense
How could I make typing in this style work?
from typing import Any, Callable, ParamSpec, TypeVar
T = TypeVar('T')
P = ParamSpec('P')
def test_function(f: Callable[P, T]) -> Callable[P, T]:
def test(*args: P.args, **kwargs: P.kwargs) -> T:
return f(*args, **kwargs)
return test
class Sla:
a: int
b: int
response = test_function(Sla)
#response(
# a=1,
# b=2
# )
I don't want to put the parameters in init
is there any way?
use a dataclass or namedtuple
I didn't want to use dataclass.. and named tuple is something very static
I wanted to do something dynamic
something similar to dataclass
you could look into @dataclass_transform if you're trying to implement an alternative dataclass
and want it to be typed
but so far I haven't found anything that takes the class annotations and turns them into a type...
I've only recently started using mypy --strict and came across am [arg-type] warning when using list unpacking on a function call that I'm not sure how to fix (python 3.10).
def validate(cmin: int, cmax: int, char: str, word: str) -> bool:
charcount = word.count(char)
if cmin <= charcount <= cmax:
return True
return False
testlist = [1, 3, "a", "wasd"]
validate(*testlist)
Mypy error: Argument 1 to "validate" has incompatible type "*list[object]"; expected "str" [arg-type]
And if I add a type hint to testlist such that testlist: list[int | str] I still get Argument 1 to "validate" has incompatible type "*list[int | str]"; expected "str" [arg-type]
I was looking at Unpacking from typing_extensions but that seems to be for *args and **kwards at function definition.
what's weirder is that arg1 should be int, not str
Sla is not callable 
You have to add __init__ or use a namedtuple/dataclass
does anyone know how I get this to work? https://mypy-play.net/?mypy=latest&python=3.12&gist=7a0b28937ca2f2137a55f29610da1d16&flags=strict
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
I'm looking to make a function with multiple @overload definitions also accept its main definition
I think
I would guess you need to move the union outside the callable in l68
that doesn't work either https://mypy-play.net/?mypy=latest&python=3.12&gist=e5c8615b1e1b1b6c9b80e795fa6ab018&flags=strict
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
that was not what i meant. This now gives some different errors that let me think that there are some other issues in play.
https://mypy-play.net/?mypy=latest&python=3.12&gist=1e4b8ed59682af6b9da33f9e9bafeeac&flags=strict
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
I tried this: https://mypy-play.net/?mypy=latest&python=3.12&gist=893fd12235573446da081e5fb3b018b6&flags=strict
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
@dry fossil here's the PR https://github.com/twisted/twisted/pull/11986/files#r1323100535
I also seem to be having issues with overloads, but maybe that's my lack of understanding. https://mypy-play.net/?mypy=latest&python=3.10&gist=dae368817733e41934d94913dc7f3004
main.py:7: error: Overloaded function signatures 1 and 2 overlap with incompatible return types [misc]
I can fix it by removing the default on the Literal[False] but then everywhere I've called the function, I have to set folder=False because it otherwise defaults to True, even though the actual function has a default of False. Any ideas where I'm going wrong?
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
Okay, just did the reverse. Removed the Literal[True] default and kept the Literal[False] default and it works. Is this what is recommended or have I misunderstood something with overloads?
I don't know if it's a mobile bug, but when I open your link it goes to some default code
from typing import Literal, overload
class DataFrame:
...
@overload
def extract_s3(bucket: str, key: str, folder: Literal[False] = False) -> DataFrame | None:
...
@overload
def extract_s3(bucket: str, key: str, folder: Literal[True] = True) -> tuple[list[DataFrame], list[str]]:
...
def extract_s3(
bucket: str, key: str, folder: bool = False
) -> DataFrame | None | tuple[list[DataFrame], list[str]]: ...
Works on pc. Here is the code
you could remove the = True from the second overload
though I think type checkers will interpret it correctly anyway
Yeah, that worked all fine. I will assume that is how overloads are meant to work then
How do I typehint a List with 2 different arguments, like so:
from typing import List, Dict
List[str, Dict[str, str]] # Isnt valid
lists dont have a concept of having length in the type system
if you're expecting specific types at specific indices, there's not a way, but Union of types works™️
so it depends on what youre trying to do either its a union or you should be using a tuple
Or use a tuple
which of course doesn't mesh well if the type is actually a list
Or use a namedtuple/dataclass/TypedDict so that it's not a mystery to the reader what the str and the dict[str, str] are
If the dict[{"foo": int, "bar": str}] syntax gets accepted it's going to be great
(reminder to people that sometimes we have to deal with types we can't change and aren't ours, telling people "just use a tuple" needs investigation first to see if that's viable)
that is a good point
Does it have to be a list and not e.g. a tuple?
Yeah, cause I have to index it. Any mypy yells at me if I do it that way
You can index into a tuple
Can you show more code maybe? And the way in which mypy yells
there's an open proposal for a more flexible, but still statically typed dictionary, I wonder if there should be a mirror to that for this soemwhat common case with list, rather than tuple just being special cased as non-homogenous sometimes.
Say I have a Point class with x and y attributes.
I have function f elsewhere which receives a Sequence of point objects.
I want f to be agnostic of the Point type, but still hint that sequence elements are expected to have x and y attributes.
Can this be done with typehinting?
error: Value of type "tuple[str, dict[str, str]] | None" is not indexable
That's because it can be None, it's a different issue
from collections.abc import Sequence
from typing import Protocol
class HasXY(Protocol):
@property
def x(self) -> int:
...
@property
def y(self) -> int:
...
def f(things: Sequence[HasXY]):
...
Ohh right, protocols. That's f-ing beautiful, thank you.
Just be careful with the x-y stuff
if something has a z the function will probably treat it in unintended ways
Oh but f only operates on thing.x and thing.y, it ignores any other attributes
I know
I meant generally about structural typing
You might accept something you didn't anticipate, because it accidentally matches the interface
like my favourite example which integrates smoothly into yours: ```py
class Vec2D(Protocol):
@property
def x(self) -> float:
...
@property
def y(self) -> float:
...
def length(vec: Vec2D) -> float:
return (vec.x2 + vec.y2) ** 0.5
@dataclass(frozen=True)
class Vec3D:
x: float
y: float
z: float
l = length(Vec3D(3, 4, 12)) # hmm...
Actually, could you elaborate on what kind of objects do you want to abstract over? Do you have several types of Points?
Oh, I see what you mean. In your example, I'd encode the restriction into the name length_vec2d or something
Nah, I have just the one type
This is it in my actual code:
class TextAndMetadata(Protocol):
@property
def text(self) -> str: ...
@property
def metadata(self) -> dict: ...
and then
def main(sourced_texts: Sequence[TextAndMetadata]) -> None:
...
docs = tuple(
Document(page_content=src_txt.text, metadata=src_txt.metadata)
for src_txt in sourced_texts
)
oh, that was hypothetical naming
Yeah, sorry, I wanted to simplify to focus on my issue 😅
That's perfectly fine
Updated my example; so that's the only use I make of that sequence of objects
I think it should be fine
Maybe it wouldn't if the caller sends an object with text, metadata and other attributes he expects Documents to be created with, but hey, that's what I support
An alternative design might be: accept a sequence of a concrete struct like ```py
@dataclass(frozen=True)
class Source:
text: str
metadata: dict[str, Any]
If it's concrete, the caller to main has to be aware of Source so they can instantiate it and pass it
A tuple...wouldn't enforce attributes. A namedtuple would, but yeah more code on the caller site
the caller to main has to be aware of Source so they can instantiate it and pass it
Well, the caller has to be aware of the interfacemainsupports anyway, otherwise it doesn't know how to call it
True
Oh I'm an idiot. TextAndMetadata could just be a TypedDict and I can play with total=True and Required
I don't really care for them being attributes, I just want a mapping with exact names
Simple as this
class TextAndMetadata(TypedDict, total=True):
text: str
metadata: dict
def main(sourced_texts: Sequence[TextAndMetadata]) -> None:
...
docs = tuple(
Document(page_content=src_txt["text"], metadata=src_txt["metadata"])
for src_txt in sourced_texts
)
main([{"text": ..., "metadata": ...} for x in y])
whereas before I was requiring the call to have their own class or namedtuple with text and metadata attributes
Anyhow, thanks!
How can I type hint "if return_count is given as False, function will return a Tuple" with overloads?
return_count: Optional[bool] = False,
) -> Union[List[Mapping[str, Any]], None, Tuple[List[Mapping[str, Any]], int]]:
``` this parameter in original
return_count: Optional[bool] = True
) -> Tuple[List[Mapping[str, Any]], int]:
``` and this parameter in overload?
typehint return_count as Literal[False]
should'nt matter but you can pass the default value
thanks a lot
This is a very common occurrence, not sure why
I would recommend splitting such function into two functions
Or replacing it with some other construct
The caller will have to provide a concrete flag anyway, so might as well call a different function. Makes the interface a lot simpler
Agree. Any time you write a new interface using @overload you should ask yourself whether you should be using multiple different functions instead
What do you all think about inventing something like typing.Logical? It would be an alias to object.
Why: some arguments are passed only to be converted to boolean value (implicitly or explicitly). Annotating this args would be too restrictive, because passing 0/1/[]/"abc" would not be allowed. So the only option (while allowing arbitrary objects) is to annotate arg as object, which is also bad, because it is not clear from type annotation that the only important thing is a boolean value of this arg.
So i propose typing.Logical (i dont like this particular name), which is an alias to object, but says to the reader that it is only used in logical context.
(one problem i see: object is not inferred to Truthy/Falsy inside if checks, so it might require typechecker changes, if it is necessary)
To mny chrs
k
Why does mypy reject this code? I have strict=True
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
class X(Enum):
X1 = 1
X2 = 2
class A(ABC):
@abstractmethod
def a(self) -> X:
pass
@dataclass
class B:
a: A
def b(self) -> int:
match self.a.a():
case X.X1:
return -1
case X.X2:
return 4
test.py:18: error: Missing return statement [return]
(at def b)
oh nvm
all that complexity isn’t necessary
this is enough
from enum import Enum
class X(Enum):
X1 = 1
X2 = 2
def a() -> X:
raise
def b() -> int:
match a():
case X.X1:
return -1
case X.X2:
return 4
interesting, this one works:
def b() -> int:
r = a()
if r is X.X1:
return -1
elif r is X.X2:
return 4
thats because a() -> X doesnt necessarily return exactly X, it can as well return subclass of X, which can have some other keys, so your cases are not exhaustive
(actually, at runtime you cant subclass enum and add items, so typecheker is wrong there)
enums are implicitly final
why does my example work then?
yeah
can you subclass bool?
nop
ok it rejects this too
def mk_bool() -> bool:
raise
def do_something() -> int:
match mk_bool():
case True:
return -1
case False:
return 4
then i think it has problems with working with type inside of match parameter
x=...; match x: works fine, while match ...: doesnt work
mypy had problems related to this in the past, im pretty sure
ok yeah and it accepts this
def mk_bool() -> bool:
raise
def do_something() -> int:
the_bool = mk_bool()
match the_bool:
case True:
return -1
case False:
return 4
this works tho ```py
from typing import Literal
def foo(x: Literal[1, 2]) -> int:
match x:
case 1:
return -1
case 2:
return -4
it is rejected?
oh
didn't notice that, yeah ok that's the same example
so it doesnt work, iff you put nontrivial expression in match *:
only accepted if you extract the function call to an assignment
yep, mypy bug
def mk_bool() -> bool:
raise
def do_something() -> int:
x = mk_bool()
match x:
case True:
return -1
case False:
return 4
``` this doesn't produce error, while `match mk_bool()` does, like denball said
expression
or match target
or not
it is called "subject" (in ast, syntax and pep)
thing in case ...: is called "pattern" (and is called once "target" in tutorial pep)
I have reported it
Bug Report Exhaustiveness check on match fails if checked expression is function call To Reproduce https://mypy-play.net/?mypy=latest&python=3.11&gist=e12e8ee00b037639ff0887ef65d01d11 is re...
at least extracting to a variable is a good workaround
both of the python playground examples seem to be the same?
they aren’t for me
What I’d guess happens here is that mypy treats the match as a series of ifs and narrows the type to a literal union until it reaches never, and if you did this with ifs it would be wrong to narrow the return type to a literal union because the function could return different values/types each time
yup, this is basically correct
@wooden panther https://mypy-lang.org/ it's static type checking, at least at compile time. You still have to check __annotations__ for things like numpy array shape, and there isn't an agreed upon convention for it.
Mypy is an optional static type checker for Python.
m
why not use a statically typed language then
one of the main ideas of python is that its dynamic
ducktyping and all that
It's also nice to not have to do this. If I need some of the safety of actual type checking, I can get it. If I don't need or want it, I don't have to.
the same can be said about using different languages
need type checking -> strongly typed language
needn't type checking -> weakly typed language
Then the real answer would be that I just like writing code in python, despite how silly the type hinting can be.
to be honest, you probably should, but generally, the idea is that typing often makes it much easier to intuitively see a lot of things, without it, a function like add(x, y) can do a bunch of different things, it can be a function adding 2 custom types that you have, and if you pass it something else, it might even accidentally work, but do something completely unexpected. If you do add(x: int, y: int) -> int it's suddenly very clear what this fucntion is for.
Knowing the type can then bring a lot of advantages in terms of quickly detecting bugs, and having nice auto-completion in your editor. That's because if you have a function argument x the type of whcih you know to be str, your editor will auto-complete say x.removesuffix, since it's a method of the string type, if you don't know the type it can't do that for you. Additionally, if you did x.deletesuffix your editor can immediatley let you know that this fucntion doesn't exist for strings, since it knows x must be a string.
in my mind, add(x, y) would call x.__add__(y), if that's not implemented then y.__radd__(x), then raise an error
however yes, python is dynamic, and these types are purely hints, they're not anything more, and they don't affect performance (at least they generally shouldn't), like they would in compiled langs, where these types are necessary for the compiler to be able to make a bunch of optimizations, since it will know that x will be of certain type
thats fairly straight-forwards
sure, it's just a simple example there, the point was a bit more general about those functions, __add__ would generally be a better thing to implement add shouldn't ever need to be a function
a more helpful one could for example be: get_data(client: httpx.Client), from which you'll now know you're expected to pass in a httpx.Client instance, rather than say a requests.Client instance, or aiohttp.Client instance
if you passed anything else but httpx.Client, your editor would be able to warn you with the annotation there, but without it, you may run into a runtime error instead
a runtime error however only gets triggered if that part of the code does actually run, that means you'd need it to ideally be covered by unit-tests, and can generally just be much more annoying in comparison to seeing the issue right there from the editor
This is also the thing with specifying multiple types. By doing process_data(form: str | os.PathLike) I can let the person know that this function needs some sort of path, but if I just did os.PathLike then just a file string wouldn't be a valid input, and the editor would complain.
why not just read documentation and/or function source
Because that's extra work, when most editors will show you this info with a simple hover. For a lot of things, you don't need to know more than what types it wants, especially if it is well named.
it's much quicker to just read the signature and immediately see what you're expected to pass than having to go to the docs
editors can often show you both the signature and the docstring
but who has time to read the docstrings
consider a func like this
it's really neat to just immediately see what are all of the arguments this fucntion can take, whether they have default values, and what are the types I can pass there
without that, I would have to go to the docs which would really just tell me the same thing
but with extra work
for a lot of things, you don't need to know what types it expects either
perhaps you don't, perhaps you do, it's safer when you do
Sometimes you just forget
because editors will catch your mistakes, which I know I made a bunch of times
or to phrase it better, in most cases its obvious what type it expects
also, if you really don't care about the type, you can type-hint that, you can do x: object
what's obvious to you, might not be obvious to everyone, and even to you after a long enough break from that code base
I wonder if there is any difference between x: object and x: Any
sometimes you forget what a function does also yes?
there is, Any will let you do x.foo, while object will tell you foo doesn't exist
using the same argument, we should encode the functions docstring as part of the function name
because sometimes we forget what functions do
yes?
object is almost always better, since you generally can't trust that x will have a foo param
That's just a part of good naming
But then that gets back to having the editor catch this for you
unit tests
Unit tests can take forever to write, and I don't need a unit test if I am just prototyping.
function naming is quite important, they should be as intuitive about waht the func does as possible, then again, you do need to balance it with the length of the name, as you don't want 100 character named functions
as is the signature
it's simply a much better experience to intuitively know what a fucntion will probably do, and what kinds of values you're expected to pass and what you get back immediately, just by looking at it, than having to read the docs constantly, because the fucntion isn't named intuitively enough, and the arguments are really unclear
yes, but that can be said whether or not typehints are present
But type hints are basically 0 cost for extra information, so why not
yeah, type hints then extend that name, as a part of the function's "signature", which is basically what you saw in the picture I sent above though, along with argument names
without them, you'd get say this:
(don't really have any better exaples lying around here now)
so the point is, yeah, you can have that information in the docstirng, which this function does have
The other nice thing about type hints is that they are right there, and thus easier to keep up to date. Even if the docstring is in the function, if it is a beefy docstring that could put the type info quite far from where you defined the paramaters.
but isn't it much nicer to see it immediately?
- you get that immediate sanity check by the editor, that you passed the right things
this is incredibly underrated for beginners, but it really can be very helpful and catch a ton of mistakes
it adds a lot more noise to function signatures, and takes more time to write + space to store
and, although it's 100% negligible (im just talking about it because i can and no one can stop me, muahahahaha), there is a runtime slowdown -- it takes longer to parse bigger files, and the typehints have to get put into __annotations__ at runtime
yes, but to talk about runtime slowdowns in python is a little bit pointless here, python is already really quite slow, annotations usually aren't gonna slow it down that much, where it would actually be significant for you
there may be more information overall, but the amount of information over the length of the signature is reduced
information density ig is how one would phrase it
That also goes back to the why type hint if python is dynamically typed. Why care about a negligible slowdown, if python is already slow?
I actually don't think so, it's pretty easy to not read the types if you don't want to, you can easily just read the func name and the argument names it has, but yeah, that's absolutely just my point of view, and it's comming from someone really used to looking at typed functions
however especially with libraries, I find it super important that they do define those types there, if not everywhere, at least for the functions that are expected to be used by users (are part of the public API of that lib)
that's because you get to know the return types, and hence you get that autocompletion
This also applies to function arguments, if you don't type hint them your editor won't know what type, and thus what methods/properties are available for that argument, making things a lot harder.
if we look at this for example, it's fairly obvious for most of these that they take strings
things like allow_abbrev, exit_on_error, add_help are boolflags, etc.
good naming conveys a lot of information already
the typehinting communicates a lot less information than the names do, while still taking up roughly the same amount of characters
It depends here, sometimes it's hard to make a good name, and the types can help you a lot in those cases. Also, it's not just about the amount of information you convey in the signature, it's also about getting those autocompletions and bug checking, which imo can really speed the development up
better, it's checking for bugs statically:
from dataclasses import dataclass
@dataclass
class Monster:
hp: int
name: str
def damage_monster(monster: Monster, dmg: int) -> None:
monster.health -= dmg # type checker reports an error here, that "health" attribute doesn't exist for type Monster
so you can see how this can really nicely prevent typos
Also using enums and state machines to make invalid states unrepresentable.
and you see these errors immediately
without having to wait for unit-tests to run, and inspecting the traceback you find
enums can be used without typehinting
of course, you'll still need unit-tests, but those do a very different kind of testing
Everything can, but type hints make it more manageable, especially if you start nesting dataclasses/namedtuples/enums.
typos can be found by reading the written program
without this kind of static checking, you would need your unit-tests to catch even these NameErrors, which means you'll need really big coverage, and you might even write bad mocks that make the code actually pass during unit-tests, because the whole time you thought the variable name was different, and therefore everything will seem to work, but really it won't because health attribute doesn't actually exists, suddenly your code fails in production, as opposed to it failing in your editor
and if you want static analysis why not prove the correctness of functions
or use a statically typed language
ok, if you have a really big codebase, with 50 files, all having like 200 lines, you don't exactly want to be searching through that, even with editor tooling like peek definition, you just won't think to do that every time
or both, if you really truly need correctness
typos don't appear by themselves
you only need to read the parts that you're writing right then and there
once that part is free of typos, it can be included in the codebase
repeat ad infinitum
Depending on the context, switching and duplicating your work might be more effort than gradually typing your existing code base.
if you need typechecking, use a statically typed language
don't start in another language
that would lead to wasted time/effort as you'd have to implement stuff twice
🤔
Issue: I like writing code in python
then write code that doesnt need typechecking
Seems like a bit of a reductive argument, imo, though I don’t completely disagree. There are myriad of reasons coders start things in Python; doesn’t mean the tools shouldn’t exist to statically check their code later.
you don't need to mangle the function signatures and drastically increase (in some cases more than double) the size of the function definition to do that though
Even if I didn't want to, my auto complete doesn't work without it.
to be honest, you're sort of right, and I do use different langs (primarily Rust) for a bunch of stuff like this, it's just that python is a great language, and it's nice to write code in, it does many things right, it has a lot of great libraries. Same story goes for why TypeScript was made, which is essentialyl just typed JavaScript, JS being a dynamic lang
TypeScript doesn't actually even do any optimizations based on these types, it just transpiles to javascript
you can use things like proofs if you want to verify correctness
the sole reason why typescript exists is to give developers types, because developers just really like them
That would raise the barrier to entry for new coders, imo. I guess it depends on what demographic of programmers you’re arguing this should apply to: all Python users? 10 year-olds learning it for the first time? Guido?
i would say that it would be those people who actually need to verify correctness of a function
and the reason they like them is that they convey mroe information about functions, especially for code-bases that you're not used to/haven't worked with in a long time, and for libraries, where it prevents you having to study the docs as much, and doing that much familiarizing with the lib
which is, not many
… Wouldn’t using a statically typed language necessitate functions being that big by default? I’m not sure I understand this point.
and because of the massive benefit of statically finidng a lot of bugs, and giving you that auto-completion
yes
so you get the larger size of statically typed languages without any of the enforced typesafety or optimisations that generally come with those
you get the downsides, but not the upsides
i.e., not a beneficial trade-off
that's the thing, you're not realizing just how much of an upside there is to these
This is part of the reason I like type hints: they convey so much information about the API you’re interfacing with, so using them yourself when creating APIs for others is almost paying the favor forward.
Then install MyPy to get most of the upsides. Plus you already do get the upside of autocomplete.
and to be honest, I had the exact same opinion about types a few years back
no i dont
and iirc (its been a while since i last looked at mypy) it doesnt perform any optimisations
Then stop using notepad xD
i dont
I would honestly prefer using types even if I didn't have autocompletion, i.e. on an editor like notepad, purely for the amazing bug uncovering feature of type-checkers, as you can always just run the type-checker on your code manually, with a command
it's the first thing you can do before unit-tests
because it's simply faster
unit-tests have the same property: you can always run unit-tests with a command to amazingly uncover bugs
I think an argument can be made here about ease of use and learning vs. performance, but I don’t think I know enough to really make it. Imo, if you don’t need type annotations in your Python to keep track of everything, good for you — I certainly don’t have the brain to reliably do that.
you can, but writing unit-tests is much more time taking than writing out the types
and often running unit-tests can take a while too in comparison to running a type checker
(especially when it's integrated into your editor)
and just by writing out the types, you can a lot of what unit-tests were doing for you
i dont think it has to to with the brain -- either way, you're going to have to read something and keep track of stuff
i would just rather read the stuff that is written for and designed to be read by users of the functions/classes/libraries/etc., the documentation
I'm not saying they replace unit-tests, to be clear though
or, if non-existent, the function source
The documentation will usually include the types anyways, so why not make it explicitly part of the function signature?
why have both? if unittests can do exactly what the typechecking does, and more, then why use the typechecking
types are designed to be read by users, it's almost a part of the documentation
Because unit tests can take far more effort and space?
so you would rather not have unittests?
why do ```py
def foo(x, y):
"""Do X
:param int x: This argument is for this thing
:param int y: This argument is for that thing
when you can dopy
def foo(this_thing: int, that_thing: int):
...
yes, just doing foo(this_thing, that_thing) is an improvement, but it's not always one that's intuitive enough
unit-tests can't do exactly the same though, in many cases, unit-tests won't catch what types can
You’re skewing my point. The less need there is to write out all those tests while getting a similar level of safety or at warnings, the better.
sorry if the question skewed or misrepresented your point -- i was just trying to get clarification about what you're saying so i can understand better
consider this: ```py
class WeekDay(Enum):
MONDAY = 1
TUESDAY = 2
...
def handle_day(day: WeekDay) -> str:
if day is WeekDay.MONDAY:
return "monday"
elif day is WeekDay.TUESDAY:
return "tuesday"
with a type-checker, if you didn't add ifs for all of the possible cases (variants of the `WeekDay` enum), you'd get an error, that a fucntion can return a `None` value here (because all of the ifs were just skipped, and we just got to the end of the function), this is therefore a way to know that you matched this `day` exhaustively, and accounted for everything
a unit test might catch this, by having subtests/parametrization, but it also might not, what if WeekDay was an enum comming from an external library, and they just made a change where they added a new variant
with a type-checker, you'll immedately be warned everywhere because your fucntions might now be returning something else than expected
with unit-tests, you still only have parametrizations for the variants that the original lib contained before, and you'd need to know that the lib changed this enum and update them
a type-checker can give you a lot of safety in your code-base, for a pretty small cost of just writing the types in the function definitions
in that case you can read the documentation to see what was changed
Side note, there is also the from typing import assert_never which you can use in matches/elif chains to make sure you handled all the cases.
do you read the docs for every library you update every single time?
which, if you extensively use a library, you should probably do either way
i don't re-read the entire documentation, but i do read the changelogs
because that's what they're for
you don't need to use it "extensively", even just using a few featues can get you into this case
if you know what something did previously (which you do since you wrote code using it), then by reading the changelog you can determine what it does currently
some libs don't even maintian a changelog, and while with the bigger libs I'm interested in, yeah, I also do read it, I just can't be bothered to do so whenever I'm updating the dependenceis all of my projects, as it can be hundreds of changelogs to read through
whereas with a type checker, I just update the dependencies, run the type-checker, and if it doesn't show any problems, I know that it will very likely work
if it does, I immediately see what changed and where, and perhaps that means I should read the changelog, or perhaps it's a super obvious change, and I can just figoure it out from the type checker warning I got
let me rephrase then -- if you're maintaining a tool that depends on a library, and you choose to update to a newer version of that library, you should verify that no changes have been introduced to the library which make it incompatible with the tool
what if I maintain 15 libraries?
and I want to do dependency updates every week on all of those
It's basically an extension to how fun function signatures are. If the backend of a function changes but the signature stays the same, you can be pretty confident the code will still work. If a library had no type hinting and removed support for a type, how would the user know?
all of these libs have a bunch of dependencies, say 8
the point here is that it makes your life much easier, I'm not saying it's not possible to do without them
but wouldn't you agree that having a tool to just immediately check something like this can be really nice and convenient
by reading the changelog
or documentation in general
or source code
that is the order of precedence that i would generally use, at least
See the above, I don't want to do that if I have 12 dependencies, each of which have their own dependencies, which can update often, and I have a several thousand line long program. The types let me know before I get a runtime error.
I do read source of libs I'm interested in, I don't read git diffs on every change for libs that update, sometimesnot even changelogs, because I maintain a lot of things, and it would take me a LOT more time if I had to go through it all, when in vast majority of cases, those updates are just minor things that don't really affect me
And please do not say unit tests.
in the example above, as I said, unit-tests wouldn't cover that change, even if you had them
to me, writing out a few type definitions that will then save me hours of reading changelogs is absolutely worth it
- what each of the dependencies depends on is not your concern -- the dependencies of your dependencies are the concerns of the maintainer of whatever that tool is, and to you are implementation details
- what 15 large libraries are you maintaining that each have many dependencies and all require weekly dependency updates?
it doesn't take hours to read a changelog
it also doesn't take 5 seconds which it takes to run the type-checker
I don't, others do. I don't even want to have to do that with my one active few hundred line program.
I maintain some projects at work, which aren't necessarily libraries, they're more just binaries, or APIs, I also do have some open-source libraries and projects on my github that I maintian
it's not really relevant though, the point here wasn't necessarily to just demonstrate that it fits my very specific workflow here
the point was that it's just easier to use in general, and how it can make your life easier in may ways
and faster
All I would suggest you to do is to give it a shot, just spin up a mid sized project and try to use types there, see how it feels
If you aren't already, also try using an editor with proper auto-complete, since they help each other.
Python is a dynamic language, and sure, types might not be for everyone, but a lot of people who do try usually end up sticking with them, and liking them quite a lot. Me included. I originally really liked the idea of python's dynamicness, and I think it's what made learning to code easier for me here, and why I liked it so much. But after working with it for years, and refusing to use types, all it took was to try them out for a while, and I realized just how great they can actually be, and how much time they can save you.
Even though it might seem like extra work without that much benefit, that's likely because you haven't enabled the type checking behavior in your editor, so you aren't actually even getting those benefits. Without it enabled, all you're getting is the auto-complete. No bug finding behavior and type enforcement.
and really, to just talk about those benefits just won't do it justice here, try it out, see it yourself
i dislike autocomplete/autocorrect features
they tend to misinterpret what i want to type, especially when im not using english
I can understand for autocorrect, but that doesn't make much sense for autocomplete. It should only pull from what's in your namespace/valid params/args
though it has been a few years since ive last messed about with that stuff
nobody's forcing you to use those, I'm much more excited about the bug finding behavior of type-checkers then I am about the autocompletion, although in my opinion, while it's important to be able to code without completion, and to know the language well, autocomletions can greatly speed things up once you already know those things, there's not really a great reason to keep typing everything out manually, especially when it can bring in bugs because of typos
so it might have improved since then
One of the best parts about autocomplete is I can name my functions super literally, like make_surface_from_2d_array and autocomplete will make it actually managable.
I think for me the main benefit is understanding unfamiliar code. If you go into an untyped codebase and see lots of functions with no parameter or return types, it's hard to follow what they're doing. Types help quite a lot there
Sometimes that unfamiliar codebase is whatever abomination I wrote last night at 1 am
that tends to indicate poor naming choices
this is true
i think we've reached a point where we're just going to end up repeating ourselves if we try to talk about this further lol
i might give all the typehinty stuff another try, see if its improved in the last 5-6 years
thank you all for talking about this with me and explaining/answering my questions :}
👋 It definitely has, especially in the from typing import x department. Good luck
oh yeah, there were a ton of changes in that time, new PEPs are constantly appearing about typing, and I do believe you might just end up liking it!
How could you type this?
def check(b):
if b is None:
raise Exception('None')
return b
see here for some attempts: https://github.com/hauntsaninja/useful_types/pull/7
I think I got one variant working with mypy and another with pyright
thanks
Hey guys!
How do I type hint a not-so-specific dict?
Example:
{
'engine': positions_engine,
'cfg': {'margin': 1, 'round_decimals': 0}
}
do you know about TypedDict?
The engine key must always be present, the cfg key may or may not be present, but it's contents can be anything as long as it's also a dict
Can it handle optional keys?
!pep 655
(and you can use it before 3.11 too, with typing_extensions.NotRequired)
I swear I've used NotRequired since 3.8
What is the mechanism behind the fact that when you inherit from list[int], you only get list in your MRO?
__mro_entries__ attr, iirc
If you create class, python firstly replaces all bases with their corresponding mro_entries values, and only then does all the magic
Original bases are available in orig_bases attr
Im on mobile rn, so i omitted some dunders...
!d object.mro_entries
object.__mro_entries__(self, bases)```
If a base that appears in a class definition is not an instance of [`type`](https://docs.python.org/3/library/functions.html#type), then an `__mro_entries__()` method is searched on the base. If an `__mro_entries__()` method is found, the base is substituted with the result of a call to `__mro_entries__()` when creating the class. The method is called with the original bases tuple passed to the *bases* parameter, and must return a tuple of classes that will be used instead of the base. The returned tuple may be empty: in these cases, the original base is ignored.
thanks
well, define exist 🙂
objects don't know that they are a list[str]. just list
typevars exist. Generics don't.
unless you explicitly create a class that extends list[str]
and even then they only exist in __orig_bases__
mhm
even in java, you can break shit by casting to ((List)list).append(new Object())
because generics don't exist
for now :)
I said reification excluded
which basically means: "genrics don't exist, except when they do"
Doesn't rust do monomorphization to implement generics? I'm not too clear, but it sounds like generics existing.
then rust "reifies" the generics
which is a fancy way of saying it adds a hidden argument.
yeah rust does do monomorphisation but the generics disappear dont they?
that argument can be inferred depending on the language
define exist and disappear 🙂
well i was more under the impression ```rs
fn id<T>(x: T) -> T {
x
}
id(1) // makes fn id(x: i32) -> i32 { x } appear and the original disappear
I mean when you get to the bottom, assembly doesn't have types so at some point in the processes, if a language is ever compiled to/past asm it can't have generics.
Cogito, ergo sum
sive existo.
I'm getting "x could not be determined because it refers to itself" from pylance and not sure what it means
async def ahhhh(bot: commands.Bot):
async def looper():
while True:
if <some condition>:
wait.cancel()
return
t = asyncio.create_task(looper()) # Type of "looper" could not be determined because it refers to itself
wait = asyncio.create_task(bot.wait_for('message'))
await t
if wait.done():
...
How does this code trigger it?
Why does it go away if I comment out either wait.done() or wait.cancel() in looper, or swap wait_for to something else like asyncio.sleep?
how do I get pycharm to hint to me that this goes against my type hint?
from typing import Literal
def something(mylit: Literal['taco', 'burrito']):
print(mylit)
something('whatever') # this works, I see yellow highlight
class Something:
mylit: Literal['taco','burrito'] = None
def __init__(self):
pass
Something('hi there') # this works, I see yellow highlight
x = Something()
x.mylit = 'no highlight' # this is where I wanted to see the highlight but there's not one
is there a way to typehint mixing classmethods in such a way that they self-type to the class inheriting from them
i have a dump/load mixin that sets up the serializers/deserializers but defers details to the concrete classes,
if i call the mixin methods for dumping/loading on the concrete classes, i end up with a mixing lcass instances (minimal example will follow)
🤦 turns out i accidentally had a typevar replaced with the mixing class in a spot i missed
mylit: Literal['taco','burrito'] = None
this is an issue too. Do you ever want the value to be None? if so, union with None, or use Optional from typing
Something('hi there') is a TypeError: Something.__init__() takes 1 positional argument but 2 were given
from typing import Literal
MexicanFood = Literal["taco", "burrito"]
def f(mylit: MexicanFood):
print(mylit)
f("taco") # no type issues
f("whatever") # type issue
class C:
x: MexicanFood
def __init__(self, x: MexicanFood):
self.x = x
C("hi there") # type issue
x = C("burrito") # no type issues
x.x = "no highlight" # type issue
@viscid spire "Do you ever want the value to be None?" no I do not
What am I looking for in the code you made
correctly written class
your previous code didn't even work, because you didn't have any parameters in your init (besides self of course)
this clearly does not work
class Something:
mylit: Literal['taco','burrito'] = None
def __init__(self):
pass
Something("burrito")
let me add more of the code then
the point remains
maybe of the rest will be the issue
here is the full
same issue
from dataclasses import dataclass
from typing import Literal
def something(mylit: Literal['taco', 'burrito']):
print(mylit)
something('whatever') # this works, I see yellow highlight
@dataclass
class Something:
mylit: Literal['taco','burrito'] = None
Something('hi there') # this works, I see yellow highlight
x = Something()
x.mylit = 'no highlight' # this is where I wanted to see the highlight but there's not one
sorry about not giving you the full before, hate when people do that then I do it 🙂
using a dataclass is an important detail you shouldn't have omitted yeah 😄
I don't want a default value
then simply remove that assignment, then you don't need to worry about typehinting the None-ness
I mean, I don't want them to have to input, and I don't want a default value
I don't know if you should do that
if I remove it then I get other type warnings about how Something doesn't have mylit
The original issue is this
after creating an object, it should ideally have all the attributes needed defined
I have class like Package
sometimes a package will have an rpm_path for example
maybe that's a bad example
ok here's one based on what I'm actually doing
pyright recognizes the last line as an issue 🤷♂️
I am making a hierarchical representation of my it environment in python classes mostly for fun
I have let's say Company > Product > Component > Package and for some components I have Company > Product > Component > Service
if my component is like.. apache httpd, then it has a service httpd
but if my component is notepad or something, it doesn't even have a service
not sure if that's relevant but
anyway so instead of addressing like this
comp.packages['my_package']
since my names are unique, I want to be prettier
comp.package.whatever()
sounds like when you should use generics/typevars
It's just another class with a cross link
I don't really understand any of the stuff you're saying so it's definitely not any of that
I'll show you hang on
you don't know... subclass?
!e
class Parent: ...
class Child(Parent): ... # subclass of Parent
print(issubclass(Child, Parent))
@viscid spire :white_check_mark: Your 3.11 eval job has completed with return code 0.
True
I don't know subclass
here's a part of it with a few things stripped to be more concise
ok well you still have the issue with = None with an invalid typehint
well pycharm and python aren't caring.. is that contributing?
and here's how I'm talking to it
then your pycharm is broken
my_company = Company()
dev = Environment('development')
dev.company = my_company
stag = Environment('staging')
stag.company = my_company
prod = Environment('production')
prod.company = my_company
for env in [dev, stag, prod]:
env.app = Component('app', environment=env)
env.components.append(env.app)
env.app.service = Service('appservice123', component=env.app)
env.app.package = Package('app', component=env.app)
env.app.server = Server(component=env.app)
oh I have this in there probably related: from future import annotations
when I talk to it this way, I can very cutely talk to my stuff
for example I love it: dev.app.server.reboot()
that doesn't make a difference for typecheckers, only to stop NameErrors at runtime
so idk what's wrong with your pycharm, but you can't really try to fix your typing until you fix your pycharm
and unfortunately, idk about pycharm at all
well thank you anyway. I really can't stand writing 15 words just to allow None
python really needs to sort this part out, it's very obnoxious
T | None
I don't want to allow None as a value
I don't really understand what you mean by "15 words"
Write a Maybe class.
how about just pass in the values you need
Don't know the value when it's created
well, I don't know why you have all these circular references in your design
so when I'm at one thing I can find details about it's pretend parent
like an rpm doesn't have a version, it has the version of the product
from typing import Generic, TypeVar, overload
T = TypeVar("T")
R = TypeVar("R")
class Maybe(Generic[T]):
def __init__(self, value: T | None):
self._value = value
def map(self, func: Callable[[T], R]) -> Maybe[R]:
if self._value is None:
return self
return Maybe(func(self._value))
@overload
def get(self) -> T | None: ...
@overload
def get(self, default: T) -> T: ...
def get(self, default: T | None = None) -> T | None:
if self._value is None:
return default
return self._value
no more Nones
type defaults aren't until 3.13 iirc
They're still in typing_extensions
oh already? nice... how to use?
of course i could just make an overload
I have no idea what anything I'm looking at is or does so
I guess I'll just not get a warning about setting it to the wrong type
is this properly typed? ```py
T = t.TypeVar("T")
@dataclass(kw_only=True)
class Variable(t.Generic[T]):
name: str
default: T = MISSING
cast: type[T] = MISSING
@cached_property
def value(self) -> T:
_value = os.getenv(self.name, self.default)
if _value is MISSING:
raise MissingEnvironmentVariable(self.name)
if _value != self.default and self.cast is not MISSING:
try:
_value = self.cast(_value)
except Exception as e:
raise ConversionError(self.name, self.cast, e) from e
return t.cast(T, _value)
def __get__(self, instance: t.Any, owner: t.Any) -> T:
return self.value
class Environment:
TOKEN = Variable(name="TOKEN")
LOG_CHANNEL_ID = Variable(name="LOG_CHANNEL_ID", cast=int)
class _HelpersBase(Base, Generic[_T]):
_klass: type[_T] | None = None
@classmethod
def helpers(cls) -> _T:
if cls._klass is None:
msg = 'no'
raise ValueError(msg)
return cls._klass(cls)
Anyone have an idea why mypy thinks that return is Any and not an instance of _T?
I think that's a known mypy bug/missing feature: https://github.com/python/mypy/issues/14790
awesome thank you! I'll just add a cast and a todo
I think I found yet another mypy probably typeshed bug
functools.cache breaks function arguments checking
from functools import cache
@cache
def foo(a: int, b: int) -> tuple[int, int]:
return a, b
foo()
is accepted
Yeah it's a typeshed bug
That one's basically a meme at this point -- see my comments at #type-hinting message
_ModelActions = TypeVar('_ModelActions', bound=SQLModelActions[ModelWithActions])
Got what feels like another oddity with mypy, It doesn't like me making that where ModelWithActions is another TypeVar with an upper bound
Yeah that is not allowed. You should do SQLModelAction[SomeConcreteType]
gotcha, I worked around it by using Any, just feels like it's not quiet what I want.
I have a few different classes that are interacting in generic land:
disclaimer: names are a wip
ModelWithActions = TypeVar('ModelWithActions', bound='SQLModelWithActions[Any]')
class SQLModelActions(Generic[ModelWithActions])
_ModelActions = TypeVar('_ModelActions', bound=SQLModelActions[Any])
class SQLModelWithActions(SQLModel, Generic[_ModelActions]):
And I have to do that because SQLModelActions is a base class and needs to be aware of the model it's going to work with, and SQLModelWithActions has a classmethod that returns an instance of the subclass of SQLModelActions.
Oh and SQLModelActions has a helper that returns an instance of a third generic object _Objects[ModelWithActions]
I either an doing something wrong, or just hitting the limits of the typing system in 3.11, hard to know for sure
Another pain point, is that even though mypy is resolving the types correctly at this point, pycharm is failing to do so, so I get no autocompletion for Model.actions() which returns the instance of the subclass of SQLModelActions
not sure if all that makes sense @trim tangle 🙂
Ah, sounds like you want higher-kinded types, which do not exist in Python. It's a long-standing open question
https://github.com/python/typing/issues/548
Why can't you be generic over just the model type? Can you show more code maybe?
Any ideas? This is what I get from mypy
RESPONSES: Dict[str, JSON] = {"/test1": func.res_notation("en")}
# other file
JSON = Union[Dict[str, Any], List[Any], int, str, float, bool, Type[None]]
Dict entry 0 has incompatible type "str": "dict[str, Any] | list[Any] | int | str | float | type[None] | None"; expected "str": "dict[str, Any] | list[Any] | int | str | float | type[None]" [dict-item]
here's some poc-level usage of the api
class AuditLogEntryActions(SQLModelActions['AuditLogEntry']):
"""Actions for audit log entries."""
def get_count(self) -> int:
return self.objects.count()
def get_first(self) -> Optional['AuditLogEntry']:
return next((obj for obj in self.objects.all()), None)
class AuditLogEntry(SQLModelWithActions[AuditLogEntryActions], DatedTableMixin):
"""Log of an action completed while impersonating another user."""
# database fields here
with session_maker() as session:
AuditLogEntry.actions(session).get_count()
it actually works, and mypy can infer the types (even though I'm not 100% happy with how I got it to do so), biggest annoyance is that pycharm doesn't realize that the return value from actions(session) is an instance of AuditLogEntryActions
looks like your JSON type doesn't support None as a valid value. Try response: Dict[str, JSON | None]
I've switched to using the cachetools package because of that issue.
yes, I just had to set it to None from Type[None]
JSON = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
should use "JSON" in place of Any (unless you wanna target 3.12 and its beautiful features, in which case blah blah blah)
You should be using sequence and mapping here not list and dict
Why?
I'm not 100% certain, but at least for Sequence I think it's because of the guarantees it offers. A Sequence object will always have those few methods, so when traversing through the structure, even though you lose some specific helper methods per container you know that the code you write will work with a lot more containers.
but... that's useless when you know for sure that is will only be a list, or only be a dict
because it's JSON
it's not gonna be some other sequence or mapping
It could also be meant just for the instantiation, where most Sequence objects can be cast to a list, so there is less explicit casting the user has to do. Beyond that, I also don't know.
type JSON = dict[str, JSON] | list[JSON] | str | int | float | bool | None
def do_stuff(thing: JSON): ...
def call_api() -> list[int]: ...
do_stuff(call_api()) # will not type check
change it to mapping and sequence it will work cause list[int] can be assigned to Sequence[JSON]
hmm
Hey all, I want to type hint an object that can either be accessed through dot notation or can be indexed
I was able to solve indexing by making a protocol with __getitem__
but how would I type hint that the attributes could be accessed through dot?
__getattr__
does that also work for indexable or I need two different protocols?
I fixed the error from showing by putting typing.Self but that seems inappropriate
Type hints are nearly useless when you go that dynamic
Just implement getattr and getitem, and you'll be all set.
I don't think there is, and if there were, it would be something that you do when you're declaring your TypeVar. What you did there is assign a default value to the cast varaible, not to the TypeVar itself
ah okay
think can do it with typing_extensions already, but it is a 3.13 feature if I understand what you want
Ok, I've found a weird issue with mypy that I can't quite seem to resolve... Given this code
Example = type("Example", (), {})
match Example():
case Example():
print("It's an example")
case _:
print("It's not an example")
On the first case mypy gives this error
error: Expected type in class pattern; found "builtins.type"
Is this a bug??? What am I missing?
Typing is an addiction.
After u start working with typing
u can hardly see garbage untyped code 🤔 Tbh, all untyped code looks garbage.
If it is unit tested at least, is somewhat okeyish though
Recently audited code of a project
it was not typed
it was not unit tested
it was garbage of long mutations of data through dictionaries/hashmaps
with randomly accessed dynamic keys and values through it
i can hardly imagine now... how can people enjoy working with not typed code, it is... tech debt gathering so much quickly 🤔
that code in particular was nightmare though, it was multiple times except Exception as e, obfusacted a hundred times in addition
So many runtime surprises, i could bet, debugging it was pain like nowhere else
In general mypy doesn't have (and probably never will have) a particularly good understanding of classes that are dynamically constructed using type(). It needs to emit an error here because it won't be able to do type narrowing or exhaustiveness checking in your match statement in the way you might expect. I'll admit, though, that the error message is terrible in this specific case 😄
good typing can simplify code many many many times pretty much.
its potential impact i think should not be understimated
it does more than just types, it structures the code in the way u will not have surprises in terms of your data
and communicating this data across services included
It makes a difference between keeping code hardly possible to run without breaking smth, and keeping it predictably working across big amount of code calls
To be clear, the reason why it's emitting an error here is that mypy's internal representation of a class is a mypy.nodes.TypeInfo instance. But it doesn't construct a TypeInfo internally to represent classes that are dynamically created using type(); it just sees them as "instances of type" without understanding that they're classes. So it's not emitting an error on your code for a particularly principled reason at the moment; it's emitting an error because it just doesn't understand what's going on in your code at all.
Weird, kinda figured it was something odd like that. Just seems odd I can't even cast or use a function with a return hint to tell mypy what's up.
Thanks
i feel this 😉
Is this intended behaviour in pylance??
def f(x: object):
match x:
case {"foo": "bar"}:
# ^^^^^^^^^^^^
# Pattern will never be matched for subject type "object" (reportUnnecessaryComparison)
...
this sounds really bad
and is factually incorrect
that appears to be correct behaviour
think you mean to use typing.Any or collections.abc.Mapping, or something?
object definitely isn't a Mapping, so it is clear it won't match that case
object can be literally any object, including a dictionary
that is what typing.Any is
By this logic, you could say this py def f(x: int): match x: case 42: ... is also invalid, you should've used x: Literal[42]
no, because 42 is an instance of int
No, literally any object fits object. I can say foo: object = 42 or foo: object = {"a": 1}.
!e
print(isinstance(42, object))
print(isinstance({"a": 1}, object))
@trim tangle :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | True
002 | True
that is because 42 is an instance of a subclass of object
but you could not use int methods on it then
The error is saying that the pattern will never be matched. This is easily disproven with ```py
f({"foo": "bar"})
it won't be matched by the typehint
hm?
This call passes type checking, I can pass a dictionary where an object is expected. I can pass any object
hrm
In my case I'm parsing some arbitrary JSON. I guess I could annotate with an actual JSON type, but it's a pain and doesn't add any value
That does seem buggy yeah
My worry is because of this
def f(x: object):
x.bit_count()
# ~~~~~~~~~~~~~
# Cannot access member "bit_count" for type "object"
# Member "bit_count" is unknown Pylance(reportGeneralTypeIssues)
That's different
Yeah, I'm not performing any operation that some objects don't support
There's nothing wrong with narrowing with an isinstance check before accessing
yeah
Well, first it checks if it's a dict
!e
def f(x):
match x:
case {"foo": "bar"}:
return 42
print(f(69))
@trim tangle :white_check_mark: Your 3.11 eval job has completed with return code 0.
None
Also this is fine with Pylance: ```py
def f(x: int):
match x:
case True:
...
{} is not exclusively for dicts in pattern matching, it is for Mappings (iiuc)
same like [] is for Sequence, as well as ()
Yeah maybe
Mapping is still a subclass of object
but still, it will not crash on an integer
I wonder where this would fall in https://github.com/microsoft/pylance-release/issues/497 , it is typed which might lead to being known for certain, but at the same time type hinting object doesn't give useful info.
I don't follow
How's this relevant
not 100%, just curious about where these might go from reportGeneralTypeIssues
is there a way importing type for usage from library like types-psycopg2 ?
trying to grab from types-psycopg2 type for _Cursor (that is returned by cursor() function)
!d typing.TYPE_CHECKING
typing.TYPE_CHECKING```
A special constant that is assumed to be `True` by 3rd party static type checkers. It is `False` at runtime.
Usage:
```py
if TYPE_CHECKING:
import expensive_mod
def fun(arg: 'expensive_mod.SomeType') -> None:
local_var: expensive_mod.AnotherType = other_fun()
``` The first type annotation must be enclosed in quotes, making it a “forward reference”, to hide the `expensive_mod` reference from the interpreter runtime. Type annotations for local variables are not evaluated, so the second annotation does not need to be enclosed in quotes.
unless youre using it at runtime
yeah, fixed code. type was hidden in extra routes from normal usage
if TYPE_CHECKING:
from psycopg2._psycopg import _Cursor
extracted 🙂
i've been confused about why pyright is not liking my return value, and i seems it comes down to the fact that its bugging out on this instance variable. the type of it is supposed to be type[ItemT] but even if i explicitly give it that annotation it is still Unknown
could you send surrounding code?
class Base(Generic[ItemT]):
EXPIRES_ATTRIBUTE = "__expires"
PUT_LIMIT = 25
def __init__(
self: Base,
name: str,
item_type: type[ItemT] = BaseItem,
project_key: str | None = None,
project_id: str | None = None,
host: str | None = None,
) -> None:
if not name:
msg = f"invalid name '{name}'"
raise ValueError(msg)
self.name = name
self.item_type: type[ItemT] = item_type
self.project_key = project_key or get_project_key()
self.project_id = project_id or get_project_id(self.project_key)
self.host = host or os.getenv("DETA_BASE_HOST") or "database.deta.sh"
self.util = Updates()
self._client = Client(
api_key=self.project_key,
base_url=f"https://{self.host}/v1/{self.project_id}/{self.name}",
timeout=300,
)
@overload
def get(self: Base, key: str) -> ItemT:
...
@overload
def get(self: Base, key: str, default: DefaultItemT) -> DefaultItemT:
...
def get(self: Base, key: str, default: DefaultItemT = ...) -> ItemT | DefaultItemT:
if not key:
msg = f"invalid key '{key}'"
raise ValueError(msg)
key = quote(key, safe="")
response = self._client.get(f"/items/{key}")
if response.status_code == codes.NOT_FOUND and default is not ...:
return default
self.item_type: type[ItemT]
if issubclass(self.item_type, BaseModel):
x = self.item_type.model_validate_json(response.raise_for_status().json())
return x
return response.raise_for_status().json()
the item_type variable is of interest here
if you hover over it, pyright should recognize it as being of type type[ItemT], but that information is lost in the get() method
thus, it incorrectly identifies x as BaseModel , instead of BaseModel*
oh btw, suggest to skip the typehint on self. It is known.
here's a simplified version of what I understand to be your issue (?)
class C[T]:
def __init__(self, x: type[T]):
self.x = x
def get(self) -> T:
return self.x()
This works
Did I simplify the problem correctly?
or just omit them
ruff complains
then use typing.Self instead of Base[ItemT]
disable the ruff rule
(of course without the typing. part, bcuz blegh)
is there a way to use Protocol or another typing construct to describe "any object, whose every attribute is of type X, or a mapping whose values are of type X"
working on it
i tried using __dict__: [str, X] in the Protocol, which seemed to do nothing
the problem is with union types I'm feeling
let me simplify it to "any object, whose every attribute is of type X"
thing is that X could be a union
X in this case would be Union[Mapping[str, "DataType"], Sequence["DataType"], str, int, float, bool]
class MyClass:
x: int
y: str
every attr is of type str | int
is this stuff coming from JSON?
it will be dumped to json
perhaps this is another case where showing code could help 
same code as before, i just want to restrict ItemT
so that the user of the library can't just put any type, but only one that matches the protocol
well you can restrict it to stuff which would be JSON-able at minimum
from collections.abc import Mapping, Sequence
type JSON = (
Mapping[str, JSON]
| Sequence[JSON]
| str
| int
| float
| bool
| None
)
def f[T: JSON](x: T) -> T:
return x
(3.12, sorry)
yes, that works for mapping, but how would i do the same for a class?
basically, type hint that every attribute of the class must be of type X
see, I still don't understand the why
which attributes do you care about?
are you dynamically retrieving attributes with the getattr function?
the user defines a structure, like
@dataclass
class UsersClass:
x: int
y: str
z: Foo # undesirable
and passes that over to be used in the library
it could be a dataclass, pydantic model, regular class, typeddict, dict, etc
I've got no ideas
i see, for the time being i will use the existing mapping type hint and basemodel
anything that isnt desired would raise a typeerror during serialization
thanks for the help
Hmm, I tried doing:
class OopsAllInts(Protocol):
def __getattribute__(self, k, /) -> int:
...
and this typechecks... always, even when it shouldn't. So, that doesn't help.
Does anyone know if exhaustive pattern matching (with assert_never) works with Generic Subexpressions?
from typing import assert_never
class A: pass
class B: pass
def foo() -> A|B:
return A()
value = foo()
match value:
case A():
print("A")
case B():
print("B")
case e:
assert_never(value)
> Success: no issues found in 1 source file
This code works fine with no issues
But if I try to wrap the expression in a class, it doesn't properly get rid of the matched patterns from the union
from typing import Generic, TypeVar, assert_never
from dataclasses import dataclass
class A: pass
class B: pass
T = TypeVar("T")
@dataclass
class Wrapped(Generic[T]):
__match_args__ = ("error",)
error: T
def foo() -> Wrapped[A|B]:
return Wrapped(B())
value = foo()
match value:
case Wrapped(A()):
print("A")
case Wrapped(B()):
print("B")
case e:
assert_never(value)
Argument 1 to "assert_never" has incompatible type "Wrapped[A | B]"; expected "NoReturn" [arg-type]
I don't know if this just isn't supported or I'm doing it wrong
I don't think this is possible, yeah. The syntax for that'd need to be something like case Wrapped[A](), which isn't a thing. You can instead do match value.error with cases A,B.
The main reason was I was trying to do pattern match with a result object like this
match get_user():
case Ok(user):
...
case Err(A()):
...
case Err(B()):
...
And I keep getting return type errors because it's not exhaustive
Maybe you could do ```py
match get_user():
case Ok(user):
...
case Err(e):
match e:
case A(): ...
case B(): ...
though it is more verbose
Yeah that's the problem
what's the difference between Type[Any] and Any? mypy is giving me an error if I put a custom type for Type[Any], but with Any it's not complaining
Is there some way to express that I’m using a list immutably? I want to have covariant types but use a list. collections.abc.Sequence doesn’t support appending and concatenating.
Try covariant=True on the typevar constructor.
But... appending to a list generally isn't type-safe.
sorry, did I say append
I mean just concatenating
I do not append
I need concatenation of two immutable covariant sequences
I could just do [*a, *b] but I’m told that’s slow
i don't think that's slow actually, but yeah, I'm unsure how to do this without using the not-yet-implemented type intersection
you could make your own ListLike protocol which is like a Sequence plus can be concatenated, but you'd need to write all the sequence boilerplate
(because you can't just inherit from Sequence because protocols can't inherit from non-protocols)
OK
Thanks
I also just noticed I ran into a pyright bug
it seems that if a: T, pyright will refuse to treat literal [a] as being list[U] if T is a subtype of U, even though that’d be correct (I think)
from a bit of testing, [*a, *b] is only like 10% slower than a+b
No, because lists aren't covariant.
but we’re constructing a new list
we’re not able to mutate that list based on any prior type
it’s a literal
Type[T] would be the class or type itself
x: type[int] = bool
Yeah, didn't that didn't narrow it
Ah, I think I know what you mean. I dimly remember some list-covariance-on-literals-related thing where mypy had a different behaviour compared to pyright, yeah.
also it only does this in a very convoluted situation
IIRC in only one of them you could do f([5]) if f wanted a list[int|str], something like that.
i'm confused, how is x type int and then you're setting is to be equal to bool?
bool is an int subclass, so bool fits the typehint type[int].
you could make a protocol that's like Sequence but also has __add__
hmm
pyright accepts this:
class A: pass
class B(A): pass
x: B
x = B()
y: list[A]
y = [x]
but it doesn’t accept mine
with an explicit typehint it should really be fine always
aha
I have found the problem
I have a minimal reproducible example
The following code is rejected:
class A: pass
class B(A): pass
def g() -> list[B]:
return [B()]
a: list[list[A]] = []
a += [[a] for a in g()]
However, the following code, which does the same thing, is accepted:
class A: pass
class B(A): pass
def g() -> list[B]:
return [B()]
def h(a: list[list[A]], b: list[list[A]]) -> None:
a += b
a: list[list[A]] = []
h(a, [[a] for a in g()])
those two examples should both be accepted, right?
the first is accepted if you annotate the type of the comprehension'
by assigning it to a variable or by passing to a function like in the second
ok
I am trying to type hint an AST for a toy language, it's encoded as JSON. I want to type the corresponding Python dicts
import typing as t
SyntaxTerm: t.TypeAlias = t.Union[
'LiteralSyntax',
'LiteralBooleanSyntax',
'TupleSyntax',
'IdentifierSyntax',
'LetExpressionSyntax',
'PrintSyntax',
'FirstSyntax',
'SecondSyntax',
'BinaryExpressionSyntax',
'CallExpressionSyntax',
'ConditionalExpressionSyntax',
'FunctionExpressionSyntax'
]
class SyntaxLocation(t.TypedDict):
start: int
end: int
filename: str
class LiteralSyntax(t.TypedDict):
kind: t.Literal['Int', 'Str']
value: t.Union[int, str]
location: SyntaxLocation
class LiteralBooleanSyntax(t.TypedDict):
kind: t.Literal['Bool']
value: t.Literal['true', 'false']
location: SyntaxLocation
class TupleSyntax(t.TypedDict):
kind: t.Literal['Tuple']
first: SyntaxTerm
second: SyntaxTerm
location: SyntaxLocation
class IdentifierSyntax(t.TypedDict):
kind: t.Literal['Var']
text: str
location: SyntaxLocation
class ParameterSyntax(t.TypedDict):
text: str
location: SyntaxLocation
class LetExpressionSyntax(t.TypedDict):
kind: t.Literal['Let']
name: ParameterSyntax
value: SyntaxTerm
next: t.Optional[SyntaxTerm]
location: SyntaxLocation
class PrintSyntax(t.TypedDict):
kind: t.Literal['Print']
value: SyntaxTerm
location: SyntaxLocation
class FirstSyntax(t.TypedDict):
kind: t.Literal['First']
value: SyntaxTerm
location: SyntaxLocation
class SecondSyntax(t.TypedDict):
kind: t.Literal['Second']
value: SyntaxTerm
location: SyntaxLocation
class FunctionExpressionSyntax(t.TypedDict):
kind: t.Literal['Function']
parameters: t.List[ParameterSyntax]
value: SyntaxTerm
mypy is struggling to infer the typed dicts by looking at the "kind" key, they all have a literal value defined
while parsing the JSON mypy is not able to infer the correct typed dict here
def bind_term(self, term: SyntaxTerm) -> Expression:
kind = term['kind']
# ...
elif kind == 'Tuple':
# mypy should infer "term" is TupleSyntax typed dict
return Tuple(
types=TUPLETYPES,
first=self.bind_term(term['first']),
second=self.bind_term(term['second'])
)
# ...
mypy error is error: TypedDict "LiteralSyntax" has no key "first" [typeddict-item] at term['first']
is there a way to fix it or mypy just won't work?
Pyright/pylance understands this, but mypy doesn't
from typing import Literal, TypedDict, Union, reveal_type
AB = Union["A", "B"]
class A(TypedDict):
key: Literal["A"]
class B(TypedDict):
key: Literal["B"]
def f(x: AB):
match x["key"]:
case "A":
reveal_type(x) # A
case "B":
reveal_type(x) # B
So it might just be an issue with mypy 🤷♂️
oh wait! interesting
thats small example
if I save key as a variable, it doesn't understand that as an if elif chain
in my full typehint pyright fails
but if I compare it directly, it does
nope
the issue is in saving it to a variable apparently, which is so weird
but match would make it cleaner anyways so use it lol
how do I perform "or" in match?
thanks so
you're welcome so
with match pyright can infer all types, but mypy cant
did you mean to omit some of the classes which are in the syntax term type alias? for space saving?
if those classes don't exist, that could cause an issue
all classes exist, but discord wont let me paste everything inline
I also can't change the structure, it is set by a thirdparty
but pyright is correctly inferring all types, it's mypy issue now
I am trying to use mypy because I would like to compile this python module with mypyc
we have a really good code pastebin
!paste
If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/
After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.
this AST is really bad structured but I have to work with that haha
a workaround for this is explictly cast the type with typing.cast()
but I expected mypy to infer it since the typed dict union at the key "kind" is kinda obvious
I would honestly recommend modelling the AST as dataclasses and then serializing/deserializing them separately
this way you are not coupled to the format in which you're saving and transmitting them
and it's less typing, you don't have to repeat the kind
I have another typed structure, I am doing that, parsing the JSON
and converting to a structure based on classes
mypy and pyright both understand this (I mean, of course)
from typing import Union, reveal_type
from dataclasses import dataclass
AB = Union["A", "B"]
@dataclass
class A:
...
@dataclass
class B:
...
def f(x: AB):
match x:
case A():
reveal_type(x) # A
case B():
reveal_type(x) # B
I will just manually cast types where mypy struggles
I wont convert dict to dataclasses which is another dict, I have speed constraints too
can you provide this non-working code? I want to see what you wrote
you don't have matching in there or anything
try with your sample?
def f(x: SyntaxTerm):
match x["kind"]:
case "Int" | "Str":
reveal_type(x) # LiteralSyntax
case "Tuple":
reveal_type(x) # TupleSyntax
I see that it is only failing on the first for Pylance
(I wrote the match cases out)
however, for each other case, it correctly infers the type
do pip install mypy and call mypy test.py
someone's gotta explain dataclasses.InitVar to me man
seems Pylance doesn't belive they are what I think they are
is this just Pylance being stupid?
I have been sending issue requests to Eric Traut for more than a year know. From my experience: it's probably as designed :)
Though it confuses me too.
https://github.com/microsoft/pyright/issues/3095
Looks similar
Paste your sample here:
https://pyright-playground.decorator-factory.su/
I just did and verified that there is no error, which means it's same as that issue.
is there any way to have the concrete input type of a union be considered for a output
right now if i have soemthingl ike
def maybe_get(foo: str|None = None) -> str|None:
...
i have to add 2 overloads to ensure the type of foo can correctly pass to the output - is ther any way to make a alias for a union that automatically drops the missmatch?
MaybeStr = TypeVar("MaybeStr", str, None)
def maybe_get(foo: MaybeStr) -> MaybeStr:
...
though the default will not work
maybe typevar defaults can somehow help, but i don't know tbh
Does anyone know a lxml stub library that is not potato?
(method) def xpath(
_path: _AnyStr,
namespaces: _NonDefaultNSMapArg | None = ...,
extensions: Any = ...,
smart_strings: bool = ...,
**_variables: _XPathObject
) -> _XPathObject
``` https://pypi.org/project/lxml-stubs/ shows this method returns a _XPathObject, and i cannot iterate over it
through, there is no problem with it runtime.
soup: etree._Element = ...
iframes = soup.xpath("//iframe")
for i in range(len(iframes)):
``` like, this works just fine in runtime.
but shows error with lxml-stubs
Yes, they can. Here's how you do it:
from typing import TypeVar, TYPE_CHECKING
if TYPE_CHECKING:
MaybeStr = TypeVar("MaybeStr", str, None, default=None)
def maybe_get(foo: "MaybeStr" = None) -> "MaybeStr":
...
reveal_type(maybe_get()) # None
reveal_type(maybe_get(None)) # None
reveal_type(maybe_get("Hello")) # str
@stray summit fyi
this breaks form ne, gimme asec to playground it
so this is only accepted by mypy if i add a #type: ignore[assignment] to the line with the none as implied optional is forbidden, this might be a mypy bug
Ooh you were using mypy?
It's a feature from future python so I'm not sure whether mypy already supports it. I know for a fact that pyright does because it always implements new stuff quickly
mypy reveals it as str only
hmm, i added another reply to https://github.com/python/mypy/issues/8708 - it would be really neat if there was a Generic helper that allowed to refer to typevars returning the first resolved one (or concrete type)
Running mypy --no-implicit-optional test.py with the code below gives the error Incompatible default for argument "default" (default has type "None", argument has type "D&q...
TypeVar defaults don't work for functions
i created https://discuss.python.org/t/typing-firstresolved-as-mechanism-to-refer-to-the-first-resolved-passed-typevar/34003 to discuss having something to make this nicer, not yet sure if its a good approach
the mypy issue on typevars and optional triggered this idea In utility helpers dealing with data transformation and/or loading the return value can be a product of multiple input parameters for example def get( self, key: str, default: _D | None = None, convert: Callable[[str], _T] | None = None, ) -> _D ...
but this hopefully reduces the need for certain types of overloads
guys, do i really need to import Self inside TYPE_CHECKING or not?
if t.TYPE_CHECKING:
from typing_extensions import Self
or just simply import it globally?
also, if im using typing_extensions in my module, do i need to include it in my dev requirements?
cuz i've seen someone who said he/she doesn't have that module
If you're using typing-extensions, you need to include it in your runtime requirements
I think that's a perfectly fine requirement if your library uses typing
It is not a standard library module
so, requirements in general
not just only for devs, am i right?
Yes, the import will run at runtime just like any other import
and you don't need to use TYPE_CHECKING in that case
my library does import typing
but im using py3.8
so, thats why im concerned about Self, as in here
typing-extensions is a package that backports a lot of symbols from later versions of Python. So if you're on 3.8 and want to use Self, you will need to add typing-extensions as a dependency and then import Self from typing_extensions
ok, got it
thanks
how should i call __init__ on a parametrized class? https://github.com/anand2312/postgrest-py/blob/generic-query-builders/postgrest/_async/request_builder.py#L203-L208
postgrest/_async/request_builder.py lines 203 to 208
BaseSelectRequestBuilder[_ReturnT].__origin__.__init__(
self, session, headers, params
)
AsyncQueryRequestBuilder[_ReturnT].__origin__.__init__(
self, session, path, http_method, headers, params, json
)```
pyright complains here because __origin__ only exists at runtime. if I do typing.get_origin(Base[T]).__init__(...), i get Expected 0 positional arguments
should I just cast(type[Base[T]], get_origin(Base[T]))?
The comment near that line seems incorrect unless something weird is happening in this code base elsewhere I'm not seeing at a glance.
It's perfectly fine to initialize generic specialized classes
for instance, set[int]().union(*other_sets)
(which that's required for that to properly infer the type as set[int] in that case, it won't be inferred from other_sets even if those are all Iterable[int])
this looks like it should just be using super().__init__()
and you have incompatible inheritence issues here too...
yeah you're right, it should be just super(). let me see how i can fix the weird inheritance going on. thanks!
crazy code
keep up
how nice this is
Is this a mypy bug?
Suppose I have:
from typing import Generic, Protocol, TypeVar
from collections.abc import Iterator
from typing_extensions import Self
T_Co = TypeVar("T_Co", covariant=True)
class ComposableSequence(Protocol[T_Co]):
def __iter__(self) -> Iterator[T_Co]:
...
def __add__(self, x: Self, /) -> Self:
...
This is accepted:
def bar() -> ComposableSequence[tuple[list[int | str], int]]:
return []
This is not:
def foo() -> ComposableSequence[tuple[list[int | str], int]]:
return [([], 0)]
test.py:18: error: Incompatible return value type (got "list[tuple[list[<nothing>], int]]", expected "ComposableSequence[tuple[list[int | str], int]]") [return-value]
I'm still not good with typing, however you might find this useful: this code ```py
from typing import Generic, Protocol, TypeVar
from collections.abc import Iterable
from typing_extensions import Self
T_Co = TypeVar("T_Co", covariant=True)
class Addable(Protocol):
def add(self, x: Self, /) -> Self: ...
class test(Addable, Iterable, Protocol[T_Co]):
pass
def foo() -> test[tuple[list[int | str], int]]:
return [()]
gives
main.py:19: error: List item 0 has incompatible type "tuple[]"; expected "T" [list-item]``` Which I'm don't really understand why, but it is different. You could probably also get this to be done with NewType to aviod the class overhead, but I'm too small brain to do it.
I got back to the exact same issue by moving the [T_Co] py class ComposableSequence(Addable, Iterable[T_Co], Protocol): pass
While looking around the github issues I saw a couple that related to using lists, and this appears to be one of them, since using just tuples makes it partially work. ```py
def bar() -> ComposableSequence[tuple[list[int | str], int]]:
return [([1], 0)] # Fails with the same error
def foo() -> ComposableSequence[tuple[tuple[int | str], int]]:
return [((1,), 0)] # Passes```
If a variable length tuple is used, then the empty case passes py def foo() -> ComposableSequence[tuple[tuple[int | str, ...], int]]: return [((), 0)] # Passes
you are returning empty tuple (), which is annotated as tuple[list[int | str], int]
I fixed it by moving the [T_Co] from Protocol to Iterable, though I'm not sure why it needed to be like that.
can you try on latest mypy master?
I don't think that actually fixes it
This has nothing to do with that?
Can you use it on the playground?
on master it complains with a different message:
main.py:18: error: Incompatible return value type (got "list[tuple[list[Never], int]]", expected "ComposableSequence[tuple[list[int | str], int]]") [return-value]
I wish Python typing had a way to make type parameters *variant at the usage site, like Java does it
e.g. if you use a list of T but never append to it, in Java, you could represent that as List<? extends T>, and then it just doesn’t allow you to write to it
that is, in Python typing terms, the only type that a parameter of type ? extends T accepts is Never
similarly if you only add to a list you can write List<? super T> and this will only produce values that can be converted to Object, nothing more specific
I think these are the top and the bottom of the type hierarchy, respectively
And you can convert an invariant type to a *variant one
but not back
Well, you can sorta do that via Sequence[T]? Though that's not generically applicable.
is there a type i can use or create that has the attribute access of NamedTuple combined with the Mapping compatibility of TypedDict?
can you subclass them both at the same time?
def _get_identifier(self, obj: T) -> Any:
value = None
try:
value = getattr(obj, self._field_name)
except AttributeError:
try:
value = obj[self._field_name]
except (TypeError, KeyError):
raise AttributeError
finally:
return value
How should I properly define T or bind it? Essentially looking for it to be a custom class or a dict
I tried two protocols of __getitem__ and __get_attribute__ and bound=Union but that still through off pyright
getattr with dynamic attribute cannot be typed. so just use Any
if you knew the field then it would be a union of a dict and a protocol with that field (not a type var)
do you think I should open a new issue for https://github.com/python/mypy/issues/8776#issuecomment-1729486810 ?
Assuming the following file: $ cat foo.py from typing import NamedTuple, List, Tuple, Sequence from typing import NamedTuple, List, Sequence class NT(NamedTuple): matched_text: str matches: Sequenc...
mypy considers this valid:
from typing import NamedTuple
class Example(NamedTuple):
foo: int
bar: int
def foo() -> Example:
return Example(1, 2)[:2]
looks like slice passes on the partial_fallback: https://github.com/python/mypy/blob/ff81a1c7abc91d9984fc73b9f2b9eab198001c8e/mypy/types.py#L2422C22-L2422C22
mypy/types.py line 2422
self.partial_fallback,```
I have a fix https://github.com/python/mypy/pull/16154
it's a bit horrible though but the test cases should be correct
does mypy check stuff in the TYPE_CHECKING as well?
if TYPE_CHECKING:
from slodon.slodonix.slodonix.slodonix_windows import _Info
like is the module going to be executed when I run mypy ...
modules arent executed or imported with mypy
is it correct that mypy returns different errors for these two?
def return_value_error() -> int:
value = max([1, 2], default=None)
return value
def arg_type_error() -> int:
return max([1, 2], default=None)
I was expecting the second one to also have a return type error instead of None being invalid for max.
Interesting, same in pylance. I'm guessing there's order-of-inference weirdness here, with them first inferring that for T1|T2 to be int, T2 must be int, and then noticing that None doesn't fit such a T2.
yeah I was looking at the GH issue 6692 which suggests its supposed to do that but not sure why 🤔
It's sort of expected. It's due to what mypy calls "type context": in the second case it "knows" that max has to return an int, and tailors its type inference accordingly
Ah, yes makes more sense with an example with T1 | T2 and seeing it deduce valid arg types, thanks both
In both cases it's not clear whether the return type is wrong or the argument to the function is wrong, yeah
Hey!
Should this be supported by mypy?
class Base(ABC):
pass
class Derived(Base):
pass
class Handle(Generic[T]):
def __init__(self, t: T):
self.t = t
def foo(h: Handle[Base]):
print(h.t)
d = Derived()
foo(Handle(d))

I am getting an error that Outer[Derived] != Outer[Base], but it should not matter
you're passing in Derived but the function accepts Outer
Do you know about variance? Maybe that's the problem
Sorry. Fixed.
There's an article about it: https://decorator-factory.github.io/typing-tips/tutorials/generics/variance/
But in short, list[Derived] does not fit where a list[Base] is expected, because a function accepting a list[Base] can append an object that's not a Derived to the list. So lists are invariant
If you want your generic class to be covariant, you need to specify that in the typevar ```py
T = TypeVar("T", covariant=True)
I SEE. Thank you so very much.
My google search history looks incredebly stupid having googled this issue in a hundred different ways. And yet talking to a human I understand the issue within a minute.
Yes. My generic is covariant, had to specify in the TypeVar. I'll read the tutorial a bit more
I also have a question related to variance, if that's ok!
TSchema = TypeVar("TSchema", bound=TypedDict)
TSelf = TypeVar("TSelf", bound="SerializesTo")
class SerializesTo(Generic[TSchema]):
def serialize(self) -> TSchema: ...
@classmethod
def deserialize(cls: Type[TSelf], data: TSchema) -> TSelf: ...
class SerializedAnimal(TypedDict):
legs: int
class SerializedElephant(SerializedAnimal):
trunkLength: float
class Animal(SerializesTo[SerializedAnimal]):
pass
# Base classes of Elephant are mutually incompatible
# Base class "SerializesTo[SerializedElephant]" is incompatible with type "SerializesTo[SerializedAnimal]"
class Elephant(Animal, SerializesTo[SerializedElephant]):
pass
I can't have TSchema be invariant here, because of the above error. Making TSchema covariant would fix it, but then I would be able to do stuff like this, which doesn't make sense:
def factory(data: SerializedAnimal):
return Elephant.deserialize(data)
The solution, suggested by Eric Traut the pyright developer, is to have Animal be generic:
TAnimalSchema = TypeVar("TAnimalSchema", bound=SerializedAnimal)
class Animal(SerializesTo[TAnimalSchema]):
pass
This works beautifully, but pyright now can't determine the expected type of my deserialize parameter:
Apologies for the essay, if anyone is able to offer any thoughts I'd appreciate it very much :)
This isn't correct and should not be advised, mutable fields of classes should be invariant. mypy does not yet check this, but has an open issue since 2017, and there's work on this still active. pyright checks this with strict or with the specific rule enabled
If they have a mutable public field, their class is indeed not covariant. I haven't seen the actual code
It's in the code they provided that you responded to...
it also doesn't have to be public for this to be the case
Sinbad is correct, if you intend t to be reassignable, then you should not make this class covariant. ```py
class Base(ABC):
pass
class Foo(Base):
pass
class Bar(Base):
pass
def set_bar(h: Handle[Base]):
h.t = Bar()
handle = Handle(Foo()) # Handle[Foo]
set_bar(handle) # oops, now Handle[Foo] doesn't have a Foo
because it's essentially adding this to your class ```py
def set_t(self, t: T) -> None
def get_t(self) -> T
I'm not 100% sure I'm following what your needs on this are or why the design appears to couple a dataclass with an empty subclass of that dataclass. I'm not saying that this must be indicative of an issue, but it's hard to reccomend a specific path towards making this understood without understanding the full context, and that could range from knowing immediately how to add the right type info to considering if other struture suits your task better.
Many thanks for the reply! That makes sense.
I have JSON-serializable types in my project, and I'd like the types to be aware of their serialised form. That lets me catch mistakes, for example in incorrectly expecting certain keys to be present, or forgetting to include required things in the class's serializer
Also, considering this is apears to be for serializing/deserializing things that appear to have differing fields on them, you may be reinventing the wheel here https://jcristharif.com/msgspec/structs.html
oop, yep messages at same time seems to confirm this to an extent. If you want to do this yourself, it's still possible, but I would consider looking at options already designed to help in this space as well
I see. Worth noting that the example I provided was not really accurate to what I'm doing.
t won't be assignable; in fact there isn't even a t; I'm using the Generic purely for type-hinting, similar to Rust's PhantomData.
Haven't heard of structs in python before, thank you I'll do some reading :)
I also don't quite follow what you said here though: a dataclass coupled with an empty subclass of that dataclass? Are TypedDicts dataclasses at runtime?
It also doesn't feel like this problem would be particularly niche - base class uses a generic type parameter in a method signature, but we can't infer that type in a subclass 🤔
No, sorry I could have been slightly clearer. I mean a class that's only intended to hold data, not the literal dataclasse(es) module
Ahh gotcha - the actual context is a game. The objects do have their own state and behaviour
Hmm. I'm wondering more about if this is a "change your structure more" situation or if we would need to focus more on how to ensure the type system has enough information.
Ideally, all the behavior that is actually shared could be composited in, but you really need anything that diverges to be simple in divergence and isolate well to a generic parameter or be added on a per-type basis if it can't.
Should I give a different example with more context?
In that case, covariance sounds like it would actually be fine for you, but I would check your real code against pyright with strict for that if you want to ensure it remains fine with type checkers checking more over time.
Is pyright overall preferable for new projects in 2023 over mypy? Or is it a matter of requirements with no clear answer.
The pyright page - surprise surprise - appears to make it seem surperiour in many ways. But most projects I've seen so far for reference have used mypy.
I find pyright does a better job currently, but that the end goal should be that type checkers will eventually all agree. mypy is much older, and has a lot of inertia as a result.
@rustic gullsorry, I had to step away for a moment, the following appears to all type check as I would want it to:
from typing import Generic, Self, TypedDict, TypeVar, reveal_type
class BaseDict(TypedDict):
pass
T = TypeVar("T", bound=BaseDict)
class Serializable(Generic[T]):
def serialize(self: Self) -> T:
...
@classmethod
def deserialize(cls: type[Self], data: T) -> Self:
...
class ElephantDict(BaseDict):
trunk: int
class SomeOtherSharedBehaviorClass:
pass
class Elephant(Serializable[ElephantDict], SomeOtherSharedBehaviorClass):
pass
edict: ElephantDict = ElephantDict(trunk=1)
elephant: Elephant = Elephant()
reveal_type(Elephant.deserialize(edict)) # Type of "Elephant.deserialize(edict)" is "Elephant"
reveal_type(elephant.serialize()) # Type of "elephant.serialize()" is "ElephantDict"
for slightly more detail that I think I probably should have included originally.
-
pyright has a page which shows the known differences between it and mypy here
-
users of visual studio code will (by default) get the basic type checking from pyright indirectly through VSCs language server, and could get strict with one small change, so you or your users may inadvertently end up using both type checkers as has been pointed out in some discussions regarding the long term future of type checking and (Currently) diverging behavior
-
I specified pyright on strict here in this specific case because pyright with strict is currently the only type checker I'm aware of to get this specific variance check correct, so it was more a means of confirming that you didn't have something that a typechecker still could see a reason for invariance which could either mean needing to modify structure or a bug report if pyright is tripping up on it.
Yep, this works on my end!
I don't think this method would fit my particular project, because my classes are very heirarchical. I'd rather have my serializing be heirarchical too in that case:
class SerializedElephant(SerializedAnimal):
trunkLength: int
class SerializedCat(SerializedAnimal):
furLength: int
class Elephant(Animal, SerializesTo[SerializedElephant]):
def serialize(self): return {**super().serialize(), "trunkLength": self.trunkLength}
class Cat(Animal, SerializesTo[SerializedCat]):
def serialize(self): return {**super().serialize(), "furLength": self.furLength}
you may have been right all along :)
I'm wondering more about if this is a "change your structure more" situation
Wow, I just found my one brain cell deep in my back pocket!
I said that Eric Traut recommended making Animal generic:
TAnimalSchema = TypeVar("TAnimalSchema", bound=SerializedAnimal)
class Animal(SerializesTo[TAnimalSchema]):
pass
I was using it like this:
class Elephant(Animal, SerializesTo[SerializedElephant]):
pass
Instead of this:
class Elephant(Animal[SerializedElephant]):
pass
The hierarchical serialized schema works as expected now, god i'm an idiot 😂 thank you so much for your time Sinbad :)
can type checkers recognize lazy imports as done with this method? https://peps.python.org/pep-0562/
Python Enhancement Proposals (PEPs)
no. if something false to __getattr__ it'll usually end up as whatever the return type of __getattr__ is, which is usually Any (this means you won't have false positives, but also won't get much actual type checking)
(see also this PR of mine from this week https://github.com/pytorch/pytorch/pull/109683 , where previously type checkers would not complain about torch.asdfasdf )
as in that PR, if TYPE_CHECKING is a useful tool to lie to type checkers while doing whatever arbitrarily dynamic thing you want to do out of sight
okay thank you! bummer, I was thinking about doing that in a few projects
to be clear about the if TYPE_CHECKING suggestion:
if TYPE_CHECKING:
from . import slow
else:
def __getattr__(name):
if name == "slow": ...
