#type-hinting

1 messages ยท Page 51 of 1

rapid shoal
#

i say as long as your functions are properly hinted, if the users don't follow them it's their fault

brisk hedge
#

A leaky implementation + bad debugging ux, I'll pass

#

And not everyone uses static analyzers anyways

boreal ingot
#

fair point

trim tangle
#

yeah

boreal ingot
#

tbh I am just not a big fan of putting isinstance in all my functions ๐Ÿ™ƒ

trim tangle
#

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

boreal ingot
fierce ridge
#

not all TypeErrors are surrogates for static types, i think if you raise it explicitly in code it should be checked as normal

boreal ingot
#

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?)

trim tangle
#

for example, if some constraint is not expressable in type hints

boreal ingot
#

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)

brisk hedge
#

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

boreal ingot
#

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

brisk hedge
#

even normal typing can't do refinement types or value predicates partly because python typing is duct tape

boreal ingot
#

one thing that would be very interesting is basically a Not, like you could say Not[Literal[0]]

soft matrix
#

that should be ~Literal[0]

#

gotta have syntax for everything

boreal ingot
#

ofc

soft matrix
#

but yeah a Not type would be really appreciated

boreal ingot
#

but it kinda would need to be in combination with a And, so you could do int & (~Literal[0])

brisk hedge
#

RIP type intersections

soft matrix
#

i cant wait for intersection types

#

i was planning on trying to get that through after i finish typing.Self

boreal ingot
#

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```
fierce ridge
#

why did you use T for the predicate and P for the underlying type

#

my head is so full of fuck reading this

boreal ingot
blazing nest
#

Oh I see in the if-statement

boreal ingot
#

yhe those code blocks are in the wrong order xD

fierce ridge
#

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)

boreal ingot
#

maybe, when I tested it resolved bar to T

#

main.py:39: error: No overload variant of "add" matches argument type "T"

fierce ridge
#
main.py:39: error: No overload variant of "add_overload" matches argument type "P"
#

yeah

#

weird

#

(i swapped the letters :P)

#

bug report?

boreal ingot
#

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

fierce ridge
#

is there a way to set 2 upper bounds on a typevar

#

like "must be a subclass of both int and Predicate[int]"

boreal ingot
#

I dont think so
(I would love the be able to just do that with type hints in general)

fierce ridge
#
Int = TypeVar("Int", bound=Union[int, "IntConstrained"])

class IntConstrained(int, Predicate[int]): ...
boreal ingot
fierce ridge
#

oh, yeah

#

idk why

#

also not the same thing as must be both

#

this "No overload variant" issue definitely seems like a bug

boreal ingot
boreal ingot
fierce ridge
#

i think it has to do with the IntPositive having multiple bases

boreal ingot
#

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"

fierce ridge
#

wat

boreal ingot
#

ยฏ_(ใƒ„)_/ยฏ

fierce ridge
#

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")
boreal ingot
#

wat

fierce ridge
#

ยฏ_(ใƒ„)_/ยฏ

boreal ingot
#

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

fierce ridge
#

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

trim tangle
#

I could make a pyright playground some time in the future

boreal ingot
boreal ingot
fierce ridge
#

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...

boreal ingot
#

should it not type check ๐Ÿ™ƒ

fierce ridge
#

i think it should!

#

but i'm not a type wizard either

boreal ingot
fierce ridge
#

i don't even know how to describe this bug

boreal ingot
#

well first I think we should narrow it down to the smallest example that stil gets it wrong

fierce ridge
#

"chaos ensues when a typeguard is parameterized by a typevar"

boreal ingot
fierce ridge
#

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

boreal ingot
#
main.py:13: note: Revealed type is "T`-1"```
fierce ridge
#

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?

boreal ingot
#

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`
fierce ridge
#

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

boreal ingot
#

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 ๐Ÿ™ƒ

oblique urchin
#
...     def __add__(self, other):
...             return X(1)
...     def __radd__(self, other):
...             return X(1)
... 
>>> X() +1
1
>>> type(X() +1)
<class '__main__.X'>
boreal ingot
oblique urchin
#

We may have tried to fix this in typeshed but given up, dunders tend to be tricky to type

boreal ingot
#

all subclasses of IntConstrained overwrite it, but not IntConstrained it self

boreal ingot
#
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.....

boreal ingot
boreal ingot
blazing nest
#

What did I just stumble upon?!?!

#

!e ```python
def func(arg: ('this is valid', 'huh?')) -> None:
return

rough sluiceBOT
#

@blazing nest :warning: Your eval job has completed with return code 0.

[No output]
soft matrix
#

For now ;)

#

Guido has said that he wanted to make types the only things you could put in annotations

trim tangle
soft matrix
#

Atleast at one point

trim tangle
#

and yeah, annotations were initially meant for lots of things

soft matrix
#

You can also use x: (yield 1) = 2 and do some bad stuff

empty harness
#

What's T_co?

blazing nest
empty harness
blazing nest
soft matrix
#

It does in some versions

oblique urchin
soft matrix
#

I'm pretty sure it's a syntax error in 3.10

blazing nest
#

Phew hahaha

soft matrix
#

Yeah

#

Unfortunately

oblique urchin
#

lol

#

you can also use walrus expressions in annotations

empty harness
oblique urchin
#

x: (x := 3)

blazing nest
blazing nest
#

Maybe you're looking for the typeshed repository? Afaik that is where all built-ins gets their types

soft matrix
#

I think there is in 484

#

But I might be wrong

empty harness
#

Hm. Perhaps I should use typing.Tuple, I don't think T_co is what I want

blazing nest
soft matrix
#

I meant the naming conventions

blazing nest
blazing nest
empty harness
#

Cool, I don't need typing, I can just do tuple[Tensor, Tensor, Tensor]

#

I always forget how 3.9 does type hints

soft matrix
#

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.

fierce ridge
#

kinda hungarian-ish

pastel egret
#

Covariance and contravariance defines whether the typevar accepts also subclasses or superclasses of the type.

fierce ridge
#

i meant the _co etc naming convention

twin arrow
#

there are now two salts :0 i never knenw

fierce ridge
#

who's the other one, salt-die?

oblique stone
#

type hinting

#

= nice

twin arrow
#

yes :D

#

salt is yummy with food

#

r salt rock lamps edible

#

the lamp kind

#

not the human kindomg

fierce ridge
#

Not really

#

It's technically salt but probably contaminated with other stuff

mossy leaf
trim tangle
#

for some reason it doesn't work outside the embedded editor

boreal ingot
trim tangle
#

specifically this

boreal ingot
trim tangle
#

oooo

#

nice

#

beautiful

boreal ingot
#

idk why they use a different url there (that actually works) ยฏ_(ใƒ„)_/ยฏ

trim tangle
#

obviously it's not scalable, it will break if two people use it at once

#

but it's a 50-line replit script

trim tangle
blazing nest
#

What changed?

#

Oh just a better name of the repl?

#

Cool

fierce ridge
#

Nice!

spare mauve
trim tangle
#

hm? pithink

spare mauve
#

(if you want belt and suspenders)

trim tangle
#

sorry, I'm confused

spare mauve
#
  • it's just the runtime version of the type checking in your screenshot
trim tangle
#

because someone asked if there is a playground for pyright

spare mauve
#

oh, I see, that's cool ๐Ÿ‘

tardy widget
#

how do i type hint the self keyword?

little hare
#

not really needed

#

typecheckers and everything can figure that out on their own

tardy widget
#

logically, I'd be able to annotate it with the enclosing class in 3.10 right?

little hare
#

I mean, yeah

tardy widget
#

how about type aliases?

little hare
#

but its not needed

rustic gull
#

Don't think it's needed for self. mypy doesn't complain when you don't at least

little hare
#

why do you want to typehint self?

tardy widget
#

but when I do from __future__ import annotations

#

lemme try

lone sparrow
#

That's been pushed back

#

to 3.11 (last I heard anyway, not too sure what the deal is at the moment)

tardy widget
#

ah

#

guess i'm not updated enough

#

how about type hinting for aliases?

lone sparrow
#

Doesn't matter too much, most editors would see ```py
class A:
def init(self: "A"):
pass

rustic gull
#

Oh, I though that would have worked now. Weird how the import future fixes it pithink

little hare
#

its not weird

rustic gull
#

It's importing from actual future 3.11? lemon_glass

little hare
#

the import is deferred annotations

#

eg, all annotations are strings

rustic gull
#

ah

little hare
#

this is the same as what jack sent with from future import __annotations__

tardy widget
#

!e

x: Type[Tuple[int, int]]
a: x = (1, 1)
#

oh wait

little hare
#

!e ```py
class A:
def init(self: A):
pass

rough sluiceBOT
#

@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
tardy widget
#

!e

from typing import Type
x: Type[Tuple[int, int]]
a: x = (1, 1)
lone sparrow
#

I do love abusing future annotations

rustic gull
little hare
tardy widget
#

!e

from typing import Type, Tuple
x: Type[Tuple[int, int]]
a: x = (1, 1)
#

wat

rough sluiceBOT
#

@little hare :warning: Your eval job has completed with return code 0.

[No output]
little hare
#

๐Ÿ˜“

#

there we finally go

tardy widget
#

!e

from typing import Type, Tuple
x: Type[Tuple[int, int]] = Tuple[int, int]
a: x = (1, 1)
print(a)
rough sluiceBOT
#

@tardy widget :white_check_mark: Your eval job has completed with return code 0.

(1, 1)
tardy widget
#

look at the second line lol

#

!e

from typing import Type, Tuple
x: type = Tuple[int, int]
a: x = (1, 1)
print(a)
rough sluiceBOT
#

@tardy widget :white_check_mark: Your eval job has completed with return code 0.

(1, 1)
tardy widget
#

weird

lone sparrow
#

what's weird?

tardy widget
#

the above 2 snippets

#

which do you think are more logical

lone sparrow
#

I mean

#

Technically neither and just a = (1, 1)

#

Your editor will infer it for you

tardy widget
#

@lone sparrow I have this very bad habit of taking some very obvious things NOT for granted and then doing things like above

tardy widget
lone sparrow
#

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)
rough sluiceBOT
#

@lone sparrow :white_check_mark: Your eval job has completed with return code 0.

(1, 1)
lone sparrow
#

Python doesn't care

#

Unless you tell it to care with some other tools

tardy widget
little hare
tardy widget
#

All these time I've been wasting

#

Oh well there must be SOME use to hinting

lone sparrow
#

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

tardy widget
#

Oh well, I forget what symbols do often

blazing nest
blazing nest
boreal ingot
#

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)

indigo locust
#

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?

blazing nest
#

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.

indigo locust
#

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?

brisk hedge
blazing nest
indigo locust
#

So, what do the other types (T_co, etc.) do?

#

Is there a primer? Or do the differ in name and intention only

blazing nest
#

T_co is a type variable that is covariant

#

!pep 484

rough sluiceBOT
#
**PEP 484 - Type Hints**
Status

Provisional

Python-Version

3.5

Created

29-Sep-2014

Type

Standards Track

blazing nest
#

This goes through how variance works in Python's typing

fierce ridge
#

note: you can't use covariant/contravariant type variables in certain situations

trim tangle
#

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)

blazing nest
#

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

oblique urchin
#

possibly with a bound if something has some restrictionns

blazing nest
#

Hmmm, I may need to open a PyRight issue..

brisk hedge
#

What for?

blazing nest
#
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..

brisk hedge
#

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]

blazing nest
#

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

brisk hedge
#

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

blazing nest
#

I cannot give them concrete types though hmm..

brisk hedge
#

What do you mean?

blazing nest
#

If I remove the if-statement it stops complaining so this feels like a PyRight bug yeah.

brisk hedge
#

SlashCommand[Any, Any] gives you errors?

blazing nest
# brisk hedge What do you mean?

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?

brisk hedge
#

If you want to retain that information, you'll need to change your approach to avoid having to use higher-kinded types

blazing nest
#

What do you mean by that?

brisk hedge
#

T can't be generic

#

(in this case, generic over P and RT)

blazing nest
#

T refering to my T = TypeVar('T')?

soft matrix
#

you need to use constraints unless ive over simplified this

#

cause that doesnt give me errors but if i use bounds it does error

soft matrix
#

@blazing nest does this not work?

blazing nest
#

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
soft matrix
#

I used constraints instead of bounds

blazing nest
#

I haven't heard of those, what is the difference?

fierce ridge
#

afaik TypeVar('T', bound=Union[A,B]) is the same as TypeVar('T', A, B, covariant=True)

oblique urchin
#

I don't think you can even have a covariant typevar with value restriction

fierce ridge
#

mypy let me do it!

blazing nest
#

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?

fierce ridge
#

i would have expected bound= to work too

blazing nest
#

The spooky thing is that if I remove the bound from TypeVar, or if I remove the if-statement then PyRight is happy.

fierce ridge
#

are SlashCommand and Subcommand parameterized?

blazing nest
#

So either I pick from runtime safety or I have better editor support lmao

blazing nest
fierce ridge
#

oh i missed that part

blazing nest
#

I tried to add [P, RT] inside the union but that got an error. And T[P, RT] wasn't allowed either.

fierce ridge
#

that works fine in my pyright, v1.1.173

blazing nest
fierce ridge
#

@blazing nest is pyright tmp.py enough to show that it works? i have no pyright config json, no pypyroject.toml

blazing nest
#

Umm- I think so yeah?

soft matrix
#

this code seems to work fine for me

#

if i use constraints

#

and errors if i use bound

blazing nest
#

Yeah that's interesting, huh.

soft matrix
#

im sure jelle could probably tell you if they arent busy

fierce ridge
#

as jelle quoted, constraints are exact

#

no subclasses

soft matrix
#

wait so def join(s: AnyStr) -> AnyStr: return type(s)().join(s) doesnt work for a subclass of str?

#

didnt know that

blazing nest
#

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
oblique urchin
#

Yes, that's right

soft matrix
#

bounds arent needed there

#

for SELF

oblique urchin
#

They might be depending on what you do with arg within the function

blazing nest
#

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()

soft matrix
#

anyways typing.Self soonโ„ข๏ธ

blazing nest
#

Yes I've heard some rumors about it. Sounds awesome!

soft matrix
#

the write up is nearly done i think it should be out in the next week or so unless something goes wrong

soft matrix
#

if you annotate self: SELF is every other use of SELF not of type(self) necessarily?

blazing nest
#

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

soft matrix
#

what no thats not it

#

the scope for that type var is method only

blazing nest
#

No? Aren't TypeVars scoped class-wide?

soft matrix
#

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

blazing nest
#

Oooh, right.

tawny flame
#

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
oblique urchin
#

it looks like you should return the pmem namedtuple, where is it defined at runtime?

tawny flame
#
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

oblique urchin
#

hm what if it's neither linux nor windows?

tawny flame
#

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
    )
oblique urchin
#

We don't support startswith checks on sys.platform

#

In stubs

tawny flame
#

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

rough sluiceBOT
#

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...
```...
oblique urchin
#

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

rough sluiceBOT
#

pyi.py line 270

if isinstance(comparator, ast.Str):```
tawny flame
#

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

oblique urchin
#

Right. We can pretty easily add more sys.platform values to flake8-pyi

#

I don't think type checkers will like .startswith() though

tawny flame
#

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

oblique urchin
#

Define a corresponding typing.NamedTuple in the stub

tawny flame
#

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

oblique urchin
#

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

tawny flame
#

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

upper tapir
#

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
upper tapir
trim tangle
upper tapir
#

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?

trim tangle
#

@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

upper tapir
#

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?

trim tangle
#

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

trim tangle
upper tapir
#

what does it do?

trim tangle
#

what do you think stub files are for?

upper tapir
#

typehinting?

trim tangle
#

right, but what's the point of having type hints in your library?

upper tapir
#

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

trim tangle
#

right, that's what the tooling does, pycharm just has it built in

upper tapir
#

oh mypy and pyright does that?

trim tangle
#

yeah

upper tapir
#

oh

trim tangle
#

If you use type hints inside the code:

  1. You don't have to keep stubs in sync
  2. You can run a type checker (mypy/pyright/pycharm's built-in one) on your code, and it can point out mistakes
upper tapir
#

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?

trim tangle
#

yeah

upper tapir
#

oh so it points out if you gave wrong type?

trim tangle
#

yes

#

PyCharm should also do that

upper tapir
#

ah i see

#

Thanks for clarifying me!!

void panther
#

mypy and pyright will be a bit more strict with the checking than pycharm so it may be worth using them

upper tapir
#

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

warm mountain
#

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?

trim tangle
#

I think you'll have to make a custom converter

warm mountain
trim tangle
#

monkey-patching library could should be a last resort

warm mountain
#

i alr find converter

trim tangle
#

If all you need is a very simple converter, there's no reason to monkey-patch library code

blazing nest
blazing nest
spare mauve
#

so today I learned all of typing is basically deprecated?

brisk hedge
#

the container types, yeah

spare mauve
#

and we're supposed to use "parametrizable built-in types"?

fierce ridge
brisk hedge
#

yeah, list[int] instead of typing.List[int]

fierce ridge
spare mauve
#

oh that works now?

fierce ridge
#

as of 3.9 yes

spare mauve
#

nice

fierce ridge
#
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

spare mauve
#

would something like

l = list[int]()
``` work?
brisk hedge
#

sure

brisk hedge
#

but it's not necessarily going to be statically analyzed

#

depends on the context I think

fierce ridge
spare mauve
#

that's a big change, no?

fierce ridge
#

mypy knows about it, so does pyright

fierce ridge
#

it's all over typeshed

#

which is really cool/useful/valuable

spare mauve
#

hu, it did work..

#

cool, thanks for. the info

trim tangle
#

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)

brisk hedge
#

the first

#

with __future__ annotations it's not tied to 3.9+ only

fierce ridge
fluid phoenix
#

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

blazing nest
#

Yup ParamSpec is bae

warm mountain
blazing nest
#

You ned to use typing.Literal[]

warm mountain
blazing nest
#

Yeah I am not sure what to do then. You can only pass types to Union

warm mountain
#

so how i will make it

fierce ridge
#

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

warm mountain
brisk hedge
#

make the convert a classmethod and change the annotation to a class instead of an instance

#

type checkers only expect types as annotations

warm mountain
brisk hedge
#

metaclasses

fierce ridge
warm mountain
fierce ridge
warm mountain
#

yep

#

Idk how i will fix it, so i will just make small rewrite in discord library

fierce ridge
#

ah, that's in their source? unfortunate

warm mountain
#

I wanted to make converter that will work like typing.Literal, but will trigger upper case words

warm mountain
#

if i will place my bot to host

#

it will use original library

fierce ridge
warm mountain
#

can we come to dms?

fierce ridge
#

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()
soft matrix
#

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

warm mountain
#

I have argument amount. It accepts only int type or "all". I wanna make it accept also "All", "aLl"

soft matrix
#

I think your best bet is using an error handler

warm mountain
soft matrix
#

After you check the literal is correct just reinvoke the command

warm mountain
soft matrix
#

What?

#

Coding anything in your bot slows down your bot

#

It's going to be insignificant

warm mountain
oblique urchin
#

Premature optimization is the root of all evils, it's true

fierce ridge
#

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'}
soft matrix
#

seems like a good way to reduce duplication to me

fierce ridge
#

is it possible in 3.9?

oblique urchin
#
>>> get_args(Literal[1, 2, 3])
(1, 2, 3)
fierce ridge
#

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]
brisk hedge
#

Static analyzers will only check for actual literals in the call to Literal

#

Or, at least they're not obliged to do more

soft matrix
tawny flame
#

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?

oblique urchin
#

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 ๐Ÿ™‚

tawny flame
#

Yep I always pull from the master branch of the typeshed repo and handle the merges on my fork before making a PR

tawny flame
#

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?

oblique urchin
oblique urchin
#

tuple[int, ...]

spare mauve
#

then you're separating code and data

#

I think it's absolutely fine

trim tangle
#

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[])

fierce ridge
#

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?

oblique urchin
brisk hedge
#

Typescript was kind of built around typing lol

fierce ridge
#

maybe it's because the community is just moving slower, or like you said python itself has been very conservative in rolling it out

oblique urchin
fierce ridge
#

interesting. maybe python libraries tend to be more loose about types?

oblique urchin
#

Maybe because transpilation is easier for a language you run in the browser

fierce ridge
#

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

oblique urchin
#

Right, that's a big one too

fierce ridge
#

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

oblique urchin
#

why pay for something you can get for free?

fierce ridge
#

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

indigo locust
#

Do I hint a chainmap same a dictionary, with a key and a value component?

#

chain : ChainMap[str, str] for example

indigo locust
#

Thanks Salt!

fierce ridge
#

๐Ÿง‚

indigo locust
#

You butt

solid light
#

An entire 25hrs out of date smh

fierce ridge
blazing nest
#

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?

soft matrix
fierce ridge
fierce ridge
oblique urchin
#

But MacroPy does exist

soft matrix
#

I think also people use encoding shebangs for html stuff

oblique urchin
#

Oh yes, I think it was encoding, not import hooks

fierce ridge
#

actually... maybe not ๐Ÿ˜†

oblique urchin
#

I think the trouble will be that you have to reimplement the import system

#

And the import system is dark and full of terrors

indigo locust
#

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

fierce ridge
oblique urchin
fierce ridge
#

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

tawny flame
# oblique urchin `tuple[int, ...]`

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

oblique urchin
#

Have to use Tuple

acoustic thicket
#

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
oblique urchin
#

That means something different. What context are you thinking about?

#

def id(arg: T) -> T: return arg

acoustic thicket
#

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

oblique urchin
#

T = TypeVar("T")

#

@halcyon nexus's example is more useful ๐Ÿ™‚

acoustic thicket
#

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

oblique urchin
#

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

tawny flame
trim tangle
#

@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

void panther
oblique urchin
#

(talking about Tuple vs. tuple, @void panther is right about tuple[int, ...] ts tuple[int]

tawny flame
#

Ty you both for the info!

fierce ridge
#

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 of x.a1 : float | None to float

#

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

pastel egret
#

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.

fierce ridge
#

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

tawny flame
#

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

tawny flame
#

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

tawny flame
#

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?

acoustic thicket
#

mypy complains for object, but not for Any

acoustic thicket
dim flume
#

No

acoustic thicket
#

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

brisk hedge
#

*with bonus __str__, __repr__, __eq__, ...

trim tangle
#

yay, pylance now has autocompletion for TypedDict keys

soft yarrow
#

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.

brisk hedge
#

Yep, you can

soft yarrow
#

cool, thanks ๐Ÿ˜€

fierce ridge
compact mountain
#

I'll start using typeddict more now lol

trim tangle
#

Ehhh

#

It's not suitable for general interfaces and such

#

I'm using it to describe an existing API

oblique urchin
void panther
#

and if anything tries to evaluate them, you'll get hit with an error

oblique urchin
#

yep

soft matrix
#

if you want to use them in other places you need to annotate them as TypeAliases from typing_extensions

oblique urchin
soft matrix
#

well everything that doesnt support it is deprecated now

oblique urchin
#

what do you mean?

soft matrix
#

like 3.6 has variable annotations and anything below 3.5 has been sun set

oblique urchin
#

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

tawny flame
#

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

oblique urchin
#

Maybe that's fixable in stubtest? Do mypy and pyright also complain?

tawny flame
#

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

oblique urchin
#

pytype has some strange issues sometimes ๐Ÿ˜ฆ

primal breach
#

n: int = 2 should this be Optional[int]?

ivory hedge
#

Can it be None at any point? If so, yes

primal breach
lapis zenith
primal breach
subtle creek
#

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?

oblique urchin
tawny flame
narrow goblet
#

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

tawny flame
#

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

narrow goblet
#

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

oblique urchin
#

What is your use case for this? There's various tools like # type: ignore and if TYPE_CHECKING:

narrow goblet
#

thanks i found this

if not typing.TYPE_CHECKING:
cedar gorge
#

Callable[..., T] what exactly is this saying?
T = TypeVar("T")

solid light
#

!d typing.Callable

rough sluiceBOT
#

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").
solid light
cedar gorge
#

oh

#

well whats T?

solid light
#

!d typing.TypeVar

rough sluiceBOT
#

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...
cedar gorge
#

oh i see

#

got it

#

thanks

solid light
#

๐Ÿ‘

frank glade
#

i see typeguard will become unmaintained soon, would you say pytypes is a good replacement?

soft matrix
#

i personally like the look of bear-type

frank glade
#

bear-type.. let me check it out

buoyant swift
frank glade
#

is there a way to put it in a module and have it annotate all functions/methods?

soft matrix
#

not currently

frank glade
#

aww crap

soft matrix
#

the maintainer seems like a fun dude though so thats a plus

frank glade
#

lmao

#

yeah.. seems like it

rustic gull
#

anyone here confusedcat

#

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 ?
viral bramble
#

typing.Any

soft matrix
#

!d typing.Any

rough sluiceBOT
#

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.
oblique urchin
deep pendant
#

Can I somehow type all of my variables inside class as Final?

#

So instead of typing (sic ;)) Final each time, use something shorter

soft matrix
#

you just as import it which would allow you to do var: F = "1234"

deep pendant
#

from typing import Final as F is shorter but that isn't as much fine as I expect

soft matrix
#

those should also be ClassVars not Final fyi

deep pendant
#

indeed, both ClassVar and Final (does ClassVar imply Final?)

soft matrix
#

yeah

deep pendant
#

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))

soft matrix
#

lemme find the place that i saw this

#

you cant

deep pendant
soft matrix
#

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

deep pendant
#

okey, thanks ๐Ÿ˜„

sharp jacinth
#

so like how exactly is type hinting an 'advanced' concept its so simple no?

buoyant swift
#

yes, mostly

trim tangle
#

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

trim tangle
sharp jacinth
#

lol

#

well maybe you need to say 'only pass my classes'

#

meaning not primitive ones?

trim tangle
#

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'

soft matrix
deep pendant
#

How to type returning class object inside the class ('s staticmethod)?

oblique urchin
#

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: ...

deep pendant
#

I return exactly this class ๐Ÿ˜„

vapid quarry
#

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.

oblique urchin
vapid quarry
trim tangle
#

Yeah, if you put annotations everywhere your code will get cluttered and will look like Java before type inference

oblique urchin
misty vale
#

what type hint is commonly used for something that is sortable?

oblique urchin
misty vale
#

hmm so just

class Sortable:
  def __lt__(self, other):
    ...

?

oblique urchin
misty vale
#

right, assume other was just a T = TypeVar('T', float)

vapid quarry
misty vale
# misty vale so im just gonna basically inherit from this https://docs.python.org/3/library/t...
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

oblique urchin
#

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__

misty vale
#

oh yeahh that's a good spot, thanks i'll read the docs again

vapid quarry
#

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)
oblique urchin
leaden oak
#

in particular either list the built-in or List from the typing module

vapid quarry
#

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)
leaden oak
#

!d typing.List

rough sluiceBOT
#

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]
```...
leaden oak
#
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

vapid quarry
vapid quarry
#

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]:
oblique urchin
vapid quarry
#

I also see lol:

Redundant arguments are skipped, e.g.:
Union[int, str, int] == Union[int, str] == int | str
leaden oak
brisk hedge
#

Does it return those three exact types?

#

or just one of them?

oblique urchin
#

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

brisk hedge
#

Because if you want to return a ["hi", "hello", 0] list, consider returning a tuple instead

leaden oak
#

ah I misread the question (probably took it too literally), good catch

vapid quarry
#

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

leaden oak
#

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

vapid quarry
#

oh ok thanks!

oblique urchin
#

Union[str, int] would mean you return either a str or an int

vapid quarry
#

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.

spiral garnet
#

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")

oblique urchin
spiral garnet
#

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

oblique urchin
#

you can override __str__

spiral garnet
#

Okay ty !

trim tangle
#

Actually, isinstance(x, TestType) should fail because TestType is a function at runtime

soft matrix
#

Not in 3.11 ;)

pale narwhal
#

What is typing.Protocol?

oblique urchin
oblique urchin
pale narwhal
#

ah, thank you

soft matrix
spiral garnet
brittle plover
#

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

brittle plover
#

Ok, apparently this was due to my mypy.ini where warn_return_any was configured as True

brazen horizon
#

Why does this not show a warning?

def func(data: list[str]):
    print(data)

func([1, 2, "3", 4, 5])
oblique urchin
brazen horizon
#

Not sure what you mean

oblique urchin
#

Are you just running that code with Python?

#

I mean you should use a tool like mypy or pyright to typecheck the code

brazen horizon
#

I just wanna see the warning in the IDE as usual

#

If I change one of the items to a string it accepts it

brittle plover
#

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

brazen horizon
#

Installing the mypy plugin solved it

fierce ridge
#

yes, i knows this is kind of silly, but i'm having fun

soft matrix
#

Frozen list is already a module on pypi

#

Iirc aiohttp uses it

#

Oh that's not what this is doing

fierce ridge
#

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, ...]

oblique urchin
#

Mypy might not like it, tuple is heavily special cased

soft matrix
#

You might be able to do it with pyright and pep 646

fierce ridge
#

mypy indeed didn't like it

soft matrix
#
class FrozenList(tuple[*Ts]):
  ...```should something like this be possible or is this just a worse version of subclassing tuple[T, ...]?
oblique urchin
blazing nest
solid light
#

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?

rustic gull
#

something like this?

acoustic thicket
#

yea, TypedDict is the way to go

zinc yarrow
#

I would probably end up creating some kind of dataclass if Data contained anymore values

solid light
rustic gull
#

Yw

fierce ridge
#

Wishlist item: statically type-checked jsonschema

blazing nest
#

but... PyDantic

fierce ridge
#

not just a json recursive type

#

literal jsonschema

#

which of course you can guard with a TypeGuard

nocturne snow
#

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
nocturne snow
#

idk, isn't that the way to add an attribute to something?

fierce ridge
#

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
nocturne snow
#

hmm

#

error: "FuncT" has no attribute "execute_async" if i assign the attribute directly

#

oh that's interesting

#

i will give that a shot

fierce ridge
#

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

nocturne snow
#

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

nocturne snow
#

mypy

blazing nest
nocturne snow
#

uhhh

#

maybe

#

how can i tell mypy version

blazing nest
#

At the top

nocturne snow
#

inside a huge scary monorepo with like 2000 layers of weirdness and venvs

blazing nest
#

Ah I see, and there's types crossed from everywhere that you cannot isolate?

nocturne snow
#

well i can try with latest

#

i just have no idea what version of mypy i'm using

fierce ridge
#

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

blazing nest
#

Actually, what if we make it an attribute on the protocol?

nocturne snow
#

the assigning to method thing doesn't matter i can type: ignore it

#

its code that uses the decorator i'm more concerned about

blazing nest
#

Because FuncT is a FunctionWithExecuteAsync and not a callable with the types of the method

fierce ridge
#

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?

nocturne snow
#

i tried paramspec before but it doesn't seem my mypy supports it

fierce ridge
#

it doesn't (yet)

blazing nest
fierce ridge
#

i think so? then you can type the args/kwargs/return of __call__

blazing nest
fierce ridge
#

Invalid base class "Callable"
๐Ÿ˜ 

blazing nest
nocturne snow
#

imagine if python types could just be like

#

T & {attribute: U}

#

magic

fierce ridge
#

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)

blazing nest
#

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.

nocturne snow
#

i think the two requirements are

#
  1. normal function call is fully typed
  2. 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

fierce ridge
#

it replaces the function with an instance of the class

blazing nest
#

And you'll loose the function call types

nocturne snow
#

yeah that makes sense

#

well

#

i would if that could be a hack

fierce ridge
#

some decorators in the stdlib do this, i actually prefer writing them that way

#

but yes you lose some type information

fierce ridge
#

again, paramspec would fix this

nocturne snow
#

yeah i would love to use paramspec

#

but

blazing nest
#

All praise tho mighty ParamSpec ๐Ÿ™

nocturne snow
#

whats the name of that func

#

that prints out types

#

when u run mypy

little hare
#

type()?

blazing nest
#

reveal_type()

nocturne snow
#

nice

little hare
#

ah

blazing nest
#

No need to import it, the type checker will just notice its usage

nocturne snow
#

ye

little hare
#

its part of the stdlib?

nocturne snow
#

its part of mypy

soft matrix
#

It's not just mypy

fierce ridge
#

i wish it were actually in typing so you could use it without breaking your code

little hare
#

but then how does it not error if it hasn't been imported?

soft matrix
#

Cause it adds it to the namespace

little hare
#

but wouldn't that break the code when not ran with mypy?

soft matrix
#

Yeah it breaks at runtime

little hare
#

so its basically a function that can be added, run mypy, and then delete ;-;

fierce ridge
#

it should be from typing import reveal_type imo

#

currently you have to guard it behind if TYPE_CHECKING:

soft matrix
#

I just delete them before I run

nocturne snow
#

yeah i wouldn't leave them in code

soft matrix
#

I also feel like this was considered at some point so there's probably a reason it's not in typing

fierce ridge
#

yeah i assume there's a reason for it

blazing nest
#

You're loosing type checking of the arguments

nocturne snow
#

aw

#

so close yet so far

#

how come self.__call__ = fn doesn't do it

soft matrix
#

Cause that's too dynamic

#

And Untyped

little hare
nocturne snow
#

dumbass mypy