#type-hinting

1 messages · Page 68 of 1

trim tangle
#

async functions are confusingly called coroutines in the docs though

dire bobcat
#

what

#

😢

grave fjord
#

async def _run_event(self, async_fn: Callable[P, Coroutine[Any, Any, object], /, *args: P.args, **kwargs: P.kwargs) -> None: can you use ParamSpec like this? I think it's forbidden

trim tangle
#

Sometimes the terms "coroutine" and "coroutine function/async function" are used interchangeably, like "generator" is sometimes used for "generator function". But it's confusing IMO

hearty shell
#

!e

async def foo(): ...

print(type(foo))
print(type(foo()))
rough sluiceBOT
#

@hearty shell :white_check_mark: Your eval job has completed with return code 0.

001 | <class 'function'>
002 | <string>:4: RuntimeWarning: coroutine 'foo' was never awaited
003 | RuntimeWarning: Enable tracemalloc to get the object allocation traceback
004 | <class 'coroutine'>
hearty shell
#

RuntimeWarning: coroutine 'foo' was never awaited 🤔

#

Didnt know it gave a warning x)

trim tangle
#

oh hm maybe not, it should be there

#

otherwise kwargs will not work

#

it has to be a / tho

grave fjord
#

I always get them mixed up

grave fjord
trim tangle
#

How are async generators even cleaned up? 🤔

#

wait... how are normal generators cleaned up?

grave fjord
grave fjord
#

It's particularly bad if you do something like:

def whatever():
    with open(filename) as f:
        for line in f:
            if not line.startswith('#'):
                yield line
trim tangle
grave fjord
#

It's quite easy to build a thing and do whatever().__next__()

grave fjord
trim tangle
#

sounds awful

#

+42069 levels of indentation 😩

grave fjord
#

There's some garbage collector hooks that run aclose()

#

But if your async_gen function is GC'd in a cancelled task group it can continue executing

trim tangle
#

yeah that's understandable

#

I'm starting to think that async introduces a lot of accidental complexity that most people don't really need...

dire bobcat
#

not sure if this counts as typehinting

#
from typing import Any, Dict, TYPE_CHECKING

if TYPE_CHECKING:
    from .message import Message
    from .state import ClientState


def _event_to_object(name: str, data: Dict[Any, Any], _state: "ClientState") -> Any:
    _event_converters: Dict[str, Any] = {"READY": None, "MESSAGE": Message}
``` how can I import `Message`? it just says `Message` is not defined because `TYPE_CHECKING` is set to false during runtime
oblique urchin
dire bobcat
oblique urchin
#

then you'll need to redesign your program 🙂

dire bobcat
#

w h a t

oblique urchin
#

or use absolute references, I guess

dire bobcat
#

use what?

oblique urchin
#

if your top-level package is called top_level, put import top_level in the file and use top_level.message.Message

dire bobcat
#

nvm i realisd i dont get circular imports

chilly kindle
chilly kindle
#

Assuming there is some object named “thing”, That’s an example of a circular import

proven fog
dire bobcat
#

i just thought my code was going importing something which wouldve raised circular import, but it didnt

#

ohh

#

i said i dont get them

#

i meant my code wasnt raising them

torpid yarrow
#

i don't think so...

proven fog
dire bobcat
#

is it sinful to import something in a function

#

this time i am getting circular import

#

and its pretty hard to avoid

#

nvm avoided it

foggy thicket
#

Hey I am trying to make this decorator's hinting say "Takes a function which takes any amount of arguments (first one is a typevar) and keyword arguments, returns a function (callable) which takes the same first arg, joins (concatenate) type of (type) first variables, rest of the things as is (paramspec) and the return will be the same (also a typevar)"

This is my implementation of it :

R = TypeVar("R")
ARG = TypeVar("ARG")
P = ParamSpec("P")


class Thingy:
    
    @staticmethod
    def instance_and_class_method(
        func: Callable[[Concatenate[ARG, Type[ARG], P]], R]
    ) -> Callable[[ARG, Type[ARG], P], R]:

        @wraps(func)
        def wrapper(self, *args, **kwargs):
            cls: Type[self] = self.__class__
            return func(self, cls, *args, **kwargs)

        return wrapper

This is how I tried using it :

class Foo:
    def __init__(self, x: int):
        self.x = x

    @Thingy.instance_and_class_method
    def bar(self, cls, new_x: int) -> Thingy:
        print(f"{self.x = }")
        return cls(new_x)


f = Foo(1)
b = f.bar(2)
print(b.x)

Works perfectly and as expected but decorating (@Thingy.instance_and_class_method) causes a comically large error :

#
(method) instance_and_class_method: (func: (Concatenate[ARG@instance_and_class_method, Type[ARG@instance_and_class_method], P@instance_and_class_method]) -> R@instance_and_class_method) -> ((ARG@instance_and_class_method, Type[ARG@instance_and_class_method], Unknown) -> R@instance_and_class_method)

Argument of type "(self: Self@Foo, cls: Unknown, new_x: int) -> Thingy" cannot be assigned to parameter "func" of type "(Concatenate[ARG@instance_and_class_method, Type[ARG@instance_and_class_method], P@instance_and_class_method]) -> R@instance_and_class_method" in function "instance_and_class_method"
  Type "(self: Self@Foo, cls: Unknown, new_x: int) -> Thingy" cannot be assigned to type "(Concatenate[ARG@instance_and_class_method, Type[ARG@instance_and_class_method], P@instance_and_class_method]) -> R@instance_and_class_method"
    Parameter 1: type "Concatenate[ARG@instance_and_class_method, Type[ARG@instance_and_class_method], P@instance_and_class_method]" cannot be assigned to type "Self@Foo"

"Concatenate[ARG@instance_and_class_method, Type[ARG@instance_and_class_method], P@instance_and_class_method]" is incompatible with "Foo"
    Function accepts too few positional parameters; expected 3 but received 1
dire bobcat
#

if I have a function that returns Optional[TextChannel], and i am calling it in a spot where it will always return TextChannel and never None how can I assign it to always be a textchannel?

/home/Caeden/Github/discii/discii/message.py:57:16 - error: Expression of type "TextChannel | None" cannot be assigned to return type "TextChannel"
when im doing ```py
@property
def channel(self) -> TextChannel:
"""Returns the channel that the message was sent in."""
return self._channel

basically i just need to make it so that my `self._channel` instance will always be `TextChannel`
hearty shell
#

And because you are returning a functionw with ParamSpec form instance_and_class_method, you also need Concatenate

#
class Thingy:
    
    @staticmethod
    def instance_and_class_method(
        func: Callable[Concatenate[ARG, type[ARG], P], R]
    ) -> Callable[Concatenate[ARG, P], R]:

        @wraps(func)
        def wrapper(self, *args, **kwargs):
            cls: type[self] = self.__class__
            return func(self, cls, *args, **kwargs)

        return wrapper
foggy thicket
oblique urchin
#

mypy doesn't support Concatenate yet

hearty shell
#

Yeah I dont think Mypy supports

#

print(b.x) should still error though because you are returning a Thingy

dire bobcat
#

what is mypy

hearty shell
#

And Thingy has no x

foggy thicket
hearty shell
#

Ah then 'Foo', should leave your code error-less in vscode

hearty shell
foggy thicket
hearty shell
soft matrix
#

You might have better luck with pyright

hearty shell
#

x)

foggy thicket
#

Yeah lol, should switch to pyright :p

hearty shell
#

Support for it should come soon I think? But yeah Pyright does move way faster

#

Pyright supports features even before they are actually finalised and accepted as a Pep xD

dire bobcat
#

there are 2 situations; one where the user inputs an id where it could potentially be a channel, but sometimes not and this situation where it is always a channel

foggy thicket
hearty shell
#

What pyright?

#

It does though?

foggy thicket
#

Self hint in it didn't work for me

dire bobcat
hearty shell
hearty shell
#

but you can just assert self._channel

#

before returning

dire bobcat
hearty shell
#

although you can equally just # type: ignore xD

foggy thicket
# hearty shell Can you show what you tried to do?

Nevermind, it was mypy that did it, my bad

Variable "typing_extensions.Self" is not valid as a typemypy(error)

This was my test

from typing_extensions import Self
from typing import Any


class Test:
    def __call__(self, *args: Any, **kwds: Any) -> Self:
        print(args or "", kwds or "", sep="")
        return self


t = Test()
t(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)
hearty shell
#

That is still Mypy

#

:P

dire bobcat
#

assert or type: ignore

foggy thicket
#

Yeah, and then it proceeded to say Self? Not callablemypy(error) WeirdChamp

hearty shell
foggy thicket
# dire bobcat assert or type: ignore

Type ignore will cause your linter to ignore a existing possible error
assert will cause it to raise an AssertionError if a certain condition is not met

hearty shell
#

But if forced I think assert might be better

soft matrix
#

Mypy still doesn't support it

hearty shell
#

since None might make sense somewhere else in your code

soft matrix
#

Sorry ;)

hearty shell
#

so making that more difficult to see where a bug is coming from

dire bobcat
dire bobcat
nova venture
#

what should I change about this code? Want a TypeVarTuple of (int, str) to require a (Body[int], Body[str]) as args

dire bobcat
#

i might just stick the assert

nova venture
#
from dataclasses import dataclass
from typing import Generic, TypeVar

from typing_extensions import TypeVarTuple, Unpack

P = TypeVarTuple("P")
T = TypeVar("T")

@dataclass(frozen=True)
class Body(Generic[T]):
    x: T

class Call(Generic[Unpack[P]]):
    def __init__(self, *args: Body[Unpack[P]]) -> None:
        self.args = args

Call[int, str](Body(1), Body("abc"))
hearty shell
foggy thicket
hearty shell
#

if possible, that is why I said it depends

nova venture
# nova venture ``` from dataclasses import dataclass from typing import Generic, TypeVar from ...

gives this:

  error: TypeVarTuple not allowed in this context
  error: Argument of type "Body[int]" cannot be assigned to parameter "args" of type "Body[*tuple[int, str]]" in function "__init__"
    TypeVar "T@Body" is invariant
      "int" is incompatible with "*tuple[int, str]" (reportGeneralTypeIssues)
  error: Argument of type "Body[str]" cannot be assigned to parameter "args" of type "Body[*tuple[int, str]]" in function "__init__"
    TypeVar "T@Body" is invariant
      "str" is incompatible with "*tuple[int, str]" (reportGeneralTypeIssues)
dire bobcat
#

i was thinking assert would be better because if the assert fails (which it shouldnt), its all fucked

hearty shell
#

I am very curious 🤔

oblique urchin
#

I think this needs the hypothetical typing.Map operator

hearty shell
#

Yeah I was thinking this looks like functor behaviour

hidden abyss
#

I recently started using Pyright with a codebase that I'd already been using mypy with for a while. This revealed a number of places where mypy and pyright disagree (ref: https://github.com/jab/bidict/pull/242), and uncovered a number of previously-unknown bugs in Pyright and mypy in the process.
In case it's not too late, should there be a common repository of acceptance test cases that all type checkers could use? That way, when a user is using only one type checker and finds a new bug in it, other type checkers can benefit from the corresponding test case at the same time. Ultimately, all participating type checkers should get less buggy more quickly, and agree with one another more often.
(Has this been discussed before? I searched a bit (e.g. https://mail.python.org/archives/search?mlist=typing-sig%40python.org&q=sharing+test+cases ) but didn't find prior discussion. If this isn't the right place to ask, please let me know where else I should post this.)

oblique urchin
hidden abyss
#

Neat, thanks @oblique urchin, great to hear it!

oblique urchin
hidden abyss
nova venture
mint inlet
#

for pep 646, only one type var tuple can exist in a "type parameter list"... what is a type parameter list?

more specifically, my question is: the pep doesn't seem to mention that only one typevartuple can be in an alias at a time, but that seems like a hard requirement. is this just an oversight, or me missing vocabulary?

oblique urchin
mint inlet
#

alright

#

(i was going to complain about missing the explicitness but then i reread it and wondered whether i had misinterpreted ... i had :D )

hasty hull
#

Is there any good way to have an iterable type that doesn't accept strings?

#

For context I want to expand List[str] to Iterable[str] but explicitly disallow str to be passed

hearty shell
#

I forgot the name for it but you cant exclude types from an accepted type

#

Can you go the other way around? What kind of types do you want to support?

hasty hull
#

Yes but I was hoping someone knew of a workaround

hasty hull
oblique urchin
#

there was talk on typing-sig about adding a Not type to support this

hearty shell
#

Ah, that was the name

hasty hull
#

Yeah I'd heard about that, was there any sort of consensus?

oblique urchin
hasty hull
#

Ah :/

#

What was the reasoning? I can't find the post

hearty shell
#

Also looking for it x)

oblique urchin
#

"thoughts on a nottype" is the thread title. couldn't find it online

oblique urchin
#

that's a reply, not the main thread

hearty shell
#

Humm, no links on a reply

#

Oh well x)

hearty shell
oblique urchin
hearty shell
#

He also mentions intersection but I thought I heard that it would have better prospects

oblique urchin
acoustic thicket
#

idk but def fn(x: str & ~Literal[""]): doesnt seem like a check that should be done at the type level

#

also, a NotType breaks lsp doesnt it

#

a supertype of T would be compatible with ~T but T wouldnt

#

(not that there arent any lsp violations in the stdlib)

hearty shell
#

Not necessarily, you could have an abstract set of all possible strings not including "", and that would be your "NotEmpyStringType" x)

#

but it wouldnt be a subtype of str

#

I think it is the same for how you cant pass a str to a literal

acoustic thicket
#

(edited to clarify)

hearty shell
hearty shell
#

The more I think about it the more I see how this could be very difficult to implement x)

#

But I think the idea should be that Not is contravariant

acoustic thicket
hearty shell
#

so that Not[Super] is a subtype of Not[Sub] so it isnt allowed, which means if you wanted Not[str] you couldnt pass a Iterable but you could pass a List

hearty shell
acoustic thicket
#

hmm but wouldnt it have to be covariant too

#

wait nvm variance still breaks my head

oblique urchin
#

I feel like TypeScript's Exclude is more obviously useful than Not

hearty shell
#

You mentioned that as the Difference no?

#

But yeah I mean Not would only be useful when paired up with a intersection

acoustic thicket
#

is there any pep for intersection types?

hearty shell
#

Only a very long github issue

#

and perhaps a mailing list discussion

acoustic thicket
covert dagger
#

pytype "solved" it by special casing strings to not match Sequence[str]

acoustic thicket
#

that makes me a bit uncomfortable

#

I think type should never lie, even if it is a white lie.
i agree with this

acoustic thicket
hearty shell
#

But yeah if you interpret Not to just not be exactly the type passed, then it would violate LSP

acoustic thicket
#

not just exactly the type, the type and its subclasses

hearty shell
#

actually, you are going a step further since a subclass of str should still work

acoustic thicket
#

🤔

#

a list shouldnt be compatible with ~Sequence

hearty shell
acoustic thicket
#

an object() is not a string, so it should work with ~str

hearty shell
#

Well, as I said, if you just take that interpretation, then it would definitely break lsp

#

Yeah actually when I said it just had to contravariant I forgot that it would lose semantical meaning.

#

Going back to a useful example

str
class MyStr(str): ...

def foo(i: Iterable & Not[str]): ...
#

What would make sense here? Should this accept a MyStr?

oblique urchin
#

Not[T] should mean all types not compatible with T

hearty shell
#

Humm, contravaraint so it doesnt break LSP, and covaraint so it makes seance semantically

#

And then if you really wanted MyStr you could apply an Union afterwords

trim tangle
#

should this be disallowed 🤔

#

I guess it should

blazing nest
#

Why should it be disallowed? That seems like the one use-case for it

acoustic thicket
#

i think it should be disallowed too

#

inside f you don't know whether it is a str or not, so you cant check whether its compatible with the Not[str]

tranquil turtle
#

if not isinstance(it, str): ...

soft matrix
#

would you describe```py
class Foo(Generic[T]):
def bar(self, arg: T): ...

#

vs what i think of as a generic function ```py
def noop(x: T) -> T: ...

#
class Foo(Generic[T]):
    def baz(self, arg: T, something: U) -> Spam[Self, U]: ...

also what is baz also called here?

clever bridge
hearty shell
#

No, we were talking about a hypothetical Not type

#

Although it does already have a different meaning in typing

#

!e

from typing import TypeVar

print(TypeVar("T"), TypeVar("T", covariant=True), TypeVar("T", contravariant=True))
rough sluiceBOT
#

@hearty shell :white_check_mark: Your eval job has completed with return code 0.

~T +T -T
soft matrix
#

the invariance repr would probably have to be removed if NotType was added

hearty shell
#

True, I dont think anyone would care about that

clever bridge
hearty shell
#

x)

clever bridge
#

🙂

minor nimbus
#

Is there a way to annotate a mixin class with a union of two classes, so that self assignments and method access wouldn't throw errors? Both classes have very similar interface, and the mixing touches only on the common part of them (hopefully)

#

Specifically, it's tkinter.ttk.Entry and tkinter.ttk.Combobox, where Combobox's documentation says the methods are the exact same as for the Entry. I had a class that subclassed Entry to add placeholder functionality, but now I need the same for the Combobox, and it works as a mixin class, but throws one hell of a lot of typing errors otherwise.

#

Now I have smth like this:

class PlaceholderMixin:
    # all placeholder methods and __init__ are defined here

class PlaceholderEntry(PlaceholderMixin, ttk.Entry):
    pass

class PlaceholderCombobox(PlaceholderMixin, ttk.Combobox):
    pass
grave fjord
#

Use a protocol?

#

Although Combobox is a subclass of Entry, so def whatever(self: Entry, ...) -> ...: should work

grave fjord
minor nimbus
#

Yeah, I tried a protocol, but then super() complains that the 2nd parameter isn't an instance of ... something. Also, a protocol requires me to define every single method that's already defined in the mixin, as well as all methods that are used within those methods too, which is kinda not ideal. I'll try to grab some pics in a moment

grave fjord
#

Just paste the code no need to screenshot - I'd recommend making a https://mypy-play.net

minor nimbus
#

honestly, that's the only issue it shows now

#

although having to type everything twice like this is... not ideal

#

Hmm, if Combobox is a subclass of Entry

#

I could do like a bound type variable, somehow?

#

hmmmm

#

the error message makes sense because a protocol isn't an instance of PlaceholderMixin

#

This is something MyPy should take into account, but it doesn't do so right now

#

also double-typing with a bunch of Any's to not have to deal with incompatible signatues is quite a meh solution, to the point where I could just use no_type_check on the entire class and call it a day

grave fjord
#

Your mixin extends Widget?

minor nimbus
#

that base doesn't need to be there with the protocol in place, but normally mixins are given a common base with whatever they'll be used in

#

that just lets you access the underlaying base methods

#

so, since both Entry and Combobox use Widget at base, subclassing it in the mixin prevents "no configure method" errors in the mixin, where it comes from the base Widget class

#

but as I said, with the protocol in place, that's not really needed

grave fjord
#

I don't think Entry extends Widget

minor nimbus
#

oh?

#

I thought everything extends widget

#

ttk.Widget extends tk.Widget

#

then ttk.Entry extends ttk.Widget

grave fjord
#

Sort of implies that not all Widgets are Entrys

minor nimbus
#

Yes, not all widgets are entries

#

but entries are widgets, and widgets come with a common configure method for example

grave fjord
#

Ah right

minor nimbus
#

So normally for a mixin class without base, MyPy would throw an error if you tried to use it inside the class:

class Mixin:
     def __init__(...):
         self.configure(...)  # Mixin has no configure method
grave fjord
#

Yeah but you can put the base in the self with a protocol

minor nimbus
#

but if you're sure that the mixin will be used with widgets and widgets only, you can give it a common base

#

oh

#

wait, I thought that Protocol can be the only base

#

that'd simplify things, lemme try

grave fjord
#
class Configurable(Protocol):
    def configure(self, option: SomeTypeHere) -> object:
        ...
class Mixin:
    def __init__(self: Configurable):
        self.configure(...)
#

But mixins don't usually have constructors

minor nimbus
#

this one does since it, well, does the exact same thing

grave fjord
#

But you don't need a self annotation because you already subclass widget

minor nimbus
#

the widget doesn't differ a lot

#

protocols can't take other bases btw: main.py:8: error: All bases of a protocol must be protocols

grave fjord
#

Yeah I know

minor nimbus
#

just like I remembered it

grave fjord
#

You just make a protocol with the methods that you use

minor nimbus
#

so the gist above works then?

#

minus the super() problem?

grave fjord
#

You subclass widget and use a self annotation

#

You don't need both

minor nimbus
#

Yeah, as I said, that base class is optional with a protocol usage like this. Regardless if it's there or not, super() is gonna complain though.

#

It looks like MyPy has no issue open with this, which is strange. Anyone who'd try to use protocols like this would run into it easily. Guess I'll open it then.

#

It's like my second time running into it too

grave fjord
minor nimbus
grave fjord
#

oh that sounds about right

minor nimbus
#

Huh

#

Your gist looks promising too though

#

why I didn't think of that

#

lemme try

#

wouldn't that resolve Combobox methods to Entry first?

grave fjord
#

Also try to use object before using Any

minor nimbus
#

PlaceholderCombobox(PlaceholderEntry, ttk.Combobox) means MRO goes like: PlaceholderCombobox -> PlaceholderEntry -> ttk.Entry -> ttk.Combobox

grave fjord
#

I don't know what does the __mro__ say?

#

Oh you already said

minor nimbus
#

but I think that's just a matter of reversing the order of base classes there

#

I'm not actually 100% sure it's like that, but as far as I remember, it's a depth-first search

#

lemme check

#
[<class '__main__.PlaceholderCombobox'>, <class '__main__.PlaceholderEntry'>, <class 'tkinter.ttk.Combobox'>, <class 'tkinter.ttk.Entry'>, <class 'tkinter.ttk.Widget'>, <class 'tkinter.Entry'>, <class 'tkinter.Widget'>, <class 'tkinter.BaseWidget'>, <class 'tkinter.Misc'>, <class 'tkinter.Pack'>, <class 'tkinter.Place'>, <class 'tkinter.Grid'>, <class 'tkinter.XView'>, <class 'object'>]
#

Huh

#

interesting

#

TIL it's not strictly a depth-first search

grave fjord
#

C3 superclass linearization, with preservation of local precedence order

minor nimbus
#

Very interesting, I'll definitely read up on this

#

Thanks 🙂

minor nimbus
#

what's the difference?

#

I'd assume they're almost the same thing

grave fjord
#

Any disables the type checker for the annotated thing

#

object is the type that everything inherits

minor nimbus
#

Right, so it works just like Any but doesn't disable type checking altogether?

#

I guess?

grave fjord
#

In a few places it does have the same meaning

minor nimbus
#

I see

grave fjord
#

I just try using object first unless forced to use Any as a last resort

minor nimbus
#

Makes for a good habit 🙂 I'll try to remember that

#

Thank you again 🙂

tranquil turtle
#

Any is subtype and supertype of any type
object is supertype of any type

x: object
x.a # error
x() # error
``````py
x: Any
x.a # ok
x() # ok
``````py
x: object
y: str
x = y # ok
y = x # error
``````py
x: Any
y: str
x = y # ok
y = x # ok
soft matrix
#

It's sort of like any vs unknown in ts if you're familiar with that

rustic gull
#

do type checkers care about the difference between x/2 vs x/2.0?

soft matrix
#

Yes, int is a subclass of float in the eyes of a type checker

#

Eg x: float = 5 is fine, but x: int = 3.14 is not

rustic gull
#

does it matter when deducing the type of an expression? I saw someone write this:

    return (left + right) / 2.0

but it seems to pass mypy without the .0 (when returning float)

soft matrix
#

/ 2 is gonna return a float anyways

#

It doesn't matter if it's even or not

#

Well unless you're in python 2

nova venture
#

inspect.signature has str values for each annotation, what should I do to actually have them resolved?

#

even though my function is declared like this:

#
async def handle_pre_req(
    read_administrator: AsyncDependency[Administrator, DependencyDescription],
    user_id: Parameter[int, DefaultDescription] = Parameter(1)
) -> ReqHandler:
    ...
trim tangle
#

You're probably using from __future__ import annotations

#

which turns all annotations into strings

hearty shell
#

in which case you can still use that I think?

#

I think that is what the globalns=None, localns=None are for

#

Nvm, it is right there

trim tangle
#

I think those are chosen automatically

hearty shell
#

that is literally their purpose x)

trim tangle
#

also specify include_extras=True to include Annotated

nova venture
#

Is there a nice way to get both defaults and resolved annotations?

#

Or do I have to join typing.get_type_hints and inspect.signature, taking the annotations from former and defaults from latter?

hearty shell
#

Not sure what you mean here

nova venture
#

I ended up using both typing.get_type_hints and inspect.signature and joining on parameter name, in a: int = 1 I wanted both the int and 1.

#

But now something else came up:

hearty shell
#

Oh that is what you meant

nova venture
#
from __future__ import annotations
import typing

def a():
    class Custom:
        pass

    def b(custom: Custom):
        pass

    return b

typing.get_type_hints(a())
#

This expects Custom to be in global scope instead of a's scope. If I remove from __future__ import annotations then it works but then all annotations are resolved immediately, and forward reference annotations break

#

So is there a way to get a compromise, where Custom is immediately resolved in above example but forward reference annotations are deferred?

hearty shell
#

PEP 563 mentions exactly this use case as a use case that would no longer work, so I am guessing there is no mechanism to get around that

hearty shell
#

I think there is a way so you dont have to pass locals to force, but I dont know

tranquil turtle
trim tangle
#

only a few nanoseconds faster

#

I probably spent more time typing this message than it would save in total

covert dagger
#
In [1]: %timeit 1 / 2
14.4 ns ± 0.39 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)

In [2]: %timeit 1.0 / 2.0
14 ns ± 0.113 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)
#

not even one nanosecond on my machine

trim tangle
#

try this:

x = 1
y = 1.0
%timeit x / 2
%timeit y / 2.0
covert dagger
#

oh right, bad benchmark
it is 10 ns difference with variables

devout barn
#

is there good place other than the docs which explains typing.Generic?

trim tangle
#

hey, I don't even have to plug it myself anymore 😎

soft vigil
soft vigil
#

already tried noone answered

hearty shell
#

You are free to ask again, this is however the inappropriate channel for that :P

clever bridge
#

Is there some reason I can't inherit from a Generic class and a slotted class? Trying gives me a TypeError with the message multiple bases have instance lay-out conflict, however removing the slots in the non-Generic base makes the error go away, but of course I lose the slotted nature.

oblique urchin
clever bridge
#

Far as I'm aware, Python just allocates slots for each base, or if the base has no slots a __dict__

#

Generic has it, and the generic class has it

#

But inheriting from the generic class and another one breaks somewhere

hearty shell
#

Do both classes share slots?

hearty shell
#

Is Never the new <nothing>?

brisk hedge
#

Never is the new NoReturn

hearty shell
#

But can't it be used where before mypy would say "<nothing>"?

brisk hedge
#

I'm not sure how mypy determines something to be "<nothing>"

dire bobcat
#

im asking this question again because its really annoying me having to add multiple type: ignore's. I have a function with has two use cases;
scenario a:

  • user inputs a channel id that may or may not be a valid channel id.
    -> my function checks to see if it is a channel, if it is, it returns it, if it isnt, it returns None, hence returning -> Optional[TextChannel]
    scenario b:
  • the bot receives a message with a channel id that will always be a valid channel id.
    -> this means that it will always return TextChannel

self.channel = self._state.cache.get_channel(payload["channel_id"]) when doing this i am running scenario b. this means it always is a TextChannel object.
However, because of the Optional, everytime i do self.channel.id my flake8 says id is not an attribute of TextChannel | None.

oblique urchin
#

sounds like you should have two functions

fierce ridge
#

is there a way to tell mypy to ignore type errors in other modules when invoking mypy foo.py? i don't care about errors in other modules (e.g. bar.py) even though foo.py imports them

hearty shell
#

although this is a little bit of a lie

hearty shell
fierce ridge
fierce ridge
acoustic thicket
#

--follow-imports=skip worked for me iirc

hearty shell
fierce ridge
#

ah ty, you are faster than me

hearty shell
#

x)

fierce ridge
#

silent is what i wanted

hearty shell
dire bobcat
#
 def get_channel(self, channel_id: int) -> Optional[TextChannel]:
        """
        Searches the internal cache for a channel.
        
        Parameters
        ----------
        channel_id: :class:`int`
            The channel id to find.
        
        Returns
        -------
        channel: :class:`TextChannel`
            The channel if found, else `None`
        """
        for guild in self._guilds:
            channel = guild.get_channel(channel_id)
            if channel is not None:
                return channel
        return None
#

if channel is not None:
return channel

hearty shell
#

Ah, in that case

#
 Valid = NewType("Valid", int)

 @overload
 def get_channel(self, channel_id: Valid) -> TextChannel: ...

 @overload
 def get_channel(self, channel_id: int) -> Optional[TextChannel]: ...

 def get_channel(self, channel_id):
        """
        Searches the internal cache for a channel.
        
        Parameters
        ----------
        channel_id: :class:`int`
            The channel id to find.
        
        Returns
        -------
        channel: :class:`TextChannel`
            The channel if found, else `None`
        """
        for guild in self._guilds:
            channel = guild.get_channel(channel_id)
            if channel is not None:
                return channel
        return None
#

Then when you are passing the id from the bot, you call the function with self.get_channel(Valid(...))

trim tangle
#

or you can use exceptions to signal an error

oblique urchin
#

Yes. I've been working on making our code base None-safe, and there are so many functions where people return None on invalid input, but then the call sites don't do anything useful with None. Just throw an exception already.

hearty shell
#

What does None-safe mean?

oblique urchin
#

make the type checker tell you if you use None incorrectly, basically like mypy's strict optional mode

hearty shell
#

Oh I see

dire bobcat
#

if not user_inputted: assert channel #wont assert

hearty shell
#
 @overload
 def get_channel(self, channel_id: int, user_inputted: Literal[False]) -> TextChannel: ...

 @overload
 def get_channel(self, channel_id: int, user_inputted: Literal[True]) -> Optional[TextChannel]: ...

 def get_channel(self, channel_id):
        """
        Searches the internal cache for a channel.
        
        Parameters
        ----------
        channel_id: :class:`int`
            The channel id to find.
        
        Returns
        -------
        channel: :class:`TextChannel`
            The channel if found, else `None`
        """
        for guild in self._guilds:
            channel = guild.get_channel(channel_id)
            if channel is not None:
                return channel
        return None
#

But yeah exception method is probably better

lucid kestrel
#

hello can someone help me? im new to python and i dont know how to install this module

lucid kestrel
#

thanks

dire bobcat
hearty shell
# hearty shell But yeah exception method is probably better
class ChannelNotFound(DiscordException):
    """Exception that is thrown when a get_channel operation fails"""

def get_channel(self, channel_id: int) -> TextChannel:
    """
    Searches the internal cache for a channel.
       
    Parameters
    ----------
    channel_id: :class:`int`
        The channel id to find.
       
    Returns
    -------
    channel: :class:`TextChannel`
        The channel if found, else `None`

    Raises
    ------
    ChannelNotFound
        When channel isn't found
    """
    for guild in self._guilds:
        channel = guild.get_channel(channel_id)
        if channel is not None:
            return channel
    raise ChannelNotFound(f"Could not find channel with id: {channel_id}")
dire bobcat
#

nah don’t want my get_channel to raise an error like that tho

#

cus then i have to catch it

hearty shell
#

What is the difference between doing:

if something is None:
    ...
else:
    ...

and

try:
    something
except Exception:
    ...
#

You will have to check for it being invalid either way

oblique urchin
fierce ridge
hearty shell
#

Yes, I know x)

#

I am saying in this context

fierce ridge
#

as for which one you should use, i prefer using returning some kind of failure/sentinel value "internally" and raising exceptions at your api boundaries

hearty shell
#

You will have to do one either way

fierce ridge
#

so it depends on how the method is meant to be used

#

the general principle of "don't use exceptions for control flow" is relevant here

#

if the channel being None is a normal thing that can happen, then use a sentinel value

#

if it really shouldn't happen, then it's an exceptional circumstance and you should use an exception

trim tangle
#

TypeScript has the postscript ! operator

#
const rv = x.y!.z
``` basically means ```ts
const _x_y = x.y
// assume _x_y is not null
const rv = _x_y.z
hearty shell
hearty shell
fierce ridge
hearty shell
#

everyone but python sadge

hearty shell
#

If you use None

fierce ridge
#

yeah i see what you're saying. meh, go with the exception then

#

it sounds like the channel not-existing is an exceptional circumstance, basically user error

#

it's analogous to KeyError in dicts

#

it's really either/or though. i find it slightly weird that discord.py returns None instead of raising an exception on guild.get_channel, but it's possible that they did this for performance reasons internally (catching and handling exceptions is slow)

oblique urchin
#

The nice thing about an exception then is that you can put in more information (e.g. the bad channel id) that will end up in your log. With ! all you'd get is an error

fierce ridge
#

that too

#

i wish the type signature for Exception wasn't such a mess...

trim tangle
#

! in TS doesn't throw an error, it just assumes it's not null

fierce ridge
#

it's pretty hard to make exception classes with useful structure

fierce ridge
trim tangle
#

depends 🙂

fierce ridge
#

or is it effectively an assertion?

trim tangle
#

it's javascript, so demons could fly out of your nose

oblique urchin
#

you probably get something like "null has no attribute x" at runtime, right?

fierce ridge
#

ah ok, so it's equivalent to assert x.y is not Null before taking x.y.z

trim tangle
#

no it's not

#

if you're accessing an attribute, yes. but if you have console.log(foo!) it may silently propagate

#

e.g. if you have key: 'foo' | 'bar' | null and you do myObj[key!], then with key being null you will get myObj[null] === undefined

fierce ridge
#

is there a name for that operator

#

bang?

#

this person says it is a "non-null assertion" operator

#

i know that it isn't a runtime assertion

oblique urchin
#

it's an assertion to the type checker though, not a runtime assertion

trim tangle
#

yeah yeah

#

sorry for the confusion

hearty shell
terse sky
#

kotlin has that operator as well, or I guess it's spelled !!

trim tangle
hearty shell
#

Isnt that what optional chaining is?

trim tangle
#

that's a different thing

terse sky
#

I guess it's different since in JS it seems like no code is being run at runtime, in kotlin you'll always get an exception immediately

#

optional chaining doesn't throw, you get the expression or None

#

foo?.bar?.baz

#

if foo is None, it's just None, otherwise if foo.bar is None, it's just None, otherwise it's foo.bar.baz

hearty shell
#

Oh that is what I thought was being talked about here

trim tangle
terse sky
#

it's really handy and proposed for python though probably not goign to be accepted

#

I find the absence of ?. comes up a lot for me in python

hearty shell
#

Ahh so all ! does is literally take an opntional foo!.bar() and make it concrete without doing if checks

terse sky
#

Yeah, I don't personally like that

#

when your assumption is wrong you're potentially going to get an error late

#

i'd rather get an error early

trim tangle
#

it also makes it really ergonomic to forgo null safety

terse sky
#

Yeah. That said I'm guessing most languages that have ! also have other convenient operators

hearty shell
terse sky
#

Like Kotlin has !! but also has ?: and ?.

hearty shell
#

I think Haskell has that via unsafeCoarce

trim tangle
terse sky
#

so !! is convenient but doing the right thing is just as convenient

terse sky
#

Rust has unwrap, I think, it would be the closest thing

trim tangle
#

I think unsafe functions in Haskell are the cursed things... like unsafe in Rust

#

this is just a partial function

terse sky
#

Rust's ? is for propagating errors

trim tangle
#

Yeah I like unwrap() better, it is... louder?

terse sky
#

yeah that's basically the intent I think

rough sluiceBOT
#

src/umd/ReactUMDEntry.js lines 21 to 22

__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactDOM,
__SECRET_DOM_SERVER_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactDOMServer,```
hearty shell
#

It is an unsafe function x)

trim tangle
#

it is partial, not unsafe 🙂

#

hmm actually

  1. Evaluation can lead to an exception or infinite loop. This is a relatively mild sort of unsafety ... except when it's not.
    I guess
hearty shell
#

I mean, if you just hoogle Maybe a -> a you will see that some libraries named that unsafe, so it seems a not fully defined term by convention?

trim tangle
hearty shell
trim tangle
#

anyway, this is mildly off-topic here 👀 maybe let's move to #otN if we want to continue this

hearty shell
#

"mildly" xD

#

I did have a python question, how do static analyser like pyright and mypy work? Does pyright for example have to implement its own parser? And how do they go about figuring the types in Python, I tried looking for any writing on that but I guess if someone has written about it, it is hard to find

oblique urchin
#

I think beyond that it's mostly standard compiler techniques. For each AST node you infer a type, and from that you build inferred types for more nodes. You may also use a different representation instead of the raw AST

hearty shell
#

Humm I see, I will lookup about that instead, I think that should yield better results

oblique urchin
#

Eric recently gave a talk about Pyright's architecture at the typing meetup, I think the recording for that should be available

trim tangle
#

ooh

hearty shell
#

Oh thanks! I will definitely search for that

trim tangle
#

the mastermind himself

hearty shell
#

Right?

trim tangle
#

the parser change is a CPython implementation detail

oblique urchin
oblique urchin
#

pyright's parser is designed with error recovery in mind, so it works ok-ish if you are in the middle of writing something

#

but in general, almost every new language feature requires some work in type checkers

hearty shell
#

Ah okay, interesting, thanks for the insight and the notes 🙂

dire bobcat
#
def get_channel(self, channel_id: int) -> Optional[TextChannel]:
        """
        Searches the internal cache for a channel.

        Parameters
        ----------
        channel_id: :class:`int`
            The channel id to find.

        Returns
        -------
        channel: :class:`TextChannel`
            The channel if found, else `None`
        """
        for guild in self._guilds:
            channel = guild.get_channel(channel_id)
            if channel is not None:
                return channel
        raise ChannelNotFound("Channel with id ``{}`` not found".format(channel_id))
``` i did that and its still giving me the crappy `error: "id" is not a known member of "None" (reportOptionalMemberAccess)`
oblique urchin
#

you need to change the return annotation

dire bobcat
#

oh

#

oops

#

awesome it worked

#

thanks

dire bobcat
#
self.thumbnail: Dict[str, str] = None
        self.video: Dict[str, str] = None
        self.image: Dict[str, str] = None
        self.author: Dict[str, str] = None
        self.footer: Dict[str, str] = None
        self.fields: List[Dict[str, str]] = []
#

whats wrong with this? Cannot assign member "thumbnail" for type "Embed" got an error like this for every line

upbeat wadi
#

None is not a Dict[str, str]

dire bobcat
#

oh

#

optional

#

right

hearty shell
#

Not really a typing question, the reason pyright is telling you that is because those members dont exist

#

is self and Embed?

dire bobcat
#

i think optional will do the trick

hearty shell
#

is self an embed?

#

because an embed does not have thumbnail

trim tangle
#

I think you're not supposed to add new attributes outside of __init__

hearty shell
#
    __slots__ = (
        "title",
        "url",
        "type",
        "_timestamp",
        "_colour",
        "_footer",
        "_image",
        "_thumbnail",
        "_video",
        "_provider",
        "_author",
        "_fields",
        "description",
    )
trim tangle
#

ah

#

yeah, it might be the slots thing. maybe you can show more code?

upbeat wadi
#

it wouldnt be the slots

hearty shell
#

You are supposed to use the setters

upbeat wadi
#

that would give a different error

#

"x" is not specified in __slots__

hearty shell
#

That was a type check error

dire bobcat
#

i added optional and im getting 2 different errors now

#

well

#
/home/Caeden/Github/discii/discii/embed.py:61:14 - error: Cannot assign member "footer" for type "Embed"
    Expression of type "dict[str, str | None]" cannot be assigned to member "footer" of class "Embed"
      Type "dict[str, str | None]" cannot be assigned to type "Dict[str, str] | None"
        TypeVar "_VT@dict" is invariant
          Type "str | None" cannot be assigned to type "str"
            Type "None" cannot be assigned to type "str"
        Type cannot be assigned to type "None" (reportGeneralTypeIssues)
hearty shell
#

You need to use set_thumbnail

#

and stuff like that

#
    def __init__(
        self,
        *,
        colour: Union[int, Colour, _EmptyEmbed] = EmptyEmbed,
        color: Union[int, Colour, _EmptyEmbed] = EmptyEmbed,
        title: MaybeEmpty[Any] = EmptyEmbed,
        type: EmbedType = "rich",
        url: MaybeEmpty[Any] = EmptyEmbed,
        description: MaybeEmpty[Any] = EmptyEmbed,
        timestamp: datetime.datetime = None,
    ):
upbeat wadi
#

this isnt dpy

#

it's their own library

dire bobcat
#

self.footer: Optional[Dict[str, str]] = None
and on L61:
self.footer = {"text": text, "icon_url": icon_url}

trim tangle
dire bobcat
#

uh

hearty shell
dire bobcat
#

what part

trim tangle
dire bobcat
# dire bobcat > `self.footer: Optional[Dict[str, str]] = None` and on L61: > `self.footer = {...

this is the part that raises ```bash
/home/Caeden/Github/discii/discii/embed.py:61:14 - error: Cannot assign member "footer" for type "Embed"
Expression of type "dict[str, str | None]" cannot be assigned to member "footer" of class "Embed"
Type "dict[str, str | None]" cannot be assigned to type "Dict[str, str] | None"
TypeVar "_VT@dict" is invariant
Type "str | None" cannot be assigned to type "str"
Type "None" cannot be assigned to type "str"
Type cannot be assigned to type "None" (reportGeneralTypeIssues)

upbeat wadi
#

text or icon_url is probably Optional

#

can you show their definitions

dire bobcat
#

ohh

#

true

#

i just have to add Union[str, bool]

#

hmm

dire bobcat
# upbeat wadi can you show their definitions
def set_author(self, name: str, icon_url: str = None) -> None:
        """
        Sets the author field.
        
        Parameters
        ----------
        name: :class:`str`
            The author name.
        icon_url: :class:`str`
            The icon url to assign.
        """
        self.author = {"name": name, "icon_url": icon_url}

this is where i set self.author which is defined as self.author: Optional[Dict[str, Union[str, bool]]] = None. The current error is ```bash
/home/Caeden/Github/discii/discii/embed.py:58:14 - error: Cannot assign member "author" for type "Embed"
Expression of type "dict[str, str | None]" cannot be assigned to member "author" of class "Embed"
Type "dict[str, str | None]" cannot be assigned to type "Dict[str, str | bool] | None"
TypeVar "_VT@dict" is invariant
Type "str | None" cannot be assigned to type "str | bool"
Type "str" cannot be assigned to type "str | bool"
Type "None" cannot be assigned to type "str | bool"
Type cannot be assigned to type "None" (reportGeneralTypeIssues)

#

shit

#

it should be optional not union

upbeat wadi
#
  1. icon_url should be Optional[str]
  2. consider using a TypedDict for this
trim tangle
#

{"name": name, "icon_url": icon_url} has type dict[str, str | None], hence the error

trim tangle
#

(also None isn't bool)

dire bobcat
#

yeah it worked as Optional not Union

#

thanks guys

upbeat wadi
#

a TypedDict would allow you to specify that name should be a str, but icon_url can be str | None for example

trim tangle
#

also yes, if you have a fixed set of keys, you should use TypedDict, or better a proper object (like a dataclass or NamedTuple)

dire bobcat
#

but that would require learning what a NamedTuple is

hearty shell
#

It is just a tuple that you can access the members via the names, it is still however just a tuple

#

The interface is very simple

trim tangle
#

if you're familiar with dataclasses just use that

#

sorry for the name dropping 😄

hearty shell
#

!e

from typing import NamedTuple

class A(NamedTuple):
    x: int
    y: float
    z: str

a = A(1, 3.14, "123")
b = A(z="sdf", x=1.4, y=5)
c = (1, 3.14, "123")

print(a)
print(b)
print(c)
print(a.y, a[1])
print(a == c) 
rough sluiceBOT
#

@hearty shell :white_check_mark: Your eval job has completed with return code 0.

001 | A(x=1, y=3.14, z='123')
002 | A(x=1.4, y=5, z='sdf')
003 | (1, 3.14, '123')
004 | 3.14 3.14
005 | True
dire bobcat
#

hmm

#

i might bring myself around to using a NamedTuple

#

btw should i use slots to caceh the embed attr's?

hearty shell
#

You should really just use a dataclass though

dire bobcat
#

a what

hearty shell
#

Humm, that one you might want to look at the module page for it, it has many components to it. But it is worth learning since they are usable almost everywhere

trim tangle
#

also

#

!pypi attrs

rough sluiceBOT
hearty shell
#

That has even more to it, but yeah attrs > dataclass 100%

clever bridge
#

They’ve got unique sets of slots

hearty shell
#

Not sure then, from a little searching it seems that you strictly cannot inherit from two classes directly where both have nonempty slots

clever bridge
#

God save me

#

How would you do it then?

#

My structure basically requires me to subclass a provided base class (the generic one) and then use mixins to add on to it

#

Given that my mixin is used in three different classes, I don’t want to dupe it so just used multi inheritance

oblique urchin
#

What does your hierarchy look like exactly? I tried some variations on 3.9 and didn't get an error

#
   ...:     __slots__ = ("x",)
   ...: 

In [10]: from typing import *

In [11]: class Y(X, Generic[TypeVar("T")]): pass

In [12]: class Y(X, Generic[TypeVar("T")]): __slots__ = ("y",)
hearty shell
oblique urchin
hearty shell
#

As for using slotted mixins, I think you just arent supposed to

#

Yeah, I think this issue is just from that fact that they are using slotted mixins

#

Or a Generic parents class that defines its own slots and then inheriting from it with another slotted mixin

pastel egret
#

You can't multi-inherit because the way slots work is that Python appends the pointers to the end of the object. If you single inherit that's fine since they just go after the existing ones, but if you multi inherit the two sets of base class slots are going to collide.

hearty shell
#

Is there any reason why pyright infers Literal type?

#

Even when there is a redefinition of the variable in the same scope

#
from typing import Literal

a = 5
reveal_type(a)  # Type of "a" is "Literal[5]"
a = 2
reveal_type(a)  # Type of "a" is "Literal[2]"
from typing import Literal

a: Literal[5] = 5
reveal_type(a)  # Type of "a" is "Literal[5]"
a = 2  # "Literal[2]" cannot be assigned to type "Literal[5]"
reveal_type(a)  # Type of "a" is "Literal[5]"
covert dagger
#

it's just super aggressive type narrowing I think

x: bool = True
a: int = 0

if x:
    a = 5
    reveal_type(a)  # Type of "a" is "Literal[5]"
else:
    a = 2
    reveal_type(a)  # Type of "a" is "Literal[2]"

reveal_type(a)  # Type of "a" is "Literal[5, 2]"
rustic gull
#
from collections import defaultdict, deque
from typing import *


class _:
    def numBusesToDestination(
        self, routes: List[List[int]], source: int, target: int
    ) -> int:
        if source == target:
            return 0

        mp: Dict[int, Set[int]] = defaultdict(set)  # {stop:set(bus#)}
        for bus, stops in enumerate(routes):
            for stop in stops:
                mp[stop].add(bus)

        queue = deque([(source, 0)])  # deque([(stop,bus_count)])
        visited_stops = set()
        visited_buses = set()
        while queue:
            stop, count = queue.popleft()
            if stop == target:
                return count
            for bus in mp[stop]:
                if bus not in visited_buses:  # prevent loop
                    visited_buses.add(bus)
                    for stop in routes[bus]:
                        if stop not in visited_stops:  # prevent loop
                            visited_stops.add(stop)
                            queue.append((stop, count + 1))
        return -1

why isnt mypy telling me visited_stops = set() visited_buses = set() dont have annotations? if I delete the entire while queue loop it then complains. is it inferring the types off of visited_buses.add(bus) and visited_stops.add(stop)?

covert dagger
#

yes mypy has that convenience feature, if you put a reveal_type after the loop, it would show set[int]

hearty shell
#

Yup, it infers bus is int from the enumerate and then it narrows from set[Any] to set[int]

covert dagger
#

pyright doesn't do it, and complains

hearty shell
oblique urchin
#

this is called "partial types" by the way

#

(the set() thing)

hearty shell
#

Also on top of that, if you do this

from typing import Literal, Final

x: bool = True
a: int = 0

reveal_type(x)  # Type of "x" is "Literal[True]"

if x:
    a = 5
    reveal_type(a)  # Type of "a" is "Literal[5]"
else:
    a = 2
    reveal_type(a)  # Type of "a" is "Literal[2]"

reveal_type(a)  # Type of "a" is "Literal[5, 2]"
#

So x it literal True

#

then shouldn't a = 2; reveal_type(a) be Type of "a" is Never

oblique urchin
#

Maybe, but I'm not sure that behavior would be too helpful in practice

covert dagger
#

all those int inferred in that example are Literal if you try now

hearty shell
#

I mean I agree, tbh to me the really weird one is the Literal inference

covert dagger
#

you don't get a "Never" in the int case here also

#

when doing the "Never" analysis it respects the actual annotation I guess

#

but it is confused about the Literal[3] and int together at the same time, and gives <subclass of str and int>

#

which is a bit funny looking

hearty shell
#

Yeah, it is a little bit all over the place

hearty shell
covert dagger
hearty shell
#

I agree that the underlying type is fine, just the way it reports the types and the way the types are actually being inferred is at least not correct, this is a rather pointless issue as you would only see it when under a magnifying glass

#

still weird though x)

covert dagger
#

yeah it is a bit inconsistent in its confidence, it a bit extra confident about the narrowing only during reveal_type, but not in the isinstance checks

hearty shell
#

I think it is just lying to you. It knows it is not a Literal[0], if it actually was one it would report an error

#
a: Literal[0] = 0
reveal_type(a)  # Type of "a" is "Literal[0]"
a = 1  # "Literal[1]" cannot be assigned to type "Literal[0]"
reveal_type(a)  # Type of "a" is "Literal[0]"
#
a: int = 0
reveal_type(a)  # Type of "a" is "Literal[0]"
a = 1
reveal_type(a)  # Type of "a" is "Literal[1]"
hallow flint
#

there's a difference between "current narrowed type" and "type assignable to variable"

#
def f(x: int | str):
    if isinstance(x, str):
        reveal_type(x)
        x = 5
    reveal_type(x)
covert dagger
#

yeah it knows it is not a Literal[0] but it's not lying, it is just narrowing the type to Literal[0] which is not wrong if you just assigned 0 in the previous line

#

same thing is done by mypy also for other types, but it doesn't do Literal

hearty shell
#

I find that mypy always shows me the upper bound of the type when I ask it to reveal it

#

And yeah I just noticed this is not special to Literal

#

pyright

class A: ...
class B(A): ...

a: A = B()
reveal_type(a)  # Type of "a" is "B"
a = A()
reveal_type(a)  # Type of "a" is "A"
#

mypy

a: A = B()
reveal_type(a)  # Revealed type is "__main__.A"
a = A()
reveal_type(a)  # Revealed type is "__main__.A"
covert dagger
#

ugh I mean mypy doesn't narrow it down based on assignment, but it does narrow down based on other checks like isinstance, but lets you assign a different type than the one narrowed to, like @hallow flint's example shows

hearty shell
#

pyright

a = B()
reveal_type(a)  # Type of "a" is "B"
a = A()
reveal_type(a)  # Type of "a" is "A"

mypy

a = B()
reveal_type(a)  # Revealed type is "__main__.B"
a = A()  # Incompatible types in assignment (expression has type "A", variable has type "B")
reveal_type(a)  # Revealed type is "__main__.B"
hearty shell
covert dagger
#

yup, with mypy

def f(x: int | str):
    if isinstance(x, str):
        reveal_type(x)  # builtins.str
        x = 5  # allowed
    reveal_type(x)  # builtins.int
hearty shell
#

Ah wait, nvm, that is the difference thought

#

Mypy narrows down, not up

#

Actually, thinking about it again, if you take the space between declarations as a space for narrowing, then it narrows down to a Literal and then when reassigning that is the same as leaving that function scope

hearty shell
#

While Mypy infers it to be B, and then it doesnt allow A

#

I never realised how different the two were

covert dagger
#

yeah mypy locks in to the type on the first assignment, with some exceptions for convenience

#

pyright locks the type only if you annotate

#

otherwise it keeps taking union of the stuff you assign to the variable

trim tangle
#

yeah it's often annoying

#
def foo() -> int: ...

# ok
if foo() > 5:
    x = None
else:
    x = "smol"  # ok

if not (foo() > 5):
    y = "smol"
else:
    y = None  # error: Incompatible types in assignment (expression has type "None", variable has type "str")
#

like... these are literally the same

onyx kayak
#

Can someone explain what's wrong with type hint for Generator object yielded by establishConnection()?

class TransactionRepo:
    """Class cointaining temporary transaction data and handling connection and queries to the DB during session"""

    @classmethod
    @contextmanager
    def establishConnection(cls): #-> Generator[TransactionRepo, None, None]:
        """Read .ini file and load database config parameters and database column name mappings.
        Create ContextManager handling connection with the database.

        Raises:
            Exception: raised in case of unexpected config file formatting

        Yields:
            Generator[TransactionRepo, None, None]: ????
        """

        # *snipped*

        with psycopg2.connect(**postgresConfig) as connection:
            with connection.cursor() as cursor:
                yield cls(connection, cursor)
        connection.close()
#

I get TransactionRepo" is not defined, so it seems my TransactionRepo is not yet defined, therefore how can I type hint it?

acoustic thicket
#

either do from __future__ import annotations at the top, or Generator["TransactionRepo", None, None]

onyx kayak
acoustic thicket
#

when the def establishConnection(cls) -> Generator[TransactionRepo, None, None] line is run, the class TransactionRepo isnt defined yet so it errors when it tries to evaluate the annotation
with that import, annotations arent evaluated and treated as strings by default
"" isnt special in a typehint, it makes a normal string. but typecheckers know that they should treat these as types and not normal strings

onyx kayak
acoustic thicket
#

yes

onyx kayak
clever bridge
oblique urchin
#

Also, your connection.close() call should ideally be in a try-finally block

grave fjord
trim tangle
grave fjord
#

wut

trim tangle
#

Namely the warning part

#

It is indeed strange

oblique urchin
#

wow that is unexpected

grave fjord
#

tbh all the dbapi-s seems to have all sorts of weirdness. Use them via sqlalchemy

#
with (
    contextlib.closing(psycopg2.connect(**postgresConfig)) as connection,
    connection,
    connection.cursor() as cursor,
):
    yield cls(connection, cursor)
trim tangle
#

The connection will be closed without starting or ending a transaction

grave fjord
#

shouldn't do

#

there's a yield there

trim tangle
#

sorry

onyx kayak
oblique urchin
trim tangle
#

otherwise it will fail if psycopg2.connect fails with an exception

oblique urchin
trim tangle
#

hmmmmmmmmmmmmmmmmmmm

#

it would

#
        connection = psycopg2.connect(**postgresConfig)
        try:
            with connection:
                with connection.cursor() as cursor:
                    yield cls(connection, cursor)
        finally:
            connection.close()
#

this^ seems correct?

oblique urchin
#

does connection.__enter__ return self? if not might need this ``` connection = psycopg2.connect(**postgresConfig)
try:
with connection as transaction:
with transaction.cursor() as cursor:
yield cls(transaction, cursor)
finally:
connection.close()

rough sluiceBOT
#

psycopg/connection_type.c lines 422 to 425

    rv = (PyObject *)self;

exit:
    return rv;```
grave fjord
#

so like this imho:

with (
    contextlib.closing(psycopg2.connect(**postgresConfig)) as connection,
    connection,
    connection.cursor() as cursor,
):
    yield cls(connection, cursor)
#

I don't like finally: foo.close() - I prefer contextlib.closing

onyx kayak
devout barn
#

Kind of a midly strange idea,
Wouldn't it be great if we could annotate ParamSpec args by specifying an slice?
Like suppose
P = ParamSpec("P")

And then,
we could for example modifying like

[P.args[-1], int, P.args[0], P.kwargs]

To mention that last positional arg comes first, first comes last, and there'll be an instance of int in the middle.

onyx kayak
trim tangle
devout barn
#

No really, this seems like a very good idea to me

trim tangle
#

oh I meant that it might be a bit complex to implement

#

hmm although, TypeScript does have it, kind of

oblique urchin
onyx kayak
terse sky
#

I mean the main issue here is whether there is a way for your app to address that exception without failing

#

and what that way is

#

wherever you can address it, that's the point that you catch it

#

the more context you need to address the error/exception, the earlier you would typically catch it, so that more stuff is still around.
conversely if you need almost no context (e.g. print a backtrace and exit) then you'd catch it close to main

#

You should start by thinking about all the things that could stop that connection from succeeding, and which cases you can actually do something with.
E.g. maybe for certain connection failures you can simply retry.

#

retrying is something you'd obviously want to do relatively locally, for example

onyx kayak
onyx kayak
terse sky
#

if you handle it in the function that calls the function that raises the exception

#

say you have foo(bar(baz(qux())))

#

so, you call qux first, then feed the result into baz, which gets called and ha sits result fed to bar, which then gets called and feeds its result to foo()

#

now lets say that qux just contains:

def qux():
    raise RuntimeError("oh no!")
#

You can put try/except in baz, or in bar, or in foo

#

and catch the exception in any of those places

#

You'll enter the first relevant except block

#

this is what is meant by "bubbling up"

#

actually, hah, this example is broken

#

sorry, let me write it out properly

onyx kayak
terse sky
#
def foo():
    bar()

def bar():
   baz()

def baz():
  qux():

def qux():
    raise RuntimeError("oh no!")
#

that's what I get for rushing, anyhow. Okay

#

So in this code example, we have a proper call stack

onyx kayak
terse sky
#

As written, nothign is catch the exception

#

so if I write a program that is just
foo()

#

then foo will call bar will call baz will call qux

#

so thats' your call stack

#

then qux throws. There's no except in qux so the exception "bubbles out of" qux. qux was called by baz, so now the exception keeps bubbling through baz, but there's still no except statement

#

it will keep going up the call stack until it either hits an except, or until it gets to the main function, and ends your program

#

If I change bar to:

def bar():
    try:
        baz()
    except RuntimeError as e:
        print("hello!")
#

And then I rerun my program

#

now my program doesn't crash at all, because bar catches the exception and doesn't let it keep bubbling up

#

and instead prints hello

#

Notice how the exception was thrown from qux, and went "through" baz, and gets caught in bar

onyx kayak
#

Got it

#

Geez, it's so logical after someone states it

terse sky
#

Yeah it often just takes a good explanation, most of this stuff isn't too bad, but the quality of resources can vary a lot

#

what's valued about exceptions often is that "intermediate" code doesn't have to be changed to handle errors.
In this example, qux is generating an error, bar is handling an error, but baz is sometimes called "error neutral" it doesn't create or handle errors, and its code reflects that

#

this gives you flexibility to change where things are handled while minimizing refactoring

#

the downside is that none of this is a) apparent while reading the code, and b) not tracked by the type system

onyx kayak
#

So could it happen that outermost methods/functions are actually built to handle myriads of exceptions thrown along the way inside of them?

terse sky
#

yeah, it can happen, but it usually doesn't

#

for reasons related to what I first said

#

basically, the farther you are away from the exception, the more stuff related to the exception is already gone

#

Like say there's some local variable in baz() that you needed to feed to qux, actually

#
def baz():
   x = get_some_var()
   qux(x)
#

now, say you catch the exception raised by qux in bar still (baz's parent)

#

by the time you're in bar, baz is already done (interrupted by an exception), so x is already gone

#

you can't check x's state, or use it again for the qux operation

#

So usually as you go farther and farther up the call stack, the exceptions tend to get caught and handled in more generic ways

#

exception handling utilizes inheritance, as well. So typically, as you go up and up the call stack, you are also going up and up the inheritance hierarchy of exceptions.

onyx kayak
#

more generic ways*

terse sky
#

I mean primarily, takikng the same action for a larger and alrger class of exceptions

#

But yeah the most common thing you can really do generically is notifying that it didn't work, restarting, etc

#

A lot of my scripts catch Exception at top level, and then notify in some way that there was a problem

#

To handle all the errors that are "unexpected" and don't ever come up

#

Or rather, hardly ever come up

onyx kayak
#

Ive got your point

terse sky
#

I have other scripts that for example, first catch exceptions from my notification framework itself. If I have an error from the notification framework, then I just log and exit, can't do anything else.
And then I catch all other exceptions and notify + log

onyx kayak
#

Most probably I'll read through it few more times in the coming days 😉

terse sky
#

hah yeah, I'm a repeat offender when it comes to walls of text

#

No problem, feel free to ask follow up questions though technically we should probably use a different channel

onyx kayak
grave fjord
trim tangle
#

which doesn't seem correct

#

Generator[int, int, int] shouldn't be considered an Iterator[int], should it?

hearty shell
#

Does it not follow the same semantics of an Iterator?

oblique urchin
hearty shell
#

Humm, so what is the lie here?

trim tangle
hearty shell
#

Yes but if you look at typeshed isnt it ok?

trim tangle
#
def f() -> Generator[int, int, int]:
    x = yield 37
    assert isinstance(x, int)
    yield 1
    return 5 + x

next(f())
next(f())  # aaah!
oblique urchin
#

right, the type system doesn't cover this case

hearty shell
#
class Generator(Iterator[_T_co], Generic[_T_co, _T_contra, _V_co]):
    def __next__(self) -> _T_co: ...
    @abstractmethod
    def send(self, __value: _T_contra) -> _T_co: ...
    @overload
    @abstractmethod
    def throw(
        self, __typ: Type[BaseException], __val: BaseException | object = ..., __tb: TracebackType | None = ...
    ) -> _T_co: ...
    @overload
    @abstractmethod
    def throw(self, __typ: BaseException, __val: None = ..., __tb: TracebackType | None = ...) -> _T_co: ...
    def close(self) -> None: ...
    def __iter__(self) -> Generator[_T_co, _T_contra, _V_co]: ...
#

Yeah, I understand it is incorrect

#

Is the lie that it should inherit, or that the method should be implemented this way?

#

Dont you also have to prime Generators though?

trim tangle
#

maybe it shouldn't've inherited from Iterator, but instead be something like ```py
def iter(self: Generator[_T_co, None, _V_co]) -> Self: ...
def next(self: Generator[_T_co, None, _V_co]) -> _T_co: ...

trim tangle
#

hmmmm

#

but __iter__ needs to return an iterator, right?

#

i.e. T is iterable iff T.__iter__ returns an iterator

#

isn't then the iterableness of Generator technically undecidable? lemon_exploding_head

hearty shell
#

You're breaking my head

#

x)

trim tangle
#

I think this is fully solved by defining an iterator as only having __next__. And then __iter__ is part of the Iterable protocol, unrelated to iterators

#

but that might have other issues, like breaking millions of programs perhaps

hearty shell
#

That is just a minor inconvenience

oblique urchin
#

presumably other type checkers also handle this correctly so it might be fine

#

send a typeshed PR and watch how badly mypy explodes 🙂

trim tangle
#

excusez moi but this would break my streak

#

aka margins too narrow

oblique urchin
#

it could help me improve my streak though 😄

dire bobcat
#

i added # flake8: noqa to the top of my file but im still getting flake8 issues about the file 😢

dire bobcat
#

also apparently a line at the end of a file is good

#

i thought that was bad

grave fjord
#

Nobody uses Iterable/Iterator anyway it's all about SupportsIter[SupportsNext[T]]

terse sky
#

really

#

?

#

I feel like I'm missing some context

grave fjord
#

Iterable/Iterator are the ideal understanding of what a thing you can for/next is. But SupportsIter and SupportsNext represent the actual mechanics of what you really need to get it to work

terse sky
#

I've always used Iterable/iterator in my code, and what I've seen in annotations

#

I've never seen SupportsIter/SupportsNext

#

I guess by "uses" you mean "the part of the API that people actually mean" or something like that?

#

As opposed to a more literal sense

grave fjord
#

Ah the nobody uses x is a meme

terse sky
#

oh

#

sorry, I'm too old 🙂

oblique urchin
#

lol so what does it mean?

#

I'm old too

grave fjord
#

"nobody goes to that restaurant as it is too crowded"

terse sky
#

I know that saying, but like Jelle I'm still confused on what it means here

#

That's one of many Yogi Berra sayings

#

🙂

#

also, wow, I just found out that Yogi Berra lived in Montclair (while idly looking at his wikipedia page)

#

that's a stone's throw from me

hearty shell
trim tangle
#

yep

hearty shell
#

So you would need yet another type? Like "PrimedGenerator" or another type var to indicate if it is primed or not

#

and then overload based on that typevar

grave fjord
#

I'm pretty sure yield fromables don't even need __next__

#

I think yield from makes do with whatever it can find on the object

grave fjord
#

as v.send(None) wouldn't change the type of v from an unprimed generator to a primed one

trim tangle
#

session types for generators brainmon

rose root
#

Wonder for cases where a file has a stub, do you still typehint or is it one or the other?

oblique urchin
rose root
#

Okay, thanks 👍

hearty shell
grave fjord
#

Imho the fix for this is for v.send to throw away the first call rather than force it to be None

#

(I was looking at how typescript fixed the problem and they just don't have the problem)

hasty hull
#

Why does mypy not like this? ```py
@overload
def infer_schema(pandas_obj: "pd.Series[Any]") -> SeriesSchema:
...

@overload
def infer_schema(pandas_obj: pd.DataFrame) -> DataFrameSchema:
...

#
error: Overloaded function signature 2 will never be matched: signature 1's parameter type(s) are the same or broader
#

Swapping the order doesn't work and pyright is happy with it

oblique urchin
#

Maybe it thinks these classes are Any? Then the second overload is useless because the first will always match

hasty hull
#

Ah yeah that's it, thanks

hasty hull
#

Is mypy ignoring the pandas installation because it doesn't have a py.typed file yet?

#

I'm new to pandas so I don't know what the state of typing is but it looks like it's at least partially typed

covert dagger
#

it is not very good either though

#

they were discussing with pandas devs and MS to include their tests in pandas too

hasty hull
#

Thank you!

oblique urchin
hasty hull
#

Is there a setting to report if a library is being implicitly set to Any?

oblique urchin
livid rampart
#

hey, i want to typehint some inheited classes i have

#
@dataclass
class RowLocator:
    facet: Union[Facet, RedFacet, BlueFacet, BlueTwo, BlueOne]
#

but it looks so messy

#

any ideas how to do it more correctly?

covert dagger
#

if you annotate it as the base class, all the subclasses will be allowed as well, don't have to Union them

trim tangle
#
class A:
    pass

class B(A):
    pass

class C(A):
    pass

Union[B, C] is different from A

trim tangle
#

can you explain more why you need this?

covert dagger
#

yeah depends on why they were unioning, I just guessed what they wanted

livid rampart
livid rampart
livid rampart
#

I have attributes in my base class and all the way down to the red and blue Facets

livid rampart
trim tangle
#

can you show more code maybe?

livid rampart
livid rampart
trim tangle
brazen jolt
#

what type checker are you running?

livid rampart
#
class Facet:
    FACET_FAILURE = 'facet_failure.png'
    ROW_START = 'row_start_red.png'


class RedFacet(Facet):
    COLOUR = 'RED'
    ROWS = 1
    ROW_END = 'rwo_red.png'
    BUTTON = 'red.png'
    FACET_SUCCESS = 'red_facet_success.png'
    FACET = 'red_facets.png'
    FACET_OPEN = 'red_facet_open.png'


class BlueFacet(Facet):
    COLOUR = 'BLUE'
    ROWS = 2
    FACET_SUCCESS = 'blue_facet_success.png'
    FACET_OPEN = 'blue_facet_open.png'
    ROW_END = 'row_blue.png'


class BlueOne(BlueFacet):
    BUTTON = 'Blue1.png'
    FACET = 'blue_one_img.png'


class BlueTwo(BlueFacet):
    BUTTON = 'Blue2.png'
    FACET = 'blue_two_img.png'

livid rampart
oblique urchin
#

pycharm?

livid rampart
#

yes

trim tangle
#

@livid rampart well, Facet doesn't have a BUTTON attribute

brazen jolt
#

well, you're trying to access Button, but that's not defined on facet

livid rampart
#

yes, im aware, hence my union

brazen jolt
#

it's only defined on RedFacet, or on BlueOne or on BlueTwo

#

yeah, but that union shouldn't contain Facet itself

livid rampart
#

but Facet has attributes i do need

trim tangle
#

Do you ever create instances of Facet? or any subclass?

brazen jolt
#

that's fine, those other classes inherit those attributes

livid rampart
brazen jolt
#

unless you plan on actually passing Facet instances, or BlueFacet instances, you shouldn't specify them

livid rampart
#

facet: Union[BlueFacet, BlueTwo, BlueOne] so this would work then?

brazen jolt
#

not quite

trim tangle
brazen jolt
#

bluefacet also doesn't have BUTTON

#

only BlueOne and BlueTwo and the RedFacet do

livid rampart
#

that is unique to the blue facets

brazen jolt
#

you could do so with an isinstance check before that

#

and if it's BlueFacet whcih doesn't have Button, you can set it to none, or access something else

brazen jolt
# livid rampart oh.. i see.. why?

because type-checker would then need to assume that whatever attr you access on a variable with that kind of union, it would need to implement all of the variables which are shared across the whole union

covert dagger
#

you could just make a Protocol if you just want .BUTTON

brazen jolt
#

this means that if you passed a union of some base class and a more specific class, you can only ever safely use the variables defined on that base class, even if the special class has some more, the other class doesn't and the variable can be an instance of either of those (union)

#

by using that union, you're basically just telling the type-checker "hey, this variable will be set to an instance of one of these types, only allow me to access the things that all of these types share"

covert dagger
#

if you want to say both Facet subclass and has .BUTTON that would be more problematic, I think that would need intersection types

livid rampart
#

im not sure i understand your union explaination @brazen jolt

trim tangle
livid rampart
#

becasue meow isnt in Dog?

trim tangle
#

yes

brazen jolt
#

yeah, you're telling the type-checker that it's one of those, but that means it can't allow you to access things that are only in one of those

trim tangle
#

so if animal is a dog, it will explode at runtime

livid rampart
#

what should i use instead of union then?

covert dagger
#

yeah type checker will allow only the stuff that works for each item in the Union

livid rampart
#

so only shared attributes?

brazen jolt
#

union isn't a combination, it means you'll only be able to do things that both of the classes implement

#

yes

trim tangle
brazen jolt
#

for example if both Cat and Dog implemented eat, you could access it

livid rampart
trim tangle
brazen jolt
#

this feature is here to protect you from doing something that wouldn't work if the type was the other union types, even though it would work for one of them

livid rampart
trim tangle
#

(if you're passing in the classes and not the instances)

trim tangle
#

typing.Type is deprecated as per PEP 585

brazen jolt
#

typing.Type for python 3.8 but 3.9 already supports type[]

trim tangle
#

since Python 3.9, you can use type[...]

livid rampart
trim tangle
#

right

livid rampart
#

nice

trim tangle
#

in fact typing.List and typing.Dict are deprecated as well

terse sky
#

there is a prophecy that one day, python's static and dynamic type systems will finally fully embrace each other

#

and ride off into the sunset

livid rampart
#

im on 3.9 here

buoyant swift
oblique urchin
livid rampart
#

oh ok..

#

thanks for teaching me this.. appreciate it

trim tangle
livid rampart
#

i keep doing selfself all the time as well

grave fjord
#

selfself?

#

I thought the new pycharm supported the new syntax

blazing nest
# grave fjord selfself?

When you type self, but the autocomplete is still showing with self. When you press TAB to autocomplete (and hide the menu) it still inserts self causing selfself. I get this with Visual Studio Code

grave fjord
#

Ah right I type everything in manually

devout barn
#

why doesn't this typecheck?

CT = TypeVar("CT", bound=type)


class _Finalize:
    def __init_subclass__(cls, *args, **kwds) -> None:
        if not kwds.get("_root"):
            raise TypeError(f"Inheriting from {cls.__name__} is prohibited")

    def __init__(self, **kwargs) -> None:
        # Decorator support
        self.kwargs = kwargs

    def __call__(self, cls: CT) -> CT:
        class Ret(cls, _Finalize, **self.kwargs):
            # This doesn't typecheck ?
            # Expected class type but received "CT@__call__" at passing cls
            # Expected class type but received "Self@_Finalize" at unpacking and passing self.kwargs
            ...

        # Should do `return type(...)` than doing this
        # doin some testing

        return Ret

We take in a class and return the same class?
problems are at __call__

trim tangle
trim tangle
#

faster!!!

covert dagger
#

hmm vscode doesn't even suggest self to me

#

maybe they got complaints about selfself and hacked some weird solution

trim tangle
grave fjord
#

honestly I tried an IDE once and typed a parenthetical and it ignored me and I dismissed the concept wholesale

grave fjord
trim tangle
#

sorry, we're wildly off-topic 👀

grave fjord
#

what's everyone's least favorite built in function type annotation? Mine's anext

grave fjord
#

I don't like it

oblique urchin
#

if the former, I agree; if the latter, please file a PR

grave fjord
#

it's both of course

#

and there's a PR open already

oblique urchin
#

oh yeah I think I need to review that

grave fjord
oblique urchin
#
    def pow(base: int, exp: int, mod: Literal[0]) -> NoReturn: ...
    @overload
    def pow(base: int, exp: int, mod: int) -> int: ...
    @overload
    def pow(base: int, exp: Literal[0], mod: None = ...) -> Literal[1]: ...  # type: ignore[misc]
    @overload
    def pow(base: int, exp: _PositiveInteger, mod: None = ...) -> int: ...  # type: ignore[misc]
    @overload
    def pow(base: int, exp: _NegativeInteger, mod: None = ...) -> float: ...  # type: ignore[misc]
    # int base & positive-int exp -> int; int base & negative-int exp -> float
    # return type must be Any as `int | float` causes too many false-positive errors
    @overload
    def pow(base: int, exp: int, mod: None = ...) -> Any: ...
    @overload
    def pow(base: float, exp: int, mod: None = ...) -> float: ...
    # float base & float exp could return float or complex
    # return type must be Any (same as complex base, complex exp),
    # as `float | complex` causes too many false-positive errors
    @overload
    def pow(base: float, exp: complex | _SupportsSomeKindOfPow, mod: None = ...) -> Any: ...
    @overload
    def pow(base: complex, exp: complex | _SupportsSomeKindOfPow, mod: None = ...) -> complex: ...
    @overload
    def pow(base: _SupportsPow2[_E, _T_co], exp: _E, mod: None = ...) -> _T_co: ...
    @overload
    def pow(base: _SupportsPow3NoneOnly[_E, _T_co], exp: _E, mod: None = ...) -> _T_co: ...
    @overload
    def pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M = ...) -> _T_co: ...
    @overload
    def pow(base: _SupportsSomeKindOfPow, exp: float, mod: None = ...) -> Any: ...
    @overload
    def pow(base: _SupportsSomeKindOfPow, exp: complex, mod: None = ...) -> complex: ...
grave fjord
#

nice

trim tangle
grave fjord
#

why is pow builtin and not in math.pow

covert dagger
#

the math one converts stuff to float, can't do big numbers

#

pow(10, 1000) works, math.pow(10, 1000) throws overflowerror