#type-hinting

1 messages · Page 48 of 1

trim tangle
#

so Callable[[], int] | Callable[[], str] is assignable to Callable[[], int | str], but not the other way around

fossil nest
#

why isn't it possible x is an int and y is a str? sorry, I'm not following that one. to me, one just seems like a shortened version of the other

trim tangle
fossil nest
#

ahhh I see, it collapses the union to either one or the other

#

thank you, you've helped me many times in this channel over the years :)

#

with that knowledge, I suppose you could replace this with:
type EventHandlerDict[E: Event] = dict[type[E], Handler[E]]

#

wait no

#

ignore me

trim tangle
#

This is still just a generic type alias. If you have e.g. EventHandlerDict[MyEvent], it s a dict[type[MyEvent], Handler[MyEvent]]

fossil nest
#

yeah, mb 😅

fiery canyon
#

Is this for Discord webhook events 🤨

#

Ah wait it's imported from a module called github

#

If a class that subclasses Event implements __init__, is it needed to call super().__init__()?

Note: Event has a single purpose: serving as a generic type annotation in function that takes any of the classes that subclass it.

trim tangle
#

not very relevant if you don't plan to support multiple inheritance

fiery canyon
#

I only really need it here to call BaseLobbyMessage's init

#

Now I'm unsure of why I even have an init for Event

fiery canyon
trim tangle
fiery canyon
#

expected 0 positional arguments
Well this happens when I remove Event's init

trim tangle
#

I'd recommend a classmethod like from_dict

#

__init__ is a bit stinky because it's often considered exempt from substitutability cheks

fiery canyon
#

Event?

trim tangle
#

yeah

#

assuming you can change the API

fiery canyon
#

Yeah I can, but this logic means that Event is now used as an instance

#

Do you mean an abstract classmethod?

trim tangle
#

yeah

#

abstract classmethod

fiery canyon
trim tangle
#

The idea is to have from_dict to be part of the Event API. If you have an unknown Event class event_cls, you instantiate it from a dict with event_cls.from_dict(...)

#

because the __init__ can have an arbitrary unknown signature

fiery canyon
#

So I'd still need to implement init on each one

#

And have from_dict return a new instance

trim tangle
#

For example, if you have an event that just stores a single integer (like <PingEvent id=64>), it's a pretty strange API to require PingEvent({"id": 64})

#

so you could have PingEvent(id=64) and have another method for when you need to parse a dict

#

maybe I'm misunderstanding the information flow in your project though

fiery canyon
#
class Event: ...
class ApplicationAuthorized(Event):
    __slots__ = (
        "user",
        "scopes",
        "guild",
        "is_guild_install",
        "is_user_install"
    )

    def __init__(self, data: ApplicationAuthorizedData) -> None:
        self.user = User(data["user"])
        self.scopes = data["scopes"]
        self.guild = Guild(guild) if (guild := data.get("guild")) else None
...
event_cls = EVENTS_MAP[event_type]
event = event_cls(event_data)
await handler(event, timestamp)
trim tangle
#

and what signature does it even have

fiery canyon
#

Wdym signature

trim tangle
#

argument types and return type

fiery canyon
#

Event's init takes data which is a dict[str, Any] (Would be better to make that a union type of the right TypedDicts but that's not relevant at the moment)

All subclassers follow that with their inits

As I said though, Event is used solely for typing

fiery canyon
#

And I'm also not sure if it even needs to be an ABC, if its just for typing

fossil nest
fiery canyon
#

Made myself a bucket list lol

fiery canyon
#

It's nice that I can just do this instead of using an overload

feral wharf
fiery canyon
feral wharf
fiery canyon
#

Wuh am I supposed to be looking at

feral wharf
#

All of it

fiery canyon
#

I wonder how'd that be implemented in 3.12+ syntax if even

rare scarab
bleak imp
#

I was reading astral-sh/ty#2277 , and saw that in PEP 589 it says that no methods are allowed on the class. I then wanted to look at the Typed dictionaries typing specs page to see what it says on the topic, but couldn't find anything mentioning methods being disallowed. Am I just blind or is this something missing from the spec?

mighty lindenBOT
trim tangle
bleak imp
#

I found it, I’m just blind

#

(It changed from no methods to no other non-annotation statements are allowed)

trim tangle
#

I think classmethods on typeddicts are also dubious

#

because you can typically call a classmethod through an instance

bleak imp
#

Yeah, any non-annotation statement in a typeddict is of questionable use

#

Though I guess theoretically shouldn’t that also include docstrings?

#

Oh wait I’m still blind

#

Though actually, shouldn’t that not include ? Or I guess is that grouped in with “pass statements”?

fiery canyon
#

What am I doing wrong

#

Oh and yeah I realized this is a runtime issue

#

not about typing

#

but its alriiiight

trim tangle
#

this is not a runtime issue

fiery canyon
#

TypeError: private() missing 1 required positional argument: 'func'

trim tangle
#

oh

#

It's weird that pylance doesn't show you an error

fiery canyon
#

Well this being runtime doesn't help me xd

trim tangle
#

but yeah that's not how decorator factories work

#

and typing one correctly is not easy

fiery canyon
#

Yea i assume if top level takes func, it must not take anymore arguments
so the inner level must take func and not args, kwargs, i think

trim tangle
#

yes

fiery canyon
#

can i somehow still get the args

#

actually do i even need them

#

nahhh

trim tangle
#

This is how a decorator factory works:```py
def outer(param1, param2):
def decorator(func):
def inner(*args, **kwargs):
... # use func in some way, e.g.:
return func(*args, **kwargs)
return inner
return decorator

@outer(param1="a", param2="b")
def func(arg1, kwarg1):
...

fiery canyon
#

ohhh it goes another level in

trim tangle
#

It's the same as ```py
decorator = outer(param1="a", param2="b")

@decorator
def func(arg1, kwarg1):
...

#

so outer is a function that returns a decorator, hence a "decorator factory"

fiery canyon
#
def decorator
  def wrapper
  return wrapper

or alternatively

def top
  def decorator
    def wrapper
    return wrapper
  return decorator
trim tangle
# fiery canyon ohhh it goes another level in

The correct type for this is something like ```py
class IdDecorator(Protocol):
def call[P, R](self, func: Callable[P, R], /) -> Callable[P, R]: ...

def outer(param1: str, param2: int) -> IdDecorator:
def decorator[**P, R](func: Callable[P, R]) -> Callable[P, R]:
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
... # use func in some way, e.g.:
return func(*args, **kwargs)
return inner
return decorator

fiery canyon
trim tangle
#

or this, if you don't care about mypy

#

(mypy doesn't infer return types)

fiery canyon
#

So you really have to use a protocol every time?

trim tangle
#

Yes. The only way to describe a generic funciton is with a callable protocol

#

there is some support for the old way where this "works": ```py
def make_identityT -> Callable[[T], T]:
...

fiery canyon
#

Inspect is very cool (but isn't everything accessible already via obj.__code__)

#

I might actually use this one to save time

#

Would be annoying to users without type checker lol

fiery canyon
#

Or is it intended

trim tangle
#

note that T is scoped to the return type

fiery canyon
#

cant be that hard to support new syntax

fiery canyon
trim tangle
#

Although that way may not be obvious at first unless you're Dutch.

fiery canyon
#

Is it because he's taking the top off first or

bleak imp
spiral fjord
trim tangle
#

here it'd be a fence outside the station that you chain your bike to

#

(hopefully the bike is cheaper than the fence)

meager slate
#

Can someone help me understand why does this type hint not work when trying to annotate an async property?

class AsyncStateManager(Generic[S]):
    def __init__(...) -> None:
        self._global_on_enter: Optional[Callable[[S, Optional[S]], Awaitable[None]]] = None

    @property
    async def global_on_enter(
        self,
    ) -> Optional[Callable[[S, Optional[S]], Awaitable[None]]]:
        return self._global_on_enter

    @global_on_enter.setter
    async def global_on_enter(
        self, value: Optional[Callable[[S, Optional[S]], Awaitable[None]]]
    ) -> None:
        if value:
            on_enter_signature = inspect.signature(value)
            pos_args = self._get_pos_args(on_enter_signature)
            kw_args = self._get_kw_args(on_enter_signature)

            if (
                len(on_enter_signature.parameters) != _GLOBAL_ON_ENTER_ARGS
                or kw_args != 0
            ):
                raise TypeError(
                    f"Expected {_GLOBAL_ON_ENTER_ARGS} positional argument(s) only "
                    f"for the function to be assigned to global_on_enter. "
                    f"Instead got {pos_args} positional argument(s)"
                    + (
                        f" and {kw_args} keyword argument(s)."
                        if kw_args > 0
                        else "."
                    )
                )

        self._global_on_enter = value

I get this type warning for it from basedpyright-

warning: Property setter value type is not assignable to the getter return type
    Type "((S@AsyncStateManager, S@AsyncStateManager | None) -> Awaitable[None]) | None" is not assignable to type "CoroutineType[Any, Any, ((S@AsyncStateManager, S@AsyncStateManager | None) -> Awaitable[None]) | None]"
      "None" is not assignable to "CoroutineType[Any, Any, ((S@AsyncStateManager, S@AsyncStateManager | None) -> Awaitable[None]) | None]" (reportPropertyTypeMismatch)
trim tangle
#

in your case, the getter shouldn't be async either

meager slate
#

oh

#

how do i get around it then

#

wait

#

im dumb

#

i just have to remove the async

#

thanks btw

trim tangle
#

This is yet another example of implicit Coroutine being confusing

#

If you had to do ```py
@property
async def global_on_enter(self) -> Coroutine[Optional[Callback[S]]]: ...
@global_on_enter.setter
async def global_on_enter(value: Optional[Callback[S]]) -> Coroutine[None]: ...

#

I wonder why basedpyright doesn't reject the async setter though...

meager slate
#

🫠

feral wharf
#

Does any type checker

fiery canyon
#

Whoops

trim tangle
edgy jungle
#

@tribal heart

icy obsidian
#
from typing import Self

class Test:
    def func1(self, t: Self):
        print(t)
        
    def call1(self):
        t = Test()
        self.func1(t)  # <<<<<<<< Type "Test" is not assignable to type "Self@Test"
#

I definitely am missing something here...
I was expecting func1 to take anything of type Test?

feral wharf
#

t is a new instance, not self

What if you do t: Test

trim tangle
icy obsidian
#

Definitions can only use Test with quotes

trim tangle
icy obsidian
#

The origin of the question:

def func1(self, t: Optional[Test]):
def func1(self, t: Test | None): # << Nope
def func1(self, t: "Test" | None): # Nope as well
def func1(self, t: "Test | None"):
icy obsidian
#

Or at least not the thing I thought it was

trim tangle
trim tangle
# icy obsidian As it happens no 🙂

Self varies when you make a subclass. For example:

class Box:
    def __init__(self, value: int):
        self.value = value
    def copy(self) -> Box:
        return cls(self)(self.value)

class CoolBox(Box): pass

b = CoolBox(42).copy()  # b is inferred as `Box` (because that's what Box.copy is annotated to return )
class Box:
    def __init__(self, value: int):
        self.value = value
    def copy(self) -> Self:
        return cls(self)(self.value)

class CoolBox(Box): pass

b = CoolBox(42).copy()  # b is inferred as `CoolBox` now
icy obsidian
#

So it does not allow Self to be used for arguments for basically the same reason as List is invariant?

trim tangle
# icy obsidian So it does not allow Self to be used for arguments for basically the same reason...

Maybe. You can use Self as an argument, but it's dubious:

class Foo:
    def f(self, x: Self) -> None:
        print(x)

class Bar(Foo):
    def f(self, x: Self) -> None:
        # are we allowed to assume that `x` is Bar in here? That's what Self means after all
        ...

def test(x1: Foo, x2: Foo) -> None:
    x1.f(x2)

test(Bar(), Foo())  # this will provide a `Foo` as the x argument to Bar.f()
``` Pyright actually catches this now, but mypy doesn't
icy obsidian
#

Yea, that is what I meant but worded poorly

trim tangle
#

Self is actually a shorthand for a type variable. So if you write it out long hand, it makes sense why it's not allowed ```py
class Foo:
def f[S: Foo](self, x: S) -> S:
return x

class Bar(Foo):
def f[S: Bar](self, x: S) -> S: # this has a more restrictive bound than the parent
return x

icy obsidian
#

Shorthand for a bound type variable

trim tangle
#

yes

#

pyright still allows subclassing Foo, maybe that can lead to problems anyway, since Bar.f still has a signature incompatible with Foo.f... not sure

icy obsidian
#

Btw, Pylance does not allow subclassing your code

trim tangle
#

yeah, pylance is based on pyright

icy obsidian
#

I mean it actually produces an error

#

But I get what you mean.
Guess for now I will use the old Optional for my purposes

trim tangle
#

"Test | None" also works

icy obsidian
#

Do not like (as a personal preference) the full quotation.
Might use the Future though, yea

trim tangle
#

!e

from __future__ import annotations

class Test:
    def copy(self) -> Test | None:
       return None

print(Test.copy.__annotations__)
rough sluiceBOT
icy obsidian
#

Yea, just didn't know about this before.
Will update to it a bit later (just for the rest of things to be ocnsistent)

fiery canyon
icy obsidian
#

I assume starting from 3.14?

fiery canyon
#

I think so

icy obsidian
#

In my case it is 3.13 so it does not work. Cannot move for now.

fiery canyon
#

It's usually not a big deal to move

#

Depends on your situation ig

hidden geode
#

does anybody know why this might be happening?
I have an assert before that should say that self.bar is an instance of str yet here it shows it's type is Unknown?

fiery canyon
#

Add a string annotation to bar

hidden geode
#

what if I have something like this? (I don't think I can add an annotation to tile_size and I can't import the World class because that would lead to a circular import)

#

I could create a pyi file but wouldn't that be an overkill?

fiery canyon
#

You must use quotes otherwise it can cause a NameError

hidden geode
#

okay thanks

rare scarab
fiery canyon
rare scarab
#

Nope. Annotations are lazily evaluated now.

fiery canyon
#

Didn't know that, ty

fiery canyon
#

It's only able to suggest it when it's not a property

viscid spire
viscid spire
hardy linden
#

Have I coded my Protocol incorrectly, or is there some reason that Literal doesn't support __add__?

I'm trying to do:

import typing

class SupportsAdd(typing.Protocol):
    """A Protocol for classes that have __add__."""
    def __add__(self, other): ...

def my_function(input_value: SupportsAdd):
    # I know this code only makes sense for str inputs;
    # I just needed a stub function for the mock example.
    return input_value + "foo"

my_string: str = "This string definitely supports __add__!"
reveal_type(my_string)  # Expected: str; got: Literal
_ = my_function(my_string)  # Expected no error, but got one
# because Literal[...] is incompatible with SupportsAdd

This code definitely runs IRL, because that string literal absolutely supports addition, so I'm not sure why it's not compatible with the Protocol. (In fact, if it's not compatible with the Protocol, then how can it run without crashing, if it doesn't have an __add__?)

Having said all this, I forcibly typing.casted my_string to a str and I'm still getting the same type error, so I'm now suspecting that I've coded my Protocol incorrectly.

fiery canyon
hardy linden
#

Yes.

trim tangle
#

the error says:

...
Type "(value: str, /) -> str" is not assignable to type "(other: Unknown) -> None"
Missing keyword parameter "other"
Position-only parameter mismatch; parameter "other" is not position-only
Position-only parameter mismatch; expected 1 but received 0

#
class SupportsAdd(typing.Protocol):
    """A Protocol for classes that have __add__."""
    def __add__(self, other, /): ...
#

"as designed" moment

fiery canyon
#

And add return type to __add__

trim tangle
#

oh yeah also that

#

So ```py
class SupportsAdd(typing.Protocol):
def add(self, other, /) -> Any: ...

#

or object, depends on your use case

fiery canyon
#

Quick question, does NotImplemented get resolved accordingly to the magic method it's used in

#

Or at all

trim tangle
#

wdym

fiery canyon
#

__eq__ is meant to return bool. What's happening if I return NotImplemented

#

Is it falsy?

viscid spire
#

!e

class C:
  def __bool__(self):
    return NotImplemented

if C():
  print("abc")
rough sluiceBOT
viscid spire
#

So, don't do that

fiery canyon
#

!e

from typing import reveal_type

class Foo:
    def __init__(self) -> None:
      pass

    def __eq__(self, other: object) -> bool:
      if isinstance(other, int): return True
      return NotImplemented

f = Foo()
reveal_type(f == "ok")
rough sluiceBOT
fiery canyon
#

Looks like it's falsy

viscid spire
#

!e print(bool(NotImplemented))

rough sluiceBOT
# viscid spire !e print(bool(NotImplemented))

: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 |     print(bool(NotImplemented))
004 |           ~~~~^^^^^^^^^^^^^^^^
005 | TypeError: NotImplemented should not be used in a boolean context
viscid spire
#

no, it's not

viscid spire
#

__eq__ may return False when NotImplemented is returned

fiery canyon
#

How about operations like __mul__

viscid spire
#

that's generally how NotImplemented works

#

something special is done by whatever is calling the method

hardy linden
# trim tangle the error says: > ... > Type "(value: str, /) -> str" is not assignable to...

yeah, you're absolutely right.

I had a difficult time parsing/understanding that error-message stack and I think my attention black-holed on "str" is not assignable to "LiteralString" and got convinced that it had something to do with the fact that my_string was being interpreted as a Literal.

I understand its syntax, now, what it means when it's saying that "__add__" is an incompatible type. (I wish that was a bit more verbose to add clarity, but the blame is definitely on me here.)

Thanks for pointing out the obvious.

trim tangle
#

which is also why passing obj.__eq__ as a function to map/filter is wrong

#

or any of these magic methods really

viscid spire
#

makes sense

fiery canyon
#

Would be cool if you could tell type checker it should only work with a certain type, causing it to error-highlight the ==

rare scarab
#

Maybe an overload

#
@overload
def __eq__(self, other: Vector) -> bool: ...
@overload
def __eq__(self, other: object) -> Literal[NotImplemented]: ...
#

Would that work?

#

If that doesn't work, add a @warnings.deprecated() to one.

fiery canyon
#

its a variable

rare scarab
#

Figured. Use @deprecated

fiery canyon
#

no why

#

its not "deprecated"

rare scarab
#

I'm aware, but there's no other way to create your own type diagnostics

fiery canyon
#

hm typing_extensions got deprecated decorator too

rare scarab
#

Note it will be a false positive if reveal_type(other) is object but runtime is Vector

fiery canyon
rare scarab
#

Yeah, it was added to typing_extensions since it's technically a typing feature

fiery canyon
#

imported from warnings

rare scarab
#

if you're on an earlier version of python, it will be its own implementation

fiery canyon
#

which one do i add that to

rare scarab
#

the one you don't want to use.

fiery canyon
#

an overload?

rare scarab
#

yes.

#
@overload
def __eq__(self, other: Vector) -> bool: ...
@overload
@deprecated("Vector should only compare with itself")
def __eq__(self, other: object) -> bool: ...
fiery canyon
#

"FunctionType" is not assignable to "LiteralString"

#

ah wait

#

ok gotta pass a string

#

well whats the point

#

i dont need all that just for a runtime thing

rare scarab
#

This isn't a runtime thing

fiery canyon
#

?

#

ahhh it cant be empty string

#

if it is then its not detected

rare scarab
#

Seems to work.

fiery canyon
#

Hm could i make a single decorator for the implemented __eq__ that will automatically do this

#

i guess not

#
@deprecate_object
def __eq__(self) -> bool:
  pass
fiery canyon
rare scarab
#

You can't wrap deprecated

fiery canyon
#

aw man

rare scarab
#

It's one of those special cased functions used by the type checker

#

It has to be statically resolvable to the original function

fiery canyon
#

Alright

#

Anyway I think it's an overkill

#

Dont like having to define overloads just for this

rare scarab
#

There's always disjoint_base, but I don't think it's implemented in typecheckers yet

#

!pep 800

rough sluiceBOT
fiery canyon
#

Imported from?

rare scarab
#

Not sure how that would effect equality though

#

typing_extensions, but it's not implemented. It's present in typeshed stubs though

#

I don't think it would be useful here. After all, who can say you didn't implement def __eq__(self, other): return True

fiery canyon
#

How'd I implement that in my context in general lol

rare scarab
#

from what I can tell, disjoint_base is mainly for creating new classes from multiple bases

fiery canyon
#

Its doc only talks about classes

rare scarab
#

The #reachability section is similar, but focuses on isinstance instead of eq

lunar dune
rare scarab
#

pyright supports it for reachability from what I tested

#

still not useful for saying something will always return NotImplemented

#

or False

oblique urchin
#

oops alex said that already

rare scarab
#

You're right.

prisma talon
#
@staticmethod
async def fetch(id: int) -> Self:

Why is this not giving proper typehinting in python 3.12, intellisense?

rare scarab
#

There's no self context to satisfy the Self typevar

prisma talon
#

for its return value

rare scarab
#

use classmethod instead

prisma talon
#

hmm ok that makes sense I think

#

although I dont need the class at all

rare scarab
#

call cls() instead of using your owning class

prisma talon
#
@classmethod
async def fetch(cls, id: int) -> Self:
    r = cls._cache.get(id)
    if r is None:
        sql = "SELECT id, points, share_points FROM discord_user WHERE id=$1;"
        r = await cls.from_db(await db.fetch_one(sql, id))
        cls._cache[id] = r

    return r

I assume this also works? I never instantiate a class in this function

#

Basically
r = User._cache.get(id) -> r = cls._cache.get(id)?

rare scarab
#

does from_db also return Self?

prisma talon
#

yes

rare scarab
#

from_db should instantiate it

prisma talon
#

it is classmethod that instantiates a User object via the cls argument.

#

seems to work now thanks

rare scarab
#

Remember Self is equivalent to this. ```py
class MyClass:
@classmethod
def fetch[Self: MyClass](cls: type[Self]) -> Self: ...

prisma talon
#

Is that a generic? I have not seen that syntax in python before

rare scarab
#

added in 3.12 or 3.13, I don't remember

#

!pep 695

rough sluiceBOT
prisma talon
#

nifty

rare scarab
#

Sentinal types could solve the useless equality check if NotImplemented was one and an overload returned it

bleak imp
#

Would there be any way to make this function actually safe?
basedpyright playground

def narrow_tagged[*TS, T](tag: T, data: object | tuple[T, *TS]) -> tuple[*TS]:
    if isinstance(data, tuple) and data[0] == tag:
        return data[1:]  # Return type, "tuple[Unknown, ...] | tuple[*TS@narrow_tagged]", is partially unknown
    else:
        raise ValueError(f"Internal Error: Tried to get tagged valued {tag} on unexpected item {data}")

Basedpyright rightfully complains, since the object part of data could be some random tuple that just so happens to have T as the first element but doesn't have *TS as the last elements, so would there be any way to make it safe? I'm stumped.

rare scarab
#

Why have object there just for the error case?

bleak imp
#

I guess never mind since it doesn't even work since the if the tag is a literal it decays to a str and doesn't narrow

bleak imp
#

I would have thought this would work, but I guess that's not really what LiteralString is meant for ```py
def narrow_tagged[*TS, T: LiteralString](tag: T, data: Regex | tuple[T, *TS]) -> tuple[*TS]:

viscid spire
umbral stump
#

Hello. How do you people usually fix this problem? I'm not using async/await
code:
data = json.loads(self.valkey_client.get(key).decode())
warning:
Attribute `decode` may be missing on object of type `Unknown | Awaitable[Any]` (ty possibly-missing-attribute)

rare scarab
#

the valkey and redis libraries have Any | Awaitable[Any] defined as the return type for the base client class. A cast is needed

umbral stump
#

you mean like str(client.get(key)) ?

rare scarab
#

valkey-io/valkey-pi#164

fiery canyon
#
thing = self.valkey_client.get(key)
assert isinstance(thing, str)
data = json.loads(thing.decode())```
rare scarab
#

Hello? bot?

umbral stump
#

thank you

fiery canyon
umbral stump
#

I liked the isintance usage

rare scarab
#

!pip types-redis may be helpful too, but there isn't one for valkey.

rough sluiceBOT
rare scarab
fiery canyon
#

Can [Bot] raise a NameError due to it being in a type declaration?

rare scarab
#

Only if you access it at runtime, via Interaction.__value__

feral wharf
#

am I spoiler by pyright that ignored the type if the func had ellipsis or is that the norm

lunar dune
fiery canyon
lunar dune
lunar dune
# fiery canyon 🤔

Does that show ty not emitting a diagnostic or pyright not emitting a diagnostic?

fiery canyon
#

But with pass it does complain

#

I guess with Ellipsis it doesn't cuz of stub behavior?

#

Unreasonable for normal functions

lunar dune
#

But you're allowed to have pass statements in stub files? it's a pretty arbitrary exemption to the general rule IMO, which is why we didn't do that in ty

fiery canyon
#

Well an abstract method might as well return a default according to its return type, or use ellipsis

lunar dune
#

Yes, you have to be careful to allow the former to be called via super() from concrete subclasses but not allow the latter to be called via super() from concrete subclasses

#

ty doesn't have this check implemented yet but it's on the roadmap

rare scarab
#

Like inside a protocol

#

Adding @abc.abstractmethod might help

icy obsidian
#

Would it be possible to make some kind of a None check like this:

class Test:
    def __init__(self) -> None:
        self.t: int | None = None
        self.d: dict[int, int] = {}
        
    def has_t(self) -> bool:
        if self.t is None:
            return False
        else:
            return True
        
    def func(self):
        if self.has_t():
            # assert self.t is not None
            tmp = self.d[self.t]  # <<<<<< Error
        else:
            tmp = 0
icy obsidian
#

Can use TypeGuard but it will require passing self.t there...

viscid spire
#

yeah unfortunately you can't have typeguard methods with only self

icy obsidian
#

Unfortunate. Will have to add assertions after...

feral wharf
#
class _NoValue:
    __slots__ = ()

    def __eq__(self, other) -> bool:
        return False

    def __bool__(self) -> bool:
        return False

    def __hash__(self) -> int:
        return 0

    def __repr__(self):
        return "..."


NoValue: Any = _NoValue()

https://images.soheab.com/G4CtlsGYfLM.png?f=scs hope this is a bug in ty?
param: T = NoValue would also be valid with pyright

#

oh nvm

#

param: T | NoValue works

vernal surge
#

Hi all,
for Python 3.7 to Python 3.9, is it considered good practice to use from __future__ import annotations and the new pipe convention (type1 | type2 | None), or is it better to stick to the convention that was present when that Python version was released (Optional[Union[type1, type2]])?
For projects that should work from Python 3.7 to the most recent version, what should I use for type hints?
Thanks in advance!

rare scarab
#

Yes.

#

Those versions of EOL though.

viscid spire
rare scarab
#

Yes

viscid spire
rare scarab
#

That means both options are correct

grave fjord
fiery canyon
#

What's the reason for supporting 3.7 if I may ask

vernal surge
#

It's a library available on Pip. I think it was born with that version and they wanted to maintain retro-compatibility for people that used it

hallow flint
#

as long as you don't have runtime usage of type hints the from future annotations will work well

gusty wigeon
#

What's the difference between None and typing.NoReturn? I see people using None without returning this specific type

rare scarab
gusty wigeon
rare scarab
#

Must be raise or while True

gusty wigeon
viscid spire
#

You can also use typing.Never for a function that won't return

rare scarab
#

Both are equivalent

#

Never can also be used in function overloads

viscid spire
#

I personally prefer Never

feral wharf
#

Does Never also indicate that it'll only raise?

trim tangle
#

I'd always use Never because it's just a newer alias for the same thing

feral wharf
#

Ooh

fiery canyon
#

Doesn't Never have more usages

trim tangle
#

I don't know how real this convention is, but in two other languages I know (Rust and TypeScript) there's no such distinction, never/! is used in both data and control flow contexts

fiery canyon
#

TIL typing.Final exists

hasty phoenix
#

Given

def f() -> Any: ...
def g() -> str:
    # Assume its known that f() will return str in this context
    return f()  # mypy: Returning Any from function declared to return "str"

# A possible fix
def g() -> str:
    v: str = f()
    return v

Is there a more elegant to do this? A temp variable isn't eye candy. This needs to be backwards compatible with 3.10 (but I'm curious to how it can be done in 3.14 too)

stable fjord
#

just type: ignore[no-return-any] it

meager slate
#
from typing import Any, cast


def f() -> Any: ...
def g() -> str:
    return cast(str, f())
trim tangle
buoyant swift
meager slate
hasty phoenix
fiery canyon
#

You'd typically not just wrap f like that

#

g should have a check whether the found entry in the db is a string

hasty phoenix
#

I think this has been discussed here before. Some propose using a typed proxy function get_str_from_db() -> str or get_int_from_db() -> int and so on

hasty phoenix
# fiery canyon You can use overloads

Yes, in this example that could be done I think, since it can be inferred from the returned type. But there are other cases where it's not that clear

fiery canyon
#

Based on your needs

hasty phoenix
#

Yes, and then you introduce runtime code to please the type checker

fiery canyon
#

This isn't what I put the isinstance there for

#

Based on your needs
If you actually want to know if it's an int, do this and that, and so on

hasty phoenix
#

yeah, sure, in that case. Agreed

fiery canyon
hasty phoenix
fiery canyon
#

I mean that's the obvious though. I can't think of more complex scenarios

fiery canyon
#

Unless it's from an input for example

#

And then again I'd use isinstance idk

hasty phoenix
fiery canyon
#

If you're sure it's going to be string (your specific example wouldn't make sure), then you may just cast, assert, whatever.

hasty phoenix
#

There's no way to make a TypedDict (or like) class for integer keys? It doesn't seem so at least

fiery canyon
#

Nawr, you can use an overload for that with Literal.

hasty phoenix
#

Is there a way to type annotate certain members of a dict-like object? Some types like: {1: A, 2: B, 3: C}

fiery canyon
#

Yes

#

You want to annotate them specifically as their values?

#

dict[int, Any] vs dict[Literal[1, 2, 3], Any]

hasty phoenix
#

Perhaps this is how it can be done: dict[Literal[1], A] | dict[Literal[2], B] | dict[Litera[3], C]. Gets messy quickly thou

trim tangle
#

it doesn't fit e.g. {1: A(), 2: B()}

fiery canyon
#

But you'd almost never need to do this generally

#

This behavior is a little too specific

fiery canyon
trim tangle
#

🤔

#

how would that help

fiery canyon
#

Because wouldnt work with instances

#

Anyway I wouldn't do this

trim tangle
#

I'm assuming sveinse wants 1 to correspond to an instance of A and 2 to an instance of B

hasty phoenix
#

My use is not generic. It's specific. I know all types. Specifically, what I'm trying to do with the non-working code:

TPDOParameterRecord = TypedDict('TPDOParameterRecord', {
    1: 'LtPDOCobid',
    2: 'LtPDOTransmissionType',
    3: 'LtPDOInhibitTime',
    5: 'LtPDOEventTimer',
    6: 'LtInt',
})
fiery canyon
#

Yeah but once you have a dict that is more than a few keys this gets very impractical

#

You can use overloads to do it

trim tangle
fiery canyon
hasty phoenix
fiery canyon
#

Yeah it works for a dict of this size

#

I believe you cannot make this dynamic at all

trim tangle
# hasty phoenix This is something existing. It's from a CAN communication standard, where one ge...

You could reify keys as instances like this:

class Key[T]:
    def __init__(self, number: int) -> None:
        self.number = number

    def __invariance_hack(self) -> list[T]: raise NotImplementedError  # variance inference was a mistake >:(

K_APPLE = Key[int](1)
K_BANANA = Key[list[int]](2)
K_CHERRY = Key[str](4)

class Store:
    def __getitem__[T](self, key: Key[T]) -> T: ...
    def get[T](self, key: Key[T]) -> T | None: ...
    def __setitem__[T](self, key: Key[T], value: T) -> None: ...

def f(s: Store) -> None:
    x = s.get(K_APPLE)  # x is `int | None`
    
    s[K_CHERRY] = 42  # error
    s[K_CHERRY] = "aaa"  # ok
``` but it effectively makes every key optional
#

though if you already have an existing API, it might be hard to retrofit

fiery canyon
#

?

#

or function

hasty phoenix
#

__invariance_hack() ? Is that something from typing?

fiery canyon
#

Unrelated:
Why does Search, being a class, not trigger type checker?

trim tangle
trim tangle
fiery canyon
#

Ah oopsie right

#

itll prob fail

trim tangle
hasty phoenix
#

thanks

fiery canyon
#

Ok it does work, it auto applies it on __init__

trim tangle
#

which is probably confusing to users (and also doesn't let you use isinstance etc.)

trim tangle
fiery canyon
#

You don't see the result in this pic

trim tangle
#

you understand how decorators work, right?

fiery canyon
fiery canyon
#

Ohh, it converts it into a decorator function

trim tangle
#

it means the same as ```py
class Foo:
...

Foo = enforce_annotations(Foo)
``` so Foo is whatever enforce_annotations returns, in this case a function

fiery canyon
#

Yep

trim tangle
#

try doing print(Foo)

fiery canyon
#

function

but type(foo_instance) is <class '__main__.Foo'>

trim tangle
#

right

fiery canyon
#

🤔

#

I'm trying to think where this could be a real problem

trim tangle
#

the class still exists, it's just not bound to the name Foo

trim tangle
#

or if you want to use Foo in a type annotation

fiery canyon
#

Could I change behavior of enforce_annotation to return a type and not a function when it's a class?

#

Yer right

trim tangle
#

enforce_class_annotations, whether it only changes the __init__ or all the methods, will have to mutate the class. But enforce_annotations doesn't mutate the argument

fiery canyon
#

Should work only for init

trim tangle
#

Imagine that you do py x = map(enforce_annotations(some_user_callable), things) you wouldn't expect this to permanently alter RandomClass when passed through some_user_callable

fiery canyon
#

Maybe I should leave it as is and put straight on init when I want it

trim tangle
#

yeah, that's the least surprising behaviour

#

if I put it on a class I'd expect it to enforce annotations on all the methods (and I would also wonder what happens to subclasses)

fiery canyon
#

I can also raise if its a class

hasty phoenix
#

What's the paste limit here? Is 25 lines ok?

#
class A: ...
class B: ...
class C: ...
class DictLike(MutableMapping[KT, VT]):
    """ Some placeholder dict-like class """

class D(DictLike[int, Any]):
    @overload  # type: ignore[override]
    def __getitem__(self, key: Literal[1]) -> A: ...
    @overload
    def __getitem__(self, key: Literal[2]) -> B: ...
    @overload
    # Using `int` for catching all other int keys doesn't work, mypy gives:
    # "Overloaded function signatures 1 and 3 overlap with incompatible return types"
    def __getitem__(self, key: int) -> C: ...

    # Had to do this proxy to avoid mypy error
    def __getitem__(self, key: int) -> Any:
        return super().__getitem__(key)

d=D()
reveal_type(d[1])  # revealed type is "A"
reveal_type(d[2])  # Revealed type is "B"
reveal_type(d[3])  # Revealed type is "C" if the 3rd overload is used
#

Here's my attempt. But it does get messy (i.e. wordy) and its not error free yet.

#

Baah. I think I need to rethink this whole setup. It's too complicated.

fiery canyon
#

Real talk

eternal pulsar
trim tangle
#

(the cls: type[range] bit)

eternal pulsar
#

That cls argument should not be there, that is the signature of __new__.

late socket
#

@trim tangle

trim tangle
feral wharf
#

How dare you ping our typing proficient

pale birch
#

!e

Can someone explain what I'm doing wrong here?

class SupportsSlots:
    def __instancecheck__(self, instance):
        print("SS.__")
        return hasattr(instance.__class__, '__slots__')

class Foo:
    __slots__ = ['a', 'b']
    def __init__(self, a, b):
        self.a = a
        self.b = b

obj = Foo(1, 2)

print(f'{hasattr(Foo, "__slots__")=}')
print(f'{hasattr(obj.__class__, "__slots__")=}')
print(f'{isinstance(obj, SupportsSlots)=}')
rough sluiceBOT
rare scarab
pale birch
#

Yeah, probably. Can you demo?

rare scarab
#

On phone

#

You'll be waiting a while, typing speed is slow and inaccurate

pale birch
rare scarab
#

!e ```py
class FooMeta(type):
def instancecheck(cls, instance):
return True

class Foo(metaclass=FooMeta):
pass

print(isinstance(None, Foo))

rough sluiceBOT
pale birch
#

To save you some time, would I make a metaclass with the __instancecheck__ method, and make the metaclass a metaclass of my SupportsSlots class?

#

Ah, right. Thanks, I'll take this for a spin.

warped jungle
#

Hi all - first time posting here.

I’ve published a Python 3.14 type hinting guide built around two goals:

Technical correctness - strict alignment with current Python 3.14 typing semantics, the typing spec, and relevant PEPs, with direct references where possible.

Pedagogical clarity - presenting typing as a coherent static-analysis system (propagation, narrowing, widening, loss of specificity), rather than a list of isolated syntax features.

I’d like this guide to meet the standards of community typing documentation, and I’m looking for help identifying where it should be improved - technically or pedagogically - so it can be shaped into something broadly useful rather than a standalone personal reference.

If you’ve contributed to typing docs, teaching materials, or have strong opinions about how typing should be explained, your input would be especially valuable.

I’m also interested in feedback from people using this as a learning or reference document: where explanations break, ordering fails, or you had to re-read to proceed. And if a section clarified a failure mode you were already fighting or changed how you structured code, I’d like to hear that as well.

Repo:

https://github.com/JBogEsq/python_type_hinting_guide

GitHub

Modern Python Type Hinting Guide (current as of Python 3.14). Shared publicly for review and comment. Not licensed for redistribution or reuse. - JBogEsq/python_type_hinting_guide

#

One question I’d value input on: whether these topics adequately cover the conceptual surface area needed for a general guide to Python typing, and whether anything important is missing that would prevent a reader from forming a correct working mental model of the system.

Top-level TOC

Overview of Python Type Hinting (aka typing)
Python’s Native Typing Support
Core Annotation Syntax
Tuples: Structured Records vs. Uniform Collections
Typing Collections
Type Inference and Resolution
Advanced Narrowing: Promises vs. Proof
CRITICAL USE CASE: Typing Data from the Edge
Typing Functions: Synchronous vs. Asynchronous
Creating Generic Functions with TypeVar
Constraining TypeVar: bound and Variance
Advanced Generic Callables: Higher-Order Functions
Advanced Generic Callables: Wrappers
Additional Typing Module Functionality
Appendices

  • Architectural Best Practices
  • Anatomy of Type Declarations and Associated Vocabulary
  • The Typing Tools Ecosystem
  • Major Typing Enhancement Proposals (PEPs)
    Glossary of Typing Terminology
trim tangle
warped jungle
# trim tangle very concerning amount of em dashes

Yeah, I hear you. There is a danger in a lack of confidence that some big dollar firms think is fixed with lots of em dashes. But don't read too much into them - the document is well outside the capacity of AI trash. (And I never used an em dash in my life until they were foisted upon me.)

trim tangle
# warped jungle Yeah, I hear you. There is a danger in a lack of confidence that some big dolla...

I am a bit confused by the "Numeric Type Promotion" section. It seems to conflate two different things:

  • the fact that int is assignable to float and that float is assignable to complex. There's no subclass relation between them, it's just special cased and is considered a design mistake by many people (see e.g. https://discuss.python.org/t/54289)
  • the fact that int + float is a float, which is correct at runtime (regardless or the values) and is likely taken from the typeshed without any special casing

also, what does this mean?

Note: Type checkers treat bool as a distinct type in many contexts even though it is a runtime subclass of int.

warped jungle
trim tangle
#

bool is a subclass of int both to type checkers and at runtime. Do you have any examples of special casing perhaps?

warped jungle
# trim tangle `bool` is a subclass of `int` both to type checkers and at runtime. Do you have ...

This is from the docs:

Boolean Type - bool

Booleans represent truth values. The bool type has exactly two constant instances: True and False.
...
bool is a subclass of int (see Numeric Types — int, float, complex). In many numeric contexts, False and True behave like the integers 0 and 1, respectively. However, relying on this is discouraged; explicitly convert using int() instead.

https://docs.python.org/3/library/stdtypes.html#boolean-type-bool

Python documentation

The following sections describe the standard types that are built into the interpreter. The principal built-in types are numerics, sequences, mappings, classes, instances and exceptions. Some colle...

trim tangle
#

even x: complex = True is legal

#

This so far is confirming that bool is a subclass of int, and giving advice to avoid relying on it (because it may hurt readability)

#

e.g. using sum(1 if foo(x) else 0 for x in xs) instead of sum(map(foo, xs)), which some people might disagree with

warped jungle
#

I will try to pull a source later, this is the initial PEP on the matter, and it describes bool as inheriting from int - so your position is certainly not wrong, but I am pretty sure I had a reason for trying to allow for inconsistent treatment by type checkers. https://peps.python.org/pep-0285/

This PEP proposes the introduction of a new built-in type, bool, with two constants, False and True. The bool type would be a straightforward subtype (in C) of the int type, and the values False and True would behave like 0 and 1 in most respects (for example, False==0 and True==1 would be true) except repr() and str()...

warped jungle
# trim tangle even `x: complex = True` is legal

Without re-creating the wheel just yet, maybe this context gives some substance to my note:

"The annotation int | bool describes exactly the same type as the annotation int. The two types are equivalent. If a type checker treats them differently, that’s a clear indication of a bug in the type checker. In this case, pyright treats the two types as equivalent and mypy incorrectly treats them differently, so this is a bug in mypy. That said, I appreciate that detecting equivalency between types is difficult and costly (prohibitively so in some cases). If you search hard enough, I’m sure you would find cases where pyright’s behavior differs between two equivalent types."

https://discuss.python.org/t/type-hints-for-bool-vs-int-overload/72239/59

trim tangle
#

Eric is correctly pointing out that treating int | bool and int differently is a bug

warped jungle
oblique urchin
#

haven't reread that discussion but I suspect othet subclasses of int would behave the same

grave fjord
tropic glen
#

Why can I type hint with an Enum created by subclassing but not with the functional syntax?

#
from enum import Enum

class A(Enum):
    pass

B = Enum()

def a(x: A):
    pass

def b(x: B):
    pass```
#

on the typehint B, I get Variable not allowed in type expression

feral wharf
#

what if you do type B = Enum() or B: TypeAlias for <3.12

trim tangle
#

the second doesn't work for the same reason the original code doesn't work

tropic glen
#

that's insane

#

thank you

#

so much of working with types in python seems to be spending 2 hours trying to get a trivial thing working and discovering you have to just not type it

#

I suppose I could construct a union of a large number of literals

azure frigate
#

hi guys

#

Where can I use Python for real-world applications?
I’m interested in practical use cases such as network scanning or automation. I’m currently using VS Code as my development environment. Do I need additional tools or guidance to get started? If anyone can help or point me in the right direction, I’d really appreciate it.

tropic glen
#

This doesn't seem to have much to do with typehinting, I would suggest asking in #1035199133436354600 or #cybersecurity , I'd recommend following some kind of online course for Python basics if you aren't already familiar, and then maybe some cybersecurity specific ones

viscid spire
fiery canyon
fiery canyon
trim tangle
fiery canyon
#

And as of the rest, yeah I guess

bleak imp
#

Is there a reason for the type of data not being narrowed here, or is it just a limitation of type checker implementation?

from typing import final, Literal

@final
class InvariantContainer[T]:
    __match_args__: tuple[Literal["item"]] = ("item",)
    def __init__(self, item: T):
        self.item: T = item
    def set(self, item: T):
        self.item = item
    def get(self) -> T:
        return self.item

@final
class A: ...
@final
class B: ...

def foo(data: int | InvariantContainer[A | B]) -> None:
    match data:
        case InvariantContainer(A() as item):
            reveal_type(data)  # Type of "data" is "InvariantContainer[A | B]"
            reveal_type(item)  # Type of "item" is "A"

Surely since the type of InvariantContainer.item is T, and we know the type of item is A, and A is final, that must mean data overall is InvariantContainer[A], right?

oblique urchin
#

Though mypy and pyright don't narrow either if you make it covariant. But I think in general, it's not true that just because it contains an A, it's valid to narrow the generic argument to A

spice talon
#

Would any Pydantic gurus be able to answer this question I have on StackOverflow? In a nutshell, I'm trying to write a custom schema to validate/serialize a tuple[SomePydanticModel, *tuple[SomeOtherPydanticModel, ...]]. I know the core library supports it, but the high-level part that reads type annotations doesn't.

bleak imp
warped jungle
#

Does anyone have any thoughts about my guide's general quality? I'd like to share it more broadly, but I want to be sure that there is nothing majorly stupid, distracting, etc. that would prevent people from taking it seriously.

trim tangle
#

And what's the overall goal?

warped jungle
#

The most likely audience is Python devs who are past beginner stuff but still unsure what typing is really buying them. That includes people who’ve only used Python and see types as IDE noise, and people coming from typed languages who find Python’s typing model confusing or half-baked. No type theory assumed, just basic comfort writing non-trivial code.

The goal is to pull together the scattered “how do I type this” and “what even is typing in Python” advice into a coherent picture of why typing is useful beyond “the linter wants it.” It’s mostly about building intuition for how types help you reason about code, catch mistakes earlier, and make intent clearer, so typing feels like a tool you choose rather than a chore. Ideally, it would sit next to Python’s official typing docs as a conceptual companion to the quick reference.

trim tangle
warped jungle
#

Yes. Seems fair.

trim tangle
# warped jungle Yes. Seems fair.

I think what you set out to do is very ambitious and doesn't fit into a single markdown document, it's more like an entire (small) book. So it ends up being very "underextracted": there are not nearly enough examples and elaboration, so it's probably difficult for a new person to fully understand provided information.

#

So the overview chapter doesn't really introduce type hints to newbies. The concrete tools part is light on examples (and seems a bit chaotic, e.g.: unusual order, sometimes using pre-3.12 generic syntax, variance is a very difficult concept IME and just listing the definition is insufficient). And the strategy part only elaborates on one bit of advice (rigorously turning edge inputs into typed values).

#

For example, take a look at this article on the typing site that would fit in the "strategy" category: https://typing.python.org/en/latest/guides/libraries.html
It discusses pros and cons of types in libraries; how to actually add them (with links to other detailed articles on stubs); and provides a lot of best practices with examples and links to even more material.

#

underextracted
As one illustration, in the "Expressive Structural Narrowing with match" section, you define a union of classes whose name all end with Command. It is a pretty powerful concept and it's a great tool to design your program with static types in mind. But the reader might not be familiar with it: I'm assuming the audience has very few people who know Haskell/F#/Rust. So it's probably worth dedicating an entire chapter to this general idea of carving out your data model with records and unions (something like https://fsharpforfunandprofit.com/series/designing-with-types/)
This section would also benefit from introducing assert_never and generally having reachability analysis discussed somewhere in the guide (e.g.: why does the type checker complain that i is potentially unbound in for i in range(69):? And what do you do about it? (good opportunity show that type checkers are sometimes neither smart nor wise so you can silence them using directives))
(Also, pre-3.12 syntax is used for the type alias, even though the type statement was already introduced and the guide targets python 3.14. Is this code written by a human?)

#

What I wanted to say overall is that it would take an enormous amount of work from turning this from a draft into a comprehensive book

#

I do think that there's a lack of tutorial-level information on type hints. I've been working on a guide myself for a while: https://decorator-factory.github.io/typing-tips/tutorial/0-start-here/. Instead of making a rough overview with the intention of filling stuff in later, I decided to make articles that are feature-complete and have the appropriate level of detail, but limited in scope. It seems like very few people are interested in this though, so I kind of abandoned it

grave fjord
#

can I get a hand with my AsyncIteratorBytesResource here https://github.com/django/new-features/issues/117

from useful_types import SupportsAnext

class AsyncIteratorBytesResource(Protocol):
    """
    all the machinery needed to safely run an AsyncGenerator[Bytes]

    (for django-stubs) this allows AsyncGenerator[bytes] but is less strict
    so would also allow a anyio MemoryObjectStream[bytes]]
    """

    async def __aiter__(self) -> SupportsAnext[bytes]: ...
    async def aclose(self) -> object: ...


async def news_and_weather(request: HttpRequest) -> StreamingCmgrHttpResponse:
    @contextlib.asynccontextmanager
    async def acmgr_gen() -> AsyncGenerator[AsyncIteratorBytesResource[bytes]]:
        async def push(ws_url: str, tx: MemoryObjectSendStream) -> None:
            async with tx, connect_ws(ws_url) as conn:
                async for msg in conn:
                    await tx.send(msg)

        async with anyio.create_task_group() as tg:
            tx, rx =  anyio.create_memory_object_stream[bytes]()
            with tx, rx:
                tg.start_soon(push, "ws://example.com/news", tx.clone())
                tg.start_soon(push, "ws://example.com/weather", tx.clone())
                tx.close()
                yield rx  # yield inside asynccontextmanager, permitted inside TaskGroup

    return StreamingCmgrHttpResponse(acmgr_gen())
GitHub

Code of Conduct I agree to follow Django's Code of Conduct Feature Description see https://forum.djangoproject.com/t/streamingresponse-driven-by-a-taskgroup/40320/4 I'd like to be able to w...

#

I want to be able to run something akin to this:

async with response.streaming_acmgr as resource, aclosing(resource) as stream:
    async for v in stream:
        await send(compressor.compress(v))
    await send(compressor.eof())
#

I'm also not sure if it should be called AsyncIteratorBytesResource or AsyncIterableBytesResource

warped jungle
# trim tangle So the overview chapter doesn't really introduce type hints to newbies. The conc...

Thanks for the feedback.

I agree that it is ambitious. The guide is not intended to be short or exhaustive; rather it is meant to be conceptually complete within its scope and sufficiently elaborated to teach/convey the substance. A usage guide, technical manual, etc. is a different beast - I am actively trying to maintain a narrative/conceptual flow (even if trying to make each "section" stand on its own without extensive reading of other sections). It is a hard balance, which is part of the reason I am asking for comments.

I took a look at your guide after you commented the other day. While we are not doing the same thing, I am sure that my project would benefit from your participation.

trim tangle
#

whether TypeIs/TypeGuard exists or how numbers are handled is probably not important for that aspect

#

honestly I might be completely missing the idea behind the project

warped jungle
# trim tangle honestly I might be completely missing the idea behind the project

I tried this explanation elsewhere:

The guide largely parallels the typing spec, but it approaches the material from a different perspective and for a different purpose. The intended audience is Python developers who want to understand Python’s typing system as more than just syntax or tooling, regardless of whether they already know how to use it. It’s meant as a bridge from “I can write type annotations” to “I understand why I would choose to use them,” and it tries to build both pieces together rather than assuming either one up front.

A lot of existing material explains how typing works, but spends much less time on why it’s worth the effort in a dynamic language. Saying that typing “permits static analysis” is true, but it doesn’t really explain what that buys you in practice. This guide is focused on making the motivation explicit and showing how typing supports reasoning, design, and long-term code clarity, not just mechanical correctness.

novel notch
#

Does @functools.lru_cache() hide the return type for decorated function?

I'm on python 3.10 and mypy 1.19.1 and it doesn't seem to understand the type 🤔

hallow flint
#

iirc preserves return type but hides arg types

#

(based on the typeshed stubs, due to some issues around methods. probably something we should special case in mypy though...)

novel notch
#

Or rather I guess it is python-lsp-server that doesn't understand the type. Because goto_type doesn't find anything.

#

pylsp --version = 1.13.1

fiery canyon
#
ClientT = TypeVar('ClientT', bound=Client, covariant=True, default=Client)


class DynamicItem:
  async def callback(self, interaction: Interaction[ClientT]) -> Any:
    pass

Now "frontend":

class MyItem(DynamicItem):
  async def callback(self, interaction: Interaction[Bot]):
    ...

That causes:

Method "callback" overrides class "DynamicItem" in an incompatible manner
  Parameter 2 type mismatch: base parameter is type "Interaction[ClientT@callback]", override parameter is type "Interaction[Bot]"
    "Interaction[ClientT@callback]" is not assignable to "Interaction[Bot]"
      Type parameter "ClientT@Interaction" is covariant, but "ClientT@callback" is not a subtype of "Bot"
        "Client*" is not assignable to "Bot"```


For clarity, `Bot` is a subclass of `AutoShardedBot` that is a subclass of `Client`.
Is there any way to change `ClientT` or whatever that'll make this work?
trim tangle
# fiery canyon ```py ClientT = TypeVar('ClientT', bound=Client, covariant=True, default=Client)...

Suppose that you have this function: py def f(d: DynamicItem) -> None: ... the signature of DynamicItem.callback means that for every T: Client, Interaction[T] will work as the argument.
But MyItem violates this requirement, because it only accepts Interaction[Bot]. Hence the error

One potential solution is making DynamicItem generic over the type of client. For example (in 3.12 syntax for conciseness) ```py
class DynamicItem[C: Client]:
async def callback(self, interaction: Interaction[C]) -> object:
pass

class MyItem(DynamicItem[Bot]):
async def callback(self, interaction: Interaction[Bot]) -> object:
pass

fiery canyon
#

I see
But nah the concept of dynamicitem would have nothing to do with client

#

As a matter of fact this is a reoccuring problem for every function that takes Interaction

#

One way of avoiding the issue is just not passing the type arg (and then interaction.client is typed to the default Client)
then you can do bot: Bot = interaction.client

trim tangle
#

you might need to provide more details

fiery canyon
#

The topic is more around override-able functions that take interaction. Not really about DynamicItem as that was just an example to a class that has such a function. And so, the client isn't related to that class

The Interaction object has a client attribute, which is by default typed to Client.
By passing a custom bot class as a type arg to interaction, it types .client to that bot class, but at the same time complains as I showed in the beginning

#

From what I understood the only solution is leaving interaction with no type arg and doing bot: Bot = interaction.client

trim tangle
fiery canyon
#

Fair enough cRy

trim tangle
#

If you want users to be able to create DynamicItems that don't work with every possible client, but only with their application-specific client, then the way to do that is to to make DynamicItem generic as I showed

fiery canyon
#

I just don't really get this whole thing of client not being assignable to bot
But Idk much about subtypes or supertypes

For clarity, Bot is a subclass of AutoShardedBot that is a subclass of Client.

trim tangle
# fiery canyon I just don't really get this whole thing of client not being assignable to bot B...

Imagine that you have this: py class Animal: def eat[F: Fruit](self, food: F) -> Packaging[F]: # this is a good animal that returns packaging back for recycling ... this signature means that if someone has access to an Animal, they are allowed to provide any fruit to it: py def feed_animal(a: Animal): a.eat(Apple()) a.eat(Banana()) a.eat(Cherry()) so if you wanted to make this: py class Monkey(Animal): def eat(self, food: Banana) -> Packaging[Banana]: ... that would be an "incompatible override", because now passing Monkey to feed_animal would cause type problems, as e.g. Apple() would be assigned to food: Banana

fiery canyon
trim tangle
#

exactly

fiery canyon
#

Hm so then there's no way to support custom bot type

#

Fair

#

Though you could do

banana: Banana = animal.fruit```
#

I think

trim tangle
#

where?

fiery canyon
#

If you'd have animal.fruit

fiery canyon
trim tangle
#

that would require typing.cast or a # type: ignore comment

#

assuming that animal.fruit is just an arbitrary Fruit

#

or maybe I don't get what you're saying

fiery canyon
#

Welp with bot: Bot = interaction.client it works just fine without cast or type ignore
But nah it's fine 🙏🏻

viscid spire
#

uh-oh

from dataclasses import dataclass

@dataclass
class A[T = int]:
    x: T = 1 # Type "Literal[1]" is not assignable to declared type "T@A"

a = A()
reveal_type(a.x)
#

from pyright playground. Works with a "normal" class.

#

and looks like mypy doesn't even support typevar defaults yet?

trim tangle
#

oh

#

The warning seems correct to me, otherwise you'd be able to do e.g. ```py
a: A[Literal[2]] = A()

#

pyright has another custom feature that prevents this (because of the synthesized __init__), but otherwise yeah

#

How are you doing it with a normal class?

viscid spire
#

That works fine

trim tangle
trim tangle
# viscid spire ```py class A[T = int]: def __init__(self, x: T = 1): self.x = x a ...

As I said there, allowing x: T = 1 is a pyright-specific feature (i.e.: mypy or ty won't understand this and will think that it's just a normal optional parameter).
e.g. if you try to do py a = A[bool]() pyright will complain that the x parameter is wrong

So maybe pyright adds some extra checks in a class body in addition to the check on the synthesized __init__ method

trim tangle
trim tangle
viscid spire
#

it doesn't complain about the assignment in the non-dataclass for me

viscid spire
#

so what you are saying is that pyright is not handling the typevar default correctly, but rather has a specific behaviour that's non spec which just so happens to do the same thing, except in dataclasses?

#

It's hard to compare stuff because mypy doesn't even support typevar defaults right now (at least in the playground). It just syntax errors.

trim tangle
# viscid spire Basically doing the job of typevar default before it existed

According to the spec, typevar default does two unrelated things:

  1. in classes, allows you to omit the type parameter
class Foo[T = int]:
    ...

def f(x: Foo) -> None: ...
#    ^ same as "x: Foo[int]". Otherwise it would be Foo[Any]

This is useful, for example, if you are making an existing library class generic and don't want to add hidden Anys for users

  1. In functions, guides inference when it would otherwise be impossible to solve a typevar
def first_twice[T = Never](x: list[T]) -> tuple[T, T] | None:
    return (x[0], x[0]) if x else None

u = first_twice([])
# u is inferred as `tuple[Never, Never] | None`. Without the default it would be `tuple[Unknown, Unknown] | None`
# bizzarely pyright doesn't eliminate the `tuple` variant...
trim tangle
rough sluiceBOT
trim tangle
#

I think it's related to this

trim tangle
#

what a wild programming language

trim tangle
# viscid spire so what you are saying is that pyright is not handling the typevar default corre...

So, to summarize: pyright synthesizes an __init__ that makes use of this pyright-specific feature that allows providing defaults to generic arguments until the function is called ```py
@dataclass
class Foo[T]:
x: T = 69

this is synthesized:

def init(self, x: T = 69) -> None

x = Foo() # x is inferred as Foo[int]
Foostr # error: Literal[69] is not assignable to str

however, `x: T = 69` in the dataclass also creates a class variable, and pyright is complaining about that (because generic class variables are generally dubious)
hasty phoenix
#

It there a way to test on member types that the type checker is able to follow? Consider the example:

def fn(a: list[str] | list[int]) -> list[str]:
    if len(a) == 0:
        return []
    if isinstance(a[0], int):
        b = [str(x) for x in a]
    else:
        b = a  # b: list[str] | list[int]
    return b

Functionally its guaranteed that b is of type list[str] at the return statement (given that all elements are the same), but the type checker is not able to infer that. Is there a way that this can be done?

trim tangle
#

since int and str never overlap, I think there's no way to break this even with evil subclasses of list[str] or list[int]

hasty phoenix
#

Not even with a class DubiousStrInt(str, int) object? 🤔

#

not sure that works thou

trim tangle
rough sluiceBOT
# trim tangle !e ```py class Strumber(str, int): pass ```

: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 |     class Strumber(str, int): pass
004 | TypeError: multiple bases have instance lay-out conflict
fiery canyon
#

!e

from collections import UserString

class S(UserString, int): pass
rough sluiceBOT
fiery canyon
feral wharf
#

Wth is UserString

rare scarab
rough sluiceBOT
#

stdlib/collections/__init__.pyi line 162

class UserString(Sequence[UserString]):```
rare scarab
#

if you just inherit from str, these methods will return str instead of your custom type.

feral wharf
#

Oh interesting

rare scarab
#

Same with UserDict and UserList

feral wharf
#

that's nice

fiery canyon
#

Yeah, I discovered it when I searched for ways to make a string subclass that returns its own type and not str

#

Also I'm pretty sure that at an early stage you had to use that if you wanted to make a string/dict/list subclass, but Idk for sure

spiral fjord
#

yes

barren rampart
#
if TYPE_CHECKING:
    # These would be circular imports if it wasn't in TYPE_CHECKING
    from my_django_app.models import (
        Builder,  # noqa: F401
        City,
        CityLocation,  # noqa: F401
        FeatureBundle,  # noqa: F401
        FeatureItem,  # noqa: F401
        GeoMarket,  # noqa: F401
        Location,
        Marker,  # noqa: F401
    )

class BuilderQuerySet(OurCustomQuerySet["Builder"]): ...

class CityQuerySet(OurCustomQuerySet["City"]): ...

def some_function(items: "list[City]"): ...

I have some types (Builder, City, etc) that I import in a TYPE_CHECKING block so that I can reference them without creating circular dependences. For many of them I need the # noqa: F401 comment since my type checker (pyright) considers them unused. But some types like City are considered used because they are used in "list[City]".

  1. Is there a better pattern for dealing with circular types that are only imported to be used as generic type arguments and type annotations?
  2. Why are some types (like City) considered used and others not used when they are only ever used as type annotations and generic type args?
viscid spire
#

I'm not quite understanding that second point

#

Builder and City are used because... you are using them. The other not because you are not

barren rampart
#

I didn't give you all 2000 lines of our code, I used those two examples to show that one is only used in OurCustomQuerySet["Builder"] and the other is also used in a list[...].

viscid spire
#

I don't understand what you're getting at

barren rampart
#

If I were to simply import Builder, it would get removed by the linter because it's not used. It's used as a type arg in quotes, but that is somehow considerd unused. Therefor I added the # noqa: F401 to prevent that.

But City, which is also only ever used inside a quoted type annotation is considered "used" by both the linter (ruff) and the type checker (pyright).

grave fjord
#

Try using from __future__ import annotations?

meager slate
#

i had that same issue using basedpyright type checker in zed but i havent had that problem in vscodium's basedpyright extension

#

and it was only a zed warning, when i ran the type checker manually i got no errors in the terminal

barren rampart
#

from __future__ import annotations doesn't seem to change it. I thought it would allow my to remove the quotes, which would then consider all these classes "used", but that fails at runtime since they are only imported in the TYPE_CHECKING block. It seems from __future__ import annotations only allows my to reference types out of order, but not handle types imported inside if TYPE_CHECKING:.

viscid spire
#

Sounds like it's an issue with your linter

barren rampart
#

Correction, it's only ruff that considered them "unused". I thought pyright was giving me that warning, but I was wrong. (editor plugins)

#

Which may help now that I am blaming the right tool..

meager slate
#

which editor are you using?

barren rampart
#

vscode, with pyright (the default microsoft pylance plugin) and ruff extensions.

meager slate
#

does the error come up when you run ruff check ? Or is it only visually

barren rampart
#

but this issue is about ruff now. I was using the editor for realtime ruff feedback, but this issue of removing imports can happen even when pre-commit runs ruff. It's not vscode-related.

#

yea, all ruff.

#

I just realized I can write it like this, and not have all the individual comments:

    from my_django_app.models import ( # noqa: F401
        Builder,
        City,
        CityLocation,
        FeatureBundle,
    )

This doesn 't really answer my question about why some are "used" and others not, but it makes me care less since I don't have to keep adding removing the comments on individual lines based on what other code I edit.

meager slate
#

okay yeah, im getting the error too, might just be a ruff issue

meager slate
# barren rampart but this issue is about ruff now. I was using the editor for realtime ruff feedb...

There was an issue on this: https://github.com/astral-sh/ruff/issues/9298

Seems like they recommend configuring your extended-generics: https://docs.astral.sh/ruff/settings/#lint_pyflakes_extend-generics

GitHub

To give some context, I have from future import annotations required in every-single file, and the TCH rule-set enabled across my codebase, along with the full Pyflakes rule-set. I'm runnin...

#

not the cleanest way but no other option really (other than supressing it completely)

barren rampart
#

Yea, this looks like my issue. Thanks.

feral wharf
#

how does one type a typeddict?

#

as in valid for Unpack

class Gimme[K: ???]:
  def __init__(self, **kwargs: Unpack[K]) -> None:
    ...
#

[K: TypeGuard[Annotated[True, is_typeddict]]] would've been cool KEKW

rare scarab
#

Mapping[str, Any]?

feral wharf
#

still Expected TypedDict type argument for Unpack PylancereportGeneralTypeIssues

rare scarab
feral wharf
#

oh

rough sluiceBOT
#

src/pycpio/cpio/archive.py line 168

self.logger.debug(f"Added entry: {c_(entry_name, 'green')}")```
orchid orchid
#

why does mypy only complain about this line??

#

the loggify decorator adds self.logger

rare scarab
orchid orchid
#

pypi won't host it because some person made "zen-lib" 10 years ago and never added code

rare scarab
#

Wouldn't loggify be better as a Mixin class?

orchid orchid
#

maybe in this case, i have it as that

rare scarab
#

The loggify decorator doesn't have any types

orchid orchid
#

but this was made after i made the decorator

#

hmmm mypy is passing on zenlib

rare scarab
#

The current type system doesn't support this

#

A base class would work better

orchid orchid
#

like ClassLogger?

rare scarab
#
orchid orchid
#

the decorator is just a bit more powerful than a base class imo, idk i like the metaprogramming aspect

rough sluiceBOT
#

src/pycpio/header/cpioheader.py line 43

setattr(self, name, value)```
orchid orchid
#

it sets attributes based on the header type you feed it

#

ah yeah i don't like using the mixin because then i have to make sure to call super in the init

#

the decorator is just automagic :/

rare scarab
#

What about using __init_subclass__?

orchid orchid
#

just instead of __init__ in classlogger?

#

it has to get args/kwargs from init

rare scarab
#

Yeah. You can provide things like the logger name like class MyFoo(LoggerClass, logger_name="foo")

#

It'll be passed to kwargs

orchid orchid
#

the logger name is passed when the object is made

#

for lineage

#

like it reads the parent logger and stuff

rare scarab
#

Well you won't get type hints. Add it manually.

#

Calling super is good for you.

orchid orchid
#

i add one line and get full logging

rare scarab
#

<@&831776746206265384>

topaz dove
#

!compban 927187476563496981

rough sluiceBOT
#

:incoming_envelope: :ok_hand: applied ban to @proud valve until <t:1770339210:f> (4 days).

icy obsidian
#

Is it possible to have one of the branches in a function as a no return?
⁨```Python
def fun(a: int) -> str:
if a >= 0:
return "+"
else:
critical("ERROR") # This internally raises an exception
# raise # This fixes typing

viscid spire
icy obsidian
#

Yeah, this is the way I would like to do it. Though as far as I understand ⁨NoReturn⁩ is preferable here.
However currently I cannot modify ⁨critical⁩ and it is typed as ⁨None⁩. Guess no other option then.

viscid spire
#

Never⁩ means exactly the same as ⁨NoReturn⁩ for return type

#

but it also has other uses

#

I believe ⁨Never⁩ is the "modern" way of typing it

icy obsidian
#

Docs do state they are equivalent, but I do remmber seeing somewhere the recommendation to use ⁨NoReturn⁩ for return types.

viscid spire
#

NoReturn⁩ existed before it was changed to allow ⁨Never⁩ to be used

raw tulip
viscid spire
#

the problem is that ⁨T | Never == T

#

so typing ⁨critical⁩ as returning ⁨None⁩ even if it never does, is valid

#

because the default return of a function is None, and ⁨None | Never == None

#

at least, from a typechecker checking ⁨critical

#

but the problem arises when you try to use it, as you can see here

icy obsidian
#

Yea, unfortunately I do understand this issue, but cannot modify that one part right now (will be updated later).
So was just wondering if there is "local" option for such a case I might have missed.

viscid spire
#

yeah basically the problem is that you are allowing ⁨fun⁩ to return ⁨None⁩ according to the typechecker, because you do not return in the else

trim tangle
#

like assert False, "critical always raises"

#

If critical does have a bug and doesn't in fact raise an exception, then raise can do something unintentional. E.g. if you are calling fun while handling an exception

icy obsidian
#

Hmmm.... that makes sense actually. Thanks!

rare scarab
#

Could also raise critical()

#

Critical should raise before the raise is applied

icy obsidian
rare scarab
#

not if critical always raises

#

It's what I've done with flask's abort() function

icy obsidian
#

That was the point before - if critical does not raise by whatever reason.

rare scarab
#

Do you expect it not to?

#

You could make a wrapper function

icy obsidian
#

No, I expect it to always raise. And I do get what you mean.

rare scarab
#

⁨```py
def critital2(msg: str) -> Never:
critical(msg)
raise AssertionError("critical did not raise")

icy obsidian
#

Changing typing for that function will be done later.
The wrapper might also be the way, but it will marginally complicate finding usages later.

rare scarab
#

write a .pyi file for it?

icy obsidian
#

I will definitely have a look at it (as have never did it before). At least to learn how to work with it.

fiery canyon
#

Why is .pyi needed for libraries that run Python code thinkexplode

#

Just put the typing in the code directly

viscid spire
#

sometimes you have to lie about the typing, and it's a lot easier to do so in a typestub

rare scarab
#

.pyi is for

  1. Third party typings
  2. Non-python or compiled libraries
  3. Python 2 support
fiery canyon
#

Yep that's about it

plain dock
#

feel like pyi might also be handy when typing the code itself could make it far less readable

cinder bone
#

usually type aliases prevent that from being the case

fiery canyon
#

Realized this doesn't work for annotations with type args
I feel like there wouldn't be a way to support that for any class

#

For example I can use typing.get_args to know a given list should be a list of ints, then I actually can compare that in runtime
But if I'm given a non iterable custom class, the type args of it become meaningless

#

Might just ignore them totally, or keep support for builtins

fiery canyon
hasty phoenix
#

I'm getting error while trying this. Perhaps the first question: Is it not possible to inherit TypedDict like this:

from typing import TypedDict, Unpack

class KW(TypedDict, total=False):
    a: str

class KW2(KW):
    b: str

def fn(**kwargs: Unpack[KW2]):
    pass

fn()  # mypy: Missing named argument "b" for "fn"
fiery canyon
#

Try adding total=False to KW2

rare scarab
#

When you merge those two typeddicts, you get ```py
class KW2(TypedDict):
a: NotRequired[str]
b: Required[str]

knotty oxide
#

what's the difference between these? is either preferred?

from typing import Self

class Foo:
    def __enter__(self) -> Foo:
        ...
        return self

class Bar:
    def __enter__(self) -> Self:
        ...
        return self
terse sky
#

does the former not require quotes anymore?

restive rapids
terse sky
#

but yeah Self helps with things like subclasses

#

or generics

restive rapids
knotty oxide
#

Well, in 3.13 it does, or a from __future__ import annotations but I think in 3.14 this doesn't need quotes because types are not evaluated eagerly

terse sky
#

it's also arguably nicer to not repeat the specific type

knotty oxide
#

this was a reply to quicknir

terse sky
#

it's just another place to change Foo that's not particularly meaningful

#

Like if you were to rename Foo to Glug then obviously you wouldn't want enter to still return a Foo

#

So at the simplest level it's just a form of DRY

knotty oxide
#

that's a good point

terse sky
#

e.g. Rust has a very similar thing and you will basically always see people write -> Self

#

and not -> Foo

restive rapids
# terse sky So at the simplest level it's just a form of DRY

a form that is only applicable if you return self or do some shenanigans with type(self)(...) or something
otherwise, there is no guarantee that you're constructing an instance of the same class
in rust there's no such problem because there's no subtyping

terse sky
#

not quite sure what you mean

#

if you don't intend it for subtyping then it makes no difference anyway

restive rapids
#

you might "not intend it", but that is what the typechecker will be checking.

class Foo:
  def foo(self) -> Self:
    return Foo()

does not typecheck

terse sky
#

That's an instance function

#

you typically use this for static functions that create an instance of the class

#

and there it will type check

#

but yeah, in some cases you won't be able to use it

knotty oxide
#

you mean it's a valid typechecking result when you say "does not typecheck"? or it actually doesn't check the type?

terse sky
#

unfortunate there's no way to prevent inheritance I guess

restive rapids
knotty oxide
#

ok

#

which is good, imo

#

because you're not actually returning self

restive rapids
restive rapids
terse sky
#

oof it doesn't work even for static methods, nevermind

#

pretty lame

restive rapids
#

it would work for a classmethod, because cls: type[Self]
and im not sure if it even should work tbh because the constructor can be overriden

terse sky
#

yeah

#

idk it seems correct for it to work to me

trim tangle
terse sky
#
from typing import Self

class Foo:
    
    @classmethod
    def from_whatever(cls) -> Self:
        return cls()
restive rapids
terse sky
#

Sure, but this function is still correct

trim tangle
restive rapids
terse sky
#

Yes, I understand

restive rapids
#

so it is not correct under parametric polymorphism

knotty oxide
terse sky
#

it's correct under whatever typing system python actually has 🙂

rare scarab
#

So we need a marker to tell the type checker that overridden __init__ functions must be compatible.

restive rapids
terse sky
#

and yet this type checks - not going to argue with you further

trim tangle
terse sky
#

anyway, I was going to say I hate writing classmethod when I don't intend to use the class with inheritance

#

I'd much rather use staticmethod and keep everything concrete, exactly because of issues like that

knotty oxide
terse sky
#

so I guess I won't be using Self very much

#

I remember once writing some code that actually had inheritance and was using a sort of classmethod function as the main method of creating the class

#

I really didn't like it - just kind of confusing and complex relative to what's actually being accomplished

knotty oxide
#

I just reread the discussion and I think I'm clear on what everyone is saying. Thanks for the in-depth replies!

novel notch
trim tangle
novel notch
#

Aha! 👍

feral wharf
#

is it safe to eval() annotations?

#

if no, how are we supposed to handle it?

#

just literally check the string for the type we want to force?

rare scarab
#

Use typing.get_type_hints()

#

That will eval it for you

#

And handle py3.14 annotations

viscid spire
feral wharf
#

right

viscid spire
feral wharf
#

basically telling me to still eval if it's a string

#

I need to support >= 3.8 here

#

i'm using inspect.signature currently as I need the default values too

viscid spire
#

my condolences. Why support EOL versions?

feral wharf
#

wait my bad

#

does that help with anything?

viscid spire
#

"Accessing The Annotations Dict Of An Object In Python 3.10 And Newer" I would say so, if you were thinking supporting 3.8

feral wharf
#

but i'm still getting stringified annotations when using from __future__ import annotations

#

anything for that?

rare scarab
#

That's what that import does

#

!d typing.get_type_hints will unstringify them

rough sluiceBOT
#

typing.get_type_hints(obj, globalns=None, localns=None, include_extras=False)```
Return a dictionary containing type hints for a function, method, module or class object.

This is often the same as `obj.__annotations__`, but this function makes the following changes to the annotations dictionary:
feral wharf
#

but that uses eval?

#

and I would need to use that + inspect.signature

oblique urchin
#

I generally recommend against using get_type_hints() in favor of inspect.get_annotations (or annotationlib.get_annotations in 3.14+). get_type_hints() does a bunch of things most of which are questionable; the most generally useful thing is that for a class it also returns annotations defined in base classes.

oblique urchin
# feral wharf is it safe to eval() annotations?

Depends on what you mean by "safe". User code could have def f() -> "__import__('os').system('rm -rf /')": ... and if you eval() that you're in trouble. But if you imported that code, they could already have done whatever they wanted outside of an annotation.

#

Even well-behaved annotations could raise exceptions (most likely NameError) if evaled, so if you use eval(), you need to guard for that.

feral wharf
#

It's a discord api wrapper in this case that needs to map the annotation to an enum

#

What would you recommend here?

feral wharf
fiery canyon
#

Couldn't you compare the wanted type to the type of the passed argument

feral wharf
#

I could but it'll be kinda hard to get it right for all the 20+ types since it's a string

#

A string of exactly what the user has written
Which could include the module or maybe it's a type alias? Or something

fiery canyon
#

If you can get the annotation as a string then you could compare it to names of the enums (without having to get the actual type as an "object")
But it could get a bit complicated for some cases where you'd probably need to use regex, or get_type_hints etc.

feral wharf
fiery canyon
#

Does get_annotations use straight up eval?

regal summit
#

How can I allow something like this

class Foo[T]:
    value: T

    def is_int_foo(self) -> TypeGuard['Foo[int]']:
        return isinstance(self.value, int)```
#

Pylance giving me User-defined type guard functions and methods must have at least one input parameter

regal summit
feral wharf
#

Don't you want TypeIs there

regal summit
#

replacing TypeGuard with TypeIs giving me the exact same error

feral wharf
#

Ah I guess that makes sense

#

What would there be to guard on

regal summit
#

oh py f = Foo() if isinstance(f.value, int): reveal_type(f) its still unknown at this point

#

thats unfortunate, i feel like this would be a nice thing to have

trim tangle
regal summit
#

yeah i guess, so how would I do something like this?

#

in my context I have T be pretty general and in all cases its either That general or Very specific so I would liek the API so be .is_specific()

trim tangle
#

If Foo is covariant and you know for sure that f.value being an int means that f is a Foo[int] (e.g.: Foo is immutable), you could make a free-standing TypeGuard/TypeIs function I think

regal summit
#

true but that would make the api a lot worse imo

trim tangle
#

yes

fiery canyon
#

I wish you could narrow self

fiery canyon
#

!e

from annotationlib import get_annotations
from enum import Enum
import ast

class E1(Enum):
    THING = ...

enums = [E1]

def test[**P, R](func: Callable[P, R], /) -> Callable[P, R]:
    anns = get_annotations(func, eval_str=False)
    parsed: dict[str, ast.Expression | None] = {}

    for name, ann in anns.items():
        if isinstance(ann, str):
            try:
                parsed[name] = ast.parse(ann, mode="eval") # "eval" mode does not actually eval()
            except SyntaxError:
                parsed[name] = None
        else:
            parsed[name] = ann

    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        for name, tree in parsed.items():
            if tree is None:
                continue

            node = tree.body
            
            # Case 1: "E1"
            if isinstance(node, ast.Name):
                match = next((e for e in enums if node.id == e.__name__), None)
                if match is not None:
                    print(f'"{name}" matches with Enum: "{match.__name__}"')

            # Case 2: "E1 | None"
            elif isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
                left_id = getattr(node.left, "id", None)
                right_id = getattr(node.right, "id", None)
                match = next((e for e in enums if e.__name__ in (left_id, right_id)), None)
                if match is not None:
                    print(f'"{name}" matches with Enum: "{match.__name__}" ({match.__name__} | None)')

        return func(*args, **kwargs)
    
    return wrapper

@test
def foo(x: "E1", y: "E1 | None"):
    pass

foo(E1.THING, None)
rough sluiceBOT
fiery canyon
#

Seems to work with no unwanted evaluation happening

#

@ Soheab maybe that can work Thonk

fiery canyon
#

You can also support Optional[T]

And for type aliases, you could do func.__globals__.get(node.id).__value__:

# Case 1:
if isinstance(node, ast.Name):
    resolved = func.__globals__.get(node.id)

    if isinstance(resolved, TypeAliasType):
        resolved = resolved.__value__

    name_str = resolved.__name__ if isinstance(resolved, type) else node.id

    match = next((e for e in enums if name_str == e.__name__), None)
    if match is not None:
        print(f'"{name}" matches with Enum: "{match.__name__}"')```
feral wharf
#

Idk if we want to use ast lol

#

Looks overcomplicated

fiery canyon
#

Alright (looks fine to me what can I say)
You'd have to use a formatter somehow unless you want to drop this whole idea or use "eval" in some way

soft matrix
#

Is it the same or you asked for my review on?

feral wharf
#

Did you reply to the wrong message lol?
-# this sounds a bit harsh in my head. I don't mean it that way...

#

I've reverted to eval for now as that works fine

But a proper solution would still be neat

#

I feel like eval is fine for my use case as it's not exactly user input

trim tangle
#

or annotationlib.get_annotations if you're on 3.14+

feral wharf
#

But that uses eval?

#

Latter if explicitly allowed else it's a string

trim tangle
#

yes, it uses eval internally

feral wharf
#

So it's fine to use eval for annotations

fiery canyon
#

Well just be aware that running get_annotations on this with eval_str=True will run that code

#

But atp it's the user's fault for putting something like that there
Like someone can only use that against themselves 😭

#

Test

#

<@&831776746206265384> uh yeah

trim tangle
#

what's up?

#

!cleanban 1470090413611290838 selfbot

dawn crescent
#

!ban 1470090413611290838 selfbotting

rough sluiceBOT
#

:x: User is already permanently banned (#108378).

#

failmail :ok_hand: applied ban to @distant lotus permanently.

fiery canyon
#

Self botting apparently

#

Yep ty 🙏🏻

trim tangle
#

ty mentioned

pliant vapor
#

is there currently a way to type hint that a dictionary is the kwargs of a function without manually creating the TypedDict?

#

i wanna do something like

kwargs: ... = {}
# add stuff to kwargs
foo(**kwargs)
feral wharf
cloud locust
#

I've currently got a function with the following signature def read_excel(filepath: Path | str, Table_Format: type) -> Table_Format, where the actual value passed for Table_Format is only ever one of two namedtuple types, and the return value is an instance of that type. Is there any way I can be more specific than type for the type hint of that parameter? Maybe with Generics?

#

If it could accept any (and only) namedtuple types, that would be ideal, but I'll happily settle for hardcoding the two types it could be.

cloud locust
#

I think I figured it out on my own: def read_excel[Table_Format: (Format_A, Format_B)](filepath: Path | str, format: type[Table_Format]) -> Table_Format:

viscid spire
fiery canyon
#

-# And parameters should be snake_case

viscid spire
#

Everything should be snake_case except types which should be PascalCase.
Constants are a variant of snake_case which is SCREAMING_SNAKE_CASE

#

tho if the codebase is mature and does not use this convention, then the convention of the codebase should be followed

stark crow
viscid spire
knotty oxide
#

you can also make a multiline code block with basic python syntax highlighting by starting with ```py and ending with ```

knotty oxide
#

I have a function that returns a set of Foo's and I have type hinted it as -> set["Foo"]
However, sometimes the return value might be the empty set. Do I need to change my type hint? (I'm not using a type checker yet, but at least my IDE is not complaining but I've learned not to trust it too much lately)

knotty oxide
#

thx! I also just found out about mypy playground and basedpyright playground, so in the future i'll check there first before bothering the ppl here. Until I find the courage to integrate mypy in my local environment/workflow, that is 😅

novel notch
#

There is also ty, which has a playground here: https://play.ty.dev/
It's relatively new. I don't know how it compares to mypy.

An in-browser playground for ty, an extremely fast Python type-checker written in Rust.

rare scarab
#

I like how Ty uses webasm and runs type checking locally. Even Pyright which is written in JavaScript uses web requests

trim tangle
#

maybe it was easier for microsoft to just make it remote like that, but it is very laggy as a result for me

rare scarab
#

Can the mypy playground be converted to webasm too?

trim tangle
#

yeah, with pyodide or something like that

cinder bone
#

I have high hopes for it but it's not mature enough for me to integrate it in my workflow

trim tangle
fiery canyon
#

Only reason I use Pyright specifically is because it comes with Pylance, and I'm fine with it

fiery canyon
rare scarab
fiery canyon
#

Yes to the first question?

rare scarab
#

Yes

fiery canyon
#

So I can't use it without internet? Damn never thought of that

#

(It doesn't really happen obviously)

rare scarab
#

Just the playground. VSCode is fine offline

fiery canyon
fiery canyon
rare scarab
#

You replied to my message talking about the playground

fiery canyon
#

Sorry I thought it could be understood in my message 🫡

#

So then yeah it's local on Vscode

rare scarab
#

So the reply was unrelated

fiery canyon
#

I mean I wasn't sure whether your statement was talking about the playground or not, my bad

novel notch
# fiery canyon So then yeah it's local on Vscode

Does staticmethod insinuate that the method is final and shouldn't be overridden? Or does it imply that any subclass overriding the method should also have it as a staticmethod in that case? I ended up with some code like this to avoid lint warnings. Are there any typing notations to help me here?

#

It doesn't have to subclassed. The issue was solved with:

class Foo:
    def base_func(self):
        del self  # subclasses might override and use self
        print("potato")

class Bar(Foo):
    def base_func(self):
        print("banana", self)
fiery canyon
novel notch
#

My question is, is the del self a good way to mark self as unused in the base class's method?

fiery canyon
#

Well your question made it seem like you don't 😅

novel notch
#

Linters would otherwise suggest to mark Foo.base_func as staticmethod.

novel notch
fiery canyon
#

Ah lemme see

fiery canyon
#

You want a method that is a staticmethod by default, but can be an instance method when overriden?

novel notch
#

That's how the (really old) code already was (staticmethod in base class and using self in subclass)

I just wanted to quiet the warnings.

I'm not sure/can't remember why self was used in only the subclass.

trim tangle
trim tangle
novel notch
#

Since it uses self today, I guess it makes sense 😅 yeah, that's what I think as well. The upside with del self is that would silence any linter.

#

The linter this time was ruff

brazen jolt
# novel notch Since it uses self today, I guess it makes sense 😅 yeah, that's what I think as...

I assume you mean this rule?
https://docs.astral.sh/ruff/rules/no-self-use/

I'd do a noqa instead of a weird looking del. You're explicitly saying that you're right and the rule is wrong in this case, don't try to work around that to make the linter think it's right, declare it's not.

I don't think the rule is entirely misguided, as many people, especially beginners, don't know what is a staticmethod and do the incorrect thing, but yes, for your purposes, it's wrong here.

trim tangle
# brazen jolt I assume you mean this rule? https://docs.astral.sh/ruff/rules/no-self-use/ I'd...

don't think the rule is entirely misguided
consider something like this: ```py
class Fruit(Protocol):
def is_round(self) -> bool: ...

class Banana:
...
def is_round(self) -> bool:
return False # TODO: investigate if there are round banana varieties

class Pepper:
...
def is_round(self):
return self.variety in {"cascabel", "cherry_bomb"}

there are multiple reasons an instance method might ignore `self`:
- It implements some protocol (ruff's rule does ignore `@typing.override`, but it would require explicitly inheriting from a protocol) in a trivial way
- it is not yet implemented fully, or it is made an instance method to future proof (making a static method into an instance method will be a breaking change in a library)
- the method used to look at `self` in a previous version of a library, but now it doesn't (but the meaning of a method hasn't changed, only some library details: e.g. the buffer size used to vary but now it's a constant)
In general, it's about semantics: an instance method tells you something about an instance or does something to the instance, a class/static method represents something about an entire class but not on an instance (I think being able to do `("69").from_bytes(b"0")` is not good, but we can't remove this obviously).
So changing `Banana.is_round` to a static method (even in abscence of `Fruit`) would be misleading and generally a bad change in my opinion.
#

As an example: int.is_integer always returns True (for compatibility with float), and it wouldn't make sense to make it a class or static method.

#

TL;DR: i think the lint rule is teaching the wrong lesson about when you should use instance methods vs class/static methods

hard forge
#

There's no great tactic for telling mypy/typing systems "this mixin is meant to only be used with subclasses of a certain class" right? Protocols require me to define a whole thing, but adding the class to the mixin leads to awkward MRO conflicts in existing code

tawdry heath
#

Im not sure why you would want to limit your mixin to specific classez. Like ever.

But I suppose you could try to use issubclass() or other type checks in __init_subclass__ of your mixin. But I'm not sure if that alone would work.

Otherwise you could try to use @overload in combination with __new__ and specify a specific signature, with helper classes either containing or not containing that mixin's provided attributes

#

In any case, your request will likely lead to a solution (if any at all) that feels hacky

#

You may also be able to inherit both Protocol and the mixin together, which could limit the amount of redefining entire Protocol classes.
-# im just making a guess on this option though. I can't test it myself currently

hard forge
#

For stuff like Django model or views a mixin as a way to provide a bit of extra functionality is nice, because you might have mixins A, B, C, D, E but you'll only want B and D in some cases, and if you mashed them all together into one base class suddenly the entire thing is quite messy with config bits etc

eternal trellis
hard forge
#

But IMO most mixins are written with the idea that they are applied to some base class I think

eternal trellis
hard forge
#

@eternal trellis so unfortunately that doesn't work on mypy in my experience

class MyMixin:
   @classmethod
   def some_clsmethod[M: models.Model](cls: type[M], ...):
      ...

^ the above ends up with an error like "cls needs to be a subtype of MyMixin" which makes sense if you think about it

#

like M needs to be at least MyMixin right? since it's a class method on MyMixin . But we don't have intersection types so I can't say [M: models.Model & MyMixin].... wonder if I make something subclass models.Modeland Protocol....

eternal trellis
hard forge
#

oh if that just works then I'm actually fine. Will try that

prisma talon
#
NestedComparison: TypeAlias = dict[str, "NestedComparison" | DefaultComparison]
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unsupported operand type(s) for |: 'str' and 'type'
#

I guess useing Union works good enough, just adds another import from typing

viscid spire
#

or use the type statement added in python 3.12

#
type NestedComparison = dict[str, NestedComparison | DefaultComparison]
prisma talon
#

thanks, i need it to be 3.10 compatible

glossy moat
#

Question: I think I have found a bug with stubgen in mypy and I'm not sure if this belongs here or in #c-extensions , but I want to run it by at least one other person before I report it as an issue in github:

OTHER_OP_TYPES = Union[complex, int, float]
_OTHER_OP_TYPES = (complex, int, float)

I find that stubgen handles _OTHER_OP_TYPES as a tuple fine but it completely misses OTHER_OP_TYPES? Worst of all, if I make an __all__ that has both of these, then the created stub looks like this:

from typing import OTHER_OP_TYPES as OTHER_OP_TYPES

Which is obviously just crazy wrong... I'm using --include-private and --inspect-mode. Does this seem like a bug worth reporting or am I just doing something weird?

#

Note: instead of setting __all__, it does the same thing if I set a type annotation of TypeAlias on OTHER_OP_TYPES

knotty oxide
#

This might be totally off base but does mypy do its own sort of name mangling for types with leading single underscore or something?

rare scarab
knotty oxide
#

I was replying to @glossy moat

glossy moat
#

Oh sorry, I thought you were referring to something else. Just to be clear, the one with the underscore is just fine. Its the type alias without the underscore that gets messed up with the typing import

rare scarab
#

What if you annotated it with TypeAlias?

glossy moat
knotty oxide
glossy moat
# knotty oxide It looks to me like it's possible that the one with the underscore is breaking t...

Yeah, I honestly don't think thats it. In that file there is another variable which is also a TypeAlias called OP_TYPES and there is no _OP_TYPES and its also happening with that one.

Here is a minimum file to reproduce the issue:

from typing import Union

__all__ = ['OP_TYPES']

OP_TYPES = Union[int, float]

Running that with stubgen --inspect-mode I get:

from typing import OP_TYPES as OP_TYPES

__all__ = ['OP_TYPES']

# Names in __all__ with no definition:
#   OP_TYPES