#type-hinting
1 messages ยท Page 51 of 1
A leaky implementation + bad debugging ux, I'll pass
And not everyone uses static analyzers anyways
fair point
yeah
tbh I am just not a big fan of putting isinstance in all my functions ๐
although if you always check for all the types, your code will be 50% type checks
and probably slower if you e.g. check every item in a list
one idea is only to do type checks behind a if __debug__
not all TypeErrors are surrogates for static types, i think if you raise it explicitly in code it should be checked as normal
that makes sense
so in that sense, TypeError should be hinted/checked if they can be raised if if the arguments follow the type hints imo (cant think of a good example of that atm, but I guess there are some?)
for example, if some constraint is not expressable in type hints
so ```py
def foo(a: int) -> None:
if not isinstance(a, int):
# no need to hint as if the caller follows type hints this error wont be raised.
raise TypeError(...)
def bar(a: int) -> None | Raises[TypeError]:
if a < 0:
# should be hinted as it can be raised when following type hints
# ValueError is the correct error to raise in this situation, but this is an example :P
raise TypeError(...)
atleast that is imo of how it should work
but as mentioned, there are many times you know something wont error, but it is not possible to encode that in the type hints (at least without a lot of work on both type-checkers and the person type-hinting)
hinting errors is kind of useless unless you can encode predicates in the hints
in which case you reach a nightmare of syntax and implementation
one example I can think is ```py
@overload
def div(a: int, b: Literal[0]) -> Raises[ZeroDivisionError]: ...
@overload
def div(a: int, b: int) -> float: ...
def div(a, b): ...but then type checkers need to understanding stuff like `if value > 0` to mean that `value != 0` so they can know `div` wont raise a error on code likepy
if value > 0:
return div(10, value)```
ofc this is a very simple example, more complex stuff is basically going to be impossible to do
even normal typing can't do refinement types or value predicates partly because python typing is duct tape
actually with TypeGuard you could maybe get something close https://mypy-play.net/?mypy=latest&python=3.10&gist=bcf49530c31450b7df369851073d7a1f but I am very unsure on what I think about this style
one thing that would be very interesting is basically a Not, like you could say Not[Literal[0]]
ofc
but yeah a Not type would be really appreciated
but it kinda would need to be in combination with a And, so you could do int & (~Literal[0])
RIP type intersections
i cant wait for intersection types
i was planning on trying to get that through after i finish typing.Self
is there an online playground for pyright?
because it is not wanting to work on computer and mypy is not being smart
(I am on my school laptop, and at my home setup where I do use pyright I have found it to be smarter than mypy)
nvm I just used replit
so I have had some fun with TypeGuard ```py
@overload
def add(value: IntPositiv) -> IntPositiv: ...
@overload
def add(value: int) -> int: ...
def add(value):
return value + 5
bar = 5
if IntZero.test(bar):
reveal_type(add(bar))
else:
reveal_type(add(bar))and pyright is correct in sayingpy
/home/runner/pyright-tester/main.py:39:17 - info: Type of "add(bar)" is "IntPositiv"
/home/runner/pyright-tester/main.py:41:17 - info: Type of "add(bar)" is "int"```
P = TypeVar("P")
T = TypeVar("T", bound="Predicate")
class Predicate(Generic[P]):
@staticmethod
def _test(value: P) -> bool:
return value == 0
@classmethod
def test(cls: Type[T], value: P) -> TypeGuard[T]:
return cls._test(value)
class IntPositiv(Predicate[int], int):
@staticmethod
def _test(value: int) -> bool:
return value >= 0
class IntZero(IntPositiv):
@staticmethod
def _test(value: int) -> bool:
return value == 0```
why did you use T for the predicate and P for the underlying type
my head is so full of fuck reading this
because I created T before I figured out I would need P ๐
I don't understand, is test every even called?!
Oh I see in the if-statement
yhe those code blocks are in the wrong order xD
huh, yeah mypy does seem to not handle this right
i think having a thing contain its own predicate is confusing it
like the proof and the type itself can't be the same thing
IntPositive(Predicate[int], int)
maybe, when I tested it resolved bar to T
main.py:39: error: No overload variant of "add" matches argument type "T"
main.py:39: error: No overload variant of "add_overload" matches argument type "P"
yeah
weird
(i swapped the letters :P)
bug report?
looks like it gets confused and cant seem to figure out the cls: Type[P] and TypeGuard[P] are the same ones, or something thing
is there a way to set 2 upper bounds on a typevar
like "must be a subclass of both int and Predicate[int]"
I dont think so
(I would love the be able to just do that with type hints in general)
Int = TypeVar("Int", bound=Union[int, "IntConstrained"])
class IntConstrained(int, Predicate[int]): ...
okay so Union works, but not |?
https://mypy-play.net/?mypy=0.910&python=3.10&gist=2b84f5b605b26e76bcce839f8e1ab7a5
oh, yeah
idk why
also not the same thing as must be both
this "No overload variant" issue definitely seems like a bug
right, but still weird only one works
definitely, well it is not something to do with overloads, but just figuring out what bar should be in the first place
i think it has to do with the IntPositive having multiple bases
ah so they have different types py reveal_type(Union[str, float]) reveal_type(str | float) main.py:3: note: Revealed type is "builtins.object" main.py:4: note: Revealed type is "types.Union"
wat
ยฏ_(ใ)_/ยฏ
also the return type inference in add isn't right
Int = TypeVar("Int", bound=Union[int, "IntConstrained"])
class IntConstrained(int, Predicate[int]): ...
def add_typevar(value: Int) -> Int:
if isinstance(value, int):
return value + 5 # <- error here
elif isinstance(value, Predicate):
result = value + 5
assert value.test(result)
return result
you'd think this would work
main.py:36: error: Incompatible return value type (got "int", expected "Int")
wat
ยฏ_(ใ)_/ยฏ
it clearly knows value is an int at this point, so it should know Int=int (in this context) also, wtf
can I get the hole code for that?
wanna run it in pyright
hm, pyright is having the same error
https://mypy-play.net/?mypy=0.910&python=3.10&flags=strict&gist=6fe677f7bd24575756119a2e9e6535c8
from __future__ import annotations
from typing import Any, Generic, TypeGuard, TypeVar, Union, cast, overload
T = TypeVar("T")
_Predicate = TypeVar("_Predicate", bound="Predicate[Any]")
class Predicate(Generic[T]):
@staticmethod
def _test(value: T) -> bool:
return value == 0
@classmethod
def test(cls: type[_Predicate], value: T) -> TypeGuard[_Predicate]:
return cls._test(value)
_IntConstrained = TypeVar("_IntConstrained", bound="IntConstrained")
class IntConstrained(int, Predicate[int]):
...
_IntPositive = TypeVar("_IntPositive", bound="IntPositive")
class IntPositive(IntConstrained):
@staticmethod
def _test(value: int) -> bool:
return value >= 0
@overload
def __add__(self: _IntPositive, other: _IntPositive) -> _IntPositive: ...
@overload
def __add__(self: _IntPositive, other: int) -> int: ...
def __add__(self: Any, other: Any) -> Any:
return super().__add__(other)
_IntZero = TypeVar("_IntZero", bound="IntZero")
class IntZero(IntConstrained):
@staticmethod
def _test(value: int) -> bool:
return value == 0
_AnyInt = TypeVar("_AnyInt", bound=Union[int, IntConstrained])
def add(value: _AnyInt) -> _AnyInt:
return value + 5
foo = 5
if IntZero.test(foo):
reveal_type(add(foo))
else:
reveal_type(add(foo))
main.py:49: error: Incompatible return value type (got "int", expected "_AnyInt")
main.py:54: error: Value of type variable "_AnyInt" of "add" cannot be "_Predicate"
main.py:54: note: Revealed type is "_Predicate`32"
main.py:56: note: Revealed type is "builtins.int*"
Found 2 errors in 1 file (checked 1 source file)
i give up
I could make a pyright playground some time in the future
please do ๐
pyright does not error /home/runner/pyright-tester/main.py:33:23 - warning: TypeVar "_IntPositive" appears only once in generic function signature (reportInvalidTypeVarUse) /home/runner/pyright-tester/main.py:54:17 - info: Type of "add(foo)" is "IntZero" /home/runner/pyright-tester/main.py:56:17 - info: Type of "add(foo)" is "int"
huh
when do we get pyrightc
i wonder what exactly mypy is doing "wrong" here
or what pyright is doing "wrong" that causes this to typecheck...
should it not type check ๐
in another case I also found pyright to be "smarter"
#type-hinting message
i don't even know how to describe this bug
well first I think we should narrow it down to the smallest example that stil gets it wrong
"chaos ensues when a typeguard is parameterized by a typevar"
I see nothing wrong with this!
this example is pretty minimal imo
well there are actually two issues here
but they both seem to be the same underlying issue... maybe
Value of type variable "_AnyInt" of "add" cannot be "_Predicate"
this IMO is the issue
also
main.py:54: note: Revealed type is "_Predicate`32"
wat
okay so is the smallest example of that seconds issue (TypeVar in TypeGuard)
https://mypy-play.net/?mypy=0.910&python=3.10&gist=4d837424ea9da7a3f7f19c5231d0c39b
main.py:13: note: Revealed type is "T`-1"```
maybe that's just an implementation detail of typeguards leaking
like maybe they do some internal name mangling
...which maybe is the source of the problem?
I have noe clue ยฏ_(ใ)_/ยฏ
because this code should work exactly like isinstance does (right?)
main.py:15: note: Revealed type is "def [T] (value: Any, check_for: Type[T`-1]) -> TypeGuard[T`-1]"```
look like they are the same `T`
i believe that's the idea, yeah
the "`-n" thing does seem to be name mangling
interesting implementation
wait
im so dumb
i had IntZero.test and not IntPositive.test
or wait that should work
(note: "positive" usually means > 0, "non-negative" is >= 0)
tbh these things should just be marked @final anyway
although i guess not
but it's up to subclasses to respect their semantics
fyi your add signature is incorrect, if I pass a IntNegative like -3 it can stop being a IntNegative
wait I think the main.py:73: error: Incompatible return value type (got "int", expected "_Int") is actually correct
because doing + on a int is typed to always return an int (even when subclassed)
so if you passed in a IntNegative, the return would be int (and the signature says the return type should be same as the argument)
but this way of using TypeGuard and types is wrong as we never have an instance of a Predicate, so we are abusing the type system ๐
That's no actually how it happens at runtime, right?
... def __add__(self, other):
... return X(1)
... def __radd__(self, other):
... return X(1)
...
>>> X() +1
1
>>> type(X() +1)
<class '__main__.X'>
idk, but that is what reveal_type is telling me will happen
We may have tried to fix this in typeshed but given up, dunders tend to be tricky to type
That's correct at runtime
oh wow it gets this right too: https://mypy-play.net/?mypy=latest&python=3.10&gist=4597fc54a9092d89258a9562cabb70ef
right this returns the X type, but in our guest to abusing the type system, the type it sees hinted in that function is int and IntConstrained (which is just a subclass of int without overwritting __add__)
all subclasses of IntConstrained overwrite it, but not IntConstrained it self
but pyright is not giving this error, but it should be ..
hmmm. ......
I think we have really upset the typing gods today xD
from typing import TypeVar
class Foo(int):
abc = 4
T = TypeVar("T", int, Foo)
def add(value: T) -> T:
# this produces an int
# but pyright is not complaing, wtf
return value + 5
# errors at runtime becuase add(Foo(5)) returns an int
add(Foo(5)).abc```
pyright has no issues with this code ........
(while mypy does correctly see the error in add)
this should not even be a hard one, right.....
the strangest thing?
reveal_type(value + 5) says int,
returning int(5) makes it error (as expected)
so why wont this code error ๐คฆโโ๏ธ
well I submitted a issue https://github.com/microsoft/pyright/issues/2363
What did I just stumble upon?!?!
!e ```python
def func(arg: ('this is valid', 'huh?')) -> None:
return
@blazing nest :warning: Your eval job has completed with return code 0.
[No output]
For now ;)
Guido has said that he wanted to make types the only things you could put in annotations
any Python expression is a valid annotation ๐
Atleast at one point
and yeah, annotations were initially meant for lots of things
You can also use x: (yield 1) = 2 and do some bad stuff
What's T_co?
Type_covariant?
That doesn't yield does it?!
It does in some versions
That's getting disallowed in 3.10 ibelieve
I'm pretty sure it's a syntax error in 3.10
Phew hahaha
Can you point me to some documentation? I can't seem to find any
x: (x := 3)
I don't think there is any documentation about the naming. That was just a guess.
Maybe you're looking for the typeshed repository? Afaik that is where all built-ins gets their types
Hm. Perhaps I should use typing.Tuple, I don't think T_co is what I want
Yeah covariance, contravariance and invariance is explained there
I meant the naming conventions
T_co is a type variable, it means that if you use tuple[int] then T_co becomes int
Ah
Cool, I don't need typing, I can just do tuple[Tensor, Tensor, Tensor]
I always forget how 3.9 does type hints
By convention, it is recommended to use names ending in _co for type variables defined with covariant=True and names ending in _contra for that defined with contravariant=True.
i've never seen this convention but i like it
kinda hungarian-ish
Covariance and contravariance defines whether the typevar accepts also subclasses or superclasses of the type.
i meant the _co etc naming convention
there are now two salts :0 i never knenw
who's the other one, salt-die?
yes :D
salt is yummy with food
r salt rock lamps edible
the lamp kind
not the human kindomg
```py
def foo():
... x: (yield 1) = 2
... print(x)
...
foo()
<generator object foo at 0x031D5418>```
@boreal ingot @fierce ridge lo and behold https://replit.com/@dfactory/PyrightPlayground#index.js
for some reason it doesn't work outside the embedded editor
๐ ๐ ๐ ๐ ๐
hmmm
specifically this
well the url for the iframe on the embedded page works: https://26dd7aa0-974c-42d4-820d-20b440ff37c4.id.repl.co/
idk why they use a different url there (that actually works) ยฏ_(ใ)_/ยฏ
obviously it's not scalable, it will break if two people use it at once
but it's a 50-line replit script
Works now: https://PyrightPlayground.dfactory.repl.co
Nice!
typeguard also does basically that but checks the runtime types passed in and out
hm? 
(if you want belt and suspenders)
sorry, I'm confused
- it's just the runtime version of the type checking in your screenshot
ah, well, all I did was hide pyright a web interface, like https://mypy-play.net
because someone asked if there is a playground for pyright
oh, I see, that's cool ๐
how do i type hint the self keyword?
logically, I'd be able to annotate it with the enclosing class in 3.10 right?
I mean, yeah
how about type aliases?
but its not needed
Don't think it's needed for self. mypy doesn't complain when you don't at least
why do you want to typehint self?
I just went through a lot of my symbols and thats the only i've been doind for like a week or so
but when I do from __future__ import annotations
lemme try
That's been pushed back
to 3.11 (last I heard anyway, not too sure what the deal is at the moment)
Doesn't matter too much, most editors would see ```py
class A:
def init(self: "A"):
pass
-\_("/)_/-
Oh, I though that would have worked now. Weird how the import future fixes it 
its not weird
It's importing from actual future 3.11? 
ah
this is the same as what jack sent with from future import __annotations__
!e ```py
class A:
def init(self: A):
pass
@little hare :x: Your eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 1, in <module>
003 | File "<string>", line 2, in A
004 | NameError: name 'A' is not defined
!e
from typing import Type
x: Type[Tuple[int, int]]
a: x = (1, 1)
I do love abusing future annotations
Are you talking like SomeAlias = Union[int, float]? You can do def f(arg: SomeAlias): ...
!e ```py
from future import annotations
class A:
def init(self: A):
pass
yup
!e
from typing import Type, Tuple
x: Type[Tuple[int, int]]
a: x = (1, 1)
wat
@little hare :warning: Your eval job has completed with return code 0.
[No output]
!e
from typing import Type, Tuple
x: Type[Tuple[int, int]] = Tuple[int, int]
a: x = (1, 1)
print(a)
@tardy widget :white_check_mark: Your eval job has completed with return code 0.
(1, 1)
look at the second line lol
!e
from typing import Type, Tuple
x: type = Tuple[int, int]
a: x = (1, 1)
print(a)
@tardy widget :white_check_mark: Your eval job has completed with return code 0.
(1, 1)
weird
what's weird?
@lone sparrow I have this very bad habit of taking some very obvious things NOT for granted and then doing things like above
Does annotating something with typing actually increase performance by a noticeable amount?
It doesn't do anything
Type hints are not enforced
!e
from typing import Type, Tuple
x: type = list[tuple[dict, str, int, "other stuff I guess"]]
a: x = (1, 1)
print(a)
@lone sparrow :white_check_mark: Your eval job has completed with return code 0.
(1, 1)
I heard in many places that hinting with inbuilt types optimizes something
evil thoughts spread
I have been duped
All these time I've been wasting
Oh well there must be SOME use to hinting
Type hinting for the most part is just for the developer
To remind people working on the code of things like what a function expects and returns so they don't have to refer to the docs every two seconds
And they can have actual effects on the code itself, since unlike comments they're not just thrown away during compile time
But for the most part, they're just for helping people working with the code in some way
Oh well, I forget what symbols do often
If anything it decreases the performance. Because now Python has to load the typing module.
I find them really useful. Love the readability it provides and the editor support.
they also help you catch potential bugs (by using tools like pyright and mypy), like not checking for None when a function might return None, or giving a function a value of the wrong type (will most likely error on runtime, but only when the program gets to that point instead of it being pointed out earlier)
I think I just had a teenie epiphany
from typing import Iterable
from typing import Iterator
from typing import TypeVar
T = TypeVar('T')
class Thing(Iterable[T]):
def __iter__(self) -> Iterator[T]:
By doing this, I'm creating a variable type that goes into the class, and comes back out of that same class
I'm saying that this is an iterable that can take one parameter. The type of that parameter is variable. When I iter, I get an iterable of that same paramater type
Am I on the right track?
Yeah, but this is rather uncommon to do.
Because you (Thing) is an iterator that means that if you return yourself, it returns an iterator with that type variable.
Sorry, its a bit early for me
The english in your last sentence isn't landing for me. Could you rephrase
Is this not the correct pattern?
Yes this is the concept behind type parameters, or generics as they're often called
No, that works!
So, what do the other types (T_co, etc.) do?
Is there a primer? Or do the differ in name and intention only
This goes through how variance works in Python's typing
note: you can't use covariant/contravariant type variables in certain situations
there should be an article on variance somewhere
btw, I like this pair of equations for some reason ```
((A => B) => (A => C)) => (B => C)
((B => A) => (C => A)) => (C => B)
I have a function that looks like this: ```python
def func(something):
... # Some code
return something # Return exactly the same thing
How would I type this? There are two types that this can take the shape of
func(something: T) -> T where T = TypeVar("T")
possibly with a bound if something has some restrictionns
Hmmm, I may need to open a PyRight issue..
What for?
from typing import Any, Callable, Dict, TypeVar, Type, Union
from ...utils import MISSING
from .slash import *
T = TypeVar('T', bound=Union[SlashCommand, Subcommand])
def option(
param: str,
name: str = MISSING,
description: str = MISSING,
required: bool = MISSING,
choices: Dict[str, Union[str, int, float]] = MISSING,
type: Type[Any] = MISSING
) -> Callable[[T], T]:
"""Option decorator for updating an option.
This decorator needs to be placed outside of the command decorator.
Parameters:
param: The name of the parameter for this option.
name: The new name of the option.
description: The new description of the option.
required: Whether the option should be able to be omitted.
choices: Set choices that the user needs to pick from.
type: New type of the option, overriding the annotation.
Exceptions:
ValueError: This decorator was not used on a command.
"""
# Because of the fact that we delete the type variable below, it won't be
# available when this function is created.
def decorator(command: 'T') -> 'T':
if not isinstance(command, (SlashCommand, Subcommand)):
raise ValueError(
"The 'option' decorator can only be used on commands."
)
command.update_option(
param, name=name, description=description,
required=required, choices=choices, type=type
)
# Return the command again for chaining.
return command # ERROR HERE?
return decorator
Expression of type "SlashCommand[Unknown, Unknown]* | Subcommand[Unknown, Unknown]*" cannot be assigned to return type "T@option"
Type "SlashCommand[Unknown, Unknown]* | Subcommand[Unknown, Unknown]*" cannot be assigned to type "T@option"
I cannot add the type variables as to not make it Unknown though..
Well what are the type parameters for slashcommand and subcommand
If you can't give any guarantees about them in your code, you can pad them with Any
Blah[Any, Any]
They wrap a Callable, so ParamSpec and a TypeVar
T = TypeVar('T', bound=Union[SlashCommand[P, RT], Subcommand[P, RT]]) (with P being ParamSpec and RT being TypeVar) gives me a type error from PyRight which is: TypeVar bound type cannot be generic
I think you'll have to give them concrete types as parameters
Because python typing doesn't support higher-kinded types (i.e. generic generics)
I'm assuming Any is most appropriate, haha
I cannot give them concrete types though hmm..
What do you mean?
If I remove the if-statement it stops complaining so this feels like a PyRight bug yeah.
SlashCommand[Any, Any] gives you errors?
Well I want this function to be able to be put on any command. So I guess Any fits here.. but then won't that discard the values because now it returns Any and Any?
If you want to retain that information, you'll need to change your approach to avoid having to use higher-kinded types
What do you mean by that?
T refering to my T = TypeVar('T')?
Any tips on typing this then?
you need to use constraints unless ive over simplified this
cause that doesnt give me errors but if i use bounds it does error
@blazing nest does this not work?
Ah sorry I didn't see this. What did you change?
I created this reproducible example: ```python
from typing import Callable, Generic, TypeVar, Union
from typing_extensions import ParamSpec
P = ParamSpec('P')
RT = TypeVar('RT')
class WrappedCallable(Generic[P, RT]):
callback: Callable[P, RT]
def __init__(
self,
callback: Callable[P, RT],
) -> None:
self.callback = callback
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> RT:
return self.callback(*args, **kwargs)
class AnotherCallable(WrappedCallable[P, RT]):
...
T = TypeVar('T', bound='Union[WrappedCallable, AnotherCallable]')
def troublesome_func() -> Callable[[T], T]:
def decorator(command: T) -> T:
if not isinstance(command, (WrappedCallable, AnotherCallable)):
raise ValueError(
"This decorator can only be applied to WrappedCallable instances"
)
... # Some code that calls a method on these classes
return command # ERROR HERE
return decorator
I used constraints instead of bounds
I haven't heard of those, what is the difference?
afaik TypeVar('T', bound=Union[A,B]) is the same as TypeVar('T', A, B, covariant=True)
No, it's not
I don't think you can even have a covariant typevar with value restriction
mypy let me do it!
An upper bound cannot be combined with type constraints (as in used AnyStr, see the example earlier); type constraints cause the inferred type to be exactly one of the constraint types, while an upper bound just requires that the actual type is a subtype of the boundary type.
I found this in PEP 484. I don't understand why this plays an important in my issue?
i would have expected bound= to work too
The spooky thing is that if I remove the bound from TypeVar, or if I remove the if-statement then PyRight is happy.
are SlashCommand and Subcommand parameterized?
So either I pick from runtime safety or I have better editor support lmao
Yeah they're generics like in my reproducible above.
oh i missed that part
I tried to add [P, RT] inside the union but that got an error. And T[P, RT] wasn't allowed either.
that works fine in my pyright, v1.1.173
Can you comment that on this issue?
https://github.com/microsoft/pyright/issues/2370
It must be an update regression because I am using V1.1.174 released today (well, 16 hours ago according to GitHub).
@blazing nest is pyright tmp.py enough to show that it works? i have no pyright config json, no pypyroject.toml
Umm- I think so yeah?
this code seems to work fine for me
if i use constraints
and errors if i use bound
Yeah that's interesting, huh.
in response to this i dont actually know what the difference between bound=A | B and A, B actually is but ik you probably need the latter most of the time i my experience
im sure jelle could probably tell you if they arent busy
wait so def join(s: AnyStr) -> AnyStr: return type(s)().join(s) doesnt work for a subclass of str?
didnt know that
My only major experience with bound is through making a ```py
SELF = TypeVar('SELF', bound='MyClass')
I have also used it with callable because I thought it meant that it would tell type checkers that I returned the same callable I got...
I understand now that bound is basically doing `def func(arg: BOUND as T) -> T: ...`* (replacing `BOUND` with whatever you have as `bound=...`)
*This is kind of fake code
Yes, that's right
They might be depending on what you do with arg within the function
I instantiated it, this was in a classmethod that created an instance.
So I did ```python
@classmethod
def func(cls: Type[SELF]) -> SELF:
return cls()
anyways typing.Self soonโข๏ธ
Yes I've heard some rumors about it. Sounds awesome!
the write up is nearly done i think it should be out in the next week or so unless something goes wrong
what cases require them?
if you annotate self: SELF is every other use of SELF not of type(self) necessarily?
Ah that may have been my issue. I didn't add SELF to all the other methods of my class as I found it would clutter the class
It was only present on my classmethod
No? Aren't TypeVars scoped class-wide?
not when used like that
class Foo(Generic[T]): ...
T is scoped class wide but in
class Bar:
def baz(self, spam: T): ...
T is only has meaning in the method signature + body
Oooh, right.
So I'm working on adding typing to the typeshed repo for the psutil module, and I'm running into an issue
There's a function definition def memory_info(self): .. which in the actual psutil module does this:
@memoize_when_activated
def memory_info(self):
"""Return a namedtuple with variable fields depending on the
platform, representing memory information about the process.
The "portable" fields available on all plaforms are `rss` and `vms`.
All numbers are expressed in bytes.
"""
return self._proc.memory_info()
The self._proc class variable is defined earlier as self._proc = _psplatform.Process(pid), where _psplatform is defined based on what OS the system is using
Looking the windows version, the function self._proc.memory_info() looks like this:
@wrap_exceptions
def memory_info(self):
# on Windows RSS == WorkingSetSize and VSM == PagefileUsage.
# Underlying C function returns fields of PROCESS_MEMORY_COUNTERS
# struct.
t = self._get_raw_meminfo()
rss = t[2] # wset
vms = t[7] # pagefile
return pmem(*(rss, vms, ) + t)
The actual issue I'm running into is that pmem is a collections.namedtuple defined like so: ```py
pmem = namedtuple(
'pmem', ['rss', 'vms',
'num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool',
'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool',
'pagefile', 'peak_pagefile', 'private'])
And I have no idea what type this is all supposed to return
I tried adding a condition to the pyi file to import the pmem variable from the proper OS version, but when I go to run typeshed's mypy test for Windows I get these errors:
stubs/psutil/psutil/__init__.pyi:165: error: Name "pmem" is not defined
stubs/psutil/psutil/__init__.pyi:166: error: Name "pmem" is not defined
stubs/psutil/psutil/__init__.pyi:167: error: Name "pmem" is not defined
psutil apparently is a mess to type right, sobolevn was also running into issues ๐ฆ
it looks like you should return the pmem namedtuple, where is it defined at runtime?
if sys.platform == "linux":
from ._pslinux import (
IOPRIO_CLASS_BE as IOPRIO_CLASS_BE,
IOPRIO_CLASS_IDLE as IOPRIO_CLASS_IDLE,
IOPRIO_CLASS_NONE as IOPRIO_CLASS_NONE,
IOPRIO_CLASS_RT as IOPRIO_CLASS_RT,
pfullmem as pfullmem,
pmem as pmem,
pmmap_ext as pmmap_ext,
pmmap_grouped as pmmap_grouped,
ppid_map as ppid_map,
scputimes as scputimes,
svmem as svmem,
)
elif sys.platform == "win32":
from ._psutil_windows import (
ABOVE_NORMAL_PRIORITY_CLASS as ABOVE_NORMAL_PRIORITY_CLASS,
BELOW_NORMAL_PRIORITY_CLASS as BELOW_NORMAL_PRIORITY_CLASS,
HIGH_PRIORITY_CLASS as HIGH_PRIORITY_CLASS,
IDLE_PRIORITY_CLASS as IDLE_PRIORITY_CLASS,
NORMAL_PRIORITY_CLASS as NORMAL_PRIORITY_CLASS,
REALTIME_PRIORITY_CLASS as REALTIME_PRIORITY_CLASS,
)
from ._pswindows import (
CONN_DELETE_TCB as CONN_DELETE_TCB,
IOPRIO_HIGH as IOPRIO_HIGH,
IOPRIO_LOW as IOPRIO_LOW,
IOPRIO_NORMAL as IOPRIO_NORMAL,
IOPRIO_VERYLOW as IOPRIO_VERYLOW,
pfullmem as pfullmem,
pmem as pmem,
pmmap_ext as pmmap_ext,
pmmap_grouped as pmmap_grouped,
scputimes as scputimes,
svmem as svmem,
win_service_iter as win_service_iter,
)
then pmem is acquired from either _pswindows.py or _pslinux.py which looks like what I posted above
hm what if it's neither linux nor windows?
I was going to add the different versions that get imported but I want to fix this issue first
for example, for AIX systems in the _psaix.py file pmem is defined as pmem = namedtuple('pmem', ['rss', 'vms'])
So I would simply add this to the __init__.pyi file
elif sys.platform.startswith("aix"):
from ._psaix import (
pmem as pmem
... # for toher related imports
)
That's also another issue
That's how psutil's _common.py checks whether the system is AIX: AIX = sys.platform.startswith("aix")
Then __init__.py imports from ._common import AIX
Weird because they could just do equality for it
!d sys.platform
sys.platform```
This string contains a platform identifier that can be used to append platform-specific components to [`sys.path`](https://docs.python.org/3.10/library/sys.html#sys.path "sys.path"), for instance.
For Unix systems, except on Linux and AIX, this is the lowercased OS name as returned by `uname -s` with the first part of the version as returned by `uname -r` appended, e.g. `'sunos5'` or `'freebsd8'`, *at the time when Python was built*. Unless you want to test for a specific system version, it is therefore recommended to use the following idiom:
```py
if sys.platform.startswith('freebsd'):
# FreeBSD-specific code here...
elif sys.platform.startswith('linux'):
# Linux-specific code here...
elif sys.platform.startswith('aix'):
# AIX-specific code here...
```...
hm that's tricky. I think in typeshed we generally haven't bothered with support for anything other than linux/mac/windowos
if I recall correctly flake8-pyi restricts what sys.platform strings you can compare with
pyi.py line 270
if isinstance(comparator, ast.Str):```
Yrah trying to test using sys.platform.startswith returns ./stubs/psutil/psutil/__init__.pyi:91:6: Y002 If test must be a simple comparison against sys.platform or sys.version_info
Not that I'm saying "hey we should really support this dying OS" but this also applies to FreeBSD, OpenBSD, NetBSD, and SunOS
Right. We can pretty easily add more sys.platform values to flake8-pyi
I don't think type checkers will like .startswith() though
Seems the startswith usage is just for backwards compatibility
Changed in version 3.3: On Linux, sys.platform doesnโt contain the major version anymore. It is always 'linux', instead of 'linux2' or 'linux3'. Since older Python versions include the version number, it is recommended to always use the startswith idiom presented above.
Changed in version 3.8: On AIX, sys.platform doesnโt contain the major version anymore. It is always 'aix', instead of 'aix5' or 'aix7'. Since older Python versions include the version number, it is recommended to always use the startswith idiom presented above.
But back to the original issue, I'm not sure why pmem isn't being properly imported, and wat type to return when using a namedtuple
The type to return is simple: the namedtuple
Define a corresponding typing.NamedTuple in the stub
Interesting, I tried doing collections.namedtuple and that failed, I foolishly didn't look into using typing.NamedTuple
Strangely enough, another namedtuple called pconn that comes from _common.py imports fine - it's only when the namedtuple that's being imported comes from something that you have to do some if sys.platform == X check
hm that's why I asked about what if it's not windows or linux - maybe mypy is failing when it's checking with sys.platform = macos
It's doing fine with MacOS - there's a separate check like this:```py
if sys.platform != "darwin":
from ._common import pio as pio, pionice as pionice
And later on there's this:```py
if sys.platform != "darwin":
def io_counters(self) -> pio: ...
And there are no errors for that when running the mypy test on a MacOS system or with another system using the --platform darwin flag
Well, that actually makes sure the system is NOT MacOS, my mistake
I want to make stubs
in which directory should i keep them?
i dont want the stub file to be in a seperate directory not along with the .py files
my project is like
myproj
- myproj
- module.py
why do you need stubs?
cos typehinting in the source code well doesnt look nice
Are you making a library?
yes first time, im new to this
thats why i need... a bit help
myproj
- myproj
- module.py
- module.pyi
this works fine.. but like can we have a seperate directory only for stubs?
@upper tapir
I'd prefer types inside the code. I hate it when I press "jump to definition" on a library function and have to figure out what type everything is. And then it turns out that the stub went out of sync with the code, which is easy to do.
However, if you really want to make stubs, mypy says that you can put .pyi files next to the .py files. https://mypy.readthedocs.io/en/stable/stubs.html#creating-a-stub
It also seems like you can make an external package with the stubs https://mypy.readthedocs.io/en/stable/getting_started.html#stubs-intro
I found this issue https://github.com/python/mypy/issues/4933 but I can't really make out how to put the files in a separate folder
oh if i want to make all the stubs in a seperate folder i shud do it as a seperate project is that what that means?
I'm not sure, but it seems like so.
just keep in mind that if you're not using a type checker on the library code itself, you're losing half the benefits
type checker?
like mypy or pyright
what does it do?
what do you think stub files are for?
typehinting?
right, but what's the point of having type hints in your library?
to specify type of args, variables and return types?
im not sure how to exactly explain, but like i use pycharm and it makes writing code so fast with autocomplete if the code is typehinted thats why i wanted to make exactly
right, that's what the tooling does, pycharm just has it built in
oh mypy and pyright does that?
yeah
oh
If you use type hints inside the code:
- You don't have to keep stubs in sync
- You can run a type checker (mypy/pyright/pycharm's built-in one) on your code, and it can point out mistakes
what does it do? like (as an example) mypy.check(function) tells the arg type and return type?
oh you mean if you do x:int and do x+'1' it says thats an error?
yeah
oh so it points out if you gave wrong type?
mypy and pyright will be a bit more strict with the checking than pycharm so it may be worth using them
How to write stubs for class properties?
i mean if i write the stub, in the setter pycharm complains function has no attr to setter even tho its actually a property
in the stub file
discord.py + typing:
i have some command:
@commands.command()
async def ...(self, ctx, args: typing.Literal["test"]):
...
How do i make args work with Test?
@warm mountain you should ask in #discord-bots
I think you'll have to make a custom converter
Or edit conversion in discord module. Just make it lower case argument
monkey-patching library could should be a last resort
Yep but it's best solution for me(i think)
i alr find converter
If all you need is a very simple converter, there's no reason to monkey-patch library code
If you use stubs in another file then this benefit doesn't apply to your library.
While you are writing the library you won't get this convenience. With typings inside the code you do get this.
I can't tell why that's wrong? It looks correct.
so today I learned all of typing is basically deprecated?
the container types, yeah
and we're supposed to use "parametrizable built-in types"?
to answer the original question: yes, you can. pyright supports a separate typings directory. mypy supports a stubs directory that must be added to MYPYPATH https://mypy.readthedocs.io/en/stable/stubs.html, or with a PEP-561 "stub package" https://www.python.org/dev/peps/pep-0561/
yeah, list[int] instead of typing.List[int]
tldr if it is a builtin or if it exists in collections.abc, use that, instead of the version in typing
oh that works now?
as of 3.9 yes
nice
if sys.version_info < (3, 9):
from typing import List
else:
List = list
i've done this in some code
not really necessary, but makes the deprecation path clearer/easier
would something like
l = list[int]()
``` work?
sure
oh i see
but it's not necessarily going to be statically analyzed
depends on the context I think
yep that is valid
that's a big change, no?
mypy knows about it, so does pyright
mypy supports sys.version_info checks
it's all over typeshed
which is really cool/useful/valuable
speaking of, what would you prefer?
# a)
tokens: list[Token] = []
tokens: dict[str, Token] = {}
tokens: set[Token] = set()
# b)
tokens = list[Token]()
tokens = dict[str, Token]()
tokens = set[Token]()
(assuming the performance difference is negligible)
the latter might be kinda wacky and wild, especially since you no longer know what's a "type" and what's "a thing you're calling getitem on"
Hey I'm having a bit of a problem currently; I have this decorator:
from asyncio import get_event_loop
from functools import wraps
from typing import Awaitable, Callable, TypeVar
T = TypeVar("T")
def task(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
@wraps(func)
def wrapper(*args, **kwargs) -> Awaitable[T]:
_loop = get_event_loop()
return _loop.create_task(func(*args, **kwargs))
return wrapper
```which basically lets you do `@task` on a coro function and make it into a task (so you can await it for its result of just leave it be), and it works fine and the return types work on it, but it shows the function signature as `*args, **kwargs` rather than the original signature of the function it decorates, does anyone know how I can get stuff like vscode's 'intellisense' to pick up the correct original function signature?
ParamSpec was the answer, just involved me discovering that typing_extensions exists in 3.8
Yup ParamSpec is bae
yep, but i have a problem , i need to use typing.Union[int, configs.Literal(...)]
You ned to use typing.Literal[]
Literal - my converter that works like typing.Literal, but lowers input
Yeah I am not sure what to do then. You can only pass types to Union
so how i will make it
what kind of converter?
mypy doesn't understand runtime code
its understanding of Literal is more or less hard-coded
you would need to write a mypy plugin to support your custom lower-casing Literal
discord.ext.commands.Conventer. I think i need to go to #discord-bots
make the convert a classmethod and change the annotation to a class instead of an instance
type checkers only expect types as annotations
i also need get args that will be cheked
metaclasses
what are those args supposed to be?
Converter("enable", "disable")
sounds like you need to have -> str on that function?
ah, that's in their source? unfortunate
no
I wanted to make converter that will work like typing.Literal, but will trigger upper case words
..., it isnt best way
if i will place my bot to host
it will use original library
can you be more specific? i still don't understand. you just want the type to be "all upper-case strings"?
can we come to dms?
i'd rather not, sorry
but i was going to suggest maybe using a TypeGuard here
class UpperCaseString(str):
pass
def is_uppercasestring(text: str) -> TypeGuard[UpperCaseString]:
return text.isupper()
You can do stuff at runtime here to generate the Literals
Or you could use the error handler for the command to check if arg.lower() == the lowered literal
I have argument amount. It accepts only int type or "all". I wanna make it accept also "All", "aLl"
I think your best bet is using an error handler
but what will i do next
After you check the literal is correct just reinvoke the command
it will more freeze bot
What?
Coding anything in your bot slows down your bot
It's going to be insignificant
i think better just keep it only work with all
Premature optimization is the root of all evils, it's true
is it acceptable/sensible to access the literal values at runtime?
i've often had cases like
ValidColumn = Literal['a', 'b', 'c']
valid_column_set = {'a', 'b', 'c'}
seems like a good way to reduce duplication to me
is it possible in 3.9?
>>> get_args(Literal[1, 2, 3])
(1, 2, 3)
ah, ty
In [12]: Literal[3+5]
Out[12]: typing.Literal[8]
mypy would not allow that, right?
e.g. i can't construct a literal from an expression of literals
so i can write this:
ValidColumn = Literal['a', 'b', 'c']
valid_column_set = set(typing.get_args(ValidColumn))
but i assume it's not possible to write something like this:
valid_column_set = {'a', 'b', 'c'}
ValidColumn = LiteralFrom[valid_column_set]
Static analyzers will only check for actual literals in the call to Literal
Or, at least they're not obliged to do more
is there much point in using get_args over .__args__ since dunder args are documented in 3.9?
I don't really care
So for psutil there's a namedtuple that may or may not get certain keys inserted into it depending on a condition```py
fields = ['user', 'nice', 'system', 'idle', 'iowait', 'irq', 'softirq']
vlen = len(values)
if vlen >= 8:
# Linux >= 2.6.11
fields.append('steal')
if vlen >= 9:
# Linux >= 2.6.24
fields.append('guest')
if vlen >= 10:
# Linux >= 3.2.0
fields.append('guest_nice')
scputimes = namedtuple('scputimes', fields)
For the related pyi file I have this:```py
class scputimes(NamedTuple):
user: Any
nice: Any
system: Any
idle: Any
iowait: Any
irq: Any
softirq: Any
steal: Any
guest: Any
guest_nice: Any
Is this correct since those last 3 values are only available under certain conditions?
Yes I guess that's the best you can do. You should add a comment saying something like "this field is only available when X"
Also we merged a couple of psutil PRs recently, make sure you don't merge conflict ๐
Yep I always pull from the master branch of the typeshed repo and handle the merges on my fork before making a PR
Also is there a specific reason that so many imports are like import test as test when there is no name change for that import?
Yes, that means the name is re-exported
tuple[int, ...]
yes
then you're separating code and data
I think it's absolutely fine
In typescript there's the typeof operator, so you can do this:
but no, in Python you can't do that
(as const tells the compiler to infer ["yes", "no", "maybe"] as an immutable literal type and not string[])
interesting indeed
i wonder why it seems so much harder to make typed python than typescript
or was typescript just a matter of "brute force" language design/implementation effort?
Maybe Python has been too conservative in adding new features to the type system. TypeScript is a separate language compiling into JavaScript, which gives them more freedom. For Python, a new type system feature needs agreement from several type checkers and lots of people.
Typescript was kind of built around typing lol
indeed. i suppose that's partly my question, why no "typescript but for python" has emerged yet
maybe it's because the community is just moving slower, or like you said python itself has been very conservative in rolling it out
Mypy was originally intended as that but it didn't work out that way
interesting. maybe python libraries tend to be more loose about types?
Maybe because transpilation is easier for a language you run in the browser
possibly, i'm not sure. i know that an enormous and impressive amount of transpilation happens in the javascript ecosystem every day
it might be a matter of who found it worth their while to fund it
Right, that's a big one too
i continue to be dismayed at how little actual support python core development seems to get from mega corporations
you'd think the big cloud providers at least would have an interest in dumping funding into pypa stuff
why pay for something you can get for free?
well, i'd imagine it benefits them to have better python packaging
imagine super-fast VM builds from having a network-localized/cached package index
or even just better tooling
i guess it doesn't benefit them enough, but still
Do I hint a chainmap same a dictionary, with a key and a value component?
chain : ChainMap[str, str] for example
https://docs.python.org/3.9/library/typing.html#typing.ChainMap seems that way, yes
Thanks Salt!
๐ง
eww 3.9 docs
Did you just make a crack at 3.9 for being outdated? XD XD XD
You butt
An entire 25hrs out of date smh
it was just in search history!!
Yeah Typescript can add whatever shit they want and just have it compile away at runtime.
You also have to consider the update process, I mean Python updates once every at least 1 year..
But TypeScript can rather trivially roll out an update on NPM and people will quite quickly naturally update.
It's much less often you have people update Python version. Do you install the bugfixes that Python releases?
chainmap can be directly subscripted you dont need to use the typing variant anymore
yep, but they don't yet list the Mapping[KT, VT] stuff in collections.abc yet, do they?
yeah for sure, but i guess that's my point. why not make a superset of python?
Some people have done that with import hooks but it never really took off. I think Guido wrote once about how they used to use import hook magic for something at Dropbox (embedding HTML?) but they got rid of it because it was too annoying to maintain
But MacroPy does exist
I think also people use encoding shebangs for html stuff
Oh yes, I think it was encoding, not import hooks
They do in 3.9+
isn't it a lot easier to compile to python than to use an import hook though?
actually... maybe not ๐
I think the trouble will be that you have to reimplement the import system
And the import system is dark and full of terrors
Noooooooo its not
Its bogged down with a lot of version checks, the "the import system" for any given version isn't all that complex
Building a compiler, thats... thats a form of black magic until itself. Not necessarily difficult, but it takes some modes of thought that don't come easily
i don't know how typescript does it, but Hy for example just compiles (import foo) to import foo
Sure, but the type checker needs to know what import foo refers to
ah, valid
also the issue in hy is hy-specific modules (with the require macro to import hy macros), which i'm not sure even have a well-defined package search mechanism yet
Any idea why the mypy test in typeshed for python 3.6 would say that a function return dict[Any, tuple[int, ...]] has an issue with the ... portion?
The same idea applies that the tuple is of variable size but contains only integers
Mypy doesn't support lowercase tuple[int, ...] yet I think
Have to use Tuple
TypeVars are pretty much what the name tells you - its a variable representing a certain type
Generic classes/functions are those that can work with arguments of various types, for example if you were making your own list class from scratch that could only hold elements of the same type, you'd do:
from typing import Generic, TypeVar, Iterator
T = TypeVar("T")
class MyHomogenousList(Generic[T]):
def __init__(self, *items: T):
...
def __getitem__(self, index: int) -> T:
...
def __iter__(self) -> Iterator[T]:
...
you could then create my_int_list = MyHomogenousList[int](1, 2, 3, 4) or my_str_list = MyHomogenousList[str]("a", "b", "c") etc
when your class/function is generic, its essentially taking a type as a parameter
ParamSpec is for storing information about the parameters of a callable
if you were writing and typehinting a decorator, you previously couldn't do better than:
R = TypeVar("R")
def decorator(func: Callable[..., R]) -> Callable[..., R]:
def inner(*args, **kwargs) -> R:
func(*args, **kwargs)
return inner
you had no way of saying that the annotations of *args and **kwargs should be the same as that of func, so you lost that information
ParamSpec allows you to do just this:
P = ParamSpec("P")
R = TypeVar("R")
def decorator(func: Callable[P, R]) -> Callable[P, R]:
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
func(*args, **kwargs)
return inner
That means something different. What context are you thinking about?
def id(arg: T) -> T: return arg
well, that's a different thing
if you do that mytype represents a single, constant type
a TypeVar is a variable representing any type
if you have like
def f(l: list[T]) -> T:
T would be int when you did f([1, 2, 3]), or str if you did f(["a", "b"])
as opposed to
def f(l: list[Optional[foo]]) -> Optional[foo]:
which would only work with Optional[foo]'s
yeah
you're referring to my_int_list = MyHomogenousList[int](1, 2, 3, 4)?
yeah they are
if you werent using a typechecker it would be equivalent to just MyHomogenousList(1, 2, 3, 4)
just that typecheckers will now enforce the int everywhere there's a T
Generally it's useful when there's two or more types (like an arg type and a return type) in a signnature and you want them to be all exactly the same
Yep I have to use typing.Tuple instead of the built-in tuple
Is there any reason why tuples of unknown sizeuse ... to convey that while lists do not need to do that?
Since a list can contain any number of element types just like tuples
Then again I am using list instead of typing.List
@rustic gull A TypeVar is like a function parameter, but on types.
Python is the only language I know where you have to declare them separately
e.g. in TypeScript you'd do ts function identity<T>(arg: T): T { return arg; } and in Haskell you would do ```hs
identity :: a -> a
identity arg = arg
-- or:
identity :: forall a. a -> a
identity arg = arg
lists are assumed to be any size as that's their main use, while with tuples the main use would be some structured data of a known size
Just mypy implementation details. I think this is fixed in the next release
(talking about Tuple vs. tuple, @void panther is right about tuple[int, ...] ts tuple[int]
Ty you both for the info!
wishlist item: be able to tell mypy something like this:
if
foo(x)returns a value (e.g. does not raise an exception), then refine the type ofx.a1 : float | Nonetofloat
like a TypeGuard on a specific attribute of an argument, not the argument itself
def check_x(thing: Thing) -> TypeGuard[Not[None], Thing.x]:
return thing.x is not None
another wishlist item:
_Num = TypeVar('_Num', int, float)
class Base:
def foo(self, val: _Num, option1: Thing1 | None = None) -> _Num: ...
class Sub(Base): ...
def foo(self, val: float, option1: Thing1 | None = None) -> float: ...
indicate that Sub.foo argument val has type float and not int | float without rewriting the rest of the signature for foo
That was mentioned when TypeGuard was discussed, but it might be difficult to implement.
If the condition is something inherent to the class, what you could do is define a property which checks the condition, then casts & returns.
Does mean the check's done each time which might be a perf issue, but it makes it type-safe.
yeah i've had to write stuff like that before
definitely would hurt performance in hot areas
probably better to just assert and run python with -O
Not sure how this is possible but I'm getting this error when trying to do from typing import NamedTuple:
stubs/psutil/psutil/__init__.pyi (3.9): pytype.load_pytd.BadDependencyError: No NamedTuple in module typing, referenced from 'psutil'
Yet I can see that same import being done in other libraries with no issue, real odd
TypeGuard? ๐
Nevermind I see the issue
I was doing def memory_info(self) -> NamedTuple: ... for some functions which is not correct, ended up using the imported namedtuples that are defined in other psutil files -> def memory_info(self) -> pmem: ... with pmem being a namedtuple
The docs say that Any doesn't actually mean "anything" - that would be the object type hint
So then what's the purpose of Any?
if you typehint as object, your typechecker will only allow operations valid for all objects
if you typehint as Any, your typechecker will basically ignore everything you do with it
consider this for example https://mypy-play.net/?mypy=latest&python=3.10&gist=5d01d2976b84aa8125f8512ca416d87a
mypy complains for object, but not for Any
are you familiar with type narrowing?
No
if you have like
def f(x: int | None):
if x is not None:
...
within that if, the type of x is narrowed down to just int
so you'd be able to do normal int operations within that if without your typechecker complaining
in this case it was a simple is not None check, but if you want a more complex condition for narrowing, you use TypeGuards
consider this example from the PEP
class Person(TypedDict):
name: str
age: int
def is_person(val: dict) -> "TypeGuard[Person]":
try:
return isinstance(val["name"], str) and isinstance(val["age"], int)
except KeyError:
return False
def print_age(val: dict):
if is_person(val):
print(f"Age: {val['age']}")
else:
print("Not a person!")
in print_age, the type of val is narrowed done from dict to Person within the if
Yeah, Any functions as both a top type (anything coerces to it) and a bottom type (it coerces to anything) whereas object is just a top* type
*with bonus __str__, __repr__, __eq__, ...
yay, pylance now has autocompletion for TypedDict keys
hi! if i from __future__ import annotations, can i use the py3.9 syntax for type annotations (for variables) even if the code needs to be able to run on py3.7+? are there gotchas? mypy is up to date.
Yep, you can
cool, thanks ๐
btw in this case it's not inherent to the class, it's something that is None when initialized, but not-None in certain private methods that will only be called in certain situations. right now i'm just sticking an assert in those private methods
ok that's epic
I'll start using typeddict more now lol
Ehhh
It's not suitable for general interfaces and such
I'm using it to describe an existing API
Yes but only in actual annotations, not in places like type aliases, base classes, TypeVar bounds, cast()
and if anything tries to evaluate them, you'll get hit with an error
yep
if you want to use them in other places you need to annotate them as TypeAliases from typing_extensions
I think mypy doesn't support that yet. Also, it doesn't help with syntax that's not valid on old Python versions
well everything that doesnt support it is deprecated now
what do you mean?
like 3.6 has variable annotations and anything below 3.5 has been sun set
Right, but that doesn't mean TypeAlias works with mypy
And there are various other contexts where you might want to use new syntax at runtime
So psutil is super hard to add definitions for importing
If something is defined in it's _common.pyi file, then it gets imported to both the individual platform's pyi files and well as the __init__.pyi file
For example, the __init__.py file has a function called disk_usage. This calls the disk_usage function defined for the specific platform that's in use (windows, linux, etc.), which returns a namedtuple sdiskusage that is imported from _common.py
The issue is that if you import sdiskusage from _common.pyi into the _pswindows.pyi file to define the return value for the disk_usage method, and you do the same import into __init__.pyi then the stubtest will throw an error
The only way around this is to import sdiskusage from the individual platform's definition file
Maybe that's fixable in stubtest? Do mypy and pyright also complain?
Normally pytype will complain
I'm fixing it by manually adding the _common.pyi imports into each platform's pyi file, then importing from them into __init__pyi
pytype has some strange issues sometimes ๐ฆ
n: int = 2 should this be Optional[int]?
Can it be None at any point? If so, yes
ah, it cannot be None, thanks
Afaik you don't need to add type when you do n = 2
that would make sense yea but I recently read https://www.python.org/dev/peps/pep-3107/#syntax
I'm getting bpython/formatter.py:28: error: Skipping analyzing "pygments.formatter": found module but no type hints or library stubs with the latest mypy on Python 3.9, is there something I'm missing? pygments is in typeshed, which should be used automatically, right?
Typeshed stubs for third-party libraries are no longer bundled with mypy. You probably need to install types-pygments
Optional would just say it can be None, i's the same as doing n: Union[int, None] = 2
is there a way to ignore mypy type check inside if block?
if x:
name: str = 123 # no error
age: int = "alex" # error
something like that
What is the error you're getting though
Cause you're asking to ignore errors inside if blocks but your error is outside of it
that's just an example, I'm looking for something like that.
as you can see, the error is ignored only inside the if block
What is your use case for this? There's various tools like # type: ignore and if TYPE_CHECKING:
thanks i found this
if not typing.TYPE_CHECKING:
Callable[..., T] what exactly is this saying?
T = TypeVar("T")
!d typing.Callable
typing.Callable```
Callable type; `Callable[[int], str]` is a function of (int) -> str.
The subscription syntax must always be used with exactly two values: the argument list and the return type. The argument list must be a list of types or an ellipsis; the return type must be a single type.
There is no syntax to indicate optional or keyword arguments; such function types are rarely used as callback types. `Callable[..., ReturnType]` (literal ellipsis) can be used to type hint a callable taking any number of arguments and returning `ReturnType`. A plain [`Callable`](https://docs.python.org/3/library/typing.html#typing.Callable "typing.Callable") is equivalent to `Callable[..., Any]`, and in turn to [`collections.abc.Callable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable "collections.abc.Callable").
Saying it takes any amount of arguments of any types and returns T
!d typing.TypeVar
class typing.TypeVar```
Type variable.
Usage:
```py
T = TypeVar('T') # Can be anything
A = TypeVar('A', str, bytes) # Must be str or bytes
``` Type variables exist primarily for the benefit of static type checkers. They serve as the parameters for generic types as well as for generic function definitions. See [`Generic`](https://docs.python.org/3/library/typing.html#typing.Generic "typing.Generic") for more information on generic types. Generic functions work as follows...
๐
i see typeguard will become unmaintained soon, would you say pytypes is a good replacement?
i personally like the look of bear-type
bear-type.. let me check it out
https://github.com/beartype/beartype looks pretty neat
i remember that being brought up once in #internals-and-peps
is there a way to put it in a module and have it annotate all functions/methods?
not currently
aww crap
the maintainer seems like a fun dude though so thats a plus
anyone here 
so i have my method
def __setitem__(self,index : int ,value):
self.List[index] = value
return bool(self.List)``` i want to type hint value to anything , what should i use ?
typing.Any
!d typing.Any
typing.Any```
Special type indicating an unconstrained type.
โข Every type is compatible with [`Any`](https://docs.python.org/3/library/typing.html#typing.Any "typing.Any").
โข [`Any`](https://docs.python.org/3/library/typing.html#typing.Any "typing.Any") is compatible with every type.
Most likely you should make the class Generic over a TypeVar T, then type value as T
Can I somehow type all of my variables inside class as Final?
So instead of typing (sic ;)) Final each time, use something shorter
you just as import it which would allow you to do var: F = "1234"
from typing import Final as F is shorter but that isn't as much fine as I expect
those should also be ClassVars not Final fyi
indeed, both ClassVar and Final (does ClassVar imply Final?)
yeah
hm, https://docs.python.org/3/library/typing.html#typing.Final provides an example with Final in class ๐
nevermind
btw I'm wondering how can I type a length of string (bussiness logic - Youtube video ID [11 characters] vs YT track url (much longer))
https://stackoverflow.com/questions/44833822/specify-length-of-sequence-or-list-with-python-typing-module but I am not sure how to properly use Annotated
theres no builtin way to do it Annotated is for adding special info about the type of something but it doesnt have actual use to type checkers cause they just make whatevers in the first argument the type of the variable
some weird things are up with the typing docs, the code blocks are all non-fixed width fonts for some reason
okey, thanks ๐
so like how exactly is type hinting an 'advanced' concept its so simple no?
yes, mostly
Depends on what you mean by 'advanced'
Type hinting in python is just developing, so there are no clear standards and it's all very messy
If all your code needs is str, int, list and dict, then it is indeed very simple
lol
well maybe you need to say 'only pass my classes'
meaning not primitive ones?
I meant not using typevars and beyond
And even then you'll need to understand e.g. why you can't pass list[int] to a function expecting list[Optional[int]]
But again, how do you define 'advanced'
Sorry this was wrong @deep pendant it's the other way round it's in pep 591
How to type returning class object inside the class ('s staticmethod)?
You can put the class name in quotes in the return type (e.g. -> "MyClass"
on 3.7+ you can use from __future__ import annotations and then you don't need the quotes
But this means you'll return exactly that class. If you care about subclasses that would return an instance of the subclass, define a TypeVar bound to your class (e.g. MyClassT = TypeVar("MyClassT", bound="MyClass") and write your method as def clsmethod(cls: Type[MyClassT]) -> MyClassT: ...
I return exactly this class ๐
I already do function annotation, which looks like following:
def do_something(info_foo:str, info_bar:int) -> int:
...
...
...
But I was also thinking of specifying what data types variables are inside a function or method, so:
info:int = do_something()
I wanted to eliminate this extra overhead of having to trace things back or hover over something to know what data type it is.
That's legal in 3.6 and higher. Usually it's not necessary for static type checkers though, because they can infer local variable types.
Is it considered a good or bad practice, or depends on the use case?
Because I keep finding myself tracing through different parts to determine, if it's a str or int
Yeah, if you put annotations everywhere your code will get cluttered and will look like Java before type inference
I would usually consider it excessive. Editors like VSCode should be able to infer the type and show it on hover already
what type hint is commonly used for something that is sortable?
There's no very convenient way to do it. In typeshed we define sorted with a custom protocol that requires __lt__
hmm so just
class Sortable:
def __lt__(self, other):
...
?
You need to inherit from Protocol, and I'd need to think more about what the argument to other should be
right, assume other was just a T = TypeVar('T', float)
so im just gonna basically inherit from this https://docs.python.org/3/library/typing.html#typing.Protocol like
from typing import Protocol, TypeVar
T = TypeVar('T', float)
class Sortable(Protocol[T]):
def __lt__(self, other: T) -> bool:
...
if that's even right
Oh no I dont mean to place it everywhere, just in the beginning where the variable is declared, and afterwards no, so something like
quantity:int = do_something()
if info >= 9:
info = 9
elif info < 9:
info = 0
Does this still seem cluttered?
from typing import Protocol, TypeVar, Iterable, runtime_checkable
T = ('T', float)
@runtime_checkable
class Sortable(Iterable[T], Protocol[T]):
def __lt__(self, other: T) -> bool:
return self < other
sorry for the ping @oblique urchin but this would be fine right? if it's of any help, i only want to use this as a type hint for things that i'm going to pass to certain sorting algorithms that i want to implement. T can just be float for now
Do you mean TypeVar("T", bound=float)? That doesn't seem terribly useful
Also your Sortable class conflates the type of the collection and the member object
The collection is the one that's Iterable, but the member is what has __lt__
oh yeahh that's a good spot, thanks i'll read the docs again
If we wanted tp declare a list of type string, is below how we would do it?
llist:[str] = []
llist.append("123")
llist.append("456")
llist.append("789")
print(llist)
No, use list[str] (or List[str] on earlier versions)
in particular either list the built-in or List from the typing module
oh ok like this:
llist:List[str] = []
# or, but only use one not both
llist:list[str] = []
llist.append("123")
llist.append("456")
llist.append("789")
print(llist)
!d typing.List
class typing.List(list, MutableSequence[T])```
Generic version of [`list`](https://docs.python.org/3/library/stdtypes.html#list "list"). Useful for annotating return types. To annotate arguments it is preferred to use an abstract collection type such as [`Sequence`](https://docs.python.org/3/library/typing.html#typing.Sequence "typing.Sequence") or [`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable "typing.Iterable").
This type may be used as follows:
```py
T = TypeVar('T', int, float)
def vec2(x: T, y: T) -> List[T]:
return [x, y]
def keep_positives(vector: Sequence[T]) -> List[T]:
return [item for item in vector if item > 0]
```...
Yup! Just make sure if you're using the older List form to import it from typing beforehand
from typing import List
l: List[str] = []
list[str] can be used on Python 3.9 or higher while typing.List[str] can be used since 3.6
oh ok ok thanks that answers the next question I was about to ask. Thanks!
can we do this for return type that returns a str, str, and int:
def do_something() -> List[str, str, int]:
or do we not do that and we do something like following:
from typing import TypeVar, List
T = TypeVar(str, str, int)
def do_something() -> List[T]:
Neither. You want something with a Union (or | in 3.10+)
so we do?:
from typing import Union
def do_something() -> Union[str, str, int]:
I also see lol:
Redundant arguments are skipped, e.g.:
Union[int, str, int] == Union[int, str] == int | str
Yes! although the extra str is unnecessary ^^
Yes I'm not sure if you mean it returns a list or ints or strs, a list of ints or a list of strs, a list containing a str, a str, and an int, or something else
Because if you want to return a ["hi", "hello", 0] list, consider returning a tuple instead
ah I misread the question (probably took it too literally), good catch
Yeah, I agree you have a good point. I should return a tuple instead
so in terms of type annotation:
def do_something() -> Union[str, int]:
return ("foo", "bar", 123)
We could use the same type annotation for a List or a tuple? Of course, we just return a tuple instead of a list inside the function
You should do something like
def do_something() -> Tuple[str, str, int]:
pass
tuples are usually considered struct-like objects so such a type annotation signifies a tuple of three items with types in that order
oh ok thanks!
Union[str, int] would mean you return either a str or an int
Actually I think I have misused lists tbh. So many cases like above where I could have returned a tuple(because that data would have been used as is with no append/insert/pop and so on), but I returned a list instead.
Hello guys, i have a question about type guards. Admit we create a type
TestType = NewType("TestType", str)
can we further do a type check like
test-var = TestType("jean")
if isInstance(test-var, TestType):
print("somthing")
No, NewTypes do nothing at runtime so you can't isinstance check them
Hmm okay i was thinking about creating a class TestType so that we can use isInstance but the problem is we cant access directly the value of the object
test-var = TestType("thing")
print(test-var) # without writing test-var.property
but idk if this is possible :x
you can override __str__
Okay ty !
Actually, isinstance(x, TestType) should fail because TestType is a function at runtime
Not in 3.11 ;)
What is typing.Protocol?
It should still return False though. Maybe we need to put some check at runtime to make sure people don't do that
See PEP 544 and https://docs.python.org/3/library/typing.html#typing.Protocol. Basically, it's used for structural typing, where you base type compatibility on the presence of particular methods instead of inheritance
ah, thank you
that does sound like a good idea
mypy's docs are also a useful explainer, see https://mypy.readthedocs.io/en/stable/protocols.html in particular https://mypy.readthedocs.io/en/stable/protocols.html#simple-user-defined-protocols
its failing if i create the type with NewType but it works if NewType is a real class
I'm trying to type hint code that is using a lot of numpy functions. When I run mypy on the last example given in the numpy typing docs https://numpy.org/doc/stable/reference/typing.html#numpy.typing.NBitBase I get the following error message:
๎ฐ mypy tmp.py
tmp.py:11: error: Returning Any from function declared to return "floating[Union[T1, T2]]"
tmp.py:20: note: Revealed local types are:
tmp.py:20: note: a: numpy.floating[numpy.typing._16Bit*]
tmp.py:20: note: b: numpy.signedinteger[numpy.typing._64Bit*]
tmp.py:20: note: out: numpy.floating[numpy.typing._64Bit*]
Found 1 error in 1 file (checked 1 source file)
Can somebody verify that it is not just me
Ok, apparently this was due to my mypy.ini where warn_return_any was configured as True
Why does this not show a warning?
def func(data: list[str]):
print(data)
func([1, 2, "3", 4, 5])
Type annotations are ignored at runtime. A static type checker should catch this though.
How is this not "static"?
Not sure what you mean
Are you just running that code with Python?
I mean you should use a tool like mypy or pyright to typecheck the code
I just wanna see the warning in the IDE as usual
If I change one of the items to a string it accepts it
That is probably related to whatever typechecker your IDE is using in the background. With mypy this gives me the following errors:
๎ฐ mypy tmp6.py
tmp6.py:5: error: List item 0 has incompatible type "int"; expected "str"
tmp6.py:5: error: List item 1 has incompatible type "int"; expected "str"
tmp6.py:5: error: List item 3 has incompatible type "int"; expected "str"
tmp6.py:5: error: List item 4 has incompatible type "int"; expected "str"
Found 4 errors in 1 file (checked 1 source file)
@brazen horizon
https://mypy-play.net/?mypy=0.910&python=3.10&flags=show-error-codes%2Cstrict&gist=a9d2aeb5a20e2024458a93b2bcab6575
is there a correct way to write class FrozenList(tuple[T, ...]):?
yes, i knows this is kind of silly, but i'm having fun
Frozen list is already a module on pypi
Iirc aiohttp uses it
Oh that's not what this is doing
yeah this is a "silly" implementation, but the practical question is about if it's possible to write a Generic[T] class that is a subclass of tuple[T, ...]
Mypy might not like it, tuple is heavily special cased
You might be able to do it with pyright and pep 646
mypy indeed didn't like it
class FrozenList(tuple[*Ts]):
...```should something like this be possible or is this just a worse version of subclassing tuple[T, ...]?
Maybe in the PEP 646 future
Isn't Literal also that?
Is there a way to typehint dictionaries to show the keys and corresponding type?
E.g. if I store a person's data in a dictionary it may be something likepy people = [{"name": "Alex", "age": 21, "height": 1.58}, {"name": "Bob", "age": 20, "height": 1.73}, ...]If I then pass people into a function, how should I typehint it?
something like this?
yea, TypedDict is the way to go
I would probably end up creating some kind of dataclass if Data contained anymore values
Perfect, thanks
Yw
Wishlist item: statically type-checked jsonschema
but... PyDantic
not just a json recursive type
literal jsonschema
which of course you can guard with a TypeGuard
does anyone know if there's a way to make the type system know that fn has an execute_async method on it?
FuncT = TypeVar('FuncT', bound=Callable[..., Any])
def gevent_coroutine(fn: FuncT) -> FuncT:
def execute_async(*args, **kwargs):
return ExecuteAsyncWrapper(fn, args, kwargs)
setattr(fn, 'execute_async', execute_async)
return fn
why are you using setattr?
idk, isn't that the way to add an attribute to something?
fn.execute_async = execute_async would work too, and would probably work better with type checkers and IDEs
you'd probably have to do something like this:
from collections.abc import Callable
from typing import Protocol, TypeVar
class HasExecuteAsync(Protocol):
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
def execute_async(self, *args: Any, **kwargs: Any) -> Any: ...
FuncT = TypeVar('FuncT', bound=HasExecuteAsync)
def gevent_coroutine(fn: FuncT) -> FuncT:
def execute_async(*args, **kwargs):
return ExecuteAsyncWrapper(fn, args, kwargs)
fn.execute_async = execute_async
return fn
hmm
error: "FuncT" has no attribute "execute_async" if i assign the attribute directly
oh that's interesting
i will give that a shot
right, that's because Callables have no execute_async attribute that the type checker is aware of. setattr just bypasses the type checker, it doesn't really fix the problem
cannot assign to method
heh
maybe execute_async: Callable[..., Any]
nah its still angry
well i can use type: ignore there anyway
error: Value of type variable "FuncT" of "gevent_coroutine" cannot be "Callable[[int, bool], Optional[Foo]]"
hmm
Is this PyRight or MyPy?
mypy
Are you able to generate a playground example? https://mypy-play.net/
At the top
inside a huge scary monorepo with like 2000 layers of weirdness and venvs
Ah I see, and there's types crossed from everywhere that you cannot isolate?
ugh, that issue
i forgot about that one
i guess use setattr and leave a comment. but at least with the bounded TypeVar + Protocol you know that the method is expected to exist
Well that's not an issue ๐ . From what I've seen MyPy just doesn't allow it.
Actually, what if we make it an attribute on the protocol?
the assigning to method thing doesn't matter i can type: ignore it
its code that uses the decorator i'm more concerned about
gist with more errors heh https://mypy-play.net/?mypy=latest&python=3.10&gist=2ba65d6584378a17a08134d5494dd8a2
i did!
So I guess the issue is that MyPy is really not happy with methods that turn into something else ๐
Because FuncT is a FunctionWithExecuteAsync and not a callable with the types of the method
ah i think i see the issue
the decorator needs to accept a plain callable as input, and emit a "wrapped" callable with that extra attribute as output
i don't think this can be typed correctly without ParamSpec
that, or, somehow that Protocol can inherit from a parameterized Callable?
i tried paramspec before but it doesn't seem my mypy supports it
it doesn't (yet)
Even then, would that fix it?
i think so? then you can type the args/kwargs/return of __call__
It doesn't no ๐
Invalid base class "Callable"
๐
I already tried that yeah lol
yes, please
or in this case |
you'd need some way to distinguish syntactically between TypedDict and class instances
but otherwise yes
i'd even settle for object(attribute: U)
I've been able to create this @nocturne snow:
from typing import TypeVar, Protocol, Any, Optional, Callable
from typing_extensions import ParamSpec
P = ParamSpec('P')
RT = TypeVar('RT')
class ExecuteAsyncWrapper:
def __init__(self, fn, args, kwargs):
self.fn = fn
self.greenlet = gevent.spawn(fn, *args, **kwargs) # type: ignore
def result(self):
self.greenlet.join()
return self.greenlet.value
class AsyncCallable(Protocol):
def __call__(self, *args, **kwargs) -> Any: ...
def execute_async(self, *args, **kwargs) -> ExecuteAsyncWrapper: ...
T = TypeVar('T')
def gevent_coroutine(fn: Callable) -> AsyncCallable:
def execute_async(*args, **kwargs):
return ExecuteAsyncWrapper(fn, args, kwargs)
fn.execute_async = execute_async
return fn
class Foo:
@gevent_coroutine
@classmethod
def bar(cls, a: int, b: bool) -> Optional[str]:
pass
Foo.bar(1, True)
Foo.bar.execute_async(1, True)
There's two errors though, and that's on fn.execute_async = execute_async ("Callable[..., Any]" has no attribute "execute_async")
As well as Incompatible return value type (got "Callable[..., Any]", expected "AsyncCallable") when returning
Sadly, you loose the arguments of the function when this is done. Which is exactly what ParamSpec solves.
You'll basically have to pick between having type checking of arguments or typechecking execute_async.
i think the two requirements are
- normal function call is fully typed
- execute_async exists as a function (but the args and return can be any)
currently i've decided to do this as a not-decorator
actually i wonder
what happens if a decorator is a class
it replaces the function with an instance of the class
And you'll loose the function call types
some decorators in the stdlib do this, i actually prefer writing them that way
but yes you lose some type information
property ๐
again, paramspec would fix this
All praise tho mighty ParamSpec ๐
type()?
reveal_type()
nice
ah
No need to import it, the type checker will just notice its usage
ye
its part of the stdlib?
its part of mypy
It's not just mypy
i wish it were actually in typing so you could use it without breaking your code
but then how does it not error if it hasn't been imported?
Cause it adds it to the namespace
but wouldn't that break the code when not ran with mypy?
Yeah it breaks at runtime
so its basically a function that can be added, run mypy, and then delete ;-;
it should be from typing import reveal_type imo
currently you have to guard it behind if TYPE_CHECKING:
I just delete them before I run
yeah i wouldn't leave them in code
I also feel like this was considered at some point so there's probably a reason it's not in typing
yeah i assume there's a reason for it
Try adding a function call that would be illegal
You're loosing type checking of the arguments
honestly dynamicness is why i love and hate argparse
dumbass mypy