#type-hinting

1 messages · Page 47 of 1

little hare
#

change to this

    T = TypeVar("T", bound=int | str)

def get_parameter(n: str, rtype: type[T]) -> T: ...

``` in the bottom half
#

(use type or Type depending on your python version)

#

remember, you can always change an instance to a class with type[] but you can't take a type and make it an instance as easily (i don't actually know how to do that with typing)

#

i have still not closed the editor

novel notch
#

I'll try this in playground, let's hope it works

restive rapids
#

i would be fine with just using 2 different functions for this, or doing int(f(n)) if its the same thing
its not like you'll be passing a non-constant to this, ever, so there isn't really a point in making it generic

feral wharf
#

Can always use typing.cast too

novel notch
#

There are currently type warnings everywhere that someone didn't notice

restive rapids
little hare
#

i added basic pyright to my discord bot and recently and... it was so much fun

#

only 300+ typing issues to get to basic....

novel notch
#

But nevertheless, I would like to learn how to write this function, if possible

#

Incompatible return type got "int" expected T. Do I need to import from future? 🤔

feral wharf
novel notch
#

Why annotations from future and why typevar from extensions? Can't I just use typevar from typing?

little hare
#

also don't you contribute to dpy

#

:^)

#

-# didn't that start untyped

restive rapids
little hare
#

but ye under suggestion from @trim tangle I ended up using basedpyright to implement pyright with a baseline file in order to be able to add ci without fixing all of the errors

#

its so great now to be able to do refactors and have ci complain at me

restive rapids
novel notch
novel notch
restive rapids
novel notch
#

And if only mypy is requirement?

restive rapids
novel notch
#

(I would love to go to newer python, but that's not an option in near time)

novel notch
restive rapids
#
from typing import overload
@overload                                 # this is making the default
def get_parameter(n: str, rtype: type[str] = ...) -> str:
  ...
@overload
def get_parameter(n: str, rtype: type[int]) -> int:
  ...
def get_parameter(n: str, rtype: type[str] | type[int] = str) -> str | int:
  return rtype(n)

something like this
(im not a fan of using type when what you really are expressing is a callable constructor but w/e, i get why people do it, its because you're only really thinking of passing the types here)

grave fjord
novel notch
#

Is the annotations and future to get the python 3.13 behavior? How to express argument default value with it?

Or do I need to fully run python 3.13?

Does the overload method have any drawbacks?

#

What du you mean by callable constructor?

grave fjord
#

You're getting the parameters from some serialized form right?

#

So you could use a Callable that processes that raw value

restive rapids
novel notch
#

I don't have a full compile time schema for what can be in this serialized data though

little hare
#

And I want to type that the method returns an instance of the type of T

restive rapids
#

in general you cant make something like type Destructure[type[T]] = T but in particular in a method where the typevar is bound in the class you can by doing narrowing on self

class F[T: type]:
  def f[U](self: "F[type[U]]") -> U:
    ...
reveal_type(F[type[int]]().f()) # int

but the proper way ofcourse is to just change the T typevar bound from type[A] to just A, and use type[T] where you want a type

little hare
#

Yeah for sure

novel notch
little hare
#

Huh it works for me

novel notch
#

🤔

glossy trellis
#

gerald symptoms

trim tangle
novel notch
novel notch
trim tangle
novel notch
#

No

trim tangle
#

What about this? ```py
def identity[T](x: T) -> T:
if random.random():
return x
else:
return "banana"

novel notch
#

Oh wait, why is T object?

#

It should be int or str

#

Not any type

trim tangle
#

Well, this is a simpler example to demonstrate the idea

novel notch
#

?

trim tangle
novel notch
#

Nope

#

Oh wait

#

Yes

trim tangle
#

In the first example, if I do py strings: list[str] = ["a", "b", "c"] s = foo(strings) s has to be a str

novel notch
#

One is generic one is not. But this doesn't look like my case at all?

#

I want to specify the return type with an input argument. Not make a generic function.

trim tangle
#

the function signature says that it's str

#

Maybe I'm misunderstanding what you're trying to do?

#

Oh I see

#

how are you currently getting the paremeters?

novel notch
trim tangle
#

that is not a str

novel notch
#

5 should only be returned when the input argument is rtype is int

#

The literal 5 is just to get a smaller example

trim tangle
#

Do you want to decide which value to return based on rtype?

#

Or do you want to validate that it's the right type?

#

Maybe you meant something like this ```py
T = TypeVar("T", bound=str | int)

def get_parameter_any(parameter: str) -> object:
...

def get_parameter(parameter: str, rtype: type[T]) -> T:
param = get_parameter_any(parameter)
assert isinstance(param, rtype)
return param

novel notch
#

I updated the example in stack overflow, hopefully it's more clear

novel notch
trim tangle
#

If you are asking how to ignore the error that mypy is correctly issuing, you can use typing.cast or a # type: ignore comment

novel notch
#

I'm hoping to use a default rtype. See original question

trim tangle
# novel notch In 1000 places?
from __future__ import annotations

from typing import TypeVar, cast
    
T = TypeVar("T", str, int)
VALUES: dict[str, str] = {"SIZE": "100", "ADDR": "0x100", "NAME": "potato"}

def get_parameter(parameter: str, _rtype: type[T]) -> T:
    value: object
    if parameter.startswith("N"):
        value = int(VALUES[parameter], 0)
    else:
        value = VALUES[parameter]
    return cast("T", value)

x = get_parameter("SIZE", str)  # x is a str
y = get_parameter("NAME", int)  # y is an int
z = get_parameter("ADDR", int)  # z is an int to mypy, but at runtime it's a string 
#

If you want to be able to omit rtype, then you will have to use an @overload with mypy

novel notch
#

Does cast do anything when not type checking? Will it infer a function call?

trim tangle
#

typing.cast is a no-op, it's similar to # type: ignore

trim tangle
novel notch
#

Ok

trim tangle
#
@overload
def get_parameter(parameter: str, _rtype: type[int] = ...) -> int: ...
@overload
def get_parameter(parameter: str, _rtype: type[str]) -> str: ...
    
def get_parameter(parameter: str, _rtype: type[object] = int) -> object:
    if parameter.startswith("N"):
        return int(VALUES[parameter], 0)
    else:
        return VALUES[parameter]


x = get_parameter("SIZE", str)  # x is a str
y = get_parameter("NAME", int)  # y is an int
y2= get_parameter("NAME")  # y2 is an int
z = get_parameter("ADDR", int)  # z is an int to mypy, but at runtime it's a string 
novel notch
#

Why do I need to cast a literal int to T when int is "part of" T?

trim tangle
#

You mean in this example? ```py
def foo[T: int | str](things: list[T]) -> T:
return 100

novel notch
#

No

trim tangle
#

Because if you do py x = get_parameter("SIZE", str) x is supposed to be a str, according to the function signature.

novel notch
#

🤔

trim tangle
# novel notch 🤔

If you have a function with this signature: ```py
T = TypeVar("T")

def identity(x: T) -> T: ...
It means that the return type is the same as the argument type. So if I dopy
number: int = 5
another_number = identity(number)
``` according to the function signature, another_number here has to be an int. Does that make sense?

#

and this implementation would be incorrect: py def identity(x: T) -> T: return "banana" even though sometimes this function might be called with a str argument

novel notch
novel notch
trim tangle
novel notch
trim tangle
novel notch
trim tangle
trim tangle
novel notch
trim tangle
novel notch
trim tangle
novel notch
#

Is the first ... meant to be something else?

trim tangle
# novel notch There are 2 ellipsis in your example, not only the function body?

@overload is not special syntax. It's just a decorator on a function definition```py
@overload
def get_parameter(parameter: str, _rtype: type[int] = ...) -> int:
print("you could put something here but it's never gonna run")

@overload
def get_parameter(parameter: str, _rtype: type[int] = ...) -> int:
print("same here")

def get_parameter(parameter: str, _rtype: type[object] = int) -> object:
# this is the actual function definition that will be called at runtime
``` ... is mean to literally be ..., the ellipsis object

novel notch
novel notch
#

Decorators or overload is not new to me.
Ellipsis is not new to me.

trim tangle
novel notch
#

I'm asking about the very first ... in your code

novel notch
#

What's wrong here:

def  get_parameter(parameter: str, rtype: type[T]) -> T:
    """Get the parameter"""
    if rtype is int:
        return 5
trim tangle
#

that seems like a bug/limitation in mypy, there's nothing wrong with this function

novel notch
#

Ok, thanks

terse sky
#

maybe people who are used to python and its hybrid approach to typing don't feel this way, but for someone coming from statically typed languages the code feels kind of wrong

#

you're basically putting a lot of weight on the type checker/"smart casting" here

#

If I saw real code written this way... I think most likely I'd suggest a different way to write it

#

but it's hard to say that in a vacuum

trim tangle
terse sky
#

the issue is that in general, if statements are dynamic. So in general, this kind of code is wrong.
What you're asking is for the type checker to recognize that this particular if statement has a condition that matches the surrounding static context.
Which is a lot like what happens when you do smart casting/type narrow inside an if body.

trim tangle
terse sky
#

But this isn't quite type narrowing, even though it's similar. So basically you're asking for an extra feature/complexity that some type checkers do not have

#

or well, I guess they sort of have it. but it's at the edge so to speak, and janky.

trim tangle
#

If you have a fixed set of parameters, a safer approach would be something like ```py
IntParam = Literal["NROWS", "NCOLS"]
StrParam = Literal["NAME", "DESCRIPTION", "AUTHOR"]

@overload
def get_parameter(p: IntParam) -> int: ...
@overload
def get_parameter(p: StrParam) -> str: ...
@overload
def get_parameter(p: str) -> str | int: ...

#

(this also doesn't require any code changes in callers)

#

If you want it to be extra relaxed and gradual, you could do ```py
@overload
def get_parameter(p: IntParam) -> int: ...
@overload
def get_parameter(p: StrParam) -> str: ...
@overload
def get_parameter(p: str) -> Any: ...

viscid spire
novel notch
novel notch
novel notch
# restive rapids you could indeed use the standard typevar from typing here, it supports bounds o...

Is bounds in contrast to constraints? And what's the difference?

Pasting the th overload suggestion to avoid scrolling past yesterday's discussion.

from typing import overload
@overload                                 # this is making the default
def get_parameter(n: str, rtype: type[str] = ...) -> str:
  ...
@overload
def get_parameter(n: str, rtype: type[int]) -> int:
  ...
def get_parameter(n: str, rtype: type[str] | type[int] = str) -> str | int:
  return rtype(n)
novel notch
terse sky
novel notch
#

Not in more detail as it's proprietary code

#

It's about handling serialized data, and with small overhead fix the currently existing mypy errors in the code. And the idea was to be able to fix convey type information with a function argument rather than appending a verbose assert or cast at the calling site.

The get_parameter function is called in many places, even in code I don't have easy access to.

rough kettle
#

There's no way to map a tuple of types into a tuple of iterables of those types right?

viscid spire
#

if only Union[*Ts] was allowed

trim tangle
#

only if you want to have something like this ```py
@overload
def foo(t: tuple[()]) -> tuple[()]: ...
@overload
def foo[A](t: tuple[type[A]]) -> tuple[A]: ...
@overload
def foo[A, B](t: tuple[type[A], type[B]]) -> tuple[A, B]: ...
@overload
def foo[A, B, C](t: tuple[type[A], type[B], type[C]]) -> tuple[A, B, C]: ...

repeat until you're bored

#

which is what the built-in map and zip do

rare scarab
#

Please let us have ```py
def foo[Ts](t: tuple[(type[A] for A in Ts)]) -> tuple[*Ts]: ...

restive rapids
trim tangle
#

I think only having an unsafe way to do this is better than having no way of doing this. Just like we've had @overload for 11 years without validation of the implementation

restive rapids
#

just put the dependent types in the bag bro

restive rapids
jolly cipher
#

i otoh just wish there was specifically a way to refer to other callable's paramspec as a special form.
e.g. ```py
type PrintP = ParamsOf[print]

def tee(*args: PrintP.args, other_file: StrPath, **kwargs: PrintP.kwargs) -> None:
print(*args, **kwargs)
print(*args, **{**kwargs, 'file': os.fspath(other_file)})

trim tangle
#

yeah that's also a cool feature in typescript

jolly cipher
#

well, that's a dependent type as well

trim tangle
#

not really, it's just a shortcut

jolly cipher
#

but i believe it would be hella useful from time to time

#

why not? isn't print a value?

trim tangle
#

we still don't have ParamSpec literals that support keyword arguments :(

jolly cipher
#

print can be anything in code, correct understanding of that ParamsOf type depends on what you read before
it may be the thing from builtin namespace but it may as well be something locally defined

trim tangle
#

If it's something like that then yes

#

But then it's also not very useful, because you can only pass print as a value of this type, and also you can't do anything with a value of that type

#

So a more useful interpretation would be "make PrintP the type that the type checker believes print to be according to stubs or type definitions"

jolly cipher
#

when exactly would i be passing print somewhere

trim tangle
#

oh wait it's ParamsOf and not just typeof

jolly cipher
#

yup
the point is to capture the paramspec without having to make a decorator or something

#

to capture it naturally

#

in that sense it's also a shortcut

meager slate
#

Hey there, I was experimenting with some stuff a while ago and found this unexpected behaviour-

import random
from dataclasses import dataclass

@dataclass
class Parent:
    a_attribute: int = 10

@dataclass
class Child1(Parent):
    b_attribute: str = "Child1 attribute"

@dataclass
class Child2(Parent):
    c_attribute_1: str = "Child2 attribute 1"
    c_attribute_2: str = "Child2 attribute 2"


group: list[Parent] = [random.choice((Child1, Child2))() for _ in range(10)]


for i in range(10):
    if isinstance(group[i], Child1):
        print(group[i].b_attribute)  # [reportAttributeAccessIssue] error

Even though I've explicitly type narrowed group[i] to Child1, I still get attribute access error. Why is that?

I know I could've done something like for element in group: instead which would fix it, but i found this weird

oblique urchin
#

It's a bit more tricky to implement than narrowing a subscript with a constant

meager slate
#

Oh, I see

mossy rain
oblique urchin
hasty phoenix
#

How does one properly type annotate this:

@overload
def fn(key: CT='KEY', value: CT='VALUE') -> dict[DT, DT]: ...
@overload
def fn(key: CT='KEY', value: None='VALUE') -> dict[DT, dict[CT, DT]]: ...
def fn(key: CT='KEY', value: CT|None='VALUE') -> dict[DT, DT|dict[CT, DT]]:
#

Both mypy and pylance is reporting a series of errors about this "Overloaded function implementation cannot produce return type of signature" and "Overload 1 for "asdict" overlaps overload 2 and returns an incompatible type".

#

I always get lost on how to write the type hints when the type of the input argument determine the return type.

restive rapids
#

i suppose CT and DT are typevars of the class (since this had self before you edited it)?
how is 'KEY' supposed to be a valid default value for any CT?
and 'VALUE' is definitely not None

trim tangle
#

Return dict[DT, DT] | dict[DT, dict[CT, DT]]

hasty phoenix
trim tangle
#

How is CT defined?

hasty phoenix
trim tangle
#

then "KEY" and "VALUE" are definitely not valid values for CT

#

I'm a bit confused as to what you want

#

do you have some examples of calls to this function?

restive rapids
#

is value more like.. key2? so if its not provided, you get a nested dict, and if its provided - you get a specific thing out of it
why not just fn(key)[key2]?

hasty phoenix
#
CT = str
DT = TypeVar('DT', default=str | int | float | None)
trim tangle
#

Ah, CT is not a typevar

hasty phoenix
#

So "KEY" and "VALUE" are literal strings

hasty phoenix
trim tangle
#

It is a type alias. Writing CT in an annotation is the same as writing str

#

What do you want fn(), fn("apple") and fn("apple", "banana") to be?

#

(or rather, what do they return at runtime?)

hasty phoenix
#

A little hard to understand since we're talking multiple dimensions here, but let's try:

fn() -> {data_from_KEY: data_from_VALUE, ...}
fn("apple") -> {data_from_apple: data_from_VALUE, ...}
fn("apple", "banana") -> {data_from_apple: data_from_banana, ...}
fn("apple", None) -> {data_from_apple: {raw_data...}, ...}
restive rapids
#

key having a default makes this suck to typehint with overloads without making value keyword-only

from typing import overload
class F[T = str | int | float | None]:
    def t(self) -> T: ...
    @overload
    def fn(self, key: str='KEY', value: str='VALUE') -> dict[T, T]: ...
    @overload
    def fn(self, key: str='KEY', *, value: None) -> dict[T, dict[str, T]]: ...
    def fn(self, key: str='KEY', value: str|None='VALUE') -> dict[T, T] | dict[T, dict[str, T]]:
        if value is None:
            return {self.t(): {"x": self.t()}}
        else:
            return {self.t(): self.t()}
hasty phoenix
#

ah, ok

restive rapids
#

if only we could do

def fn[V: (str, None) = str](self, key: str = 'KEY', value: V = 'Value') -> dict[T, T if V is str else dict[str, T]]

or something

#
from typing import overload, reveal_type
class F[T = str | int | float | None]:
    def t(self) -> T: ...

    @overload
    def fn(self, key: str='KEY', value: str='VALUE') -> dict[T, T]: ...
    @overload
    def fn(self, key: str, value: None) -> dict[T, dict[str, T]]: ...

    def fn(self, key: str='KEY', value: str|None='VALUE') -> dict[T, T] | dict[T, dict[str, T]]:
        if value is None:
            return {self.t(): {"x": self.t()}}
        else:
            return {self.t(): self.t()}

def test():
    f = F[int]()
    reveal_type(f.fn()) # dict[int, int]
    reveal_type(f.fn("apple")) # dict[int, int]
    reveal_type(f.fn("apple", "banana")) # dict[int, int]
    reveal_type(f.fn("apple", None)) # dict[int, dict[str, int]]

maybe this, if you dont expect to be able to do fn(value=None) as fn("KEY", None)

simple field
#

hello it is time for my annual/biannual asking of:

  • is there an official way to specify Iterable[str] that isn't str now?
  • how's the progress on Intersection coming along?
trim tangle
#

the answer to 1 is no

fiery canyon
#

Bruh I've been using | None for optional keys just to now find out there's typing.NotRequired

#

Now I'll have to go through all of these typeddicts lul

feral wharf
#

Wait until you hear about total=False and typing.Required

fiery canyon
viscid spire
#

!d typing.Required

rough sluiceBOT
#

typing.Required```
Special typing construct to mark a [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict) key as required.

This is mainly useful for `total=False` TypedDicts. See [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict) and [**PEP 655**](https://peps.python.org/pep-0655/) for more details.

Added in version 3.11.
viscid spire
#

when you want to undo the total=False for a specific key apparently

fiery canyon
#

Ah makes sense

fiery canyon
#

Hm NotRequired is only available to me via typing_extensions

feral wharf
#

Why are you below 3.11

fiery canyon
#

3.14

spiral fjord
#

It's in typing then

feral wharf
#

^ added in 3.11

fiery canyon
#

Well lemme check again then

#

Bruh now it decided to work

viscid spire
fiery canyon
#

Most of the time I don't even have copilot enabled lol

radiant oriole
#

hey if I have overloaded functions such as these, is there any easy way of finding out which if the two different callables I could be getting I am dealing with?

@overload
def func(cb: Callable[[Path, bool], None]) -> None: ...
@overload
def func(cb: Callable[[Path, bool, T], None], c: T = None) -> None: ...

def func(cb: Callable[..., None], c: T = None) -> None: 
  # implementation    

restive rapids
fiery canyon
#

Cmon man

#

Is it cuz str() should always return string?

#

I'll just do this ig

#

Still not done sadly

radiant oriole
# restive rapids you could check the parameter count of its `inspect.signature`, i guess though t...
@overload
def func(cb: Callable[[Path, bool], None]) -> None: ...
    
@overload
def func(cb: Callable[[Path, bool, T], None], c: T) -> None: ...

def func(cb: Callable[[Path, bool], None] | Callable[[Path, bool, T], None], c: T = None) -> None: 
    callback: Callable[[Path, bool, T], None]

    if (len(inspect.signature(cb).parameters) == 2):
        callback = lambda a, b, _ : cb(a, b) # pyright: ignore[reportCallIssue, reportUnknownLambdaType]
    else:
        callback = lambda a, b, _ : cb(a, b, c) # pyright: ignore[reportCallIssue, reportUnknownLambdaType]


    callback(Path("test"), False, c)

so this is my best bet? 😔

restive rapids
fiery canyon
#

Slowly I start to find more and more of these typing thingies useful (focus on Literal and Protocol)

random atlas
#

Hey, I've got a problem and was wondering if anyone had any advice, i have a PyQt6 threaded worker class, and I'm using the typing module to dynamically create the finished signals with custom types, the logic should work, however I'm not entirely sure how to type hint the code segment, which is required for it to function, any help appreciated, Thanks

trim tangle
#

If you forgot to instantiate the class, do that. If you intended to return a class, specify the return type as type[QObject]

#

(I don't know anything about Qt, but generating a new class at runtime is generally a sign that there's a better way to do it)

random atlas
#

thanks, this fixes the runtime class generator part, but were still getting red squiggly lines with the self.signals inside the run function, do you possibly have any way to type hint so we recognize the attributes of the runtime class? thanks for your help btw

trim tangle
#

you'll need to show the error messages you're getting

#

and what tool you're using for type checking

random atlas
#

I was wondering if it was possible to type hint such that we can recognize the attributes of the return class, so the following lines are type hinted correctly, the error when we hover over one, im using vs code so it'll be pyright and mypyCannot access attribute "finished" for class "type[QObject]"   Attribute "finished" is unknown

mossy rain
#

having like 15 untracked files, that you are gonna add with one single commit is not really good design (but i do it too often too tbh, im too lazy to make the remote)

meager slate
fiery canyon
#

When I'm done with it which is soon

mossy rain
#

Oh, well if thats how you want it to go. Its just harder to trace file-changes, make backups and such (and GH has a Releases feature...). But i mean, that works too if you want

fiery canyon
#

By saying this I do not intend to say it's a good thing

fiery canyon
#

Screenshot: code from the builtin enum.py.

Is it possible to make something like IntEnum but one that takes any object?

#

I'd imagine it like:

class MyEnum(AnyEnum[whatever_obj]): ...```
meager slate
fiery canyon
#

Saves doing tons of .values lol

meager slate
#

but i feel like however you're using your enums is a bit odd

#

if you're having to do a ton of .value on your enum

fiery canyon
#

But actually yea this is good enough too

#

I'm probably not gonna do that since there's already StrEnum and IntEnum

#

Nvm typechecker doesn't care (but the functionality of the .value thingy works!!)

meager slate
fiery canyon
#

oh wait 💀

meager slate
fiery canyon
#

yer right

#

but eh it works the current way too

meager slate
#

if you can narrow a type of a parameter, then you should always go for it

#

narrowing the type leaves out ambiguity

fiery canyon
#

Although here there isn't such a "has_flag" func

meager slate
#

Same thing, annotate value in the __init__ as StickerTypes

errant tulip
#

Can somebody please help me with this? I am losing my mind.

I am working on a library where I have created a sentinel called TERMINATE. In vs code, in the library directory, I get Type of "TERMINATE" is "TERMINATE". But when I am trying to use my library from other folder, and importing this sentinel, I get Type of "TERMINATE" is "Sentinel".

This is creating problem with typing in the directory where I am using the library: Variable not allowed in type expression

How do I solve this?

stable fjord
#

how are you defining TERMINATE?

errant tulip
#
from typing_extensions import Sentinel
TERMINATE = Sentinel("TERMINATE")
#

I just realized I had to enable experimental features for pyright in the other directory. I just completely forgot about this little thing over the months.

[tool.pyright]
enableExperimentalFeatures = true

Sorry for the noise

feral wharf
#

iirc object is better than Any ? So I'm a little curious why it's complaining here

class AutoSyncTree(app_commands.CommandTree):
    def __init__(self, *args: object, **kwargs: object) -> None:
        super().__init__(*args, **kwargs) 
# Argument of type "object" cannot be assigned to parameter "client" of type "Client" in function "__init__"
#  "object" is not assignable to "Client"PylancereportArgumentType
#

unless object means class and not instance

trim tangle
feral wharf
#
def __init__(
    self,
    client: ClientT,
    *,
    fallback_to_global: bool = True,
    allowed_contexts: AppCommandContext = MISSING,
    allowed_installs: AppInstallationType = MISSING,
):
trim tangle
feral wharf
#

ooh

trim tangle
#

and so pyright is correctly complaining that you cannot pass those args and kwargs to the parent __init__

#

there's currently no way to express argument forwarding like that, so you'll have to copy-paste the signature

feral wharf
#

Any is fine in my case since it can change / doesn't matter

#

thanks!

pastel egret
bleak imp
#

When I make a function like foo[T](x: Sequence[T]) -> T: …, is there any special name for the thing I’m doing with the x argument that’s like implicitly putting bounds on the types T can have?

rare scarab
#

Narrowing?

bleak imp
#

Hm, that works

terse sky
#

FWIW narrowing has another meaning

#

So I don't think I would apply it here

bleak imp
#

That’s why I didn’t really want to use it, but also I still can’t think of a better word even if it’s not the most accurate.

viscid spire
#

I mean, it is narrowing, no?

#

tho are there actually implicit bounds on T here?

#

can't Sequences contain any value?

mossy rain
#

depends on the way you define the T. Doing def foo[T: ...](x: Sequence[T]): ...

#

e.g. something like this

#
def foo[T: int](x: Sequence[T]) -> T: ...


foo([1,2,3])    # Works
foo(["1", "2"]) # Errors
mossy rain
#

you can do def foo[T](x: Sequence[T]) -> T: ..., you can call it as foo([1,2,3]) and T will implicitly be bound to int. Doing foo([1, "2", 3.0]) however will make T implicitly be bound to Union[int, str, float].

viscid spire
#

that's what they meant?

mossy rain
#

Seems like it

viscid spire
#

That's just typevar binding. It's no different that it's in a sequence.

mossy rain
#

I mean, some1 already said narrowing, and they responded with it working

viscid spire
#

Nah that's not narrowing

mossy rain
#

Yes it is, it narrows a Sequence[T], with T being bound to Any to a Sequence[...], for some T.__bound__ == ..., e.g. Sequence[int] and T.__bound__ == int

#

Atleast thats how i understood that

#

Cause now you narrow T: Any -> T: ...

restive rapids
mossy rain
restive rapids
#

is there any special name for the thing I’m doing with the x argument that’s like implicitly putting bounds on the types T can have?
there are no bounds on T here

mossy rain
#

Otherwise, the foo[T: restriction](x: Sequence[T]) -> T: ... i mentioned would work, no?

restive rapids
mossy rain
#

the T: restriction is explicit

mossy rain
#

But i suppose the question could be phrased better, yeah

restive rapids
#

my only guess is what was meant is that x has to be a sequence, but that is not imposing any restrictions on T, its imposing it on the type of x

mossy rain
#

Well then Sequence[Any] works, right?

restive rapids
#

from a perspective of "what is allowed to be passed" - yeah, but it will allow the implementation to do anything with the elements
and T is also used in the return type so you cant just remove it

hearty shell
#

I was trying to experiment with a class that depends on protocols such as SupportsDunderLT, SupportsDunderGT and specifically typeshed's SupportsRichComparison, as opposed to just using a concrete comparable implementation as I usually do, and I am having some issues...

from typing import Any, Protocol

class SupportsDunderLT[T_contra](Protocol):
    def __lt__(self, other: T_contra, /) -> bool: ...

class SupportsDunderGT[T_contra](Protocol):
    def __gt__(self, other: T_contra, /) -> bool: ...

type SupportsRichComparison = SupportsDunderLT[Any] | SupportsDunderGT[Any]

class Foo[T: SupportsRichComparison]:
    def __init__(self, value: T) -> None:
        self.value = value
    def __gt__(self, other: T) -> bool:
        # Operator ">" not supported for types "T@Foo" and "T@Foo"
        # Operator ">" not supported for types "SupportsDunderLT[Any]*" and "SupportsDunderGT[Any]*"
        # when expected type is "bool" (Pyright reportOperatorIssue)
        return self.value > other
    def __lt__(self, other: T) -> bool:
        # Operator "<" not supported for types "T@Foo" and "T@Foo"
        # Operator "<" not supported for types "SupportsDunderGT[Any]*" and "SupportsDunderLT[Any]*"
        # when expected type is "bool" (Pyright reportOperatorIssue)
        return self.value < other

I tried running down the cases to convince myself that this was reasonable, and it seems it is? Did I make a mistake or do type checkers not special case comparison operations?

# Strict subclass (Non Virtual)?
# strictsub(a, b) = lambda: type(a) in type(b).__mro__[1:]
# 
# Case 1: (fx: Foo[SupportsDunderLT[Any]]) < (y: SupportsDunderLT[Any])
#   > strictsub(y, fx)
#   Case True:
#       > y.__gt__(fx)                                                      (1)
#       Case "Implemented": done
#       Case NotImplemented:
#           > fx.__lt__(y)                                                  (2)
#           > x.__lt__(y)  ==> x supports LT, done
#   Case False: fx.__lt__(y)  ==> goto(2)
#
# Case 2: (fx: Foo[SupportsDunderLT[Any]]) > (y: SupportsDunderLT[Any])
#   > strictsub(y, fx)
#   Case True: > y.__lt__(fx)  ==> y supports LT, done
#   Case False:
#       > fx.__gt__(y)                                                      (3)
#       > x.__gt__(y)
#       Case "Implemented": done
#       Case "NotImplemented": > y.__lt__(x)  ==> y supports LT, done
#
# Case 3: (y: SupportsDunderLT[Any]) < (fx: Foo[SupportsDunderLT[Any]])
#   > strictsub(fx, y)
#   Case True: > fx.__gt__(y)  ==> goto(3)
#   Case False: > y.__lt__(fx)  ==> y supports LT
#
# Case 4: (y: SupportsDunderLT[Any]) > (fx: Foo[SupportsDunderLT[Any]])
#   > strictsub(fx, y)
#   Case True: > fx.__lt__(y)  ==> goto(2)
#   Case False: > y.__gt__(fx)  ==> goto(1)
#
# The same applies if SupportsDunderGT is used instead of SupportsDunderLT
mossy rain
#

I suppose type-checkers dont understand the type(a) in type(b).__mro__[1:] part.

rare scarab
#

Is that supposed to be like issubclass(type(b), type(a)) and type(a) is not type(b)?

mossy rain
#

I suppose, thats exactly it imo

hearty shell
#

The errors and actual code are in the first snippet

hearty shell
bleak imp
#

Fun thought I just had, is there a type safe way to define a non-empty recursive list? Defining the type is pretty easy, it's just type Recursive = list[Recursive], but can you make it? The issue I see is with the base element that gets swapped, ie in x = x[0] = [1] the initial list is a list[int] that gets turned into a Recursive, which type checkers don't like. So would there be a way to construct one without a ignore/Any/cast?

#

And by "non-empty" I mean a true recursive list, since x: Recursive = [[]] does work, but it's not a true recursive list where x[0] is x

#

Figured it out, you can use a helper function to preserve the recursive-ness of the list while making it actually recursive

type Recursive = list[Recursive]

def make_list_recursive(x: Recursive) -> Recursive:
    x.append(x)
    return x

x: Recursive = make_list_recursive([])
print(x)
trim tangle
#
x: Recursive = []
x.append(x)
bleak imp
#

Is type[type] not the same as type? Since no type checkers seems to treat it that way, but isn’t type also a type?

trim tangle
#

Like type or abc.ABCMeta

bleak imp
#

Hm. So then what would type[type[type]]] be? Would it also be any metaclass, or would it be any metametaclass?

#

I guess it’s confusing since there’s not a good distinction between the type class and type type system generic thing

trim tangle
#

type[type[type]] would mean a meta-metaclass

#

a class whose instances are metaclasses

bleak imp
#

Hm, but aren’t meta classes also themselves classes?

trim tangle
#

Yes

bleak imp
#

So then shouldn’t these all be the same, since they describe the same group of objects?

trim tangle
#

ABCMeta is a metaclass, but it's not a meta-metaclass

#

because when you instantiate ABCMeta, you get a plain class which is not a metaclass

#

my brain is currently fried so I am short circuiting on a more coherent explanation

viscid spire
#

same with meta-metaclasses

#

all meta-metaclasses are metaclasses, but not all metaclasses are meta-metaclasses

mossy rain
#

Is it better practice to use cast(tp, val) or just some (like 2) # type: ignore[...] comments? Because it might be a little slower at runtime, and if i have to import typing (even lazy), that would still add some overhead. Should i just type: ignore in this case?!

feral wharf
#

Or assert and then running with the -O flag is an option too

mossy rain
# feral wharf Or `assert` and then running with the `-O` flag is an option too

assert does not work with -O (i suppose that's what you mean), and i am using -O. Also, i still can't do something like isinstance(val, tp), as that doesn't work with types, but only classes (e.g. works with dict, not dict[str, int]). So if i have val.update(...), it knows that val is a dict, because of a isinstance(val, dict), but not that val.update(...), recives dict[str, int].

#

The narrowing doesn't work at runtime

#

typing.assert_type(val, tp) would work, but that introduces runtime overhead again

feral wharf
#

Oh, good to know!

#

I personally do the ignore comment route if I really can't narrow it because of the runtime cost, however small it is

mossy rain
#

Same, but i wanted to hear other peoples opinions on this, type: ignore[...] is obviously the simplest way aswell, but im not too sure about just ignoring errors, just to have less

jade viper
#

What's the proper way of documenting a function signature that yields instead of returns?

mossy rain
#

sad

#

!doc collections.abc.Generator

rough sluiceBOT
#

class collections.abc.Generator```
ABC for [generator](https://docs.python.org/3/glossary.html#term-generator) classes that implement the protocol defined in [**PEP 342**](https://peps.python.org/pep-0342/) that extends [iterators](https://docs.python.org/3/glossary.html#term-iterator) with the [`send()`](https://docs.python.org/3/reference/expressions.html#generator.send), [`throw()`](https://docs.python.org/3/reference/expressions.html#generator.throw) and [`close()`](https://docs.python.org/3/reference/expressions.html#generator.close) methods.

See [Annotating generators and coroutines](https://docs.python.org/3/library/typing.html#annotating-generators-and-coroutines) for details on using `Generator` in type annotations.

Added in version 3.5.
trim tangle
mossy rain
#

forgot about that

trim tangle
#

It is a bit strange that that information was added to the docs of typing.Generator several versions after it had been deprecated

#

instead of collections.abc.Generator

#

thought collections.abc.Generator does link to a page which gives examples of that

mossy rain
#

Yeah, the whole deprecation process for typing exports of collections.abc is a bit wired imo

trim tangle
#

neither page tells you what the defaults are...

#

is it None? object? Any?

mossy rain
#

don't the stubs do that?

trim tangle
#

Yes, typeshed does contain that information, but typeshed contains a lot of implementation details and is generally not the same as the docs

#

it's like if you had to read CPython's C code to understand what a public function's defaults are

mossy rain
#

Yeah

#

And neither collections/abc.pyi, nor _collections_abc.pyi contain these stubs, abc.pyi just imports the _collections_abc.pyi, which mostly just imports from typing.pyi

#

(as if the typing stubs werent long enought already xd)

rough sluiceBOT
#

stdlib/typing.pyi lines 542 to 543

_SendT_contra = TypeVar("_SendT_contra", contravariant=True, default=None)
_ReturnT_co = TypeVar("_ReturnT_co", covariant=True, default=None)```
mossy rain
#

default is None

jade viper
#

Thank you guys!

mossy rain
#

np

soft matrix
#

I think they wouldn't want to tie the parameters to the runtime guarantees just yet

#

I have a couple of issues open about this I just never did anything with them

soft matrix
#

like they dont wanna say class Generator[YieldT, SendT = None, ReturnT = None] at runtime because there isnt a stability policy for it afaik

cosmic plinth
#

Hello team! I was wondering if folks could chime in to this msgspec PR discussion. I'm trying to wrap my head around, for our library's purposes, if removing our own use of (not support for) typing.[Type|Union|Optional] in favor of the built-in approaches would somehow cause an issue or if there is some nuance we should be aware of.

rare scarab
cosmic plinth
#

Would there be a difference in runtime inspection somehow? The library in question uses type information for serialization.

rare scarab
#

IIRC get_origin(List[X]) will return list in py3.9+

stoic ether
#

abc.pyi

mossy rain
#

?

tranquil wave
#

is there any way to annotate a function's arguments as being equivalent to another, without just copying the arguments? for example:

def a(*, foo: bool = False, bar: bool = False) -> None:
    ...

def b(*args: ???, **kwargs: ???) -> None:
    return a(*args, **kwargs)

b(foo="foo")  # Type checker error
b(foo=True)  # OK

I tried a nasty hack with ParamSpec but the type checker didn't seem to like it and ended up annotating the args as ....

restive rapids
tranquil wave
#

:(

rare scarab
#

I've used a decorator that lies in the past

#
def copy_params[**P, R](source: Callable[P, R]) -> Callable[P, R]:
  def decorator(target: Callable[..., Any]) -> Callable[P, R]:
    return cast("Callable[P, R]", target)

  return decorator 

def a(*, foo: bool = False, bar: bool = False) -> None: ...

@copy_params(a)
def b(*args, **kwargs):
  return a(*args, **kwargs)
trim tangle
#

You don't have to lie, e.g. you can do ```py
def same_signature_as[**P, T](fn: Callable[P, T]) -> Callable[[Callable[P, T]], Callable[P, T]]:
...

#

it will still let you have an (*args: Any, **kwargs: Any) -> Any, but it will prevent from supplying a knowingly incompatible signature

rare scarab
#

I wish we could infer unannotated parameters with a decorator

#

Or even via x: ... to infer

trim tangle
#

yeah, this is common in typescript iirc ```rs
someWebThing.onRequets("bananans", (req, res) => { // req, res are inferred

})

rare scarab
#

Python only supports that in lambdas

trim tangle
#

yep

restive rapids
#

all type systems should be based on hindley milner

rare scarab
#

.wiki hindley milner

mighty lindenBOT
#
Wikipedia Search Results

Hindley–Milner type system
A Hindley–Milner (HM) type system is a classical type system for the lambda calculus with parametric polymorphism. It is also known as Damas–Milner or

Type inference
9.0). The majority of them use a simple form of type inference; the Hindley–Milner type system can provide more complete type inference. The ability to

trim tangle
#

that's propaganda from Big Lambda

hasty phoenix
#

Given this construct, how can I tell the type checker that v cannot be of type Missing?

class Missing:
    """ Missing value """

MISSING: Final[Missing] = Missing()
"""Missing value singleton"""

def fn(data: dict[str, int], default: str | Missing = MISSING):
    v = data.get("key", default) if default is not MISSING else data["key"]
    reveal_type(v)  # type of "v" is "int | str | Missing"
    return v
restive rapids
hasty phoenix
restive rapids
#

well, there is no guarantee that MISSING is the only value of type Missing, so you dont get narrowing
there is a trick with a single member enum ⬇️

rare scarab
hasty phoenix
#

Right, because there is no way to say use that object to the type annotator. Ok, I'll see how to do this with an enum.

rare scarab
#

Or changing it to use isinstance

restive rapids
rare scarab
#

You can use Literal[MISSING_T.MISSING] as well.

sharp snow
#

i like how theres a separate dedicated channel for a simple feature in python.

#

peak sophistication right here.

trim tangle
#

simple feature

#

unfortunately, it's not very simple...

sharp snow
meager slate
sharp snow
#

how many types are there anyway?

meager slate
#

Starts off simple and you end up doing all sorts of wierd stuff

meager slate
#

You can make your own types

sharp snow
sharp snow
meager slate
meager slate
sharp snow
#

just saying.

meager slate
sharp snow
#

ok

icy obsidian
#

How bad of an idea is it to do something like this:

if TYPE_CHECKING:
    from abc import ABC, abstractmethod
    
    class Cls(ABC):
        def __init__(self) -> None:
            self.attr = None

        @abstractmethod
        def meth(self) -> None:
            raise NotImplemented 
else:
    class Cls:
        def __init__(self) -> None:
            self.attr = None
        
        def meth(self) -> None:
            raise NotImplemented 

That is disregarding the "premature optimization" thingy as it is indeed a small thing - was just wondering.

mossy rain
icy obsidian
#

Yea, that was just a copy implementation :> an ellipsis is fine as well there

#

Would that be a somewhat reasonable thing to do for an object that is created often? And yea... ABC enforces at runtime for Cls not to be instantiated.

restive rapids
meager slate
#

Damn

meager slate
#

And as you say yourself, it's nothing more than premature optimization. If that REALLY matters to you and plays a significant role then sure, if not then avoid it

icy obsidian
#

As long as there are non-implmented methods.

#
Exception has occurred: TypeError
Can't instantiate abstract class Test without an implementation for abstract method 'test'
meager slate
#

Interesting

meager slate
icy obsidian
#

3.13

#

Maybe there is a misunderstanding - the thing I did is only for static check. My code bypasses the runtime instantiation checks, as at runtime it does not use the ABC.

meager slate
#

Ah yeah that's what I was asking

icy obsidian
#

The unfortunate thing about ABC (and Protocol) is the overhead they create :<

meager slate
meager slate
icy obsidian
#

But the thing I asked will be the very last thing to add 🙂

meager slate
#

Lol alright

icy obsidian
#

As a separate question is there a way to work with generic classes without explicitly specifying types?

class Test[_T]:
    def __init__(self, t: _T) -> None:
        self.t: _T = t
        
class IntTest(Test):  # <<<<< No explicit [int]
    def __init__(self) -> None:
        super().__init__(5)
        
i = IntTest()
reveal_type(i.t)  # Unknown
trim tangle
#

you have to explicitly specify that you're inheriting from Test[int] (or Test[a_typevar] if you want your class to be generic too)

icy obsidian
#

Yea, couldn't find anything about implicit one. Thanks

mossy rain
#

One could define _T = TypeVar("_T", default=int), and use old-style generics (iirc defaults don't always work with new-style ones)

trim tangle
#

yes

mossy rain
#

But that wouldn't make it stay modular for this kind of inheritance, although one might be able to do class IntTest[T](Test[T]): ...

icy obsidian
# mossy rain What about TypeVar defaults?

There is no need for TypeVar - you can specify defaults in new style as well class Test[_T = int]:
Though this does not implicitly derive the type based on the __init__ arguments - those are just defaults.

oblique urchin
#

and analogously make a do-nothing decorator called abstractmethod

#

whether it's a good idea, it may solve some problems but it also makes your type checker less aware of what's actually going on in your program, so it may may become less useful

icy obsidian
#

However this gives exception:

#
Exception has occurred: TypeError
Cannot create a consistent method resolution order (MRO) for bases object, Generic
trim tangle
#

!e ```py
class FooT:
pass

print(Foo())

#

🥴

rough sluiceBOT
# trim tangle !e ```py class Foo[T](object): pass print(Foo()) ```

:x: Your 3.13 eval job has completed with return code 1.

001 | Traceback (most recent call last):
002 |   File "/home/main.py", line 1, in <module>
003 |     class Foo[T](object):
004 |         pass
005 |   File "/home/main.py", line 1, in <generic parameters of Foo>
006 |     class Foo[T](object):
007 |         pass
008 | TypeError: Cannot create a consistent method resolution order (MRO) for bases object, Generic
viscid spire
#

Man wtf

feral wharf
#

Why in the illegal are we doing that

pastel egret
#

When you define a typevar on a class, the new syntax injects typing.Generic into the end of base class list. Probably shouldn't be producing metaclass conflicts though.

oblique urchin
#

When I implemented this I decided it was OK because class Foo(object, Generic[T]): in the old syntax raised the same error

lunar dune
trim tangle
#

some guy on a remote island still using an old version of wemake-python-styleguide

sharp snow
#

guys isnt this supposed to be immutable?

#

its literally being anything but immutable

#

i want const in python rn

trim tangle
#

Moreover, you're not reassigning the variable

sharp snow
#

what is Final() even for then?

little hare
#

Telling the developer that they shouldn't reassign that variable.

#

its like "as long as you follow our contract, this variable will always refer to the same instance/object"

oblique urchin
little hare
#

(also it would be Final[int], not ())

sharp snow
#

ahhh got it
its the IDLE thats making me confused.

trim tangle
# sharp snow what is Final() even for then?

It's for type checkers. If you're familiar with C++, they're linters like "clang-tidy". The most popular type checkers for Python are mypy and pyright.

$ cat test.py
from typing import Final

X: Final = 42

X += 1
$ mypy test.py
test.py:5: error: Cannot assign to final name "X"  [misc]
Found 1 error in 1 file (checked 1 source file)
little hare
little hare
trim tangle
# sharp snow no pylance?

Pylance is a closed-source extension for VSCode that's based on pyright. By default it doesn't do type checking, you have to change it in the settings

little hare
#

iirc it does basic typechecking? Er not pyright basic

sharp snow
trim tangle
sharp snow
#

gimme a vs code extension for a great python type checker please.

sharp snow
sharp snow
meager slate
sharp snow
#

is pyright good or smth?
Should i switch to pyright?

meager slate
#

if you wanna go full on type checking use it from the terminal via mypy/pyright

meager slate
little hare
#

pylance is pyright..

sharp snow
#

im confused

little hare
#

in terms of typechecking at least

meager slate
# sharp snow im confused

pyright is a static typecheker. You can use this tool via your terminal also. Pylance on the other hand is a vsc extension which uses pyright under the hood with some extra stuff (it's closed source)

#

so what im saying is that configure your pylance's typechecker mode to basic so you don't have to deal with ugly squiggly lines all the time

little hare
#

ye

#

or used basedpyright to add types to an existing codebase 🤡

fiery canyon
little hare
#

effectively is pyright

rare scarab
#

pylance bundles pyright

fiery canyon
#

erm akshually

little hare
#

erm akshually this is the type checking channels and when checking if something is an instance of another object, pylance would therfore be a subclass of pyright in type modes

fiery canyon
tired pilot
#

I have a dependency on a library (pint) which recently updated some of its main types to be generic. I would like to keep supporting older versions, but it seems like it might not be possible just because pint>0.25 wants Quantity[float] and pint<=0.25 wants Quantity. Other than just pinning the version or ignoring the types, am I missing any obvious solutions to this predicament?

#

I think what I might do is define _Quantity: TypeAlias = Quantity[float] # type: ignore[type-arg]. That seems to be working, but it's not ideal since I have to turn off warn_unused_ignore because it's fine in newer versions.

rare scarab
tired pilot
#

That exact situation was throwing the type-arg error, saying that Foo is not generic (for the old version of the library)

rare scarab
tired pilot
#

That's a good point, and likely what I'll do in the CI. I appreciate the feedback!

feral wharf
#

object vs Any for annotation?

rare scarab
feral wharf
#

thanks

quartz hare
#

hi everyone

tropic sun
#

How do I type annotate a function that takes a variable list of types and returns a sequence of instances of those types respectively?

def get(*types: type[V]) -> tuple[V]:
    return tuple(t() for t in types)
get(int, str, dict)  # -> (0, '', {}) should be typed as tuple[int, str, dict]

I tried using TypeVarTuple and Unpack.

trim tangle
# tropic sun How do I type annotate a function that takes a variable list of types and return...

You'll have to make an overload ```py
@overload
def get() -> tuple[()]: ...
@overload
def get[T0](t0: type[T0], /) -> tuple[T0]: ...
@overload
def get[T0, T1](t0: type[T0], t1: type[T1], /) -> tuple[T0, T1]: ...
@overload
def get[T0, T1, T2](t0: type[T0], t1: type[T1], t2: type[T2], /) -> tuple[T0, T1, T2]: ...

repeat until you're bored...

def get(*types: type[Any]) -> tuple[Any, ...]:
# actual implementation

#

(if you can't use 3.12 yet you'll need to use the old syntax for typevars)

leaden ember
#

Is this a bug in mypy? Or am I just a beginner who doesn't really know what's up?

from typing import Any


def my_func(s: Any) -> str:
    assert isinstance(s, str)
    reveal_type(s)
    s = s.replace(": ", ":")
    reveal_type(s)
    return s
❯ mypy --version
mypy 1.18.2 (compiled: yes)
❯ mypy t.py
t.py:6: note: Revealed type is "builtins.str"
t.py:8: note: Revealed type is "Any"
Success: no issues found in 1 source file

Why does the s.replace call "un-narrow" the type?

trim tangle
leaden ember
#

Thanks for checking against pyright! Didn't think about that. Okey dokes, might check the mypy issue tracker later

leaden ember
leaden ember
meager slate
stable fjord
#

if multiple type checkers have the same problem it's often a problem in typeshed

#

though that seems less likely here

leaden ember
trim tangle
#

pyright and mypy have different error codes and such otherwise

leaden ember
#

Actually, I was just checking myself with pyright playground and they have the same output as mypy...?

trim tangle
#

yeah, I meant in general

#

there's no unified formatting or convention for type checking errors

cold nimbus
#
T = typing.TypeVar("T")
DisjointSet = dict[T, [tuple[int, int]]]

mypy says error: Bracketed expression "[...]" is not valid as a type [valid-type] on that second line. Why? What's not valid about it? Do I need to use Hashable or something?

fiery canyon
cold nimbus
#

Ah. Good question 😄 I think that's meant to be a set[tuple] 😄 Thanks!

fiery canyon
#

Yuh

cold nimbus
#

Or just drop the [] actually. But yes. I couldn't see that. Thank you!

fiery canyon
#

Glad to help

#

Hmm are you using py between 3.9 and 3.12

#

9/10/11

cold nimbus
#

3.13

viscid spire
#

well the newer syntax still uses typevars

fiery canyon
#

Hmmm I think it was actually not deprecated, my bad

viscid spire
#

@fiery canyon

#

Excuse me

#

I have a reply to you in there

viscid spire
#

Why would you delete your msgs tho

#

even if they're wrong

#

Just strike through them

fiery canyon
#

Sorry I'm just used to doing that lmao

#

Good point

#

Anyway at some places it'd be more convenient to use the new syntax instead of a typevar directly

austere cosmos
barren rampart
trim tangle
barren rampart
#

strange. But still, s is a str and should be typed as such. Also it does then output the right types.

fiery canyon
#

The 3.12+ syntax really makes my life easier

#

That's just some random code I converted from java

viscid spire
viscid spire
#

and of course the staticmethod there

fiery canyon
#

Just bored lol

#

I would've also just made a copy() function instead of having to use temps

#

The school-work requirements are weird

fiery canyon
solemn sapphire
fiery canyon
#

Indeed

charred summit
fiery canyon
fiery canyon
#

There's probably a better way to explain it but that's what I got

charred summit
#

so its just like a general type element, so this function / class can work on generic types

#

similar to haskell if you know that

function :: (a,b) -> (b,a)
function (x,y) = (y,x)

the function can work on any type

fiery canyon
#

:: hmm

trim tangle
hasty phoenix
#

I've implemented an abstract base class which I'm implementing in several derived classes. However, I get an error from mypy when I use overload on the derived class to specify what type is returned. What can I do to make mypy happy here? See https://bpa.st/RVJQ

#

Everything is good when @overload isn't used. No errors. Except that the static type checker is unable to resolve statically the type it returns. Which is why I have the @overload here.

#

To make it more compact. This is giving no errors:

class Memory(ABC):
    @abstractmethod
    def view(self, padding: int | None = None) -> Memory: ...

class Segment(Memory):
    def view(self, padding: int | None = None) -> Segment: ...

class MemoryMap(Memory):
    def view(self, padding: int | None = None) -> MemoryMap | Segment: ...

While this is:

class MemoryMap(Memory):
    @overload
    def view(self, padding: None = None) -> MemoryMap: ...
    @overload
    def view(self, padding: int) -> Segment: ...
    def view(self, padding: int | None = None) -> MemoryMap | Segment: ...
#

Is there a way to combine @overload and abstract methods like this?

solemn sapphire
#
from __future__ import annotations

from typing import Protocol, overload

class Memory(Protocol):
    def view(self, padding: int | None = None) -> Memory: ...

class Segment(Protocol):
    def view(self, padding: int | None = None) -> Segment: ...

class MemoryMap(Protocol):
    @overload
    def view(self, padding: None = None) -> MemoryMap: ...
    @overload
    def view(self, padding: int) -> Segment: ...
    def view(self, padding: int | None = None) -> MemoryMap | Segment: ...
hasty phoenix
#

The classes in the example where reductions of the actual classes, not protocol classes. Do I need to have a declarative protocol class for each of them?

solemn sapphire
#

I wonder if you can just have a single protocol and have your classes adhere to that protocol

from typing import Protocol

class Memory(Protocol):
    def view(self, padding: int | None = None) -> Memory: ...
#
class A:
    def view(self, padding: int | None = None) -> Memory:
        return A()

class B:
    def view(self, padding: int | None = None) -> Memory:
        return B()

class C:
    def view(self, padding: int | None = None) -> Memory:
        return C()

def f(m: Memory):
    m.view()

f(A())
f(B())
f(C())

seems to work 🤔

hasty phoenix
#

I think I prefer that e.g. B.view() declares that it returns B type, when B is an implementation of the generic A type.

solemn sapphire
#

wait

#

you can just @abstractmethod on view

hasty phoenix
#

My code above works runtime as ABC. It's the typing annotation that's not completely like I want it.

rare scarab
#

What if view returned Self?

#

could be implemented via type(self)() or self.__class__()

hasty phoenix
#

It's not always returning Self thou

solemn sapphire
#

I'm surprised that even @override doesn't work

#

but override overrides implementation not type signature

#

maybe you should just rename it to view_map 😅

hasty phoenix
#

Here's a complete general example

class A(ABC):
    @abstractmethod
    def view(self, x: int | None = None) -> A: ...

class B(A):
    def view(self, x: int | None = None) -> B:
        return B()

class C(A):
    @overload  # mypy complains: "Signature of "view" incompatible with supertype "A""
    def view(self, x: int) -> B: ...
    @overload
    def view(self, x: None = None) -> C: ...
    def view(self, x: int | None = None) -> B | C:
        if x is None:
            return C()
        else:
            return B()

# This works as expected
reveal_type(C().view(None))  # Revealed type is C
reveal_type(C().view(5))     # Revealed type is B
#

Everything works as it should be, except for that single mypy error on the @overload. I'm considering to silence it. I think its a bug.

solemn sapphire
#

No definitely not a bug, C inheriting from A is a problem
because you've got the same method returning two completely different things
if you change class C(A) to class C(ABC) it'll work

hasty phoenix
#

Look at B. It's view() reduce the type from A to B. Is that permitted according to the Liskov principle? Because neither mypy nor pyright is complaining about that reduction.

trim tangle
#

pyright accepts this code, mypy just doesn't understand that the signatures are compatible

hasty phoenix
solemn sapphire
#

Oh hmm

charred mauve
#

Hii

#

How i can get job after learning python

solemn sapphire
trim tangle
hasty phoenix
trim tangle
#

one possible hack would be checking if the implementation signature is compatible with the parent, to cover cases like yours

hasty phoenix
trim tangle
#

yeah

#

definitely search for existing issues though

#

there are over 2600 open issues in mypy

hasty phoenix
#

Yeah, I see that 😄

trim tangle
#

pyright only has a backlog of 177, which is achieved by marking some bugs/inconsistencies as asdesigned

hasty phoenix
stiff shale
#

Just wanted to say, that I love type hints!

hasty phoenix
#

There is a principle to keep try...except loops small and confined to what can fail. So its tempting to do a small try block which sets flags that the code later can act on.

def do_something() -> int: return 42
def do_something_else() -> str: return "fallback"

try:
    data = do_something()
    primary = True
except Exception:
    primary = False
    alternative = do_something_else()

if primary:
    print(f"Primary succeeded with data: {data}")
else:
    print(f"Primary failed, alternative data: {alternative}")

However pyright doesn't seem to be able to deduce that the type of data is known when the state of primary is True. Is there an overall better pattern for this type of setup?

trim tangle
#

(and use the same variable name)

#

pyright can't even deduce this... ugh ```py
try:
data = True, do_something()
except Exception:
data = False, do_something_else()

#data is inferred as tuple[Literal[True], int] | tuple[Literal[False], str]
if data[0]:
reveal_type(data[1]) # int | str...

fiery canyon
#

It's not good practice to do a general exception catch like that. You should narrow it or remove it

#

Unless that goes way out of our topic lmao

trim tangle
#

i think it's just a minimal example

fiery canyon
#

Yeah lemme reread that thang

hasty phoenix
trim tangle
#

For more complicated scenarios, you can introduce a "tagged union" ```py
@dataclass(frozen=True, slots=True)
class Primary:
foo: int

@dataclass(frozen=True, slots=True)
class Secondary:
foo: str
bar: bool

try:
thing = get_primary()
except MyException:
thing = get_secondary()

if isinstance(thing, Primary):
...
else:
...

hasty phoenix
#
data: int | str
try:
    data = do_something()
except Exception:
    data = do_something_else()

Works, but I need to declare data up front. So these two methods must have accessible types I can type out.

trim tangle
#

you only need to declare data up front for mypy, not pyright

rare scarab
#

I'd probably do a try/else. ```py
try:
data = do_something()
except Exception:
alternative = do_something_else()
print(f"Primary failed, alternative data: {alternative}")
else:
print(f"Primary succeeded with data: {data}")

#

data is available inside else, but not except.

trim tangle
#

a library function returning a type that a user cannot mention usually leads to annoying situations in general

hasty phoenix
fiery canyon
#

It does make sense that the type is not known though, data is not guaranteed to be assigned

rare scarab
#

Sure. The except was already triggered. Raising a new exception will just raise it

#

if you want, you could even add a nested try/except/else

hasty phoenix
trim tangle
#

yep

spiral fjord
meager slate
fiery canyon
#

I don't want to export them to the main part of the lib

#

Or at all

#

Maybe I should..

hasty phoenix
#

You know, the user cannot type check the result without them

trim tangle
#

If I have code like this: py thing = somelib.get_thing() thing.prefetch() for i, thong in enumerate(thing.thongs(), start=0x3f00): thong.frobnicate(i) I want to be able to extract the loop into a function, so it will be extremely annoying if I cannot mention the return type of thing

fiery canyon
#

Yk technically I can make a dedicated import for them
like
from lib import types

trim tangle
#

If you don't want to expose some internal class, that's fine, you can use a protocol or base class as the return type

fiery canyon
#

I can also do the classes directly since you can't get privates from them in my case

fiery canyon
#

Damn I still got like 20 commits I haven't pushed yet on there

trim tangle
fiery canyon
#

Alternatively I can make functions raise if they don't return the exact thing they should
But that's annoying

#

Okie saving this info for later

feral wharf
trim tangle
feral wharf
#

oh damn

lunar dune
trim tangle
#

mom, I'm on television hyperlemon

trim tangle
lunar dune
#

It means we're now confident ty should understand your code pretty well and catch most errors mypy/pyright would catch

#

There are still some Todo types left to work out. But there are many fewer than there used to be

hasty phoenix
#

I'm augmenting int(obj) in a function with some preprocessing. The IDE told me the int() expected a type ConvertibleToInt, so I searched up and down in cpython for it, only to realize that its in typeshed :D. It is ok to import it via from _typeshed import ConvertibleToInt, or is that the back door we frown at? Is there a more correct way to import it?

trim tangle
hasty phoenix
rough sluiceBOT
#

stdlib/_typeshed/__init__.pyi lines 368 to 372

# Anything that can be passed to the int/float constructors
if sys.version_info >= (3, 14):
    ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex
else:
    ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc```
trim tangle
#

you could use typing.SupportsInt | str if you don't care about covering all edge cases

hasty phoenix
#

Why can't I simply use the already available alias in typeshed for this? Isn't that what its for?

trim tangle
#

So you can use it, but it might stop working tomorrow. Even if it is stable, it might stop working after 366 days.

#

(of course, the code will still run, but that would only contribute to the image of python's typing ecosystem being bolted on and not having similar backwards compatibility)

hasty phoenix
#

OK, thanks. -- I'm looking forward to the day when I get "copy-this-function's-signature" in a compact way.

trim tangle
#

yeah, something like ArgumentsOf[int][0] would be cool

hasty phoenix
#

Heh, mypy have zero trust. One could infer that if k exists in d it is int, but no.

def f(k: int|str, d: dict[int, str]):
    if k in d:  # error: Invalid index type "int | str" for "dict[int, str]"; expected type "int"  [index]
        return d[k]
viscid spire
hasty phoenix
#

What's the relationship between the static type bytes and isinstance(x, bytes)? It seems the latter is leaving out bytearray and memoryview. What's the right way to make sure they are consistent?

#

Its a pyright thing. mypy have no problem with it

trim tangle
#

I think it's similar to the whole int | float thing

trim tangle
hasty phoenix
#
def fn(v: bytes|int|None) -> None:
    if isinstance(v, bytes):
        v = None
    reveal_type(v)
# mypy: Revealed type is "Union[builtins.int, None]"
# pyright: Type of "v" is "bytearray | memoryview[_I@memoryview] | int | None"
trim tangle
#

oh, mypy doesn't do this promotion in --strict mode because it implies --strict-bytes

#

In pyright, there's a disableBytesTypePromotions setting

hasty phoenix
#

ty

trim tangle
#

you can imagine type checker authors have very worn down i and f keys on their keyboards 🥴

hasty phoenix
#

Doing full type annotation on py code is honest and real work. Especially when there's dynamic types involved.

#

I made an interesting observation at work. C++ ppl (in particular) likes to rant about what they call "Python typelessness" (and they are wrong). But at the same time, when the same C++ devs write any Python snippet, they are not consistent with type usage. I find that ironic and amusing.

trim tangle
#

to be fair, python's type system is a second thing you need to learn on top of python, and it's not sometimes you'll find extensive docs on, compared to just "learning to write Python scripts as a sane replacement for Bash"

feral wharf
#

we can't map a dict to an attribute at type checking without hardcoding overloads right?

#
class MyData(TypedDict):
  example: int

class MyClass:
  def __init__(self, data: MyData) -> None:
    self.__data: MyData = data

  def __getattr__(self, name: str) -> Any: # ???
    ...

reveal_type(MyClass({"example": 1}).example) # <class 'int'>
hasty phoenix
#

What would you like differently here? It seems the type checker is able to infer the right type returned by __getattr__, isn't it?

feral wharf
#

that's how I want it to be haha

#

the actual type there would be Any

#

i wonder if it's even possible to type __getitem__ without overloads

fiery canyon
#

What does __ before attr name indicate

trim tangle
# feral wharf ```py class MyData(TypedDict): example: int class MyClass: def __init__(sel...

This is theoretically possible, if you accept a generic typeddict: ```py
from typing import TypedDict, Protocol, LiteralString

class HasKeyK, V:
def getitem(self, key: K, /) -> V: ...

class Record[TD]:
def init(self, d: TD) -> None:
self._d = d

def __getattr__[K: LiteralString, V](self: Record[HasKey[K, V]], key: K) -> V:
    raise NotImplementedError

class Fruits(TypedDict):
apple: int
banana: list[str]

f: Fruits = {"apple": 420, "banana": ["b", "a", "n"]}
thing = Record(f)

reveal_type(thing.apple) # theoretically, should be "int"
reveal_type(thing.banana) # theoretically, should be "list[str]"
``` unfortunately, no type checker currently understands this, both mypy and pyright say that the types are object and object

feral wharf
#

ugh damn

gray gorge
#

How to pass type as a Literal[] value instead of general int based on function variable?py def fetch_cursor_paginated_response[M: Schema, S: int]( cursor: CursorWrapper, model: type[M], status_response: S = status.HTTP_200_OK, ) -> tuple[Literal[S], Paginated_Response[M]]: This have an error on return type Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value
If I use S instead in called function it would be typed as int

trim tangle
gray gorge
#

didn't get your question
the lib use return types in the endpoint to handle responses

so if endpoint have type like tuple[Literal[200], ...] then it handle it as so
but when in endpoint function I call return fetch_cursor_paginated_response(...) it is erroring that Tuple entry 1 is incorrect type "int" is not assignable to type "Literal[200]"

trim tangle
gray gorge
#

because it handle other statuses, more specifically tuple[Literal[200], Type1] | tuple[Literal[403], ErrorType]

#

so actual response is only on 200, but 400ish statuses use different models

trim tangle
#

Why does the fetch_ function even care about response statuses? Can't you just do ```py
def fetch_cursor_paginated_response[M: Schema](
cursor: CursorWrapper,
model: type[M],
) -> Paginated_Response[M]:
...

...
return 200, fetch_cursor_pagniated_response(...)

#

If you really want a status code to pass through the function like that, the best you can do is an overload:

@overload
def fetch_cursor_paginated_response[M: Schema](
    cursor: CursorWrapper,
    model: type[M],
) -> tuple[Literal[200], Paginated_Response[M]]: ...

@overload
def fetch_cursor_paginated_response[M: Schema, S: int](
    cursor: CursorWrapper,
    model: type[M],
    status: S,
) -> tuple[S, Paginated_Response[M]]: ...
gray gorge
#

well, this is more for convenience, the statuses that is being used is not numbers itself, but constants, like status.HTTP_200_OK, status.HTTP_403_FORBIDDEN, etc., so by default it add a shortcut version if status is 200 which it is in most cases, plus in IDE it still highlight it not as just 200, but as status.HTTP_200_OK

trim tangle
#

You could allow returning a bare Schema from a request handler, which would imply a 200 status code

gray gorge
#

there are some cases where I use 201, so it was reserved for that

trim tangle
#

I'd never expect 201 Created to be the default behaviour tbh. That's going to be surprising for people coming from other popular libraries (like Flask and FastAPI)

#

(as you said, 200 is the status returned in most cases on the happy path)

terse sky
#

If anything I still often need to explain to python devs (i.e. people who have only ever done python), some of the issues with things like Literal and TypedDict, which don't exist with things like enums and dataclasses

#

If you've done stuff with constexpr in C++ then these distinctions are natural but if you haven't done it or something similar it may not be as clear why things are murky when you have types that rely on statically known values but no rules for deciding when a value is statically known

fiery canyon
#

Man why do I gotta pass the instance to a method (in a class) that returns a type guard

#

It does just what I need except for that part

fiery canyon
# viscid spire Show code
from typing import TypeGuard, Protocol


class Guild
    name: str

class WithGuild(Protocol):
    guild: Guild

class AppAuthorized:
    def __init__(self) -> None:
        self.guild: Guild | None = None

    def is_guild_install(self) -> TypeGuard[WithGuild]:
        return self.guild is not None

Problem:
User-defined type guard functions and methods must have at least one input parameter

I can fix it by doing the following, which I don't like:

class AppAuthorized:
    ...
    def is_guild_install(self, _: AppAuthorized) -> TypeGuard[WithGuild]: # must add an arg, with this type annotation to avoid problems
        return self.guild is not None

a = AppAuthorized()
if a.is_guild_install(a): # must pass a and nothing else
    print(a.guild.name) # works
#

I just thought it'd be nice for the check to narrow .guild to not be None

#

Wouldn't it be cool if TypeGuard functions considered self as the input arg (:

spiral fjord
#

They noted that they specifically disallowed this to prevent typeguard implementation from being too complicated

fiery canyon
#

M_huh
Allowing it to serve as a class method may make it too complicated?

#

Well all I can say is that it'd be convenient for me

viscid spire
rare scarab
#

instance method

viscid spire
#

That would be a better way to emphasize just a plain-old method yeah, my bad

hasty phoenix
#

When did Python introduce the usage of using the native type for type annotation? E.g. use listinstead of List?

hasty phoenix
#

Assume designing an ABC MyBase with one or more implementations, MyImpl*. I have a function which is get_instance() (terrible name). Should this function be specfic or generic in its return type? I.e. generic as in -> MyBase or specific -> MyImpl1 | MyImpl2. When do you chose one over the other?

fiery canyon
#

I'm assuming you mean the return type that you'd write in the subclasses, because on the base it should be -> Self

#

Personally I'd do -> ThisImpl, but Idk what's the professional thing to do here

hasty phoenix
#

Yeah, thanks

hasty phoenix
#

How can I declare that a function is mimicking another function without having the actual function, like you would in a decorator? Consider

def fn(a: int, *, b: int = 1) -> None:
    ...
# This type annotation doesn't work
def replica(*args: P.args, **kwargs: P.kwargs) -> T:
    return fn(*args, **kwargs)

I'm targeteting minimum py3.10

hasty phoenix
#

I was toying with this hack which seems to work runtime and statically. However pyright definitely doesn't like it:

def replica(*args: P.args, _cls: Callable[P, T] = fn, **kwargs: P.kwargs) -> T:
    return _cls(*args, **kwargs)
hasty phoenix
#

Google AI suggests making a mimic decorator to copy the signature

feral wharf
#

Yeah that's basically how you do it

#

See how asyncio.to_thread does it too

hasty phoenix
#

I'm not too fond of it when it becomes needed to make runtime adoptions to describe the static typing (however how small) tbh. I think it highlights a shortcoming in the language. - With respect, because I don't know how it could be solved purely statically.

rare scarab
#

If you have control of both functions and only care about the kwargs, you can unpack a typeddict into **kwargs.

#

Sure would be nice if we had a @typing.satisfies(some_function) decorator

#

Could even allow usage on classes to avoid introducing a Protocol metaclass

feral wharf
#

Oo yeah

craggy sage
soft matrix
#

yonks ago I was young and naive, how much effort would it be to change this

class Baz(Bar[Self]): ...  # Rejected```in the typing spec to allow it inside of nested classes?
#

i think its still in the spirit of 673 to do this cause of the way it desugars

fiery canyon
#

I just don't get the logic of the very first thing

#

Oh wait, it's passing T which is the subclasser type

#

When is that useful

soft matrix
#
class PyLocalObject[ParentT]:
    parent: ParentT

class Parent:
    class Child(PyLocalObject[Self]):
        def meth(self):
            reveal_type(self.parent)  # Parent
fiery canyon
#

So what you're trying to do is to pass definition's type to what it is subclassing

#

Isn't that recursive

soft matrix
#

no it shouldnt be recursive

#

sorry realise the names were unintentionally very confusing

fiery canyon
#

Maybe I'm wrong

#

Maybe the type checker doesn't prioritize like this

soft matrix
#

you can already do that no?

class PyLocalObject[ParentT]:
    parent: ParentT

class Parent:
    class Child(PyLocalObject[Parent]):
        parent: Parent
        def meth(self):
            reveal_type(self.parent)  # Parent```
#

i dont think this would significantly affect performance

fiery canyon
soft matrix
#

Parent

fiery canyon
#

I thought Child

soft matrix
#

Self is the type of the enclosing class Child hasnt been defined yet

fiery canyon
#

Hmm but you wouldn't really need Self here since you can just reference Parent

#

At least on 3.12+

soft matrix
#

yes you dont technically but i dont wanna mess it up accidentally

fiery canyon
#

Generally (3.12+) isn't Self only useful for a base class

#

Unless I misunderstand its purpose

soft matrix
#

can you give an example?

#

i'd suggest looking at the typing docs cause that shows off some of the usecases

fiery canyon
# soft matrix can you give an example?

If Foo isn't meant to be subclassed, then you don't necessarily need Self (especially if it means importing it just for that)

However, if I were to subclass it as it is, type of the subclasser's bar() would be Foo instead of Bar. That's where using Self is helpful

#

It's not a huge deal xD

soft matrix
#

yeah thats one of the main usecases

fiery canyon
#

I do wonder why it doesn't allow it then

#

But should you ever even make a nested class 🤔

#

Does that follow any PEP

soft matrix
#

its generally against pep 8

#

but this code is just kinda cursed tbh

soft matrix
#

is this a bug?

rare scarab
#

Missing a Literal[]?

soft matrix
#

no im refferring to the class here

#

confused cause it works fine for SurfaceDefn but not the definition

fiery canyon
#

Check if you have a spell checker or whatever

#

Unless you mean the errors on the bottom

#

Oh you do

#

Btw on 3.14+ you wouldn't need to pass them in quotes I believe

trim tangle
#

3.14+

fiery canyon
#

!e

from typing import Module
rough sluiceBOT
# fiery canyon !e ```py from typing import Module ```

:x: Your 3.14 eval job has completed with return code 1.

001 | Traceback (most recent call last):
002 |   File "/home/main.py", line 1, in <module>
003 |     from typing import Module
004 | ImportError: cannot import name 'Module' from 'typing' (/snekbin/python/3.14/lib/python3.14/typing.py)
fiery canyon
#

Is there a type for that

#

Well it'd be kinda useless

lunar dune
#

types.ModuleType

fiery canyon
#

What's the types module for

trim tangle
#

uh

#

types

fiery canyon
#

Why can't there just be one library for all of this

#

Instead of splitting it to typing, collections.abc, types and maybe more that Idk of

trim tangle
#

!d types

rough sluiceBOT
#

Source code: Lib/types.py

This module defines utility functions to assist in dynamic creation of new types.

It also defines names for some object types that are used by the standard Python interpreter, but not exposed as builtins like int or str are...

trim tangle
#

it was added in 1994

#

(when type and class meant two different things)

feral wharf
#

Holy

#

But did they really need a separate annotationslib in 2025

fiery canyon
#

What's annotationslib for

#

Man no matter what I do I can't get rid of those errors cRy

#

data may always be Unknown no matter how i type it

#

Not to mention read is unknown

#

Guess that's how it is when their typing is like this

rare scarab
#

Fork it

fiery canyon
#

I was just about to ask if anyone knows of a different sound lib xD

rare scarab
#

probably created via typegen

fiery canyon
#

but nah not gonna fork that whole thing

rare scarab
#

Ah, it's a single module file. We can't provide typing for those.

rare scarab
#

Because it requires a py.typed marker file

fiery canyon
#

or pyi?

rare scarab
#

or providing a pyi file, yes

fiery canyon
#

Can you not just do typing regularly inside the __init__ huh

fiery canyon
#

Might search myself but we'll see

rare scarab
#

latest main has types in the source. Try installing from git

fiery canyon
#

interesting lets see

rare scarab
#

Probably won't help in mypy, but pyright should pick them up

fiery canyon
#

pyright (:

rare scarab
#

IIRC pyright doesn't care about py.typed

fiery canyon
#

shouldnt mypy be slower in general

#

cant imagine python beating typescript lol

trim tangle
#

in my tests it is faster than pyright somehow

#

(for type checking from scratch)

rare scarab
#

it's not python vs typescript. it's c transpiled code vs nodejs

fiery canyon
#

do type checkers run in a loop

rare scarab
#

I think most type checkers will make several passes.

fiery canyon
#

thats clean

#

its whatever

#

uhhh oh

#

If I install from git then it's missing some DLL stuff ;-;

#

why'd pypi or pywheels be outdated tho

rare scarab
#

Because they haven't done a release yet.

fiery canyon
#

;-;

#

Hm why does pip say its the same version

rare scarab
#

Because they didn't bump the version

fringe pivot
#

hi ist hier jemand deutsch?

viscid spire
rough sluiceBOT
#

4. Use English to the best of your ability. Be polite if someone speaks English imperfectly.

night wraith
#

I'm trying to overload a decorator, and but only half of it is working. My goal is the classic decorator that can be called to pass kwargs, or not called and use defaults. Right now I have the following:

@overload
def provides[**P, T](
    factory: Callable[P, AsyncIterator[T]],
    *,
    level: int | Enum = 1,
    coverage: list[type[T] | type[Any]] | None = None,
    cover_parents: bool = True,
    never_cache: bool = False,
) -> AsyncProviderGen[P, T]: ...

@overload
def provides[**P, T](
    factory: Callable[P, Iterator[T]],
    *,
    level: int | Enum = 1,
    coverage: list[type[T] | type[Any]] | None = None,
    cover_parents: bool = True,
    never_cache: bool = False,
) -> SyncProviderGen[P, T]: ...

@overload
def provides[**P, T](
    factory: Callable[P, T],
    *,
    level: int | Enum = 1,
    coverage: list[type[T] | type[Any]] | None = None,
    cover_parents: bool = True,
    never_cache: bool = False,
) -> SyncProvider[P, T]: ...

@overload
def provides[**P, T](
    *,
    level: int | Enum = 1,
    coverage: list[type[T] | type[Any]] | None = None,
    cover_parents: bool = True,
    never_cache: bool = False,
) -> ProviderWrapper[P, T]: ...

where ProvideWrapper is a Protocol with an overloaded __call__

class ProviderWrapper[**P, T](Protocol):

    @overload
    @staticmethod
    def __call__(factory: Callable[P, Awaitable[T]]) -> AsyncProvider[P, T]: ...

    @overload
    @staticmethod
    def __call__(factory: Callable[P, AsyncGenerator[T]]) -> AsyncProviderGen[P, T]: ...

    @overload
    @staticmethod
    def __call__(factory: Callable[P, Generator[T]]) -> SyncProviderGen[P, T]: ...

    @overload
    @staticmethod
    def __call__(factory: Callable[P, T]) -> SyncProvider[P, T]: ...

    @staticmethod
    def __call__(factory: Callable[P, Awaitable[T]] |\
                          Callable[P, AsyncGenerator[T]] |\
                          Callable[P, Generator[T]] |\
                          Callable[P, T]
                        ) -> \
                          AsyncProvider[P, T] |\
                          AsyncProviderGen[P, T] |\
                          SyncProviderGen[P, T] |\
                          SyncProvider[P, T]: ...

However, if I use the decorator and pass in kwargs, it has bad typing. If I don't pass kwargs, the typing is good.

@provides(never_cache=True)
def create_a() -> A:
    called_counter["A"] += 1
    return A(value=10)

@provides
def create_b(a: A) -> B:
    called_counter["B"] += 1
    return B(value=10 + a.value, name="B")
#

It's possible I've just hit the ceiling of python LSPs, but hopefully I'm wrong

#

This is what the type looks like when it's working

night wraith
#

For now, I'm just going to remove the good typing and replace with Any. ```py
class ProviderWrapper**P, T:

@overload
@staticmethod
def __call__(factory: Callable[P, Awaitable[T]]) -> AsyncProvider[..., Any]: ...

@overload
@staticmethod
def __call__(factory: Callable[P, AsyncGenerator[T]]) -> AsyncProviderGen[..., Any]: ...

@overload
@staticmethod
def __call__(factory: Callable[P, Generator[T]]) -> SyncProviderGen[..., Any]: ...

@overload
@staticmethod
def __call__(factory: Callable[P, T]) -> SyncProvider[..., Any]: ...
hallow flint
pulsar frost
#

is there any convenient way to type struct.unpack? I dont want to cast() everywhere in case I make mistakes and forget to change something

#

also, how do I type mixin classes for mypy? im not sure how to do this

indigo halo
#

Am I wrong in thinking that the mypy pre-commit hook checks only what's part of the commit? Here, I am finding myself in a situation where mypy on pre-commit complains about type errors in a file that is present in the worktree but unknown to Git. Doesn't pre-commit generate a temporary checkout to test and then this unstaged file shouldn't even be in there?

trim tangle
#

I don't use pre-commit, but you can pass what files you want checked tomypy

#

oh, you mean that it's not supposed to see files that are not checked into git?

indigo halo
indigo halo
#

I think it somehow special-cases this:

[WARNING] Unstaged files detected.
[INFO] Stashing unstaged files to /home/madduck/.cache/pre-commit/patch1766573681-1402765.
#

If I change b and don't stage it, pre-commit runs without errors. If I change b and stage it, then pre-commit includes files that are not part of git.

#

This does not seem right at all

feral wharf
#

When will cast be builtin :(

feral wharf
fiery canyon
feral wharf
#

Not inline and raises an error by default

#

I guess the the error part is good as it's safe but Shrug

#

Oh and it's also at runtime by default

fiery canyon
feral wharf
#

Lol true

fiery canyon
#

Probably due to the fact that read() may be Unknown too ☠️

#

They should just bump the version that has typing already

fiery canyon
indigo halo
#

This seems like a weird error from mypy:

error: Non-required key "colour" not explicitly found in any ** item [typeddict-item]

The TypedDict is:

class TCPlayer(PlayerExportStruct):
    shortname: str
    colour: NotRequired[str]

and I am calling that like TCPlayer(shortname="foo", **exported_player), where the latter is an instance of PlayerExportStruct.

#

So yeah, colour is NotRequired. Why then would I need to explicitly provide it?

trim tangle
indigo halo
trim tangle
# indigo halo ```Py class PlayerExportStruct(TypedDict): name: str club: NotRequired[s...

I think NotRequired[str] means: this key is not required, but if it is present, then it must be a str

Remember: typeddicts are "open" by default. So you could have a situation like this:

class CoolPlayerExportStruct(PlayerExportStruct):
    colour: int

cool: CoolPlayerExportStruct = {"name": "john banana", "colour": 0xff0015}  # ok

def foo(pes: PlayerExportStruct) -> None:
    tcplayer = TCPlayer(shortname="foo", **pes)
    
foo(cool)  # this would create an invalid TCPlayer
#

typeddicts are kinda annoying...

indigo halo
#

Fair, but now what? colour is not part of the **exported_player I am using. If I have to explicitly provide colour, then what do I set it to? None is not a str, but I also don't have a str that is the same as "no colour provided"

trim tangle
#

I don't know if there's any good solution

#

If you know this is fine, you could # type: ignore[typeddict-item] it, I guess

indigo halo
#

or maybe ditch the TypedDict

trim tangle
#

yeah

#

if you don't mind, you can just copy the fields manually```py
def foo(pes: PlayerExportStruct) -> None:
tcplayer = TCPlayer(shortname="foo", name=pes["name"])
if "club" in pes:
tcplayer["club"] = pes["club"]
if "country" in pes:
tcplayer["country"] = pes["country"]

#

TypedDicts are great in theory, but unfortunately there are not many tools for generic or flexible processing of them

indigo halo
#

I could do that. I just don't get the error.

trim tangle
#

PlayerExportStruct is open, so it could contain a colour, but of the wrong type

indigo halo
trim tangle
#

I think mypy just doesn't support closed typeddicts yet

#

Otherwise you could do ```py
class PlayerExportStructClosed(PlayerExportStruct, closed=True):
pass

#

pyright does support closed, but it doesn't complain in your original code in the first place (which is unsound, but it allows a bunch of unsounds things with typeddicts)

indigo halo
#

I've resorted to #type:ignore

fiery canyon
#

closed does what?

trim tangle
# fiery canyon `closed` does what?
class Foo(TypedDict, closed=True):
    x: int
    y: str
``` means that if you have a `foo: Foo`, the only keys it can contain are `"x"` and `"y"`. It cannot contain `"z"` for example.
wild valley
#

if I want to make a function wrapper that works for free functions, instance methods and class methods too, how would I type annotate it?

#

I've spent hours upon hours on this problem without a solution

#

the core problem seems to be that type checkers basically ignore the @classmethod decorator that follows my wrapper

#

it works just fine at run time

viscid spire
#

can you provide a code sample?

wild valley
soft matrix
#

There's no way to do so sadly

#

It's unspecified by pep 612

wild valley
#

why can't type checkers just respect the @classmethod and determine the correct overload for __get__()?

soft matrix
#

I think you might be running into this

viscid spire
#

wow this is a very non-minimal sample

wild valley
#

and how would you minimize it?

#

the MethodWrapper helper class is needed when you need to inject self or cls

#

case in point: I tried to implement lru_cache in a slightly different manner

viscid spire
#

what version of python are you targetting btw? I'm confused why you aren't importing Self and reveal_type from typing

wild valley
#

3.10+

#

Self is a 3.11 thing

soft matrix
viscid spire
wild valley
#

I've seen other implementations of a LRU cache and they all basically gave up on the annotations, including the stdlib one (typeshed)

#

I wanted to believe that it was at least possible but it would seem like it's not

#

@soft matrix seems like you were quite familiar with this issue already, thanks for letting me know that this remains unsolved

#

now I can rest easy and give up myself 🙂

fiery canyon
#

Is there a way to do this correctly in 3.14 without ParamSpec

#

It does that even with A not having a value

#

Oh and I'll remove the Any assignment, dunno why I added it

#

Or maybe I do want it there so it's optional and therefore can be none

#

Will assign to none in that case

trim tangle
fiery canyon
#

Hm but then R cant be optional

trim tangle
#

I guess it doesn't help if you want to specify ...

fiery canyon
#

What is the use of kwargs in types 🤔

#

**A

trim tangle
#

that's ParamSpec

#
type Fn[**Args, Ret] = Callable[Args, Ret]

type F0 = Fn[[], int]
type F1 = Fn[..., bool]
fiery canyon
#

Oh wait

#

I was talking about not using the ParamSpec from typing

#

Not entirely not using it as I didn't know that

#

I'll use that

fossil nest
#

I can't seem to figure out the typing for self.event_handler_map, pylance reports for the initialisation of event_handler_map:
Type variable "E" has no meaning in this context

from typing import Awaitable, Callable, TypeVar

from github.webhook.event import Event

E = TypeVar("E", bound=Event)
Handler = Callable[[E], None] | Callable[[E], Awaitable[None]]


class Webhook:
    def __init__(self):
        self.event_handler_map: dict[type[E], Handler[E]] = {}

    def on(self, event: type[E]) -> Callable[[Handler[E]], Handler[E]]:
        def decorator(handler: Handler[E]) -> Handler[E]:
            self.event_handler_map[event] = handler
            return handler

        return decorator

any help would be appreciated, for reference, this is the intended usage:

@webhook.on(PushEvent)
def push(event: PushEvent):
    print(event)
trim tangle
# fossil nest I can't seem to figure out the typing for `self.event_handler_map`, pylance repo...

There's no way to express a dict such that there's some generic relationship between the key type and the value type. You could make your own, like this ```py
type Handler[E] = Callable[[E], None | Awaitable[None]]

class EventHandlerDict:
def init(self) -> None:
self._data: dict[type[Any], Handler[Any]] = {}

def put[E: Event](self, key: type[E], value: Handler[E]) -> None:
    self._data[key] = value

def get[E: Event](self, key: type[E]) -> Handler[E]:
    return self._data[key]
fossil nest
trim tangle
fossil nest
#

I see. just to confirm, is there any functional difference between these?

Handler = Callable[[E], None] | Callable[[E], Awaitable[None]]

type Handler[E] = Callable[[E], None | Awaitable[None]]
trim tangle
#

No, I don't think so

#

actually, there is

#

depends on what you mean by functional

fossil nest
#

I get the irony of asking the functional difference of a python typehint xD

#

I more meant if it communicated any difference to the developer or a type checking software

trim tangle
#
E = TypeVar("E")
Handler = Callable[[E], None] | Callable[[E], Awaitable[None]]
#---
type Handler[E] = Callable[[E], None] | Callable[[E], Awaitable[None]]

these have the same meaning for type checkers, but type-based type aliases are lazily evaluated

#

Callable[[E], None | Awaitable[None]] is different from Callable[[E], None] | Callable[[E], Awaitable[None]], but I don't think any existing type checkers are able to make use of that

fossil nest
trim tangle