#type-hinting

1 messages · Page 30 of 1

regal summit
#

ah well if theres no other way, though wouldnt

class Foo:
    @classmethod
    def as_arg(cls, name: str) -> type[int]:
        return int``` do the same then
#

no huh

#

why would I use __get__, how would I give it the name

rare scarab
#

!d object.set_name

rough sluiceBOT
#

object.__set_name__(self, owner, name)```
Automatically called at the time the owning class *owner* is created. The object has been assigned to *name* in that class:

```py
class A:
    x = C()  # Automatically calls: x.__set_name__(A, 'x')
```  If the class variable is assigned after the class is created, [`__set_name__()`](https://docs.python.org/3/reference/datamodel.html#object.__set_name__) will not be called automatically. If needed, [`__set_name__()`](https://docs.python.org/3/reference/datamodel.html#object.__set_name__) can be called directly...
regal summit
#

I dont think I understand how I would use this inside of the foo example

#

also why would ruff complain here, does it just not know __class_getitem__, or is it because its inside of a function param

paper salmon
#

does the stat name have to be encoded into the type like this? or rather, would it not be acceptable to have the function take PlayerStat without a particular name? or do you intend to pass the appropriate stat at runtime based on the annotation?

regal summit
#

the name can be whatever the user wants it to be, though it being a PlayerStat, and not a GlobalStat is importatnt

#
class Foo:
    def __class_getitem__(cls, name: str) -> type[int]:
        return int

x = Foo['asdf']
reveal_type(x)``` Pylance thinks its of type `Foo` here, why
rare scarab
#

To be fair, it's trying to resolve asdf to a type

viscid spire
#

because Pylance only knows about "proper" generics

#
class Foo[T]: ...


x = Foo[str]
reveal_type(x)
Type of "x" is "type[Foo[str]]"
#

if you defined this via class getitem, it wouldn't understand it

regal summit
viscid spire
#

it's an implementation detail

rare scarab
#

For types

#

not strings

#

Can you do something with this? ```py
from typing import NewType

Foo = NewType("Foo", int)

def use_foo(foo: Foo):
pass

use_foo(Foo(0))

Foo is int at runtime.
regal summit
#

ah well that kinda sucks

#

dont think so, I need to actually be able to create Foo instances, and also annotate with it whilst giving it a name

rare scarab
#

What's the point in annotating it as int?

regal summit
#

actually it is fine if it thinks it is the same class

#

just picked int as just an arbitrary class

#

brings me back to this though ```py
from abc import ABC

class Stat(ABC):
def class_getitem(cls, name: str) -> ...:
return something_based_on_the_name

class PlayerStat(Stat):
...

def foo(p: PlayerStat['whatever']) -> None:
pass

print(foo.annotations)

{'p': <class 'int'>, 'return': None}```

#

pylance is okay with it, ruff is not

rare scarab
#

What error is ruff giving?

#

use reveal_type(p)

regal summit
#

does ruff use reveal_type also, just Pylance giving me anything with that

#

im in vscode havent really looked into much of installation for ruff just clicked some buttons like a month ago

rare scarab
#

ruff is dumb when it comes to type hints. That being said, this is an invalid type hint.

#

Only Literal is allowed to have strings

regal summit
#

well having it be PlayerStat[Literal['whatever']] kinda defeats the purpose of not making it an attribute

#

this is kind of annoying, perhaps an attribute might be best

rare scarab
#

Consider using Annotated instead.

regal summit
#

well it would still be a very large annotation

rare scarab
#

You can reuse annotated types

regal summit
#

and this is what users would have to enter, some who wont know anything about annotated nor literal

rare scarab
#

Are you the one calling the function, or the developer?

regal summit
#

the developer is creating the foo function

rare scarab
#

yes, but are you the one consuming it?

regal summit
#

so only this should be the least struggles as possible

rare scarab
#

Use Annotated.

regal summit
#

the library is the only one calling it yes

rare scarab
#

Example: ```py
def foo(p: Annotated[int, PlayerStat(name="whatever")]) -> None:
pass

#

or ```py
Whatever = Annotated[int, PlayerStat(name="whatever")]

def foo(p: Whatever) -> None:
pass

paper salmon
#

have you considered using the parameter name to define the stat instead of putting it in the type?

rare scarab
#

^ this is what fastapi does for query arguments

regal summit
#

eh this all seems a lot of trouble for a couple of characters less to type

rare scarab
#

So this would have an alias field

#

like pydantic

paper salmon
rare scarab
#

**kwargs with Unpack[TypedDict] could be another option.

regal summit
#

but eh I feel like it would make it so complicated

#

I think I will just make it an attribute and be done with it, thanks so much for the help

paper salmon
sand merlin
#

Dose you know how to annotate the decorator so that input types are shown except *args and **kwargs.

def hi(op, *nope, **kwargs):
    ... # decorator implementation

@hi
def function(hansne, iejeb, okeenn):
    ... # function implementation

It shows the function input as args and kwargs.

rare scarab
#
class EmptyDict(TypedDict):
  pass

def foo(arg: str, arg2: int, *args: Unpack[tuple[()]], **kwargs: Unpack[EmptyDict]):
  ...
regal summit
rare scarab
#

That can also be used to make aliases

regal summit
#

mmm anyway I gotta go for a bit ill play around with it some more later and see where it gets me, thanks again

reef vortex
#

I can't do that cause , before parsing data , it's anonymous

echo knot
#

What are the differences between using these two type aliases for numpy?

Vector = np.ndarray[Tuple[int], np.dtype[np.float64]]
Vector = NDArray[np.float64]

Like, I am aware that the first one is also giving you more information about its shape. But I did a few experiments with Mypy and I did not manage to trigger an error by accessing a dimension that does not exist. Therefore, the only difference I see is about documentation

Is there a way to trigger Mypy errors in these cases? (without any other library than numpy itself).

Edit: I just saw that NDArray is defined as a type alias of the first case for any size. Then what's the purpose of type hinting the size?

tranquil ledge
icy obsidian
#

Ok, noted

#
from typing import Any, Callable, Concatenate, Generic, ParamSpec, Tuple, TypeVar

T = TypeVar('T')
P = ParamSpec('P')

def deco(arg: T)-> Callable[[Callable[P, T]], Callable[P, T]]:
    def deco_inner(func:Callable[P, T]) -> Callable[P, T]:
        func.TAG = arg
        return func

    return deco_inner

def deco2(arg: T)-> Callable[[Callable[Concatenate[Any, T, P], T]], Callable[Concatenate[Any, T, P], T]]:
    def deco_inner(func:Callable[Concatenate[Any, T, P], T]) -> Callable[Concatenate[Any, T, P], T]:
        func.TAG = arg
        return func                
    
    return deco_inner

class GenBase(Generic[T]):
    def __init__(self, a: T) -> None:
        self.a: T = a

class GenTest(GenBase[int]):
    def __init__(self, a: int, b: int) -> None:
        super().__init__(a)
        
        self.b: int = b
        
    @deco(1)
    def func_a(self, b: int) -> int:
        return self.b + b
    
    @deco(complex(1, 2))
    def func_b(self, b: int) -> int:
        return self.b + b
    
    @deco2(1)
    def func_c(self, b: int) -> int:
        return self.b + b
    
    @deco2(complex(1, 2))
    def func_d(self, b: int) -> int:
        return self.b + b
    
    
g = GenTest(5, 6)
#

func_b passes type check, while func_d doen't (as expected)

undone saffron
#

The decorator doesn't seem to be doing anything here other than attachinga dynamic value to a method (Which isn't supported by type checking to begin with, and you may want to look into implementing a descriptor instead), what's this intended to be doing/ what problems is this supposed to be solving?

icy obsidian
#

That's just an example - it doesn't matter what's inside.

#

My question is why deco accepts any Number insted of only int, while deco2 works as intended - only int.

undone saffron
#

It's due to variance, the numeric tower, and the fact that only one of your decorators is binding both the input and return type to the same type of the object given to the decorator

#

as for it being "just an example", people come in with all kinds of things that people have to try and interpret the purpose of to actually determine if something should work or if there's a better way.

#

having an example that's accurate to what you actually intend to do and explains the purpose can help people actually be able to answer with better or more specific details.

icy obsidian
#

Sorry, should have removed that part, yea.

#

But even if it's binding only the return type, which is specified in all cases as "int" - why does it accept other types? Or it must bind the input only?

echo knot
#

Is there a PEP or a place where I can see the progress of covariant/contravariant TypeVarTuple? I saw in the TypeVarTuple PEP that this feature is still not supported until the community experiments a bit more with TypeVarTuple 🙂

alpine prism
#

im attempting to type the following API response; the full json response is much longer (and contains an dynamic number of inner dicts).

{
  "BTCLN_BTC": {
    "network_fee": {
      "f": "0.00054191",
      "m": "0.00046733",
      "s": "0.00000000"
    },
    "rate": "0.99502488",
    "rate_mode": "dynamic",
    "reserve": 131.82293557,
    "svc_fee": "0.500000000000000000"
  },
  "BTCLN_DAI": {
    "network_fee": {
      "f": 5.58609124842418,
      "m": 2.44248670112417
    },
    "rate": "64267.239050000003771856",
    "rate_mode": "dynamic",
    "reserve": "81781.384251849303836934",
    "svc_fee": "0.500000000000000000"
  },
}

this is my current attempt to type it, but i dont think this is correct (network_fee is not present in every "pair")

from typing import Literal, Mapping, Optional, TypedDict, Union

# alias: eXch types numbers as both strings and floats
ExchNumbers = Union[str, float]


class ExchNetworkFeeAmounts(TypedDict):
    f: ExchNumbers
    m: ExchNumbers
    s: Optional[ExchNumbers]


class ExchApiPair(TypedDict):
    network_fee: Optional[ExchNetworkFeeAmounts]
    rate: str
    rate_mode: Literal["dynamic", "flat"]
    reserve: ExchNumbers
    svc_fee: str


class ExchApiResponse(TypedDict):
    Mapping[str, ExchApiPair]

what im most hung up on is ExchApiResponse, where i dont believe Mapping[str, ExchApiPair] can be the proper way to type such a dict with an dynamic/unknown number of inner dict definitions

trim tangle
alpine prism
loud rose
alpine prism
loud rose
#

i’ll pretend i understand prettythumbsup

regal summit
#

Optional[T] meaning T or None, NotRequired[T] meaning T or absent

#

perhaps this also is the case for your network_fee but I cant tell from the example you gave

alpine prism
trim tangle
#

Ah yes, having both NotRequired and Optional with completely unrelated meanings in a library

alpine prism
#

looks like NotRequired wasnt introduced until 3.11 and im deving in 3.10

trim tangle
rough sluiceBOT
alpine prism
alpine prism
# alpine prism im attempting to type the following API response; the full json response is much...

for the sake of context and completion, this is the proper type hint (after upgrading from 3.10->3.11)

from typing import Literal, Mapping, NotRequired, TypedDict, Union

# alias: eXch types numbers as both strings and floats
ExchNumbers = Union[str, float]


class ExchNetworkFeeAmounts(TypedDict):
    f: ExchNumbers
    m: ExchNumbers
    s: NotRequired[ExchNumbers]


class ExchApiPair(TypedDict):
    network_fee: NotRequired[ExchNetworkFeeAmounts]
    rate: str
    rate_mode: Literal["dynamic", "flat"]
    reserve: ExchNumbers
    svc_fee: str


ExchApiResponse = Mapping[str, ExchApiPair]
rustic gull
#

Hello ladies and gentlemen

icy obsidian
#

I still cant understand why the next code allows a different type for a decorator.
Am I misunderstanding how it should work?

T = TypeVar('T')
P = ParamSpec('P')

def deco(arg: T)-> Callable[[Callable[P, T]], Callable[P, T]]:
    def deco_inner(func:Callable[P, T]) -> Callable[P, T]:
        return func
    return deco_inner

def deco2(arg: T)-> Callable[[Callable[Concatenate[Any, T, P], T]], Callable[Concatenate[Any, T, P], T]]:
    def deco_inner(func:Callable[Concatenate[Any, T, P], T]) -> Callable[Concatenate[Any, T, P], T]:
        return func
    return deco_inner

class DecoTest:
    @deco(1)
    def func_a(self, a: int) -> int:
        return a
    
    @deco(complex(1, 2))  # <<<<<<<<<<<<< This does not produce a type error (int, float, complex work while other types like str do not)
    def func_b(self, a: int) -> int:
        return a
    
    @deco2(1)
    def func_c(self, a: int) -> int:
        return a
    
    @deco2(complex(1, 2))  # This correctly says a wrong type
    def func_d(self, a: int) -> int:
        return a
tranquil ledge
#

deco2 is binding T in args to T in the result.

#

deco1 doesn't keep track of the correspondence at all between that argument and the result.

icy obsidian
#

Or is there a requirement for the return type to be in arguments as well?

tranquil ledge
#

The return types for all your methods are explicitly int; therefore the arg type for the outer layer of deco1 and the return type of the final callback will match up.

#

Actually . . .

#

I take that back.

icy obsidian
#

I'm confused 🙂

tranquil ledge
#

Right, this is where the aforementioned numeric tower comes into play, I think.

#

To the typechecker, int is a . . . subtype of complex, from the looks of it? Because of that, at the second layer of your decorator, the function you've passed with a type of (Any, int) -> int is incompatible with the expected function of (FooTest, complex, **P) -> complex). Your function isn't a subtype of the expected function, because your argument type is too narrow. And that's where the variance bit comes into play, because the "too narrow" condition is a matter of function arguments being contravariant.

#

I could be wrong, but I think that's the throughline for why the type checker complains about deco2.

#

You can only get narrower on the result type and wider on the argument types when subtyping a callable.

#

Something like that.

icy obsidian
tranquil ledge
#

Oh, wrong order

#

My bad.

icy obsidian
#

And, yes, it is incompatible - as expected.

tranquil ledge
#

Should've been (Any, int) -> int and (FooTest, complex, **P) -> complex).

#

And yeah, that explains why your first deco didn't catch anything: complex isn't in the arguments of the signature of the function you're passing in. That's covered up by P.

#

And return types are allowed to be wider, so complex there is acceptable.

icy obsidian
#
def test() -> int:
    return complex(1, 2) # <<<<< Error here
def test2() -> complex:
    return int(1)
#

Oh wait

tranquil ledge
#

int is a subtype of complex; thus the contract for what you've labelled as the return type for test can't be met by returning a supertype.

#

int might have methods that complex doesn't.

icy obsidian
#

Ye, I think I got it now

tranquil ledge
#

It's a bit of a mindbender, but once you get it, you get it.

icy obsidian
#

As the first decorator goes

def deco(arg: complex)-> Callable[[Callable[P, complex]], Callable[P, complex]]:
    def deco_inner(func:Callable[P, complex]) -> Callable[P, complex]:
        return func

    return deco_inner

And as we provide it with a int, which is always a valid complex - it is accepted

tranquil ledge
#

Always valid for return type, yeah.

#

Return types are covariant.

icy obsidian
#

This breaks me a bit, but (at least I do) I apply the same logic to the second one:

def deco2(arg: T)-> Callable[[Callable[Concatenate[Any, T, P], complex]], Callable[Concatenate[Any, T, P], complex]]:
    def deco_inner(func:Callable[Concatenate[Any, T, P], complex]) -> Callable[Concatenate[Any, T, P], complex]:
        return func                
    
    return deco_inner
#

Return types are accepted, yea.

#

But why cant a less generic type go into the argument?

#

As this is valid:

def func_complex(a: complex) -> complex:
    return a
func_complex(int(1))
#

And I assumed the same happens in deco2

tranquil ledge
#

Callables are a bit different.

icy obsidian
#

...

#

I got it the other way around...

#

The last code that I shared - this is not how it goes

#

The decorator says "i am waiting for a function with a complex argument" and I give it a funciton with an "int" arguement, which is indeed incorrect - funcion wants only ints, but will be given a broader type.

#

Thanks for the help!~

tranquil ledge
#

No problem.

#

It's definitely confusing.

outer lantern
#

man pydantic is great

sullen night
#

Hi everyone. I'm trying to get a better handle on how class composition using Mixin classes is used in Python.

I once wrote in a language that would allow for an interface requirement to be specified onto the composing class from within the mixin class.

E.g. say I would have a mixin class Printable which had def print(str) -> ..., it would expect the composing class to have a public method of, say, get_content() -> str.

Is such functionality available using Python's typing system?

tranquil ledge
# sullen night Hi everyone. I'm trying to get a better handle on how class composition using Mi...

Sure. Python has an ABC class that you can inherit from and an abstractmethod label for methods that you expect subclasses to implement.
e.g.

from abc import ABC, abstractmethod

class Printable(ABC):
    def print(obj: str) -> None:
        ... # some logic.
    
    @abstractmethod
    def get_content(self) -> str:
        raise NotImplementedError

class Base:
    ...

class Concrete(Base, Printable):
    ...

pr = Concrete() # Type checker should complain that get_content isn't implemented.
#

Pyright will show this, for example

sullen night
#

@tranquil ledge and hence this would be ameliorated by defining get_content on Concrete, do I have that right?

tranquil ledge
#

Yep.

sullen night
#

Splendid! Thank you very much.

tranquil ledge
#

No problem.

sullen night
#

As a followup: is there a particular reason you use the empty Base class in your example?

tranquil ledge
spice locust
#

huh, i just found out this works

class MyBaseType(BaseModel):
    pass

class MySubType(MyBaseType):
    pass

T = TypeVar("T", bound=MyBaseType)

class MyGenericClass(Generic[T]):
    foo: T

class MySubClass(MyGenericClass[MySubType]):
    pass


MyGenericClass().foo  # type inferred as Unknown
MySubClass().foo  # type inferred as MySubType
#

the odd thing is why isnt MyGenericClass().foo inferred as of type MyBaseType?

#

!pep 696

rough sluiceBOT
spice locust
#

this is why

oblique urchin
#

bounds are not automatically used as defaults

spice locust
#

what is the difference between these two statements?

connections: ConnectionCache = ConnectionCache["Location", "Location"]()
connections: ConnectionCache["Location", "Location"] = ConnectionCache()
#

the second one yields the type checking error Class definition for "ConnectionCache" depends on itself

#

wait no, they both do

#

pyright just seems to bug and fail to detect it 70% of the time

spice locust
#

is there a way to forward reference a typevar in a class definition?

#

to be honest my code has become so convoluted i'm considering moving to a different language....

# OriginT and DestinationT are not defined here
class ConnectionCache(dict, Generic[OriginT, DestinationT]):
    def to(self, destination: Location) -> Connection[OriginT, DestinationT]:
        return self[destination.id]


class Location(BaseModel, ABC):
    id: LocationId
    name: str
    connections: ConnectionCache[Location, Location] = ConnectionCache()
    land_group: str | None = None

    @property
    @abstractmethod
    def map_parameter(self) -> str: ...


OriginT = TypeVar("OriginT", bound=Location)
DestinationT = TypeVar("DestinationT", bound=Location)

oblique urchin
#

put the TypeVar before the classes and put the bound in quotes

spice locust
#

ah that worked, i assumed it didnt at first because vscode/pyright failed to recognize and highlight it

grizzled bone
#

how do i get letters and numbers in 12 digits with every possible combination and have it write it on a website

#

this is what i have

#

import itertools
import string
import pyautogui
def generate_combinations(length, characters=string.ascii_lowercase + string.digits):
"""Generate combinations of printable characters of a specified length."""
if length <= 0:
raise ValueError("Length must be a positive integer.")
printable_characters = ''.join(c for c in characters if c in string.printable)
combinations = itertools.product(printable_characters, repeat=length)
for combo in combinations:
yield ''.join(combo)
if name == "main":
combination_length = 12 # No leading zeros
try:
for combo in generate_combinations(combination_length):
pyautogui.typewrite(combo)
pyautogui.press('enter')
except ValueError as e:
print(e)

trim tangle
balmy oyster
#

Is there a better way of validating this data?

rustic gull
#

@balmy oyster does pydantic support tagged-unions?

#

then type the type field with Literal["link"]

balmy oyster
rustic gull
#

i know nothing about pydantic and fastapi honestly

#

but it should let you do what you want
see how tagged-unions work in typescript

balmy oyster
#

I'll try to look into that. Thanks for pointing it out.

balmy oyster
balmy oyster
#

@rustic gull thanks for pointing out tagged unions to me. I was able to implement them in my application.

lethal delta
#

TL;DR: Wonderig if ParamSpec is my best option here or if there's a better choice for specifying added args in a Protocol implementation:

class DimensionMapper(Protocol[_TWorld]):
    """Camera projector for mapping between world and screen space.
    
    Key goals:
    
    1. Compatible with Py 3.8+ w/  typing_extensions available
    2. ``project`` takes a _THigher and returns `Tuple[float, float]`
    3. ``unproject``:
       * takes a `Tuple[float, float]` and returns a _THigher
       * Allows providing extra arguments as part of the protocol

    """
    
    def project(self, higher: _THigher) -> _TLower:
        """Map from higher dimensional space to a lower one."""
        ...
    
    # I'm unsure about the line below. Is ParamSpec the best option?
    def unproject(self, lower: _TLowerm, *etc) -> _THigher:
        """Map from a lower dimensional space to a higher one."""
        ...
dull lance
#

Does the function accept keyword arguments in addition to positional arguments? ParamSpec-annotated functions must have both ParamSpec.args and ParamSpec.kwargs defined in the signature

lethal delta
#

I have sort of tried ParamSpec but have had trouble getting it working

#

you can assign object type to etc to indicate that the unproject method can be implemented with any number of extra arguments.
I'm interested in this

lethal delta
#

I've tried to bind a typing_extensions.TypedDict to a TypeVar, then use that in Unpack

#

however, that does not seem to go so well

#

The 3.8+ requirement seems like a hard one and I'm not having a good time with it, lol.

dull lance
#

You can also circumvent this problem altogether by having implementors store the "extra arguments" as state before being invoked. For example:

class DimensionMapper(Protocol[_THigher, _TLower]):
    def project(self, higher: _THigher) -> _TLower: ...
    def unproject(self, lower: _TLower) -> _THigher: ...

class DimensionMapperWithRange(DimensionMapper[_THigher, _TLower]):
    def __init__(self, min_z: int, max_z: int) -> None:
        self.min_z = min_z
        self.max_z = max_z

    def project(self, higher: _THigher) -> _TLower: ...

    # No need to pass min_z and max_z as arguments to unproject
    def unproject(self, lower: _TLower) -> _THigher: ...

lethal delta
#

The _TLower can probably be simplified away as just Tuple[float, float]

dull lance
#

Perhaps you could provide some examples of how the protocol is being used

lethal delta
#

Yes, hold on

dull lance
#

is there a function that accepts DimensionMapper object without knowing about its specific implementation? how does it process the extra arguments?

#

this should inform you what should be defined in the interface

lethal delta
#

I'm not sure how to solve that yet. To my understanding, we sort of have to annotate in Window

#

Searching, gimme a sec

rough sluiceBOT
#

arcade/application.py lines 219 to 225

self._current_view: Optional[View] = None
self._default_camera = DefaultProjector(window=self)
self.current_camera: Projector = self._default_camera
self.textbox_time = 0.0
self.key: Optional[int] = None
self.flip_count: int = 0
self.static_display: bool = False```
lethal delta
#

thank you bot

dull lance
#

does it actually call any of the methods defined in the interface?

lethal delta
#

Yes.

lethal delta
dull lance
#

do they care about the extra arguments?

lethal delta
#

for example, the UIManager uses the Window's current camera to perform world coordinate mapping and then offset into GUI for event handling

#

The GUI stack doesn't, but the idea of the protocol was to allow people to define their own custom cameras

#

I'm very tempted to say the 3.8+ requirement makes the nice generics too hard

#

and that saying sorry, vec2 or vec3 (pyglet.math type subclassing typing.NamedTuple) + tuple is all you get

dull lance
#

since your framework will call unproject without any extra arguments

lethal delta
#

Hmm, so tuple-only + extra args are instance state?

dull lance
#

therefore, any concrete implementation must be compatible with this way of calling unproject

#

custom cameras can still have extra arguments but they must all have default values in order to remain compatible with the interface

dull lance
lethal delta
#

mypy is very unhappy with binding the typing_extensions versions of TypedDict to a type variable + putting it in typing_extensions.Unpack, so I'll put it down as a todo for the future.

lethal delta
#

it's a little ugly, but my discussion with the new Camera system's initial implementer suggests this covers 90% of usecases.

#

Another contributor added depth buffer features for controlling sprite draw order a while back, and I think the depth keyword might integrate with that.

dull lance
#

just keep in mind that if you add depth as an argument to the interface, all implementors will have to include it

lethal delta
#

Yeah, that's a sacrifice I'm about to discuss with the GL guy.

#

It's a contentious one that I'm not in favor of due to purity issues.

#

However, the nature of the framework and the depth buffer features might mean it's worth it.

lethal delta
dull lance
#

You're welcome! You don't need to credit me, this is just a small thing xD If you insist though, I think my GitHub should be linked to my Discord profile

lethal delta
#

I don't see it lol

#

I did see the pls don't DM though

dull lance
#

click on my PFP in the menu, more info should show up

lethal delta
#

Maybe privacy settings are blocking it?

dull lance
#

click on my pfp in this menu scroll down

lethal delta
#

...this is the worst UX choice I've seen from Discord yet, lol

lethal delta
brazen jolt
#

is this a pyright bug?

#

it works when I change Self to UserJoinRequestErrorKind

chrome hinge
#

like, if there was a subclass B of this enum A, then A.smth won't be an instance of B, and so for B.from_status_error this typehint would be invalid.

brazen jolt
#

but it's using cls.smth, that would at that point be B.smth

#

and the variant just gets inherited

#

no?

#

cls should just be type[Self] by default, or am I missing something?

chrome hinge
#

hmm, fair enough, the variant should be shared..

brazen jolt
#

it also seems to be a regression

#

I'm just updating from a very old version of pyright, where this worked

#

(I haven't updated for almost a year though)

brazen jolt
#

oh wait, enums don't support inheritence at all

viscid spire
#

Because there should be a fixed amount of instances

brazen jolt
#

I think an explanaiton on why Self doesn't work here, that might make sense is that, while enum subclassing isn't possible, Self doesn't handle for that, so it assumes that subclasses are in fact possible. Now, since the enum variants are held as class variables, and are dynamically created using an EnumMeta metaclass, which basically converts the class variables into instances of the enum class, and type checkers handle for this, by making these class variables have Foo type (instances of Foo class specifically), so even though I'm accessing a cls.VARIANT here, even if a potential subclass would inherit the VARIANT class variable, this class variable would be of type Foo, rather than of type Self, making it incompatible

#

I don't actually know for sure if this is the reason for this, but it's what makes sense to me

eternal surge
#
from dataclasses import dataclass
from typing import Protocol, Sequence

class LabelProtocol(Protocol):
    name: str

class CollectionProtocol(Protocol):
    contents: Sequence[LabelProtocol]

@dataclass
class Label:
    name: str

@dataclass
class Collection:
    contents: Sequence[Label]

def f() -> CollectionProtocol:
    return Collection((Label('hello'), Label('World')))

In the code above, Pylance complains about the last line:

Expression of type "Collection" cannot be assigned to return type "CollectionProtocol"
  "Collection" is incompatible with protocol "CollectionProtocol"
    "contents" is invariant because it is mutable
    "contents" is an incompatible type
      "Sequence[Label]" is incompatible with "Sequence[LabelProtocol]"

Understandably, it complains that a sequence of Labels is not the same as a sequence of LabelProtocol, but to my mind if Label adheres to LabelProtocol, this should work out just fine.

How do I handle this without coupling the Protocol to the class?

dull lance
#

I'm able to avoid the type error by explicitly indicating that Collection implements CollectionProtocol, but I guess that would couple the protocol to your class

#

seems that this has something to do with the protocol not having dataclass

eternal surge
#

They're defined in separate modules, I bundled them here for the sake of the question, but assume the concrete implementations are imported from a separate module

eternal surge
dull lance
#

dataclass does some extra stuff to the class which might cause the type checker to think that it isn't compatible with the original class

#

seems that setting Collection.contents to Sequence[LabelProtocol] also solves the type error

#

I don't think this should interfere with downstream uses of the class?

eternal surge
#

Does using the standard __init__ instead of dataclass fix it?

eternal surge
dull lance
#

that alone doesn't appear to be enough. Collection.contents is a mutable property so the T in Sequence[T] becomes invariant

eternal surge
#

Can I make it an immutable property to fix it? I'm not sure how it being mutable or not makes any difference

dull lance
#

ok I think I solved the issue

#

just make the protocol immutable

#

yep

#
from dataclasses import dataclass
from typing import Protocol, Sequence

class LabelProtocol(Protocol):
    name: str

class CollectionProtocol(Protocol):
    @property
    def contents(self) -> Sequence[LabelProtocol]: ...

@dataclass
class Label:
    name: str

@dataclass
class Collection:
    contents: Sequence[Label]

def f() -> CollectionProtocol:
    return Collection((Label('hello'), Label('World'))) 
eternal surge
dull lance
#

If Foo is assignable to FooProto, the above code would type check successfully even though the last line causes a runtime error (since hello doesn't exist on object A)

eternal surge
dull lance
#

yeah, more specifically, the implementor of the protocol can define additional properties that are not originally in the protocol

eternal surge
dull lance
#

yeah, since it basically lets you overwrite the original "type-checked" value

eternal surge
dull lance
#

Wouldn't the concrete implementation need to have @property as well?
This is a bit more tricky. I'm not familiar with the internals of Pyright but I believe that it only analyses @property-decorated attributes to the extent of the descriptor protocol (https://docs.python.org/3/howto/descriptor.html), rather than checking that the attribute is an instance of property. As long as the interface of attribute lookup remains consistent, it should pass the type checker

eternal surge
sacred spindle
#

Is there much guidance on how to gradually roll out type-annotations to a larger library and do at least partial checks on the library to ensure correctness?

context: I want to add type annotations to pyqtgraph, ...no way I'm going to be able to do that in 1 PR, so will have to roll it out gradually, i'd like to check if the annotations I've added are correct as I'm rolling them out...

sacred spindle
cinder bone
#

How would I typehint this? I tried my best but it doesn't seem to like it because of the func(obj) call

_Self = TypeVar("_Self", bound=Person)
def apply_on_family(func: Callable[[_Self], object]) -> Callable[[_Self], _Self]:
    @wraps(func)
    def wrapper(self: _Self):
        for obj in self.get_family():
            func(obj)
        return self
    return wrapper

Note that I've changed the example for privacy, so if it seems a little contrived the naming of stuff has been changed.

tranquil ledge
#

What’s the exact error you’re getting?

weary loom
#

code

hallow flint
cinder bone
trim tangle
cinder bone
trim tangle
# cinder bone A method returning `Iterable[Person]`

Right, that's the problem. Suppose you have this:

class Person
class Programmer(Person)
class Builder(Person)
class Cook(Person)

programmer = Programmer(children=[Builder(), Cook()])

def program(p: Programmer) -> None:
    p.program(language="python")

According to the type of apply_on_family, it should be legal to call apply_on_family(program)(programmer). But the programmer's family can include Persons that are not programmers

#

the correct fix depends on what restrictions you actually need

cinder bone
#

How would I go about allowing it?

trim tangle
trim tangle
cinder bone
#

oh ok

trim tangle
#

The problem is, P is bound to a particular type (like Programmer), but the children are arbitrary Persons, not necessarily related to P

#

Maybe you wanted this?

def apply_on_family(func: Callable[[Person], object]) -> Callable[[_Self], _Self]:
    @wraps(func)
    def wrapper(self: _Self):
        for obj in self.get_family():
            func(obj)
        return self
    return wrapper
cinder bone
#

Looks like what I want, thanks!

odd lark
#

Good day guys!!

Please did someone ever use fonttools pypi package

#

I want to merge two .ttf files into one

trim tangle
eternal surge
#

What does a * after a type definition mean?
This is from Pyright hinting of a variable after typechecking it with a TypeGuard

oblique urchin
eternal surge
pale fox
#

As far as I understand, there's no way to really distinguish an unbound method from a function. Am I right on this?

I'm trying to write a decorator class that can be applied to both methods and functions. I can make it work, but it's almost impossible for me to type it.

restive rapids
#

an overload, maybe?

((T, **P) -> R) -> ...
((**P) -> R) -> ...
paper salmon
real hazel
#

I know that I can inline the function and the type checker would be happy, but that's only a good idea in the example I gave because I simplified so much

paper salmon
#

oh right, only TypeIs is allowed to narrow the falsy branch

#

but ya both would be boolean functions

real hazel
#

Okay

#

I think in practice I will stick with my workaround which was just to inline the function, because TypeGuard looks so weird (why would a type with a generic actually be a boolean) and I'm slowly introducing typing into a codebase with no previous typing lol

dull lance
#

Are you decorating the unbound method directly, like so:

class MyClass:
    @my_decorator
    def my_method(self): ...

or are you decorating the unbound method after it is assigned to the class (but not yet bound to the instance)?

class MyClass:
    def my_method(self): ...

@my_decorator(MyClass.my_method)
def my_func(): ...
#

You can still distinguish between these two cases like so:
Actually nvm, I was wrong. I tested it further and it seems that the unbound method is immediately made a descriptor in both cases. Still, you can distinguish an unbound method from a regular function if you make the assumption that the first argument is self:

from typing import Callable, ParamSpec, Protocol, TypeVar

TOwner_contra = TypeVar('TOwner_contra', contravariant=True)
P, R_co = ParamSpec('P'), TypeVar('R_co', covariant=True)

class UnboundMethod(Protocol[TOwner_contra, P, R_co]):
    def __call__(__self, self: TOwner_contra, *args: P.args, **kwargs: P.kwargs) -> R_co: ...

def any_func(f: Callable[P, R_co]): ...
def unbound_method(f: UnboundMethod[TOwner_contra, P, R_co]): ...

class MyClass:
    @any_func
    def foo(self): ...  # OK

    @unbound_method
    def bar(self): ...  # OK

@any_func
def foo(): ...  # OK

@unbound_method
def bar(): ...  # NOT OK
mental ingot
#

Is there a way to type a function that accepts N callables and returns the output of each in a tuple? I'd like to preserve the length of the tuple and the position of each callable's return type. Here's my not-yet-working code:

import typing_extensions

TTuple = typing_extensions.TypeVarTuple("TTuple")

def call_fns(
    *callables: typing_extensions.Unpack[TTuple],
) -> tuple[typing_extensions.Unpack[TTuple]]:
    return (callable() for callable in callables)

# This should be tuple[Literal[1], Literal[2], Literal[3]]
results = call_fns(lambda: 1, lambda: 2, lambda: 3)
pastel egret
#

Not with TypeVarTuple. What you can do is manually define an overload for different numbers of functions though, up to say 5. Also your implementation isn't correct, you're returning a generator expression not a tuple.

mental ingot
#

Darn, oh well. And thanks for the return value catch

oblique urchin
#

You'd need something like the hypothetical Map operation

rare scarab
#
@overload
def call_fns[T](f1: Callable[[], T], /) -> tuple[T]:...
@overload
def call_fns[T1, T2](f1: Callable[[], T1], f2: Callable[[], T2], /) -> tuple[T1, T2]: ...
trim tangle
#

Yeah it's unfortunate

#

I still see the asyncio.gather stubs in my nightmares

trim tangle
# mental ingot Is there a way to type a function that accepts `N` callables and returns the out...

If you're willing to bend the code significantly just for the sake of typing, there's this

class _Gather[*Xs]:
    def __init__(self):
        # plz don't call directly
        self._items: tuple[Awaitable[object], ...] = ()

    def __call__[T](self, item: Awaitable[T]) -> _Gather[*Xs, T]:
        g = _Gather()
        g._items = self._items + (item,)
        return g

    async def invoke(self) -> tuple[*Xs]:
        return tuple(await asyncio.gather(*self._items))  # type: ignore

gather: _Gather[*tuple[()]] = _Gather()

pyright-play link

grave fjord
#

Hopefully gather just gets deprecated then later removed in favour of task groups

trim tangle
#

import trio as asyncio

jade viper
#

How the hell do I typehint a class that must inherit from this "private" class? (_Loss)

oblique urchin
#

If you get warnings because it's private, well, maybe it shouldn't be private

#

(or maybe you shouldn't be using it, if it's a third-party module)

jade viper
#

e.g. this one, which inherits from another private class that itself inherits from _Loss

jade viper
oblique urchin
#

you should be able to import it, judging from the screenshot you shared

#

tools might complain about using the private name

jade viper
#

Oh yeah, I was importing it wrong

#

Tysm Jelle:)

trim tangle
#

It really sounds like _Loss shouldn't be private

dull lance
#

If you don't mind being a bit less strict, I guess you can type alias _Loss as Callable[[torch.Tensor, torch.Tensor], torch.Tensor]

undone saffron
dull lance
#

Not sure whether I should put this question, but it's about changing a function signature so I'll put it here. Suppose I have two versions of this function (v1, v2) and am currently in the process of deprecating v1 (in v1.5):

# v1
def foo(a_old: int = ..., b: float = ...) -> None: ...

# v2
def foo(a_new: int = ..., b: float = ...) -> None: ...

# v1.5
@overload
def foo(a_old: int = ..., b: float = ...) -> None: ...
@overload
def foo(a_new: int = ..., b: float = ...) -> None: ...

def foo(*args, **kwargs) -> None:
    # How to implement this?

How should I distinguish between the cases where a_old is passed via positional arguments vs keyword arguments during runtime so that I can emit a deprecation warning that the name of the parameter will change from a_old to a_new?

#

actually wait I think I just answered my own question (just pop from kwargs lol)

dull lance
#

hmm is there a way to keep the overloads of a function after applying a decorator to it?

#

I can use a TypeVar to annotate the function in the decorator, but then Pyright complains about the wrapped function not being assignable to the return value of the decorator

#
F = TypeVar('F', bound=Callable[..., Any])

def deprecate_kwargs(*kws: str):
    def wrapper(fn: F) -> F:
        @wraps(fn)
        def inner(*args, **kwargs):
            deprecated_kws = {k for k in kwargs if k in kws}
            if deprecated_kws:
                warnings.warn(DeprecationWarning(f"Deprecated arguments: {deprecated_kws}"))

            return fn(*args, **kwargs)
        return inner  # Pyright complains about this line
    return wrapper
oblique urchin
#

though maybe pyright can infer that?

#

the line where pyright complains can be fixed with ParamSpec

dull lance
#

if I use ParamSpec then the overload information is lost

#

using the above example,

@overload
def foo(a_old: int = ..., b: float = ...) -> None: ...
@overload
def foo(a_new: int = ..., b: float = ...) -> None: ...

@deprecate_kwargs('a_old')
def foo(a_new: int = ..., b: float = ...) -> None:
    raise NotImplementedError

if I replace F with Callable[P, R] then I won't get the error that the implementation of the overloaded function has incompatible signature

jade viper
trim tangle
jade viper
# trim tangle Can you show the function where you want to accept a `_Loss`?

Sure!

def train_model(model: nn.Module, train_loader: DataLoader, criterion: nn.modules.loss._Loss, optimizer: optim.Optimizer, num_epochs: int = 10) -> None:
    """
    Train a PyTorch model.

    Args:
        model (nn.Module): The model to train.
        train_loader (DataLoader): The DataLoader for the training data.
        criterion (nn.modules.loss._Loss): The loss function.
        optimizer (optim.Optimizer): The optimizer.
        num_epochs (int, optional): The number of epochs to train for. Defaults to 10.
    """
    for epoch in range(num_epochs):
        model.train()
        running_loss = 0.0

        for images, labels in train_loader:
            labels = labels.float()
            optimizer.zero_grad()
            outputs = model(images)
            loss = criterion(outputs.squeeze(), labels)
            loss.backward()
            optimizer.step()

            running_loss += loss.item()

        print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}')
dull lance
trim tangle
#

Yeah, if you just need a callable, then accept a callable

#

Also, remove the types and defaults from the docstring, they are kinda redundant.

#

And I guarantee eventually they'll get out of sync 🙂

jade viper
#

Looks like there's a PR on the PyTorch repo to implement a Loss typealias

trim tangle
#
LossFn = Callable[[torch.Tensor, torch.Tensor], torch.Tensor]

def train_model(
    model: nn.Module,
    train_loader: DataLoader,
    loss_function: LossFn,
    optimizer: optim.Optimizer,
    num_epochs: int = 10,
) -> None:
    """Train a PyTorch model"""
#

I don't know anything about torch, but maybe you want a Module instead of _Loss?

#

(as the PR seems to imply)

#

though I don't understand why such an alias is needed

trim tangle
#

well, why do you need it to be a _Loss and not just any callable?

jade viper
trim tangle
#

just because Google does it doesn't mean it's good 🙂

#

if you're using type annotations, there's no need to duplicate them in the docstring

trim tangle
# jade viper What do you mean?

In your function, you're only calling criterion: criterion(outputs.squeeze(), labels). So why does criterion have to be a _Loss an not e.g. an ordinary Python function?

trim tangle
jade viper
jade viper
dull lance
trim tangle
#

unless you have some more code in the function, this should work as a criterion the same way as some_loss does

some_loss: _Loss = ...

def my_awesome_loss(param1, param2):
    rv = some_loss(param1, param2)
    print(f"Is this loss? {rv!r}")
    return rv
jade viper
#

Still, doesn't it make more sense to typehint it as expected?

trim tangle
#

I don't know, that's ultimately for you to decide

#

It's a very unfamiliar domain for me

#

If e.g. you're developing a library, you might want to be conservative and require e.g. a Module instead of a callable, because in the next library version you might want to access some module-specific properties of the parameter

dull lance
#

imo Callable[[torch.Tensor, torch.Tensor], torch.Tensor] is better because loss functions are not necessarily implemented as nn.Modules

jade viper
jade viper
#

What should I do in that case?

dull lance
#

there is another reason: torch.nn._Loss doesn't actually seem to define how it should be called, so the function signature is essentially unrestricted

#

Callable[[torch.Tensor, torch.Tensor], torch.Tensor] can provide better type safety for your code

#

e.g. so you won't accidentally call the loss function with three arguments

jade viper
#

That's very fair. So I can just do like LossFunction: TypeAlias = Callable[[torch.Tensor, torch.Tensor], torch.Tensor]?

dull lance
#

yep

jade viper
#

tysm guys!

balmy oyster
#
class DmWebsocketMessage(BaseModel):
    chat: Literal['dm']
    type: str
    dm: int
    link: Optional[str] = None
    serverinviteid: Optional[int] = None
    text: Optional[str] = None
    file: Optional[str] = None # will convert into bytes
    filetype: Optional[str] = None
    otheruser: str
    username: str
    profile: str
    date: str

    @model_validator(mode="after")
    def check_sent_right_data(self):
        if self.type == "link":
            if self.serverinviteid is None:
                raise Exception("No serverinviteid was sent to match with the link")
            self.link = str(uuid4())
        elif self.type == "text":
            if self.text is None:
                raise Exception("No text was sent")
        elif self.type == "file":
            if self.file is None or self.filetype is None:
                raise Exception("Either a file or/and filetype was not sent")
            else:
                self.file = base64.b64decode(self.file.split(",")[1])
        elif self.type == "textandfile":
            if self.text is None or self.file is None or self.filetype is None:
                raise Exception("Either a file or/and filetype or/and text was not sent")
            else:
                self.file = base64.b64decode(self.file.split(",")[1])
        else:
            raise Exception("invalid message type")
        return self```
#

Any critiques about the way I'm validationg this data?

dull lance
#

This enables type annotations that are more precise

dull lance
#

Is there a way to "map" the parameters of a function to another type? I am trying to annotate a decorator that converts each argument into a different type regardless of how many args/kwargs the input function has.

#

Similar to

class StrFunc(Protocol):
    def __call__(__self, *args: str, **kwargs: str) -> str: ...

class IntFunc(Protocol):
    def __call__(__self, *args: int, **kwargs: int) -> int: ...

def str_to_int_func(fn: StrFunc) -> IntFunc: ...

but I want the output function to have the same number/name of args/kwargs as those of the input function.

#

So if I decorate a function as follows:

@str_to_int_func
def foo(a: str, b: str) -> str: ...

the resulting function should have a signature like

def foo(a: int, b: int) -> int: ...
#

My intuition tells me this is not possible because it involves higher-kinded types, but I would like to hear some thoughts from others. Is it possible to at least annotate this decorator for a decent subset of cases?

tacit sparrow
#

can someone please test this in pycharm? Does new have the doc-string and correct signature?

import functools


def test(a: int, b: str):
    "description"
    pass


new = functools.partial(test, b="25")
new()
#

in vs code it does show doc string but signature is incorrect

trim tangle
tacit sparrow
trim tangle
#

since you can call new as new(42) or new(42, b="huh"), but not new(42, "huh")

tacit sparrow
#

oh, you're right

#

I didn't know that partial still allows override keyword arguments

tacit sparrow
undone saffron
tacit sparrow
undone saffron
#

None which apply generically, but there are a few things you can do depending on use case, 1 moment

hasty phoenix
#

I have a dataclass A with type hints. A new class B is dynamically made which shall contain the same attributes a A, but with different object types. Is there a way to create the type hints of B from A? I.e. is it possible to add type hints dynamically?

undone saffron
# tacit sparrow any hacky ideas to make it possible? 👀
from typing import Protocol
import functools


def test(a: int, b: str):
    "description"
    pass


class ModifiedTest(Protocol):
    def __call__(self, a: int):
        ...

class TestLike(Protocol):
    def __call__(self, a: int, b: str):
        ...

def wrap(f: TestLike) -> ModifiedTest:
    return functools.partial(f, b="25")

new = wrap(test)

Ugly, and requires special knowledge about the signatures. Can't be inlined as:

new: ModifiedTest = functools.partial(test, b="25")

in pyright, that should also be a bug though.

tacit sparrow
#

it's basically replacing signature with TestLike._call__ signature instead of excluding one argument

tranquil ledge
#

Yeah, there isn't an easy widely applicable alternative though, at least not yet.

#

Function transforms of this kind in general are . . . I don't want to say underspecified, but unexplored within the Python type system, maybe? Beyond Concatenate and Paramspec, at least.

undone saffron
#

Literally just not something the type system supports right now. I'd just leave it be with partial personally (as you have it now), pyright (and others) can still detect misuse, and you can document the set of functions however you want

hasty phoenix
#

Is it possible to define a default type for T unless it has been explicitly specified? class A(Generic[T]) where reveal_type(A()) shows A[Unknown]

oblique urchin
#

!pep 696

rough sluiceBOT
hasty phoenix
#

thanks

oblique urchin
#

summary: yes, with TypeVar("T", default=SomeThing)

hasty phoenix
#

Yeah, I found it. I was actually reading PEP965 so very close 😄

#

Then the solution to my above question seems to be something akin to:

T = TypeVar('T', default=int)

@dataclass
class A(Generic[T]):
    a: T
    b: T

class B(A[str]):
    pass
oblique urchin
#

or in Python 3.13, class A[T=int]: ...

undone saffron
#

(could also use multiple typevars if not, but the point just... I don't see a benefit to writing it this way)

hasty phoenix
#

There are other functional reasons for having B, this was just the typing parts. If the only difference were the type, then A[int] and A[str] would be a better way to use them...

grave fjord
oblique urchin
#

to mirror a change in typeshed

grave fjord
#

And typeshed needs the change because collections.abc.Generator and AsyncGenerator are aliases of the deprecated types?

oblique urchin
#

typeshed wants this change to make Generator more convenient to use

grave fjord
#

right but only collections.abc.Generator and AsyncGenerator should get the defaults

#

The deprecated names should not get new features

hasty phoenix
#

TypeVar(..., default=...) worked brilliantly in my IDE. Not so much in actual py code. 3.13. Alas.

oblique urchin
hasty phoenix
#

thanks

grave fjord
#

(commented on the issue)

rare scarab
#

partial, as well as property, are one of those features that are special cased into the type checker.

tacit sparrow
#

then I think it will work for me, as it will move the overridden argument to the end of the signature

tacit sparrow
undone saffron
rare scarab
#

type stubs don't care about defaults.

#

It just cares that there is one.

undone saffron
oblique urchin
#

Plus, we do actually include defaults in typeshed now because they're useful for IDEs

undone saffron
#

And it isn't even a special case of untransformed function, this is just partial's special casing looking surprising to people used to how pylance normally handles defaults

rare scarab
#

Is this one of those quirks of having partial be special cased?

undone saffron
#

probably

#

partial would be great to not have special casing, or to at least have specified behavior for a documented special case

rare scarab
#

is property still special cased?

#

I know it's still not generic

oblique urchin
#

yes

undone saffron
#

pyright understands descriptors (including user defined ones)

rare scarab
#

it can be written in a type-safe way and not be special cased

undone saffron
#

but it's not specified in the type spec iirc

oblique urchin
#

the .setter stuff definitely needs special casing

rare scarab
#

maybe

oblique urchin
#

(or a far more powerful type system)

rare scarab
#

it's been a while since I tried playing with it

tranquil turtle
undone saffron
#

I've been collecting my thoughts on improving some areas of the type system while minimizing disruption. There are 4 areas I'm aware of where python isn't theoretically sound, but is close enough that if done carefully, disruption could be very minimal

#

it's a lot of effort to work through all the theory vs current underspecification though

oblique urchin
rare scarab
#

is property.setter atomic?

undone saffron
#

properties that have a setter need HKTs to be expressed generically

rare scarab
#

by that, I mean does it return a new instance or modify self?

tranquil turtle
#

returns new

cinder bone
#

quick question

#

is Iterable[T] covariant in T?

buoyant swift
#

i believe so

oblique urchin
cinder bone
#

nice thanks

#

also is type[T] covariant?

oblique urchin
#

yes

cinder bone
#

brilliant! Is there any reference for this stuff btw

#

(Sorry to keep annoying with questions)

oblique urchin
rough sluiceBOT
#

stdlib/typing.pyi line 389

class Iterable(Protocol[_T_co]):```
oblique urchin
cinder bone
#

Another question is there a difference between list[T | U] vs list[T, U]?

dull lance
#

list only accepts one type argument

cinder bone
#

Oh makes sense

tacit sparrow
#

is there way to use some default type if generic type wasn't specified? See example below

from typing import Optional, Union, Type, TypeVar, Generic

T = TypeVar("T", bound="Animal")


class Animal:
    pass


class Dog(Animal):
    pass


class AnimalContainer(Generic[T]):
    def get_class(self) -> Type[T]: ...


t = AnimalContainer[Dog]().get_class()
reveal_type(t)  # Dog

t = AnimalContainer().get_class()
# TODO how to make it Animal if generic wasn't specified?
reveal_type(t)
pastel egret
tacit sparrow
#

is it possible to make Type[T] to also accept aliases? e.g.

from typing import Optional, Union, Type, TypeVar


class Dog:
    pass


class Cat:
    pass


Animal = Union[Dog, Cat]

T = TypeVar("T", Dog, Cat)


def test(t: Type[T]) -> Type[T]:
    return t

# currently causes a type error
a = test(Animal)
# NOTE: should return Animal
reveal_type(a)
a = test(Dog)
reveal_type(a)  # Dog
a = test(Cat)
reveal_type(a)  # Cat
pastel egret
#

Unfortunately no, because type only accepts actual classes.
A PEP is currently being worked on for TypeForm, which would allow this. What you could do is make Animal a superclass, or make T bound to Union[Type[Dog], Type[Cat]].

GitHub

Optional static typing for Python. Contribute to python/mypy development by creating an account on GitHub.

dull lance
tacit sparrow
tacit sparrow
dull lance
#
from typing import Protocol

class AnimalFactory(Protocol):
    # args and kwargs corresponds to those in __init__
    def __call__(__self, *args: ..., **kwargs: ...) -> Animal: ...

def create_animal(factory_or_cls: AnimalFactory) -> Animal:
    return factory_or_cls(...)
tacit sparrow
#

okay, it's going to be kind of verbose but I think it might work for my case. Basically I've added some new type Animal_Type that will be possible to use to overload to make sure my function will return a union Animal in that case.

from typing import Optional, Union, Type, TypeVar, overload, assert_type


# fmt: off
class _base: ...
class Dog(_base): ...
class Cat(_base): ...
# NOTE: in my case there will be many unions like `Animal`
Animal = Union[Dog, Cat]
class _base_type: ...
class Animal_Type(_base_type): ...
# fmt: on

T = TypeVar("T", bound="_base")


@overload
def test(t: type[Animal_Type]) -> Animal: ...
@overload
def test(t: type[T]) -> T: ...
def test(t: Union[type[_base_type], type[_base]]) -> _base: ...


a = test(Animal_Type)
assert_type(a, Animal)
a = test(Dog)
assert_type(a, Dog)
a = test(Cat)
assert_type(a, Cat)
icy obsidian
#

Is it possible to set a default type for a generic type?

T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
class BaseMulti(Generic[T1, T2, T3]): pass
class DerivedMulti(BaseMulti[int, str, float]): pass
class DerivedMulti2(BaseMulti[int, str]): pass  # <<<<<<<< So that this line would not produce errors as only 2 out of 3 generics are set
fathom river
icy obsidian
#

I assume it's available only in 3.13 :<

oblique urchin
icy obsidian
#

Oh, I see

#

Going to give it a try

hasty phoenix
#

What is the difference doing T = str and T = TypeVar("T", bound=str) ? Similarly T2 = TypeVar("T2", str, int, None) vs T2 = Union[str, int, None].

dull lance
hasty phoenix
#

Ah, got it. Thanks

trim tangle
# hasty phoenix What is the difference doing `T = str` and `T = TypeVar("T", bound=str)` ? Simi...
def f1(x: str | int, y: str | int) -> str | int:
    ...

A = str | int
def f2(x: A, y: A) -> A:
    ...

B = TypeVar("B", bound=str | int)
def f3(x: B, y: B) -> B:
    ...

C = TypeVar("C" str, int)
def f4(x: C, y: C) -> C:
    ...

f2 is the same as f1, and can be called as:

f2(42, "foo")  # returning str | int
f2("a", "b")  # returning str | int
f2("a", f2(1, 2))  # returning str | int
``` `f3` links the `x` and `y` arguments together, and requires them to be of the same type that's a subtype of `str | int`: ```py
f3(1, 2)  # valid, B is resolved as int, call returns int
f3("a", "b")  # valid, B is resolved as str, call returns int

k: Literal["k"] = "k"
f3(k, k)  # valid, B is resolved as Literal["k"], call returns Literal["k"]

f3(f2(1, 2), f2(3, 4))  # valid, B is resolved as `str | int`, call returns `str | int`
f3(69, "yes")  # valid, B is resolved as `int | str`, call returns `int | str`
f3(69, k)  # valid, B is resolved as `int | Literal["k"]`, call returns `int | Literal[k]`
``` in `f4`, `C` is only allowed to resolve as exactly `str` or exactly `int`. ```py
f4(1, 2)  # valid, C is resolved as int, call returns int
f4("a", "b")  # valid, C is resolved as str, call returns str
f4(1, "b")  # invalid, C cannot be resolved
f4(f2(1, 2), f2(1, 2))  # invalid, C cannot be resolved

class SpecialString(str): pass
f4(k, SpecialString())  # valid, C is resolved as str, call returns str
f4(True, False)  # valid, C is resolved as int, call returns int
#

maybe I should put these examples somewhere

mental ingot
#

I have a function that accepts async and sync callbacks. If it's given an async function then it awaits it, otherwise it just calls it. How do I properly type this?

import asyncio
import typing

T = typing.TypeVar("T")

async def call(cb: typing.Callable[[], T]) -> T:
    if not asyncio.iscoroutinefunction(cb):
        return cb()
    return await cb()

async def async_fn() -> int:
    return 1

def sync_fn() -> int:
    return 1

async def main() -> None:
    # Return type should be int but it's Coroutine[Any, Any, int]
    async_res = await call(async_fn)

    sync_res = await call(sync_fn)

asyncio.run(main())
trim tangle
#

why do you want this though?

#

(I've seen this behaviour in some frameworks and it leads to some unfortunate edge cases)

mental ingot
#

I have a method that my users can pass callbacks to. I wanted to let them pass either async or sync callbacks

trim tangle
#

I'd just accept async callbacks only

mental ingot
#

(The example is async but we have sync variations for Flask folks)

trim tangle
#

yeah but you're using await anyway

rough sluiceBOT
#

examples/functions/two_steps_and_sleep.py lines 33 to 43

def fn(
    ctx: inngest.Context,
    step: inngest.StepSync,
) -> str:
    user_id = step.run("get_user_id", lambda: 1)
    step.run("print_user_id", lambda: f"user ID is {user_id}")

    step.sleep_until(
        "zzzzz",
        (datetime.datetime.now() + datetime.timedelta(seconds=3)),
    )```
mental ingot
#

Damn, that preview codeblock is an awesome Discord bot feature

trim tangle
#

If you really want this behaviour, you can use overloads: ```py
from collections.abc import Callable, Coroutine
from typing import overload, TypeVar, Any

T = TypeVar("T")

@overload
async def call(cb: Callable[[], Coroutine[T, None, None]]) -> T: ...
@overload
async def call(cb: Callable[[], T]) -> T: ...

async def call(cb: Callable[[], Any]) -> Any:
if asyncio.iscoroutinefunction(cb):
return await cb()
else:
return cb()

#

If people only use this directly on an async def'd async functions or def'd non-async functions, it shouldn't cause too many problems

restive rapids
trim tangle
#

Overloads are evaluated in declaration order, while unions should be order-independent

restive rapids
#

ah ic

mental ingot
#

Nice, the overload works. Thank you!

trim tangle
#

Of course, this will have some edge cases. For example, if you pass in a custom callable object:

class Thing:
    def __init__(self, ...): ...

    async def __call__(self, ...): ...

It will not work at all (because a Thing() is not a coroutine function)

#

The thing will be called, but the result won't be awaited. If you're luckly you'll get a warning at runtime

#

also if you have something like: ```py
async def foo(x, y, z):
...

def enhanced_foo(x, y, z):
if z is None:
raise TypeError
return foo(x, y, z)
``` enhanced_foo is not an async def function, so it won't work either. This might look silly, but you could have a decorator that only does some transformations on the arguments (like runtime type validation with beartype or typeguard)

def prohibit_none_arguments(fn):
    def decorated(*args, **kwargs):
        if None in args or None in kwargs.values():
            raise TypeError(f"None in arguments to {fn}", args, kwargs)
        return fn(*args, **kwargs)
    return decorator
#

There's inspect.markcoroutinefunction, but it's a very recent addition and a bit of a hack

mental ingot
#

Python is the best language because it's so dynamic but it's also the worst language because it's so dynamic 😆

trim tangle
#

a more reliable way to check would be to call the function and see if the result is awaitable (in which case, await it)

mental ingot
#

Great point

blazing dove
#

Is there a way to eliminate the dynamic class in the following, without explicitly defining child classes?

import typing as ty

T = ty.TypeVar("T")

class Foo:
    pass

class Ref(ty.Generic[T]):
    @classmethod
    def get_ref_type(cls, target_type):
        class RefT(Ref[target_type]):
            pass

        return RefT

    @classmethod
    def get_target_type(cls):
        return ty.get_args(cls.__orig_bases__[0])[0]

ref: Ref[Foo] = Ref.get_ref_type(Foo)()

print(ref.get_target_type()) # <class '__main__.Foo'>
trim tangle
#

You could do something like ```py
class Ref(Generic[T]):
def init(self, target_type: type[T]) -> None:
self._target_type = target_type

def target_type(self) -> type[T]:
    return self._target_type

ref = Ref(Foo) # inferred as: Ref[Foo]

blazing dove
#

Thanks! I guess I was trying to make it work somehow without storing any direct reference to the target class, but I'm not sure if that is possible

trim tangle
#

it's just that in your example it's stored in the class attributes instead of inside the object

blazing dove
#

Yeah, I'm just curious whether e.g. in your example that the type information would exist during execution if not explicitly kept in self._target_type

#

Or if the runtime simply does not have that information

trim tangle
#

What do you mean by "that type information"?

#

If you're making an instance of a generic class, the resolved generic parameters are not stored anywhere if that's what you mean. It only exists for static analyzers like mypy andpyright (and for your editor)

blazing dove
#

Yeah, that was what was helpful to confirm

#

In that case it's clear that I must keep it, and I'd then prefer to just keep a target_type attribute in the generic class

trim tangle
#

creating classes at runtime is definitely a very esoteric thing, and you probably don't need it

blazing dove
#

Thank you for helping! 🙂 Gives good clarity to complete some changes in my type setup

viscid spire
#

ty 😨

trim tangle
viscid spire
#

😮‍💨

trim tangle
# trim tangle If you're making an instance of a generic class, the resolved generic parameters...

For example when you make a list such as [1, 2, 3], it doesn't remember that it's a list of integers. That would be correct to remember anyway, it could be a list[int | str] or a list[object]. And type annotations on variables can't impact the creation of an object on the right side of the assignment

foo: list[int | str] = [1, 2, 3]  # direct annotation
bar = [1, 1, 1, "a"]  # should this be inferred as list[int] or list[object] or list[Literal[1, 'a']]?
                      # Python doesn't specify that, that's up to the type checker
``` or a more complex example, where the type is inferred through less direct measures: ```py
foo: list[int] = [1, -2, 3]
bar = list(map(abs, foo))
blazing dove
#

I guess those rules are the same for e.g. C++, however in that case there would be actual generic instances with ability to get that type

trim tangle
#

C++ is a statically typed language, so that's different

#

For example you can do ```cpp
template<class T>
void push_empty(std::vector<T>& vec) {
vec.push_back(T());
}

blazing dove
#

Sure, but with e.g. type variables the boundary feels a little more fuzzy between static and dynamic if you don't know the rules - to an extent it seems intuitive that a type variable would hold the type during runtime

#

I get why - it's just not necessarily super easy to get 😅

trim tangle
#

Well, the type might now always be passed in at runtime. As I showed above for example:

foo: list[int] = [1, -2, 3]
bar = list(map(abs, foo))
``` the type variable for `list` in the `bar` variable is only known to a type checker, it's not passed anywhere at runtime
#

In C++ the type parameter is also not stored on the instance AFAIK, that would be a huge waste. Instead it's just known statically

blazing dove
#

Yeah, in those examples it is very clear, but with e.g. generics it feels a little less, as RefMyType could also be seen as dynamically passing an argument with the MyType type information

#

With C++ historically separate code is generated per type, so it's not a direct runtime cost, but does add bloat

trim tangle
#

well, it's a tradeoff

#

It does generate more code and does make it kinda impossible to use template types as part of a stable ABI, but it removes potential levels of indirection

#

IIRC Swift chose a different route

trim tangle
night rain
#

Hello, I am having a bit of trouble with type hinting when relating to private types such as those in the argparse module.

An example of the problem:

import argparse

class GroupDict(dict[str, argparse._ArgumentGroup]):
    pass

groups = GroupDict() # Type gets reported as GroupDict[str, Any]

group = groups["Group 1"] # Type gets reported as Any
fierce ridge
# night rain Hello, I am having a bit of trouble with type hinting when relating to private t...

it works as expected with mypy 1.10: https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=749d9c4efabf3b05aa86b102ef42887c

import argparse

class GroupDict(dict[str, argparse._ArgumentGroup]):
    pass

groups = GroupDict()
reveal_type(groups)

group = groups["Group 1"]
reveal_type(group)
main.py:7: note: Revealed type is "__main__.GroupDict"
main.py:10: note: Revealed type is "argparse._ArgumentGroup"
night rain
tranquil turtle
#

don't use pycharm for typechecking, it is bad at it

trim tangle
#

Yeah, its analyzer is kinda stinky tbh

soft matrix
#

there is now an lsp plugin for using pyright in it though

trim tangle
#

huh??

#

finally

rare scarab
#

hoping for astral type checker in rust

soft matrix
trim tangle
#

wait what, PyCharm professional has LSP support but not the community one?

soft matrix
#

dont be poor ig? 😎

trim tangle
#

I am literally unable to buy it 😔

#

though not sure why I would, I'm happy with vscode

night rain
#

I have no clue what PyRight is.

rare scarab
#

it's the type checker in vscode

#

it's bundled with pylance

trim tangle
#

Well, it's not glued to vscode

#

You can run pyright with vim/emacs/sublimetext/other editor with LSP support, or run it as a CLI program

rare scarab
#

unlike pylance which is both closed source and restricted to the microsoft build of vscode

icy obsidian
#

(Not sure if this is the correct topic...)
How to define an abstract typed class attribute?

class A(ABC):
    some: int  # <<< Abstract attribute
class B(A):
    some = 5  # <<< To force this
b = B()
rough sluiceBOT
#

class typing.Protocol(Generic)```
Base class for protocol classes.

Protocol classes are defined like this:

```py
class Proto(Protocol):
    def meth(self) -> int:
        ...
```  Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing), for example...
icy obsidian
#

So it's only through the Protocol? Or is there a way to use @ abstractmethod or alike?

class Some(Protocol):
    some: ClassVar[int]
class A(Some, ABC):
    pass
class B(A):
    some = 5
b = B()
tranquil ledge
#

@abstractmethod can be layered on @property to get the behavior you want, I think.

reef quest
#

why is

@url.setter
def url(self, value: Optional[str]) -> None:
  if not isinstance(value, str | None):
    raise TypeError("err: URL must be a string")
  c_log.debug(f"url set: {type(value)}")
  self._url = value #<- error on this line

giving
Incompatible types, expression has Optional[str] Variable has None

#

isnt optional string just Str | None ?

oblique urchin
oblique urchin
#

I suspect it doesn't suggest isinstance() checks with the | syntax

reef quest
#

let me see what happens with optional

oblique urchin
#

Optional[str] doesn't work with isinstance either

#

if value is None or not isinstance(value, str) should work

reef quest
#

i see, thank you

trim tangle
#

Damn, mypy has 2.6k issues 💀

oblique urchin
#

one fewer because I just closed that one as a duplicate 😄

rare scarab
trim tangle
#

🤔

#

I know I'm not in a position to complain, mypy is run by volunteers and I'm not willing to volunteer myself

#

just a bit surprised at the volume of issues

oblique urchin
#

there's a lot of duplicates

#

sometimes I think of going in and closing some duplicates

#

but there are 2.6k issues

trim tangle
#

yeah

#

also there are things that probably won't be implemented in any reasonable timeframe, like python/mypy#17218

oblique urchin
#

yeah I feel there's tons of similar #topic-type-narrowing issues asking for support for any() or all() or whatever but I don't feel like going through them all

lunar dune
#

My favourite genre of mypy "bug report" is the "hey! Mypy doesn't understand arbitrary Python code!" genre. I feel like we get an issue every few weeks that boils down to that

trim tangle
#

I think the issue above kinda fits that 😛

#

I mean, it's understandable. False positives are literally mypy saying your code has issues when there aren't any

icy obsidian
#

It gives:
Expression of type "Literal['asd']" is incompatible with declared type "property" "Literal['asd']" is incompatible with "property"

#

And working with Protocols seems to be more intuitive and easy.

tranquil ledge
#

You have to implement it as a property in the subclass. That’s the contract specified by making a property abstract. And fair enough, a protocol can work fine, but an abstract property throws an error at runtime if not implemented in a subclass, so it’s technically “stronger”.

#

Changes the interface a bit from regular assignment, though.

icy obsidian
#

Yeah, my bad - property works

#
class A(ABC):
    @property
    @abstractmethod
    def some(self) -> str: pass
class B(A):
    @property
    def some(self) -> str:
        return "asd"
b = B()
#

It might be stronger (which I didnt know, thanks), but... just compared to Protocol...

class Some(Protocol):
    some: ClassVar[int]
class A(Some, ABC):
    pass
class B(A):
    some = 5
b = B()
summer hinge
#

Hello!
I remember that there was a site with tasks on type hints. The tasks were divided by difficulty, but that's all I can remember. It would be great if someone could tell me the name of this site. 🙂

restive rapids
tacit sparrow
#

How can I connect all these types for function what_i_need to work but avoid creating lots of overloads?

The problem with overloads - they seem very boilerplate (in real life case I'll have hundreds of classes, not just Dog and Cat) and it seems there should be a better way to organize this.

from typing import Union, TypeVar, assert_type, overload

# fmt: off
class Base: ...
# NOTE: I wish I could use generics like Dog[Good]
# but I can't since Dog[Good] supposed to have some different
# attributes than Dog and Dog[Bad]
class GoodDog(Base):
    def smile(self): pass
class BadDog(Base):
    def bark_angrily(self) :pass
Dog = Union[GoodDog, BadDog]

class GoodCat(Base):
    def purr(self): pass
class BadCat(Base):
    def destoy_things(self) :pass
Cat = Union[GoodCat, BadDog]
# fmt: on


def why_i_use_unions(cat: Cat):
    # I need warnings like this, so I could work with `Cat` classes in general
    # but be notified about the differences:
    # Cannot access attribute "purr" for class "BadDog"
    cat.purr()


# fmt: off
# T classes used to address types in general, without Unions
# as Unions doesn't allow to store any information in them
class T:
    class Base: pass
    class Cat(Base): pass
    class Dog(Base): pass

TV = TypeVar("TV", bound=T.Base)
class Cage:
    def add_animal(self, c) -> Base: ...
class GoodAnimalCage(Cage):
    # @overload
    # def add_animal(self, c: type[T.Cat]) -> GoodCat: ...
    # @overload
    # def add_animal(self, c: type[T.Dog]) -> GoodDog: ...
    def add_animal(self, c: type[T.Base]) -> Base: ...
class BadAnimalCage(Cage):
    # @overload
    # def add_animal(self, c: type[T.Cat]) -> BadCat: ...
    # @overload
    # def add_animal(self, c: type[T.Dog]) -> BadDog: ...
    def add_animal(self, c: type[T.Base]) -> Base: ...
# fmt: on


def what_i_need():
    cage = GoodAnimalCage()
    cat = cage.add_animal(T.Cat)
    assert_type(cat, GoodCat)

    dog = cage.add_animal(T.Dog)
    assert_type(dog, GoodDog)
#

or any other suggestions to reorganize this are also welcome

icy obsidian
#

Is it possible to make a generic protocol with class attribute?
Something like that:

T = TypeVar('T')
class Some(Generic[T], Protocol):
    some: ClassVar[T]   # <<<<<<<<<< "ClassVar" type cannot include type variables
class A(Generic[T], Some[T], ABC):
    pass
class B(A[int]):
    some = 5
b = B()
#

The idea is to have a required attribute in a generic abstract to force the child to implement it.

viscid spire
#

perhaps you can make a generic metaclass

icy obsidian
#

You mean with a runtime check? Is there a way to do it with a type check?

viscid spire
#

it's a difficult problem for sure

#

I'm just not sure how to add said attribute

icy obsidian
#

I might be missing something, but what is the need for metaclass then?
I can define "some" in my base class "A", but it means that "B" will have no obligation to define it.

viscid spire
#

I was meaning like this

from typing import reveal_type


class MyMeta[T](type):
    def __new__(cls, name, bases, attrs, **kwargs):
        return super().__new__(cls, name, bases, attrs)

    def __init__(self, name, bases, attrs, *, some: T):
        super().__init__(name, bases, attrs)
        self.some = some


class B(metaclass=MyMeta[int], some=5):
    pass


reveal_type(B.some)
#

although it does give that warning idk why

icy obsidian
#

"B" class definition does not force or warn you of not specifying the "some" argument.

#

Not to mention the ambiguity error :<

viscid spire
#

it's not really ambiguous IMO

viscid spire
icy obsidian
#

As far as I understand, it is ambiguous (not sure if defining class variables in any other way won't be as well...) - B[int] is the same very class as B[str]... However, in this case B is not supposed to be Generic on it's own, but rather a specific one, thus (just like you mentioned), in my opinion as well, it should not be ambiguous...

viscid spire
#

the metaclass is generic but is being specified

#

sorry didn't read you whole message, it kinda looks like you changed your opinion half way through?

nimble ridge
#

Quick question, I have a python code written I just need help with a few lines of it. Who can I contact to get help from?

icy obsidian
#

Yeah, that is what I meant in the seconde part

rough kettle
#

is there a way to type lists like how you'd do for tuples, specifying each element individually? I know tuples are supposed to used for that but the library I'm trying to add types to currently uses lists instead of tuples (kinda frustrating)

icy obsidian
#

The first part was if B was still generic

viscid spire
rough kettle
#

yeah I could just use tuple[a, b, c] instead of list[a | b | c]

#

maybe that'll get them to change the types :p (yeah right)

trim tangle
#

It's not possible to do that with lists, no

#

You'd need to remove a lot of list methods, like clear. and you'd need to tell functions like random.shuffle that this list is no good

#

so you'd only be left with __setitem__

brisk hedge
trim tangle
#

Yes, why don't I use these nats

trim tangle
brisk hedge
#

🙂

tacit sparrow
#

Can I define generic specific attributes? E.g. A[int] to have an attribute value and A[list] to have an attribute values?

restive rapids
#

Well you could do stuff with __class_getitem__ but typecheckers won't understand

#

And unless you actually instantiate it like A[int]() it wont work

trim tangle
restive rapids
#

Oh right interesting, could you combine that with property?

tacit sparrow
tacit sparrow
icy obsidian
#

Is there any way to define a generic abstract class field?
This is the closest I got, but the typing doesn't work here...

T = TypeVar('T')
class A(Generic[T], ABC):
    @classmethod
    @abstractmethod
    def some(cls) -> T: pass
class B(A[str]):  # <<<< 'str'
    @classmethod
    def some(cls) -> int:  # <<<< 'int'
        return 5
b = B()
restive rapids
#
import abc
from typing import Any, Callable

class classproperty[T, R]:
    def __init__(self, func: Callable[[type[T]], R]) -> None:
        self.fget = func
    def __get__(self, instance: T | None, owner: type[T]) -> R:
        return self.fget(owner)

class A[T](abc.ABC):
    @classproperty
    @abc.abstractmethod
    @classmethod
    def some(cls) -> T:
        ...

class B(A[int]):
    @classproperty
    @classmethod
    def some(cls) -> int:
        return 42

maybe
i feel like there should be a simpler way lmao
oh lmao it typechecks but doesnt run

import abc
from typing import Callable, cast

class classproperty[T, R]:
    def __init__(self, func: Callable[[type[T]], R]) -> None:
        self.func = cast(Callable[[type[T]], R], getattr(func, "__wrapped__"))
    def __get__(self, instance: T | None, owner: type[T]) -> R:
        return self.func(owner)

class A[T](abc.ABC):
    @classproperty
    @classmethod
    @abc.abstractmethod
    def some(cls) -> T:
        ...

class B(A[int]):
    @classproperty
    @classmethod
    def some(cls) -> int:
        return 42

print(B.some)

does though
but thats too cursed

tacit sparrow
icy obsidian
restive rapids
icy obsidian
#

Oh... after vscode reinstall it was set to basic. In strict it shows this error, yes.

#

Hmm... but why this shows only in strict?

#

And with this the code I shared earlier works as well with the same error.

#

Though I just wonder if there is a more clear way to define this?
I mean something like this (it doesn't work thoguh):

class B(A[int]):
    some = 5
restive rapids
#

do you need it to be a class var only?

#

because

class A[T]:
    some: T

class B(A[int]):
    some = 42

works (and gives an error if its not an int)

icy obsidian
#

the problem with this goes like this:

restive rapids
#

ah right

icy obsidian
#

Generic class fields do not like it :<

#

That is why I started trying the method approach, which seems ugly

restive rapids
icy obsidian
#

Yea, saw that when searching on how to do it, as it seemed strange for me that a specialised child still considers it as invalid

cinder bone
#

Has anyone made a PEP or something for Proxy[T]? If so, would it be worth a PEP or a simple PR?

rare scarab
#

I think it would be more worth it to implement composite types

#

What would Proxy even do?

#

If it's anything like javascript's Proxy, that's not type safe. It's basically return new Proxy(obj, {}) as typeof obj)

rare scarab
rough sluiceBOT
#

src/lib/es2015.proxy.d.ts line 108

new <T extends object>(target: T, handler: ProxyHandler<T>): T;```
rare scarab
#

You can implement this with __new__

leaden oak
#
def retry(wait: float, stop_after_delay: float):
    def wrapper(func):

        @functools.wrap(func)
        def wrapped(*args, **kwargs)
            start_time = time.monotonic()
            while True:
                try:
                    return func(*args, **kwargs)
                except Exception:
                    if time.monotonic() - start_time > stop_after_delay:
                        raise
                    time.sleep(wait)

        return wrapped

    return wrapper

How do I type annotate this decorator?

trim tangle
soft matrix
#

Id probably use F = TypeVar("F", bound=Callable) so it can be applied to all callables and preserve the type

trim tangle
soft matrix
#

oh yep fairs

trim tangle
#

That might actually be a consideration before ParamSpec though

trail kraken
#

Does anyone know if type hinting via comments such as # type: int will carry on being supported?

#

I vaguely remember it getting deprecated, but I cannot find a source for it, nor what is deprecating support for it.

#

Would be nice if I misremembered.

#

Just found a mypy PR for about fixing something related to it. So at least I am not the only one relying on it. Nevermind me.

soft matrix
#

they arent supported by pyright so if this a client library you shouldnt be using them

trail kraken
#

Mm... Thanks for the headsup. Can't use Pyright then.

tranquil ledge
#

To my knowledge (as of a few months ago, at least), pyright still can understand them, but the maintainer mentioned in an issue that they won't guarantee that functionality will stick around.

oblique urchin
trail kraken
#

Ah okay. Good to know. Safe for now then. Hopefully that will be after I can stop supporting Python 3.5.

cinder bone
#

Does anyone know if stuff like dict[str, int] is valid in python 3.8 or if I still have to use typing.Dict[str, int]

trim tangle
#

!pep 585

rough sluiceBOT
tranquil ledge
#

Is this overkill if trying to be compatible with 3.8 without breaking runtime introspection?

import sys
from typing import TYPE_CHECKING

if sys.version_info >= (3, 10):
    from typing import TypeAlias, TypeGuard
elif TYPE_CHECKING:
    from typing_extensions import TypeAlias, TypeGuard
else:
    if sys.version_info >= (3, 9, 2):
        from types import GenericAlias as _GenericAlias
    else:
        from typing import _GenericAlias

    class _TypeGuardGenericAlias(_GenericAlias):
        def __repr__(self):
            return f"<placeholder for {super().__repr__()}>"

    class _TypeGuardMeta(type):
        def __getitem__(self, item: object) -> _TypeGuardGenericAlias:
            return _TypeGuardGenericAlias(self, item)

        def __repr__(self):
            return f"<placeholder for {super().__repr__()}>"

    class TypeGuard(metaclass=_TypeGuardMeta):
        pass

    class TypeAlias:
        def __repr__(self):
            return f"<placeholder for {super().__repr__()}"
undone saffron
#

Actually...3.11

#

Array.array isnt generic in stdlib till 3.12

tacit sparrow
#

How can I get TypedDict from Person dataclass without recreating it explicitly?

from dataclasses import dataclass
from typing import TypedDict


class PersonDict(TypedDict):
    name: str
    age: int


@dataclass
class Person:
    name: str
    age: int


def test(p: Person | PersonDict): ...


test({"name": "Jack", "age": 5})
tranquil turtle
#

you can't

oblique urchin
undone saffron
hasty phoenix
#

When did python get support for using the types directly? E.g. var: list[str] = x as opposed to from typing: var: List[str] = x ?

acoustic thicket
#

3.9

chrome hinge
#

!pep 585

rough sluiceBOT
hasty phoenix
#

Given a class Callback(Protocol): def __call__( is there a way to declare that def my_callback() is a Callback type without needing to repeat the definition?

chrome hinge
viscid spire
rare scarab
#

I really wish we had a decorator for inferring the correct type signature

#

something like ```py
class Callback(Protocol):
def call(self, x: str, *, foo: bool) -> str: ...

@implements(Callback)
def foobar(*args, **kwargs):
...

#

I feel like this may have been a rejected idea when Protocols were being discussed.

hasty phoenix
#

That would very valuable when making proxies and decorators and ensuring the outer function can be generic, but type checked proper.

undone saffron
#

If you're intentionally just using it to copy a signature, you can already do that

def implements(sig: Callable[P, R]) -> Callable[[Any], Callable[P, R]:
    return lambda f: f

you can also do this, and keep the type consistency check:

def _foobar(*args: Any, **kwargs: Any) -> str:
    ...

foobar: Callback = _foobar

Note that the signature of your foobar isn't actually consistent with that callback, and I had to modify it adding the correct return type

leaden oak
#
.nox/test-3-13/lib/python3.13/site-packages/pip/_vendor/typing_extensions.py:1375: in _set_default
    type_param.__default__ = None
E   AttributeError: attribute '__default__' of 'typing.ParamSpec' objects is not writable

does typing-extensions not support 3.13 yet?

leaden oak
#

I guess not.

oblique urchin
#

i think it's not irredeemably broken though

#

looks like you hit this in pip?

leaden oak
trim tangle
leaden oak
#

Yea, I'm aware :)

#

I don't feel like wrangling the type system more to support that

trim tangle
#

I mean, you can do that

#

but you're returning a function

leaden oak
#

ah right

trim tangle
#

e.g. if you pass in a partial object you won't get a partial back

leaden oak
#

It turns out that @functools.wraps copies over the original attributes. TIL.

leaden oak
plain beacon
dull lance
viscid spire
#

pylance huh?

trim tangle
icy obsidian
#

Am I missing something or why this does not give an error?

from abc import ABC, abstractmethod

class A(ABC):
    @classmethod
    @abstractmethod
    def some(cls) -> int:
        pass

class B(A):
    pass

print(B.some())  # <<<<<<< "some" is not defined in B, but it is an abstract method...
rustic gull
#

Give me permission

#

?

#

I am not able to talk

#

You should give me permission

trim tangle
rustic gull
#

You should give me permission

#

I am a software developer

#

I want to interact all of you

trim tangle
#

If you're talking about voice permissions, make sure to read #voice-verification. Especially the sentence between the ⚠️ signs

icy obsidian
#

Is there any way to force a child to actually implement it?

trim tangle
#

can we not...

#

#bot-commands

trim tangle
icy obsidian
trim tangle
#

that does seem like a bug to me...

icy obsidian
#

In the code I provided earlier. The error is only given upon instantiation, not actually using the class field without the instance.

#

And it seems to be the intended behavior

#

Can't find a way to declare a generic abstract class attribute :<

trim tangle
#

Maybe __init_subclass__ is a better tool for your usecase

icy obsidian
#

Yea, was going to use it, but it means there will be no type check fir it, right?

pastel egret
#

Type checkers should be able to understand that method. If not, probably suggest an enhancement.

icy obsidian
#

Then I guess I have no idea how to do that correctly... (if it's possible)

T = TypeVar('T')
class metabase(Generic[T], ABC):
    def __init_subclass__(cls) -> None:
        cls.some = 0  # <<<<<<<<<<<<<<<<<< Error here: Cannot assign to attribute "some" for class.....
        super().__init_subclass__()
class A(metabase[int]):
    pass
a = A()
a.some  # <<<<<<< No error here
A.some  # <<<<<<< But error here: Type of "some" is unknown...
#

Or is there any way to type check either a decorated method or a class attribute against a base class generic type?

#

My case is that I would like to have a generic base class for a (the closest one would be) dispatcher. The child class has to define a set of methods (with arbitrary names) that will be used for that dispatch. And I would like to be able to type check it instead of giving a runtime error if the types are wrong.

icy obsidian
#

The best case would be to have a decorator that could type check a method against the generic parent class...

dull lance
#

that way you won't even have to define abstractmethods

icy obsidian
rare scarab
#
class Foo(Protocol[T]):
  x: T
#

Protocol and TypedDict accept type arguments like Generic

undone saffron
tranquil turtle
#

pyright doesn't think that pass is an empty body, it will complain that function doesn't return anything

icy obsidian
# rare scarab Protocol and TypedDict accept type arguments like Generic
T = TypeVar('T')
class Pr(Generic[T], Protocol):
    some: T
class A(Generic[T], Pr[T], ABC):
    pass
class B(A[int]):
    pass
b = B()   # Expected Error: <<<<<<<<<<<<<<<<< Cannot instantiate as "some" is not defined
print (B.some)   # Error "Access to generic instance variable through class is ambiguous" which does not say that it is not defined

The last line will produce the same error even if B is defined like this:

class B(A[int]):
    some = 5
rare scarab
#

did you mean B.some or b.some?

icy obsidian
#

B

#

class field

rare scarab
#

use a metaclass?

icy obsidian
#

And what to do there? As far as I know metas are not loved by type checks?

rare scarab
#

metaclasses are fine. It's mixins that type checkers don't like

#

and even then, inheriting mixins is fine.

icy obsidian
#

But what will it give me in terms of forcing a child to implement it?

rare scarab
#

It would be much better with composite types. We could type mixin functions as def foo(self: Self & Foobar)

#

What exactly is your usecase?

icy obsidian
#
T = TypeVar('T')
class metabase(Generic[T], ABC):
    def __init_subclass__(cls) -> None:
        cls.some = 0   # <<<<<<< Cannot assign to attribute "some" for class
class A(metabase[int]):
    pass
a = A()
a.some
A.some  # <<<<<< Type of "some" is unknown
rare scarab
#

just annotate it

#
class myclass:
  some = 0

class A(myclass):
  pass

class B(myclass):
  some = 2

A.some # 0
B.some # 2
#

assign it in the parent and it will be the default.

icy obsidian
#

THere is no default - it is a generic class

rare scarab
#

Can you give a more concrete example?

#

no fake names

icy obsidian
#

My use case is to have a generic static variable that will be handled by a meta parent class to initialise a dispatch method

rare scarab
#

So like this? ```py
class Parent(Generic[T]):
some: T
def init_subclass(cls, *, some: T):
cls.some = some

class Child(Parent[str], some="fooo"):
pass

icy obsidian
#

Wait... I do remember trying this way, but I was not shown an error for missing argument in class definition....

#

Hmm... this might work in my case it seems

#

Have no idea what I did differently for it not to show an error.

#

Almost related to my question - is it possible to type check against a parent class in a decorator for a child method?

class B(A[int]):
    @deco  # <<<<<<<<<<<<<<<<<< To validate that "arg" is of the same type as generic parent, i.e. A[int]
    def func(self, arg: int) -> None:
        pass
rare scarab
#

Why would you need that decorator?

icy obsidian
#

Basically for the same reason as previously - to mark methods as dispatch targets, and I can handle them in parent meta.

rare scarab
#

The only reason you'd need that decorator is if you were using B.func(b, arg) instead b.func(arg)

#

!d functools.singledispatch

rough sluiceBOT
#

@functools.singledispatch```
Transform a function into a [single-dispatch](https://docs.python.org/3/glossary.html#term-single-dispatch) [generic function](https://docs.python.org/3/glossary.html#term-generic-function).

To define a generic function, decorate it with the `@singledispatch` decorator. When defining a function using `@singledispatch`, note that the dispatch happens on the type of the first argument:

```py
>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
...     if verbose:
...         print("Let me just say,", end=" ")
...     print(arg)
```...
icy obsidian
#

I know about that one, but I need my own logic for a dispatch

rare scarab
#

It's your code ¯_(ツ)_/¯

rare scarab
#

Maybe I misread your comment

icy obsidian
#

The only thing that I would like to have is that the decorator must be applied only to a specifically typed method (according to the parent specialisation)

dull lance
icy obsidian
#

It gives this error right away: "ClassVar" type cannot include type variables

dull lance
#

this passes on my end

icy obsidian
#

This one is not generic

dull lance
#
T = TypeVar('T')

class TypeA(Protocol[T]):
    some: Callable[[], T]

class A:
    @classmethod
    def some(cls) -> int:
        return 1

def foo(cls: TypeA[int]):
    print(cls.some())

def bar(cls: TypeA[str]):
    print(cls.some())

foo(A)  # Passes
bar(A)  # Fails
icy obsidian
#

Why then type hint always complain about class variable not being allowed to be generic?

dull lance
#

I'm not sure myself, but the above example can bypass that limitation

icy obsidian
#

Oh, that's waht I was talking about:

#
T = TypeVar('T')

class TypeA(Protocol[T]):
    some: T

class A:
    some = 5

def foo(cls: TypeA[int]):
    print(cls.some)

def bar(cls: TypeA[str]):
    print(cls.some)

foo(A)  # Fails
bar(A)  # Fails
dull lance
# icy obsidian Oh, that's waht I was talking about:

you just need to define some as ClassVar

T = TypeVar('T')

class TypeA(Protocol[T]):
    some: T

class A:
    some: ClassVar[int] = 5

def foo(cls: TypeA[int]):
    print(cls.some)

def bar(cls: TypeA[str]):
    print(cls.some)

foo(A)  # Passes
bar(A)  # Fails
icy obsidian
#

... why is that allowed to do that? I mean Protocol does not state that "some" is a ClassVar...?

dull lance
#

TypeA is meant to be used where you would otherwise have type[A]

#

so an "instance variable" on TypeA would be a class variable on A. that is how it bypasses the need for ClassVar on the protocol, enabling generics to be used

icy obsidian
#

Wait... Im dumb

#

I guess this was the thing I missed

#

As I trie to declare ClassVar[T] in Protocol

#

As a bit separate question - what am I missing in this?

T = TypeVar('T')
class A(Generic[T]):
    pass

DT = TypeVar('DT')
def deco(func: Callable[[A[DT], DT], None]) -> Callable[[A[DT], DT], None]:
    return func

class B(A[str]):
    @deco  # <<<<<<<<<<<< ..... Type "A[str]" is incompatible with type "Self@B"
    def func(self, arg: str) -> None:
        pass

Or it's not possible to reference the parent class at this point?

dull lance
icy obsidian
#

Yea, tried it, but it really looks ugly...

dull lance
#

But I guess that doesn't address the root of the issue. The problem is that:

  • B is a subtype of A[str]
  • Since parameters are contravariant, Callable[[B, DT], None] is a supertype of Callable[[A[DT], DT], None]
  • So, Callable[[B, DT], None] cannot be assigned to Callable[[A[DT], DT], None] as an argument to deco
icy obsidian
#

Am I missing something?
Callable is intended to get a A[DT], while we decorate a funciton that passes B, which is a valid child of A[DT?

#

pff how do you inline that?

dull lance
#

use single backtick instead of triple

icy obsidian
#

Ty

dull lance
icy obsidian
#

And I got it... Yea... Again I just took it vice versa...

#

That is strange that self: A[str] does not trigger an error then

dull lance
icy obsidian
#

True, in this case it just works as an interface

icy obsidian
#

It seems not possible to reference the generic type of a parent in a child class without limiting it to be a parent class?

icy obsidian
#

In my previous code to check that arg is of the same type as the one specified for the parent class A

dull lance
wicked grove
#

What is this [str]?

#

I've just learnt about that >: Thing[thing]< syntax

restive rapids
wicked grove
#

Yea I'm having a research rn

#

Thank you anyways

wicked grove
#

Or can I make: list[Final[str]] or str[Final]

wicked grove
#

Yea but

#

Does str[Final] raise error?

#

I'm slightly having sense on this rn

restive rapids
wicked grove
#

Yea I gotcha

#

Thanks

restive rapids
wicked grove
icy obsidian
dull lance
tacit sparrow
#

does typing_extensions comes with python 3.11 by default?

trim tangle
#

!pypi typing-extensions

rough sluiceBOT
grave fjord
tacit sparrow
#

yeah, thank you.
mainly was interested whether it actually comes with python - had multiple pythons installed and all of them had typing_extensions but I didn't remember downloading it 😅

viscid spire
#

Could've been installed as a dependency on a package you installed

tacit sparrow
#

yeah, probably

tranquil turtle
#

it is a dependency in many projects

rare scarab
cinder bone
#

I know this isn't quite type hinting, but does anyone know if there is a rule on any linter that warns about properties with side effects?

viscid spire
#

that would require that one can typehint that a function does side effects. The best you can get with that is for example MutableSequence, but still doesn't guaranteee that the function is actually mutating.

#

and then you would need a way to typehint that something makes a network call or system call with side effects

cinder bone
#

hmm I was hoping that some linter could figure out from AST that it's a non-pure function

#

Oh well, until the python type system gets better I guess my dreams are not gonna happen xD

blazing dove
#
from typing import Optional

def get_foo() -> Optional[str]:
    return None

foo = get_foo()

# Works:
if foo is not None and True:
    print(foo.split("_"))

# Fails:
predicate = foo is not None and True
if predicate: 
    print(foo.split("_")) # "split" is not a known attribute of "None" Pylance reportOptionalMemberAccess

# PS: Removing `and True` works in both cases
#

Are there ways to reduce issues in reporting of access of None for cases which should seemingly work, such as this? (Using Python 3.11.8)

paper salmon
blazing dove
#

Checking open issues, might file if you think it is correct that this behavior should work

#

Rarely see projects with such excellent attention to issues 👀

blazing dove
rustic gull
#

I wonder how could I create a TypedDict which has keys with "normally illegal" names for example

class BMHeaders(TypedDict):
  userId: int
  # Access-Token: str
  # User-Agent: str
oblique urchin
rustic gull
#

Thanks prettythumbsup

#

it works

#

Also just to avoid future misunderstandings, when I have that BMHeaders TypedDict and I need another one related to Headers which will have the same keys as BMHeaders with some of the new keys added, would I need to rewrite all of the keys like this

NewHeaders = TypedDict(
  "NewHeaders",
  { "userId": int, "Access-Token": str, "User-Agent": str}
)

or is there any faster way like inheritance? Keeping in mind that this NewHeaders class could also contain illegal names for the keys @oblique urchin

oblique urchin
rustic gull
#

Alright, got it

#

thanks once again prettythumbsup

trim tangle
#

Honestly I don't quite understand inheritance with TypedDicts. It seems like it can't decide whether extra fields are allowed or disallowed

#

Like

class Foo(TypedDict):
    x: int

class Bar(Foo):
    y: str

foo1: Foo = {"x": 1}  # ok
foo2: Foo = {"x": 1, "y": 2}  # not ok
bar1: Bar = {"x": 1, "y": 2}  # ok
foo3: Foo = bar1  # ok???
paper salmon
oblique urchin
trail kraken
#

Is there special casing? I thought it is just allowed as part of inheritance, and conversion from dict enforces matching keys.

oblique urchin
#

It's called out as a special case in the TypedDict PEP

jade viper
#

How do I type hint a class type instead of a class instance?

#

Is it like type[Class]?

#

e.g. optimizer_class: type[optim.Optimizer] = optim.Adam for the following:

oblique urchin
brazen jolt
#

what the hell have I just done lmao

#

but hey, it passed the 3.8 test CI

rapid cosmos
#

I love statically typed python. But sometimes I hate it.

#

It is improving.

pastel egret
#

A slightly faster alternative would be to use object as the dict value, since that has no effect. Doesn't work for multiple base classes though.

brazen jolt
median tangle
#

Hey, I'm trying to type-hint my method, which should be able to take any kind of mutable sequence, that consists of comparable objects (and creates a heap).
However, while there are no type issues reported by pyright within the heapify method itself, when calling heapify(x) on my list, I'm getting: ```
Pyright: Argument of type "list[int]" cannot be assigned to parameter "lst" of type "MutableSequence[T@heapify]" in function "heapify"
  "list[int]" is incompatible with "MutableSequence[T@heapify]"
    Type parameter "_T@MutableSequence" is invariant, but "int" is not the same as "T@heapify" [reportArgumentType]


I do know about invariance with mutable containers, but I was hoping that since I used a typevar here, it should be able to just bind the `int` type and not cause such issues, but apparently, that didn't work. Any ideas how to solve this?
```python
from typing import Protocol
from collections.abc import Callable, MutableSequence


class Comparable(Protocol):
    def __eq__(self, other: object, /) -> bool: ...

    def __lt__(self, other: object, /) -> bool: ...


def heapify[T: Comparable](lst: MutableSequence[T]) -> None:
    """Convert a list into a **max** heap."""
    parent: Callable[[int], int] = lambda index: (index - 1) // 2  # noqa: E731

    # Iterate through the list in reverse order, skipping the first (index 0)
    # (note: we can't use enumerate here, as it wouldn't work with reversed)
    for idx in range(len(lst) - 1, 0, -1):
        elem = lst[idx]
        parent_idx = parent(idx)
        parent_elem = lst[parent_idx]

        if elem > parent_elem:
            lst[idx], lst[parent_idx] = parent_elem, elem


def main() -> None:
    x = [5, 8, 1, 3, 6, 10, 55]
    heapify(x)
    assert x[0] == 55
#

please ping on reply

dull lance
#

in particular, int doesn't support eq/gt comparison against object

rare scarab
#

^ comparable generally only compares against itself

#

If you want to compare against other types, you should implement a Comparable protocol.

dull lance
#

also, in your code, you're actually using __gt__ comparison, not __lt__

rare scarab
#
class Comparable(Protocol[A, B]):
  def __call__(self, a: A, b: B, /) -> int:
    pass
median tangle
#

is there a way to make a better protocol that uses the python methods?

rare scarab
#

if you will only be comparing int to int, you may be able to use def __lt__(self, other: Self, /) -> bool: ...

median tangle
rare scarab
#

!d typing.Self

rough sluiceBOT
#

typing.Self```
Special type to represent the current enclosed class.

For example...
median tangle
#

self does the same

rare scarab
#

Doing it your way, the T should be a param of Protocol

median tangle
#

it doesn't work with Self either: py class Comparable(Protocol): def __eq__(self, other: Self, /) -> bool: ... gives basically the same thing: ```
Pyright: Method "eq" overrides class "object" in an incompatible manner
  Parameter 2 type mismatch: base parameter is type "object", override parameter is type "Comparable"
    "object" is incompatible with protocol "Comparable"
      "lt" is not present [reportIncompatibleMethodOverride]

rare scarab
#

I didn't see that [T]

median tangle
#

yeah

#

the point here is that object uses : object to annotate other param

rare scarab
#

What happened to lt?

median tangle
#

since it just returns NotImplemented there

median tangle
#

interestingly enough, __lt__ doesn't have that error

#

maybe cause object doesn't even define __lt__

rare scarab
#

eq should still use Any or object

median tangle
#

so this does pass pyright: ```py
class ComparableT:
def eq(self: T, other: object, /) -> bool: ...

def __lt__(self: T, other: T, /) -> bool: ...
#

but how do I even annotate heapify now?

#

I can't do T: Comparible[T] can I?

rare scarab
#

Don't use T. Just use Self

dull lance
#

this is odd. the typeshed stub doesn't work as expected

median tangle
#

oh!

dull lance
#
from collections.abc import Callable, MutableSequence
from typing import TYPE_CHECKING, TypeVar

if TYPE_CHECKING:
    from _typeshed import SupportsRichComparison

# Sorry, I'm not using Python 3.12
T = TypeVar("T", bound=SupportsRichComparison)
def heapify(lst: MutableSequence[T]) -> None:
    """Convert a list into a **max** heap."""
    parent: Callable[[int], int] = lambda index: (index - 1) // 2  # noqa: E731

    # Iterate through the list in reverse order, skipping the first (index 0)
    # (note: we can't use enumerate here, as it wouldn't work with reversed)
    for idx in range(len(lst) - 1, 0, -1):
        elem = lst[idx]
        parent_idx = parent(idx)
        parent_elem = lst[parent_idx]

        if elem > parent_elem:   # this fails!
            lst[idx], lst[parent_idx] = parent_elem, elem


def main() -> None:
    x = [5, 8, 1, 3, 6, 10, 55]
    heapify(x)
    assert x[0] == 55
median tangle
#

but this actually worked!

from typing import Protocol, Self
from collections.abc import Callable, MutableSequence


class Comparable(Protocol):
    def __eq__(self, other: object, /) -> bool: ...

    def __lt__(self, other: Self, /) -> bool: ...


def heapify[T: Comparable](lst: MutableSequence[T]) -> None:
    """Convert a list into a **max** heap."""
    parent: Callable[[int], int] = lambda index: (index - 1) // 2  # noqa: E731

    # Iterate through the list in reverse order, skipping the first (index 0)
    # (note: we can't use enumerate here, as it wouldn't work with reversed)
    for idx in range(len(lst) - 1, 0, -1):
        elem = lst[idx]
        parent_idx = parent(idx)
        parent_elem = lst[parent_idx]

        if elem > parent_elem:
            lst[idx], lst[parent_idx] = parent_elem, elem



def main() -> None:
    x = [5, 8, 1, 3, 6, 10, 55]
    heapify(x)
    assert x[0] == 55
dull lance
#

hmm

#

if I replace SupportsRichComparison with SupportsAllComparisons then it works

median tangle
#

the SupportsRichComparison fails because the types are invariant

#

I think

rare scarab
#

What's the difference there?

dull lance
dull lance
# dull lance if I replace `SupportsRichComparison` with `SupportsAllComparisons` then it work...
from collections.abc import Callable, MutableSequence
from typing import TYPE_CHECKING, TypeVar

if TYPE_CHECKING:
    from _typeshed import SupportsAllComparisons


T = TypeVar("T", bound=SupportsAllComparisons)
def heapify(lst: MutableSequence[T]) -> None:
    """Convert a list into a **max** heap."""
    parent: Callable[[int], int] = lambda index: (index - 1) // 2  # noqa: E731

    # Iterate through the list in reverse order, skipping the first (index 0)
    # (note: we can't use enumerate here, as it wouldn't work with reversed)
    for idx in range(len(lst) - 1, 0, -1):
        elem = lst[idx]
        parent_idx = parent(idx)
        parent_elem = lst[parent_idx]

        if elem > parent_elem:
            lst[idx], lst[parent_idx] = parent_elem, elem


def main() -> None:
    x = [5, 8, 1, 3, 6, 10, 55]
    heapify(x)
    assert x[0] == 55

This works for me

median tangle
#

hmm

dull lance
#

imo the typevar is redundant here

#

or not (since MutableSequence is invariant)

median tangle
#

well it's not in my way of doing it

#

yeah

#

the SupportsAllComparisons is literally a type that has all of the dunder methods for comparison

#

the SupportsRichComparison is just one that has lt or gt (union)

#

I'm not sure why it's failing with it though, as that should actually be enough

median tangle
#

it'd either use __gt__ from elem or __lt__ from param_elem in that comparison