#type-hinting

1 messages Β· Page 42 of 1

stiff acorn
#

well tuple works too

#

any iterable will work here so im not picky, i used lists for an example

restive rapids
#

if they are all strings then a typevartuple would work, [*Row](names: tuple[*Row], rows: Iterable[tuple[*Row]])

stiff acorn
#

atleast at runtime

stiff acorn
#

Although mypy didn't complain here

#
from collections.abc import Iterable
from typing import reveal_type


class MarkdownTable[*Row]:
    def __init__(self, fieldnames: tuple[*Row], rows: Iterable[tuple[*Row]]) -> None:
        self.fieldnames = fieldnames
        self.rows = rows


t = MarkdownTable(
    fieldnames=("firstname", "lastname", "age"),
    rows=(
        ("Alice", "Smith", "30"),
        ("Bob", "Johnson", "24"),
        ("Charlie", "Brown"),
    ),
)
reveal_type(t.fieldnames)
reveal_type(t.rows)
#
#

pyright does complain about the third item

oblique urchin
#

Seems like a bug in mypy

trim tangle
#

mypy seems to have inferred Row as tuple[str, ...]

#

I think it's the same case as py def pick[T](first: T, second: T) -> T if I do pick(42, "foo"), what should T be? is it int | str, object, Literal[42, "foo"], or an error?

oblique urchin
#

Yeah I don't think TypeVarTuple is supposed to allow that

stiff acorn
#

I would assume def pick[T](first: T, second: T) -> T implies that all 3 types must be the same

oblique urchin
trim tangle
# oblique urchin Yeah I don't think TypeVarTuple is supposed to allow that

How so? Seems like it's allowed https://typing.python.org/en/latest/spec/generics.html#splitting-arbitrary-length-tuples
pyright is fine with it too ```py
from collections.abc import Callable
from typing import reveal_type

class Foo[*Ts]:
def init(self, bar: tuple[*Ts]) -> None:
...

def get_callback(self) -> Callable[[*Ts], int]:
    ...

def f(xs: tuple[str, ...]):
foo = Foo(xs)
reveal_type(foo) # Foo[*tuple[str, ...]]
reveal_type(foo.get_callback()) # (*str) -> int

trim tangle
stiff acorn
oblique urchin
#

How is that different from picking object?

trim tangle
oblique urchin
#

It's the common base type of int and str

stiff acorn
#

right

#

I didn't even consider unions or the fact that we can keep going till we hit object

oblique urchin
trim tangle
#
Protocol[__str__(self) -> Protocol[__getitem__(self, arg: Literal[0], /) -> Literal["f", "4"]]]
oblique urchin
#

that's more like it

severe perch
#

what is that

#

i am new and knmow only basis can you suggest a course that make me fully advance

feral wharf
#

But you don't really need to know typing or "become advanced" at it to use Python.

delicate kiln
#

I'm trying to remove Any from a codebase and I have a case where just switching to object doesn't work and want to know how other people solved it or if they just left it as Any.

def extract_coro_stack(
    coro: types.CoroutineType[Any, Any, Any], limit: int | None = None
) -> traceback.StackSummary:
    ...

If I change the Any here to object I can't pass any coroutine to the function as the type is invariant. I don't really care about the parameters, they have no affect on the implementation of the function.

#

And are there any other cases where Any is considered idiomatic?

#

Using TypeVars there would work and would prevent any funniness if the function were changed in the future to need to use things bound by the parameters, but creating TypeVars to not use them seems... not great. It'd be nice if there was a type like the default of a TypeVar: all types match like Any, but it has no interface like object.

trim tangle
#

I think typevars wouldn't be valid here, since they'd only appear once in the signature

delicate kiln
#

The issue with Any is that if the function changes in a way where it uses the object from the parameters, you have an Any floating around. It's correct now but maybe not in the future, but it will silently keep working in the future, which is one of the big dangers of Any.

trim tangle
rough sluiceBOT
#

stdlib/types.pyi line 438

class CoroutineType(Coroutine[_YieldT_co, _SendT_contra, _ReturnT_co]):```
delicate kiln
#

Ah yeah in this case because they are covariant and contravariant I could. Thanks!

#

I had cooked up a simpler example where the type was invariant and was having trouble with that, but grafted that issue onto my real issue.

oblique urchin
#

in cases where you want to accept all specializations

restive rapids
trim tangle
#

I'm actually not sure if it's illegal

trim tangle
#

so maybe I'm hallucinating

restive rapids
#
def f[T](x: T) -> int:
    ...
warning: TypeVar "T" appears only once in generic function signature
  Β Β Use "object" instead (reportInvalidTypeVarUse)

type Phantom[A, B] = A

def f[T](x: T) -> Phantom[int, T]: # fine
    ...

lol

trim tangle
#

pyright does complain here

#

weird

trim tangle
#

I'm gonna post it here as well I guess. Apparently it is possible to make something resembling TypeScript's zod that does not require inspecting annotation, even with a spicy partial() transformation. It is very scuffed but it does work (on pyright)

rough sluiceBOT
wooden cipher
#
a: Iterator[int] = chain([1, 2, 3], [1, 2, 3])
a: Iterable[int] = chain([1, 2, 3], [1, 2, 3])

is it normal that mypy is ok with both versions?

#

it should only be Iterator, no?

hoary pagoda
#

all iterators are iterables in Python

#

iter(an_iterator) is an_iterator for all iterators

wooden cipher
hoary pagoda
#

not necessarily

wooden cipher
#

then what's the point of iterable?

hoary pagoda
#

that it may do so

#

and that you can iterate over it

#

generators, files are some examples of iterators you usually use as iterables.

#

but list can be iterated over multiple times.

wooden cipher
#

:/
you're destroying my belief system :p

#

thanks πŸ™Œ

rare scarab
#

!e This is possible. ```py
xes = ["x", "y"] * 10
for x in (it := iter(xes)):
y = next(it)
print(x, y)

rough sluiceBOT
stiff acorn
#
def foobar(is_bar: bool):
    def decorator[**P, T](f: Callable[P, T]) -> Callable[P, T]:
        @wraps(f)
        def decorated_function(*args: P.args, **kwargs: P.kwargs) -> T:
            if is_bar:
                pass
            else:
                pass

            return f(*args, **kwargs)

        return decorated_function

    return decorator

how would i type this?

#

I tried to type it but I can't get the return type right

rare scarab
#

foobar returns a function that accepts a function that returns a function.

#
def foobar(*, is_bar: bool) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
stiff acorn
#

that worked thank you

stiff acorn
#
import typing as t

def format_int(i: t.SupportsInt)->str:
    return f"{int(i):,d}"
    
format_int("100000000") # type error but works at runtime
format_int(100_000_000)
#

How do I type this function?

rough sluiceBOT
#

stdlib/builtins.pyi line 246

def __new__(cls, x: ConvertibleToInt = ..., /) -> Self: ...```
`stdlib/_typeshed/__init__.pyi` lines 352 to 356
```py
# Anything that can be passed to the int/float constructors
if sys.version_info >= (3, 14):
    ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex
else:
    ConvertibleToInt: TypeAlias = str | ReadableBuffer | SupportsInt | SupportsIndex | SupportsTrunc```
trim tangle
#

why do you want this to accept strings though, that seems like a bit of a footgun

stiff acorn
#

I'm typing an untyped project, so I have to type what it does rather than what I want it to be

trim tangle
#

I would do something like i: int | str if that's what's being passed to the function at runtime

stiff acorn
#

Yes, it receives both integers and strings

#

I guess I can just do the simple union

trim tangle
#

but that seem unnecessary

stiff acorn
#
from typing import reveal_type

from flask_sqlalchemy import SQLAlchemy
from flask_sqlalchemy.model import Model

db = SQLAlchemy()

class BaseModel(Model):
    __abstract__ = True

class FooBar(BaseModel):
    __tablename__ = "foobar"


query = db.session.query(FooBar) 

reveal_type(query)
t.py:15: error: Call to untyped function "query" in typed context  [no-untyped-call]
    query = db.session.query(FooBar) 
            ^~~~~~~~~~~~~~~~~~~~~~~~
t.py:17: note: Revealed type is "Any"
#

Why does this fail?

#
    @overload
    def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...
_EntityType = Union[
    Type[_T], "AliasedClass[_T]", "Mapper[_T]", "AliasedInsp[_T]"
]
mint inlet
#

For overloads, if the parameter is Any the return type becomes Any. Is Model a subclass of Any?

#

Oh it isn’t! Not sure then.

rare scarab
#

If an overload accepts Any, make sure it's defined last.

solid sleet
trim tangle
solid sleet
#
Type "Self@Gaps[T@Gaps]" is not assignable to return type "Gaps[float]"
  "Gaps[T@Gaps]*" is not assignable to "Gaps[float]"
    Type parameter "T@Gaps" is invariant, but "T@Gaps" is not the same as "float"PylancereportReturnType

and

Argument of type "list[Endpoint[float]]" cannot be assigned to parameter "endpoints" of type "Sequence[T@Gaps | Endpoint[T@Gaps]]" in function "__init__"
  "list[Endpoint[float]]" is not assignable to "Sequence[T@Gaps | Endpoint[T@Gaps]]"
    Type parameter "_T_co@Sequence" is covariant, but "Endpoint[float]" is not a subtype of "T@Gaps | Endpoint[T@Gaps]"
      Type "Endpoint[float]" is not assignable to type "T@Gaps | Endpoint[T@Gaps]"
        Type "Endpoint[float]" is not assignable to type "T@Gaps"
        "Endpoint[float]" is not assignable to "Endpoint[T@Gaps]"
          Type parameter "T@Endpoint" is covariant, but "float" is not a subtype of "T@Gaps"PylancereportArgumentType
trim tangle
#

Oh I see

trim tangle
# solid sleet ``` Type "Self@Gaps[T@Gaps]" is not assignable to return type "Gaps[float]" "G...

consider this ```py
@dataclass
class Apple[T]:
banana: list[T]

@classmethod
def make_default(cls) -> Apple[int]:
    return cls([1, 2, 3])

in `make_default`, the type of `cls` is `type[Self@Apple[T@Apple]]`. I'm trying to call that and pass in a list of ints, clearly that's wrong, since `int` isn't necessarily the same as `T`. This can be fixed with a constraint on `cls`py
@classmethod
def make_default(cls: type[Apple[int]]) -> Apple[int]:
return cls([1, 2, 3])

#

or in your case ```py
@classmethod
def from_string(cls: type[Gaps[float]], gaps: str) -> Gaps[float]:

solid sleet
#

cls: type[Gaps[float]] is insane and python should be ashamed

#

but that does fix it

trim tangle
#

It's funny that pyright does tell you that the type is type[Self[T]], even though Self[T] is illegal

#

it's saying "haha, you can't have it, but I can"

trim tangle
#

actually... that doesn't seem to work

#

crap

solid sleet
#

i had already gave up on lol

trim tangle
#

seems like you're always going to have Gaps[float] in subclasses

#

If you don't plan to subclass it, make make it final and add add *bergudgingly* a staticmethod

#

or a classsmethod that calls Gaps instead of cls

solid sleet
#

i hate it

trim tangle
solid sleet
#

ok i think i fixed all the errors, i'm just gonna leave the classmethod alone and returning Gaps[float] not going to worry much about it

vague oar
#

Some standard library modules like threading are missing type annotations for some function parameters. Is there a reason for that? Would a PR adding them be welcome?

restive rapids
vague oar
#

Oh, I think I was looking at .py files instead

rough sluiceBOT
#

lib/sqlalchemy/orm/scoping.py line 1627

def query(self, _entity: _EntityType[_O]) -> Query[_O]: ...```
rare scarab
#

That's not Any. That's a generic

last venture
#

Currently, I am developing Mutli AI system.

#

I amd detecting fall, fire, violence, and choking

stiff acorn
last venture
#

using python, opencv, yolo, pytorch

#

Who's interested in this?

rare scarab
rare scarab
feral wharf
#

looks sick tho

rough sluiceBOT
#

stdlib/builtins.pyi lines 1891 to 1892

@overload
def sum(iterable: Iterable[bool | _LiteralInteger], /, start: int = 0) -> int: ...```
`stdlib/builtins.pyi` lines 240 to 242
```py
_PositiveInteger: TypeAlias = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
_NegativeInteger: TypeAlias = Literal[-1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18, -19, -20]
_LiteralInteger = _PositiveInteger | _NegativeInteger | Literal[0]  # noqa: Y026  # TODO: Use TypeAlias once mypy bugs are fixed```
stiff acorn
#

What am I missing here?

#
from collections.abc import Callable, Sequence
from typing import Literal, Any

type Decorator[**P, T] = Callable[[Callable[P, T]], Callable[P, T]]

def route[**P, T]( # type: ignore[empty-body]
    rule: str,
    /,
    *,
    methods: Sequence[Literal["GET", "POST", "PUT", "DELETE", "PATCH"]] = ["GET"],
) -> Decorator[P, T]: ... 
    
@route("blah", methods = ["GET"])
def foo(a: int, b: str) -> bool: ... # type: ignore[empty-body]

reveal_type(foo)
main.py:13: error: Argument 1 has incompatible type "Callable[[int, str], bool]"; expected "Callable[[VarArg(Never), KwArg(Never)], Never]"  [arg-type]
main.py:16: note: Revealed type is "def (*Never, **Never) -> Never"
restive rapids
# stiff acorn What am I missing here?

i mean there is technically nothing to bind the typevars from and there are no direct higher ranked types like that, though pyright works with it
i'd make the return type a protocol or something, or implement the decorator as a class

class Decorator(Protocol):
  def __call__[**P, T](self, fn: Callable[P, T], /) -> Callable[P, T]:
    ...

def route(rule: str, *, methods: ...) -> Decorator:
  ...

# :

class route:
  rule: str
  methods: ...
  def __call__[**P, T](self, fn: Callable[P, T], /) -> Callable[P, T]:
    ...
stiff acorn
#

I can't alter the implementation, I'm just trying to type it

restive rapids
stiff acorn
#

that did the trick, thank you!

spark dragon
#

Hi, I'm writing models for sqlalchemy. Two different models are in different files, with one-to-many relationship declared between them, in the type hint I write the class name in quotes to avoid cyclic imports. But Pyright gives an error that a model from another module must be imported. How to fix it?

workouts: Mapped[list['WorkoutsModel']] = relationship(back_populates='plan')

Error:

"WorkoutsModel" is not defined"
rare scarab
proper nimbus
#

is this really how typing is done in python?

def query[T](decoded: type[T], *args: str) -> T:
    if typing.get_origin(decoded) is list[Struct]:
        fields = structs.fields(cast(type, typing.get_args(decoded)[0]))
    elif typing.get_origin(decoded) is Struct:
        fields = structs.fields(cast(type, decoded))
    else:
        raise TypeError("Unsupported type passed to query():", decoded)

    names = ",".join(field.encode_name for field in fields)
    with send_unchecked("query", args[0], names, *args[1:]) as sock:
        return json.decode(sock.recv(4096), type=decoded, strict=False)
rare scarab
#

No.

#

You should use a library like pydantic or msgpack instead

proper nimbus
#

I am using msgpack

oblique urchin
#

typing.get_origin(decoded) is list[Struct] seems dubious

#

cast(type, decoded) also seems dubious if decoded is a type[T]

proper nimbus
#

it was the only way I could find to silence pyright errors

proper nimbus
#

how's this

def query[T](response_type: type[T], *args: str) -> T:
    if typing.get_origin(response_type) is list:
        inner_type = cast(type, typing.get_args(response_type)[0]) 
        if issubclass(inner_type, Struct):
            struct_class = inner_type
        else:
            raise TypeError("Unsupported type:", response_type)
    elif issubclass(response_type, Struct):
        struct_class = response_type
    else:
        raise TypeError("Unsupported type:", response_type)

    fields = structs.fields(struct_class)
    names = ",".join(field.encode_name for field in fields)

    with send_unchecked("query", args[0], names, *args[1:]) as sock:
        data = sock.recv(4096)
        return json.decode(data, type=response_type, strict=False)
tranquil turtle
#

handrolling runtime typechecking is probably not the most robust idea

proper nimbus
merry falcon
restive rapids
trim tangle
#

That's definitely a bug

#

(pyright doesn't complain)

merry falcon
#

Great, thanks!

mint inlet
#

Maybe report that to the mypy issue tracker so one day you can remove the workaround!

hardy linden
#

Is it a supported use-case for TypeVars to respect "closures" with regard to nested classes?

I've got a use-case where I've essentially got a dispatcher that's calling a function on the values of an iterable, and using a small class to track some metadata about the dispatched tasks. (There's obviously more to it than that, but I don't think the details are relevant. Feel free to ask, though, if it'd be useful.)

The class for the dispatched tasks is tiny, and won't get used anywhere else, so I'd ideally like to nest it within the class for the dispatcher. Also, I want to "bind together" the input/output types of the callable, the iterable, and the dispatched tasks so that a caller knows what types of output values will be produced (i.e. the return type from the callable).

I thought I could do this by using the same TypeVar instances between the outer and inner classes, where the inner class's TypeVars would "inherit the meaning of" the scope of the outer class. However, it doesn't seem like that actually works:

class Dispatcher(Generic[InputType, ReturnType]):
    fn_to_call: Callable[[InputType], ReturnType]
    iterable: Iterable[InputType]
    # And then a bunch of other stuff, not relevant here

    # Error: "TypeVar _ is already in use by an outer scope"
    class DispatchedTask(Generic[InputType, ReturnType]):
        input_value: InputType
        output_value: ReturnType
        # And then a bunch of other stuff, not relevant here

# No typing errors when DispatchedTask isn't a nested class
class DispatchedTask(Generic[InputType, ReturnType]):
    input_value: InputType
    output_value: ReturnType

Should this actually be working, and I'm just doing something wrong, or is this just not supported? (I'm using Pyright via Pylance v2025.6.1 in VS Code.)

(I can see how to get this to work by either using a different TypeVar name or just un-nesting the class, but I really thought this would work, and I'm curious about why not.)

trim tangle
#

I would probably just have it as a non-inner class

spiral fjord
#

Generic type alias within class cannot use bound type variables lame

hardy linden
tacit sparrow
#

A dumb question - what type aliases are needed for, and why I shouldn't just define Vector = list[float] instead of Vector : TypeAlias = list[float]?
Is there any cases when adding TypeAlias is critical and things would work differently if it's not added?

The only benefit I've found is Vector: TypeAlias = "list[int]" allows creating a type alias without evaluating list[int] at runtime.
But then if someone will miss TypeAlias nature of a variable, they might run into 'str' is not callable at runtime for Vector([1,2,3]), though there are no typing errors.

hardy linden
#

For me, the TypeAlias type hint is always just there for clarity, never for any functional reason

#

Especially in contexts like setting up a type alias within a class, where it typically goes up near the top along with all of the other class-attribute type hints; doing foo: TypeAlias = bar instead of just foo = bar makes it clear that this is just for type-aliasing purposes and isn't supposed to be a class attribute or a normal sort of class constant

viscid spire
#

don't forget the type keyword (added in 3.12)

#
type Vector = list[int]
tacit sparrow
#

I haven't read through type keyword PEP or it's discussion, but it's kind of surprising that breaking syntax change was introduced just for some code clarity πŸ˜…

tacit sparrow
novel notch
tacit sparrow
#
# SyntaxError: invalid syntax
type test = int
novel notch
#

Isn't this the new syntax that is available since 3.12?

feral wharf
#

Indeed

novel notch
#

So what does @andrej mean?

#

Of course new syntax doesn't work in old python version?

feral wharf
#

Yeah idk

#

It's not a breaking change when it was introduced in a 3.x release

rare scarab
#

if you need to use 3.10 union syntax on 3.9, wrap it in a string

feral wharf
#

Won't work at runtime though

rare scarab
#

If you string wrapped type alias has a generic, its usage much also be wrapped in string

#

or add from __future__ import annotations

#

Who cares about runtime?

feral wharf
#

Libraries that do runtime checking?

tacit sparrow
trim tangle
#

I was under the impression that type checkers would disallow using a type alias in a runtime context, but apparently I'm wrong

trim tangle
#

yeah it seems like this is the main use case for it

TypeAlias is particularly useful on older Python versions for annotating aliases that make use of forward references, as it can be hard for type checkers to distinguish these from normal variable assignments:

viscid spire
novel notch
restive rapids
tranquil turtle
tacit sparrow
#

Okay, I've meant forward compatibility, thanks for making fun of me

feral wharf
#

A bit late for that correction init

merry falcon
#

How do I create a generic class with a type parameter bound to the same generic class, whose default value is Self? Ideally, I'd like to write something like this:

from abc import abstractmethod
from typing import Self

class C[T: C = Self]: # This is the issue
    @abstractmethod
    def __matmul__(self, other: Self | T) -> Self:
        pass

For context as to why I would want this: C represents a generic class of mathematical functions that can be composed, using @, (technically a magma), T represents an additional class that is also allowable as input for composition.

I've tried using TypeVar, aliases, strings but always seem to run into some issue and can't remove the cyclic definition errors.

viscid spire
#

No ok I got it twisted, typevar default would make it fine...

#

But the typevar can never be bound otherwise then πŸ€”

#

Is there some relevant code you didn't share?

merry falcon
#

There's more to it, but I've stripped it down to the nub of (this particular) issue. The class can't be instantiated as it's an abstract class, but subclasses override the abstract methods. For example, I have AffineFunction and PiecewiseAffineFunction subclasses, where the latter would (ideally) override T's value with AffineFunction.

merry falcon
restive rapids
# merry falcon How do I create a generic class with a type parameter bound to the same generic ...

Self itself is kind of a typevar bound by implicit self: Self in normal methods or cls: type[Self] in classmethods, and you cant specify a typevar as a typevar default, so uh yeah thats why what you're trying doesn't work
if the only place you use it in is other: Self | T, the default for T could be Never, so it'd kinda cancel out to other: Self, but allow you to override it in subclasses to another type

merry falcon
viscid spire
#

but hey you found a solution

tranquil turtle
shell schooner
#
Missing: TypeAlias = Union[UnsetType, T, None]
Missing[str] # equivalent to Union[UnsetType, str, None]

This blew my mind, why is this possible and which part of the docs say that this is possible? I didn't know Union's also behaved like generics if they had TypeVars in them

trim tangle
#

You could also do e.g. py ListOrTuple: TypeAlias = list[T] | tuple[T, ...] and then use ListOrTuple[str]

shell schooner
#

thats very cool, thanks

mossy nymph
#

hello
T = TypeVar("T", decimal, float)
does that mean decimal and float are restricted??

viscid spire
#

it means that T can only represent those exact types, and not subtypes

#

so if you pass a subtype in, it will be treated as the parent

#
from typing import TypeVar

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

def f(x: T) -> T:
    return x

x: bool = True
reveal_type(f(x)) # int, even tho we specifically passed in a bool
mossy nymph
#

Thanks bud

echo knot
#

Is it normal that this does not raise any issue with Mypy strict? Doesn't it violate the Liskob Substitution principle?

from __future__ import annotations

from abc import abstractmethod

from typing_extensions import override


class A:
    @abstractmethod
    def __init__(self, label: str) -> None:
        pass

class B(A):
    @override
    def __init__(self) -> None:
        pass
#

I also tried with and without (abtstractmethod and ABC). All possible combinations

#

Apparently it raises an error only if I rename the init to anything else

spiral fjord
echo knot
#

That's terrible in my opinion πŸ™†πŸ»β€β™‚οΈ

trim tangle
echo knot
trim tangle
#

sorry, I meant that they should check all other class and instance methods

#

not just instance methods as Grote said

safe mural
#

Is it considered bad practice to have an optional argument with type hint e.g. zoom: int = None? zoom: int | None = None feels unnecessarily verbose

rare scarab
#

x: int | None = None is very common

#

But remember if x changes the behavior of the function, it's probably best to make a new function instead.

safe mural
rare scarab
#

Not sure what you mean.

safe mural
rare scarab
#

I'm asking what's the point of the optional argument?

#

python has optional arguments for a reason, but don't use them to give one function the duty of two.

trim tangle
# safe mural Is it considered bad practice to have an optional argument with type hint e.g. `...

zoom: int = None is incorrect, zoom: int | None = None would be correct
https://peps.python.org/pep-0484/#union-types

A past version of this PEP allowed type checkers to assume an optional type when the default value is None, as in this code:

def handle_employee(e: Employee = None): ...

This would have been treated as equivalent to:

def handle_employee(e: Optional[Employee] = None) -> None: ...

This is no longer the recommended behavior. Type checkers should move towards requiring the optional type to be made explicit.

terse sky
#

Am I missing something or is the interaction between typing and coroutines/asyncio, pretty bad?

#

hmm maybe I'm missing some package with typing stubs

#
async def count() -> int:
    print("One")
    await asyncio.sleep(1)
    print("Two")
    return 1

async def blub():
    y = [x.result() async for x in asyncio.as_completed(count() for _ in range(3))]

given this, when I hover over y pylance just says it's a list

#

hmm maybe it's the use of async for? mypy doesn't seem to like that

#

though it works just fine

#

same with pylance

oblique urchin
terse sky
#

yeah, if I change it to a normal for it works

terse sky
oblique urchin
#

looks like as_completed supports async for only in 3.13

terse sky
#

ahhhh got it

#

thanks for that

#

so, what is the proper way to annotate my count function itself? is it really Coroutine[None, int, None] ?

#

actually, sorry

#

[None, None, int]

#

I'm surprised there isn't some kind of convenience alias I guess

oblique urchin
terse sky
#

but maybe Any instead of None?

oblique urchin
#

I guess we always put Any for the first two type params to Coroutine, I've never invested the time to figure out exactly what they need to be

#

I think it's mostly relevant to the internals of the async framework

terse sky
#

it's the send type and yield type, I suppose reflective of the fact that you have these 3 features of coroutines, and you could use all 3

trim tangle
terse sky
#

It jsut feels like in practice, 99% of the time people are not going to be using all 3 of those types simultaneously.
And a lot of the time they'll just be writing Coroutine[Any, Any, Whatever]

trim tangle
terse sky
#

yes, you're right, my bad

trim tangle
oblique urchin
#

you can also use Awaitable[int] which is often interchangeable with Coroutine[Any, Any, int]

#

though there are some operations for which you need a Coroutine and not an arbitrary Awaitable

terse sky
#

yeah, typing basically says not to use it in most cases though

#

i think for the average person Awaitable is going to typically be a trap because it's not at all obvious when you need Coroutine, and Awaitable may not be very interesting in the sense of admitting a lot more types

trim tangle
#

in particular because async functions don't let you annotate those two parameters, and it would be an enormous breaking change if they required an explicit Coroutine type

terse sky
#

yeah I was just thinking a convenience alias

#
_T = TypeVar("_T")
AsyncFunc = Coroutine[Any, Any, _T]
#
async def run_all(coroutines: Iterable[AsyncFunc[_T]]) -> list[Task[_T]]:
    async with asyncio.TaskGroup() as tg:
        tasks = [tg.create_task(c) for c in coroutines]
    
    return tasks

I think i"ll probably just be using this simple wrapper a lot

#

Which works well vis-a-vis typing

terse sky
#

actually, going through the docs, I found this

#

Important In this documentation the term β€œcoroutine” can be used for two closely related concepts:

a coroutine function: an async def function;

a coroutine object: an object returned by calling a coroutine function.

#

so a better annotation would probably be CoroutineObject or AsyncObject for what I had above

low escarp
#

I have the following code which I'd like to have type hinted correctly... I'm not super confident with how Concatenate and ParamSpec's work which I think is why I'm getting stuck:

from ctypes import Structure
from collections.abc import Callable
from typing_extensions import Concatenate, Generic, ParamSpec, TypeVar
from typing import Any, Optional

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


class FunctionHook(Generic[P, R]):
    ...


class function_hook():
    def __init__(self, sig: Optional[str] = None):
        self.sig = sig

    def __call__(self, func: Callable[Concatenate[Structure, Any, P], R]) -> FunctionHook[P, R]:
        ...


class Thing(Structure):
    @function_hook("test")
    def my_func(self, arg1, arg2):
        pass
#

The issue is with the function_hook decorator...

rare scarab
#

You're missing arg2

trim tangle
# low escarp I have the following code which I'd like to have type hinted correctly... I'm no...

The error provides a hint: Structure is not assignable to Thing.
When you have a method inside a class, the first argument (self) doesn't require a type annotation and is assumed to have the type of Self, which must be at the very least an instance of your current class (Thing). So you can't treat it as a function that accepts any Structure -- i.e. it would be valid to do this in function_hook.__call__ ```py
class DefinitelyNotThing(Structure):
...

class function_hook:
def call(self, func: Callable[Concatenate[Structure, Any, P], R]) -> FunctionHook[P, R]:
# somewhere inside of FunctionHook:
func(DefinitelyNotThing(), 42, *args, **kwargs)

So you either need to annotate `self` to be any Structure (probably a bad idea but maybe it works in your case) ```py
    @function_hook("test")
    def my_func(self: Structure, arg1, arg2):
        pass
``` or introduce an extra typevar, which might need to be propagated to `FunctionHook` as well: ```py
S = TypeVar("S", bound=Structure)

class function_hook():
    def __call__(self, func: Callable[Concatenate[S, Any, P], R]) -> FunctionHook[P, R]:
                                                                # -> FunctionHook[S, P, R] probably
        ...
low escarp
#

I know the error says "Structure is not assignable to Thing" but this confused me because Thing is a subclass of Structure, so the error doesn't seem clear to me (at least how I am (mis)understanding it)

#

but thanks for clearing this up

trim tangle
low escarp
#

ah ok thanks, that makes sense!

low escarp
#

Ok, actually got another one I'm curious about...
So I have a decorator like so:

def on_key_release(event: str):
    def wrapped(func: Callable[..., Any]) -> KeyPressProtocol:
        func._hotkey = event
        func._hotkey_press = "up"
        return func

    return wrapped

Where

class KeyPressProtocol(Protocol):
    _hotkey: str
    _hotkey_press: str

I get a type error that _hotkey is not present when I hover over return wrapped. It goes away if I type func as KeyPressProtocol, but is that correct to do so?

rare scarab
#

This is a good use for # type: ignore

low escarp
#

Ok, and maybe last one for now...
Is it possible to indicate to a type checker that a function is static without using the @staticmethod decorator?
Similar to the above, I have another decorator which looks like:

class static_function_hook():
    def __init__(self, sig: Optional[str] = None):
        self.sig = sig

    def __call__(self, func: Callable[P, R]) -> FunctionHook[P, R]:
        ...

If I have the following:

class Thing(Structure):
    @static_function_hook("another")
    def my_static(arg1: int, arg2: float):
        pass

It complains (rightfully so!) that the type of arg1 isn't right...
I think I could add @staticmethod as the first decorator, but just wondering if there is another way as this decorator will always be applied to static methods...

rare scarab
#

You must add @staticmethod directly on the function

low escarp
#

Damn, ok. Was hoping I might be able to like... Make my own decorator which acts as a staticmethod decorator while adding some extra functionality

grand furnace
#

Hi,
I get those errors in my CI https://github.com/moi15moi/VideoTimestamps/actions/runs/15890903217/job/44813130877?pr=14#logs:

video_timestamps/video_timestamps.py:6: error: Module "video_timestamps.video_provider" does not explicitly export attribute "ABCVideoProvider"  [attr-defined]
video_timestamps/video_timestamps.py:6: error: Module "video_timestamps.video_provider" does not explicitly export attribute "FFMS2VideoProvider"  [attr-defined]

I don't understand why it happen.
So, in video_timestamps/video_timestamps.py, I import the video_provider module which import ABCVideoProvider and FFMS2VideoProvider.

Note that both of these class are defined with pybind11 (in c++), so I have created .pyi files here, but mypy seems to ignore them?

stiff acorn
fleet ember
trim tangle
grand furnace
inner kayak
#

Hello!
I'm trying to type hint a custom "container" class.
It's basically a dictionary, and you can add things to it by a key, and you can get things from it using a key.

Is it possible in python to type hint it so that the key passed to the add_your_thing function and the get_your_thing must be of the "Mapping" type the user defined?

Here's some pseudo-code to better show what I mean:

class SomeContainer[YourMappingType: dict[str, Any]]:
    _data: YourMappingType 

    def __init__(self):
        self._data = {}

    def add_your_thing(self, key: MustBeAKeyOf[YourMappingType], value: YourMappingType[key]):
        self._data[key] = value

    def get_your_thing(self, key: MustBeAKeyOf[YourMappingType]) -> YourMappingType[key] | None:
        return self._data.get(key)

Would appreciate any help!
Thanks πŸ˜„

restive rapids
inner kayak
restive rapids
#

in this exact case thats impossible with current typesystem and typecheckers because the type variable is resolved on creation, and in container = SomeContainer() there is nothing to resolve it from

#

and if you want each key to have a specific type - thats not possible with a custom class, only TypedDict gets that because its special for whatever reason
why not define classes for structures with knows keys?

inner kayak
#

I see, thats what I was assuming. Thanks for confirming it. Kind of a bummer though!

Well, I was trying to do a generic thing where the user of this container would define the "mapping" of each key to each type using a generic somehow

restive rapids
# inner kayak I see, thats what I was assuming. Thanks for confirming it. Kind of a bummer tho...
from __future__ import annotations
from typing import Any, LiteralString, Literal, reveal_type

type HasHead[Name: LiteralString, Field] = Container[Name, Field, Any]
type HasTail[Name: LiteralString, Field] = Container[Any, Any, HasField[Name, Field]]
type HasField[Name: LiteralString, Field] = HasHead[Name, Field] | HasTail[Name, Field]

class Hack[Name: LiteralString, FixContainer]:
    def __call__[Field](self: Hack[Name, HasField[Name, Field]]) -> Field: ... 

class Container[HeadName: LiteralString, HeadField, Tail = None]:
    def add[Name: LiteralString, Field](self, name: Name, field: Field) -> Container[Name, Field, Container[HeadName, HeadField, Tail]]:
        ...
    def __getattr__[Name: LiteralString](self, name: Name) -> Hack[Name, Container[HeadName, HeadField, Tail]]: ...

def test(container: 
    Container[Literal["x"], int, 
    Container[Literal["y"], str,
]]) -> None:
    reveal_type(container.x()) # int
    reveal_type(container.y()) # str
    reveal_type(container.z()) # error, as expected
    
    modified = container.add("z", 3.5)
    reveal_type(modified.x()) # int
    reveal_type(modified.y()) # str
    reveal_type(modified.z()) # float

(thanks to fix-error for inspiration)

trim tangle
#

Alternatively (what aiohttp does for example, it's more flexible because it allows different values of the same type) ```py
class Key[T]:
def init(self, name: str) -> None: ...

CEO_KEY = KeyPerson
CTO_KEY = KeyPerson

class Map:
def put[T](self, key: Key[T], value: T) -> None: ...
def get[T](self, key: Key[T]) -> T: ...

store = Map()
store.put(CEO_KEY, Person("Alice"))
store.put(CTO_KEY, Person("Bob"))

rare scarab
#

Technically you don't need to init with a name, it would only be used in the repr

restive rapids
rare scarab
#

That makes sure you can't initialize the same key with a different generic

trim tangle
tawny shore
#

hi

rustic gull
#

challenge, make this type-safe

#
class StringBuilder:
    def commasep[T](
        self,
        items: list[T],
        callback: Callable[..., None],
        pass_self: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        for i, item in enumerate(items):
            if pass_self:
                callback(self, item, *args, **kwargs)
            else:
                callback(item, *args, **kwargs)
            if i != len(items) - 1:
                self.print(", ")
restive rapids
#

overload over pass_self Literal False and Literal True, use a paramspec for args and kwargs, use typing.Self or an explicit typevar for self

rustic gull
#

its also being subclassed

#

so callback self param should know about the subclass

#

how would i do that?

brazen jolt
#

you can typevar on self

rustic gull
#

i also kinda wanted to do param spec

#

but then i have no control over the signature

restive rapids
#

why are you using variadic arguments if there is a specific signature?

rustic gull
#

there isn't a specific signature

#

only thing is that the first argument may consume self

restive rapids
rustic gull
#

if you use param spec, you have no control over the signature

#

in the type hint

restive rapids
#
from collections.abc import Callable, Sequence
from typing import Concatenate, no_type_check, overload, Literal

class StringBuilder:
    @overload
    def commasep[T, **P](
        self,
        items: Sequence[T],
        callback: Callable[Concatenate[T, P], None],
        pass_self: Literal[False] = False,
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> None: ...
    @overload
    def commasep[Self, T, **P](
        self: Self,
        items: Sequence[T],
        callback: Callable[Concatenate[Self, T, P], None],
        pass_self: Literal[True],
        *args: P.args,
        **kwargs: P.kwargs
    ) -> None: ...
    @no_type_check # why is this not the default in overload implementations? idk. its not like you can actually give a correct hint to commasep in here, thats why we're using overload in the first place
    def commasep(
        self,
        items,
        callback,
        pass_self = False,
        *args,
        **kwargs
    ) -> None:
        for i, item in enumerate(items):
            if pass_self:
                callback(self, item, *args, **kwargs)
            else:
                callback(item, *args, **kwargs)
            if i != len(items) - 1:
                self.print(", ")
#

(this doesnt really need to be a sequence tbh, iterable could work with a bit of manual next'ing and handling stopiteration)

#

and you can do this concatenate thing without concatenate btw, its not really special here

class CallbackNoSelf[T, **P](Protocol):
  def __call__(self, x0: T, /, *args: P.args, **kwargs: P.kwargs) -> None: ...

class CallbackWithSelf[Self, T, **P](Protocol):
  def __call__(self, x0: Self, x1: T, /, *args: P.args, **kwargs: P.kwargs) -> None: ...
#

tbh im not a fan of argument passthrough like that, i'd rather just pass lambda: callback(*args, **kwargs)
makes all your code littered with these paramspecs

brazen jolt
#

why the @no_type_check? I'd do; ```python
def commasep(
self,
items: Sequence[object],
callback: Callable[..., None],
pass_self: bool = False,
*args: object,
**kwargs: object,
) -> None:

restive rapids
#

this is a pretty useless signature, the body wont be typechecked properly

brazen jolt
#

it's not

restive rapids
#

the reason for using overload in the first place is you cant give a correct signature

brazen jolt
#

but it does give some value

restive rapids
#

what value?

brazen jolt
#

well, without it, you wouldn't even know that items is a sequence, or that pass_self is a bool inside of the func body, no?

#

pyright would just treat them all as unknown

restive rapids
#

with @no_type_check, its basically like slapping # type: ignore on the scope of the function, it wont care. the signatures are given by overloads.

brazen jolt
#

yeah, but that's not good

restive rapids
#

no, thats just overloads being stupid in python.

brazen jolt
#

you get no internal type checking

#

why not have the body of the func type checked

#

even if it's not as strongly type-checked

#

it's still beneficial imo

restive rapids
#

i disagree.

brazen jolt
#

well, that's obv up to you, but I like the body of my overloaded functions to be type checked, just like with any other funcs, in this case, it is a small body sure, but still, it gives you the same benefit that type checking would give you for any other code

restive rapids
#

no, its not the same benefit at all. it prevents from very trivial mistakes (which are avoided by reading the names of parameters), it does not actually enforce correct relationships between the types of arguments.

brazen jolt
#

why loose up on checking that items is used properly as a list?

#

if you were to use some specific functions to sequence inside of the func body, you'd get that validated, why give up on it?

restive rapids
#

"if i were..."

brazen jolt
#

I mean, even len is technically one

restive rapids
#

i dont care about annotating the implementation of overloaded functions because in most cases its not useful. i dont even think about it at this point.

@overload
def f(x: X1, y: Y1) -> Z1: ...
@overload
def f(x: X2, y: Y2) -> Z2: ...
def f(x: X1 | X2, y: Y1 | Y2) -> Z1 | Z2: ... # ah yes such useful, amazing! (no)
brazen jolt
#

I've never seen someone have that stance, so I find it pretty interesting, I mean, if you like type-checking in general, I'd imagine you'd want it everywhere

brazen jolt
#

or won't call something that neither X1 nor X2 has

#

I do see a benefit there

#

like sure, if you're just defining signatures, it doesn't matter

restive rapids
brazen jolt
#

but if that func has a body, and you care about type-checking your code, then that does matter

brazen jolt
restive rapids
#

the typechecker wont like you using x.f2() until you narrow x: X1 | X2 to X2 (which is usually implied by the overload context, but the typechecker doesnt get it)

brazen jolt
restive rapids
#

why should i write more code than needed? im sure the implementation is correct, the typechecker just doesnt get it.

brazen jolt
#

I just prefer to have it statically validated that it is indeed correct

restive rapids
brazen jolt
#

especially when the function body of the overload is larger

restive rapids
brazen jolt
#

and for a single flag, it's ususally managable

#

or use a higher type, if you don't need specific things and your impl works in general with just those, which it often actually does, like in the case with this one, no casts were even necessary

restive rapids
#

as i said, i dont even think about the specific case anymore as i've spent too much time proving that my overload implementations are "correct" to stupid typecheckers because python overloads suck.

brazen jolt
#

fair enough

rustic gull
#

i dont think its possible

restive rapids
rustic gull
#

i didn't read your msg

#

i concluded from my trials

#

i forgot about concatenate

#
    @overload
    def commasep[T, **P](
        self,
        items: Sequence[T],
        callback: Callable[Concatenate[T, P], None],
        pass_self: Literal[False] = False,
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> None: ...
    @overload
    def commasep[Self, T, **P](
        self: Self,
        items: Sequence[T],
        callback: Callable[Concatenate[Self, T, P], None],
        pass_self: Literal[True],
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> None: ...
    def commasep[T](
        self,
        items: Sequence[T],
        callback: Callable[..., None],
        pass_self: bool = False,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        for i, item in enumerate(items):
            if pass_self:
                callback(self, item, *args, **kwargs)
            else:
                callback(item, *args, **kwargs)
            if i != len(items) - 1:
                self.print(", ")
#

atleast it makes it more type-safe inside the definition

brazen jolt
#

yeah, I agree

#

the generic [T] in the implementation doesn't give you much though

#

that's why I suggested just Sequence[object]

rustic gull
#

yea

#

why not Any

brazen jolt
#

Any will let you do items[0].somethingthatmightnotexist()

#

without reporting a problem

rustic gull
#

fair

#

!e

print(isinstance(1, object))
rough sluiceBOT
brazen jolt
#

I always tend to prefer object if the type can be anything arbitrary, unless there's a good reason for Any, like that you want to be able to make assumptions without casting

regal summit
#
from abc import ABC
from pydantic import BaseModel

from typing import ClassVar


class Foo[E: BaseModel](ABC):
    extras_cls: type[E]

    def __init_subclass__(cls, extras_cls: type[E]) -> None:
        cls.extras_cls = extras_cls
        return super().__init_subclass__()

    def extras(self) -> E:
        ...


class Extras(BaseModel):
    ...


class Bar(Foo, extras_cls=Extras):
    pass


b = Bar()
reveal_type(b.extras())
``` hi there, pylance thinks b.extras() here returns Unknown, I would like it to be Extras
#

is there a way I can use this method of using init subclass and having the typing be correct, also noticed I cant use ClassVar with type[E] since it doesnt allow type inside of ClassVar, which is fine if I can do it another way

#

of course I can use py class Bar(Foo[Extras], extras_cls=Extras): but that requires me to type it twice, heh

rare scarab
#

You're missing a typevar on Foo

#

If you omit a typevar, it implies Any, which is resolved to the bound type, BaseModel

#

strict mode will catch this.

#

maybe pyright standard too

regal summit
#

is E not interpreted as a typevar with the new generic syntax

rare scarab
#
class Bar(Foo[Extras], extras_cls=Extras): pass
#

btw, you can access Extras without extras_cls by using cls.__orig_bases__ I think

regal summit
regal summit
rare scarab
#
class Foo[E: BaseModel]:
  def __init_subclass__(cls):
    if hasattr(cls, "_extras_cls"):
      return
    for base in cls.__orig_bases__:
      if typing.get_origin(base) is Foo:
        cls._extras_cls, = typing.get_args(base)
        return

class Bar(Foo[Extras]):
  pass
regal summit
#

ah that could work

rare scarab
#

This information should still be available via type(b)

jolly cipher
rare scarab
#

!d types.get_original_bases is in 3.12

rough sluiceBOT
#

types.get_original_bases(cls, /)```
Return the tuple of objects originally given as the bases of *cls* before the [`__mro_entries__()`](https://docs.python.org/3/reference/datamodel.html#object.__mro_entries__) method has been called on any bases (following the mechanisms laid out in [**PEP 560**](https://peps.python.org/pep-0560/)). This is useful for introspecting [Generics](https://docs.python.org/3/library/typing.html#user-defined-generics).

For classes that have an `__orig_bases__` attribute, this function returns the value of `cls.__orig_bases__`. For classes without the `__orig_bases__` attribute, [`cls.__bases__`](https://docs.python.org/3/reference/datamodel.html#type.__bases__) is returned.

Examples:
jolly cipher
rare scarab
#

fun fact: doing class X(Base[Y]) is a common way to reify generics.

feral wharf
#

Is there any way to type metaclass parameters or whatever they're called?

class MyClass(MetaClassSubclass, option1=..., option2=...):
restive rapids
feral wharf
#

Hmmmm

icy obsidian
#

Is it intended that Pylance shows hint for __init__ on hover while if you type in the brackets it shows hint from __new__?

class Base[T]:
    def __new__(cls) -> T:
        return super().__new__(cls, *args, **kwargs) # type: ignore
    def __init__(self, target: T) -> None:
        self._target: T = target

class Der(Base[int]):
    def __init__(self, target: int) -> None:
        super().__init__(target)

a = Der()
#

What I mean is this

vague oar
#

idk, but it shows this anyway

Mismatch between signature of __new__ and __init__ in class "Der"
  Signature of __init__ is "(target: int) -> None"
  Signature of __new__ is "() -> int"PylancereportInconsistentConstructor
icy obsidian
#

But is it possible to have different signature for new and init without overriding new in the child class?

rare scarab
#

T strings would be pretty useful for building typing literals I think.

#
type EventName = Literal[t"on_{str}"] 
icy obsidian
#

Would it be possible to override init arguments without overriding new?

feral wharf
icy obsidian
#

Yeah... the thing I would like to have is a proxy object (encapsulate an arbitrary object, in my case numpy.random.Generator and forward a set of methods to it).
And the code above was the closest I could get to it, but there are some typing issues I encountered and have no idea if it is even possible to resolve.
Also not sure if Pylance should show different signatures there (or am I wrong?).

rough sluiceBOT
#

Please react with βœ… to upload your file(s) to our paste bin, which is more accessible for some users.

amber kelp
rough sluiceBOT
# amber kelp

Please react with βœ… to upload your file(s) to our paste bin, which is more accessible for some users.

vague oar
#

i have an example code like this

from typing import TypedDict

user = dict(name="Name", age=100)
print(user)

items = user.items()
keys = user.keys()
values = user.values()


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


typed_user = User(name="Name", age=100)
print(typed_user)

typed_items = typed_user.items()
typed_keys = typed_user.keys()
typed_values = typed_user.values()

and items, keys, values are being typed as dict_items[str, str | int], dict_keys[str, str | int] and dict_values[str, str | int] by type checker

while typed_items, typed_keys, typed_values are being typed as dict_items[str, object], dict_keys[str, object] and dict_values[str, object]

Why is it that the dict one is being typed better than TypedDict and what could be changed to have it being typed better?

rough sluiceBOT
#

stdlib/_typeshed/_type_checker_internals.pyi lines 40 to 42

def items(self) -> dict_items[str, object]: ...
def keys(self) -> dict_keys[str, object]: ...
def values(self) -> dict_values[str, object]: ...```
vague oar
#

is it because TypedDict is a function?

#

is that for backwards compatibility?

oblique urchin
vague oar
#

why?

#

what should i use instead then instead of TypedDict?

pydantic?

trim tangle
# vague oar why?
class Foo(TypedDict):
    x: str

class Bar(TypedDict):
    x: str
    y: int

bar: Bar = {"x": "a", "y": 1}
foo: Foo = bar  # ok 
``` it's only disallowed when assigning a literal, which can be a bit confusing
oblique urchin
vague oar
oblique urchin
rustic gull
#
def record(func: Callable[..., Any]) -> type: ...


@record
def user(name: str, age: int) -> None:
    pass


foo: user = {
    "name": "Alice",
    "age": 30,
}

print(foo)
#

is it possible to type hint this?

rare scarab
#

Use a ParamSpec

rustic gull
#

how will you make it into a TypedDict?

rare scarab
#

Oh, you want it to be a typed dict.

#

converting a function spec to a TypedDict isn't supported.

rustic gull
#

i actually want it to be a named tuple

rare scarab
#

But the other way around is possible.

rustic gull
#

or dataclass

rare scarab
#
from typing import NamedTuple

class User(NamedTuple):
  name: str
  age: int

foo = User("Harry", 2)
print(foo)
#
from dataclasses improt dataclass

@dataclass
class User:
  name: str
  age: int
from typing import TypedDict

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

foo: User = {
  "name": "Alice",
  "age": 30,
}
#
def function_that_accepts_user_args(*args: Unpack[User_namedtuple]):
  print(args[0], args[1])

def function_that_accepts_user_kargs(**kwargs: Unpack[User_typeddict]):
  print(kwargs['name'], kwargs['age'])
#

I can't remember if Unpack supports named tuple or not.

#

it's probably just a tuple[str, int] type

#

It does support namedtuple. Oddly, reveal_type on args shows ```py
tuple[builtins.str, builtins.int, fallback=main.User_namedtuple]

#

Not sure what the fallback is for

stoic plover
#

ok this is probably a stupid question, but why is there a whole channel dedicated to an optional feature in python

rare scarab
#

It's a popular feature

pliant vapor
#

is variance determined by typecheckers or specified in python?

#

and following that, how do i check the variance of parameters in generic types

#

looking specifically for collections.abc.Generator

pastel egret
# pliant vapor is variance determined by typecheckers or specified in python?

Both. We've got two types of syntax for typevars, old and new. The old typing.TypeVar() syntax has explicit parameters to indicate variance, while the new dedicated class Blah[T] syntax infers it. But the inference rules are anyway a good way you can identify what variance is allowed: If a variable is present in a return type or on a readable attribute, it can be covariant. If the variable is a parameter or on a writable attribute, it can be contravariant. If both, it has to be invariant.

As to how to identify generic type variances, you can follow that rule, or in most cases look at the definition - by convention typevars have _co or _contra to indicate variance. For Generator, Yield and Return are covariant, while Send is contravariant.

#

The spec has a more precise algorithm for variance inference, but it's quite a bit more technical.

pliant vapor
#

for another question
if i have

class Foo[T](Protocol)
    def foo() -> T: ...

class Bar(Foo[Sequence[int]]):
    @override
    def foo() -> Sequence[int]: ...

is there any way to avoid the duplicate Sequence[int]? basedpyright doesn't seem to infer it if i leave it off

pastel egret
#

There isn't currently no, according to the spec etc. Unless basedpyright has an option for it.

pliant vapor
#

shame

#

i suppose i should make it a type alias anyways

pastel egret
#

There are reasons for this - it means when looking at the method body you don't have to look up the caller to check the types, and it protects against an inadvertent change in the base class later since the child would then immediately error. But it can be repetitive.

trim tangle
oblique urchin
#

Also planning to add a bit of a testing framework that validates in CI that the examples fit the criteria

#

Was going to advertise it a bit more after that's done, but feel free to contribute now

trim tangle
oblique urchin
#

thanks, fixed

trim tangle
oblique urchin
#

I did just add that nonexamples folder. I guess I could accept more there but I'm wary of this turning into an alternative bug tracker for those type checkers

#

You know what I'll just document the nonexamples folder and organize it a bit. I can always drop it if it turns out too noisy

trim tangle
#

I think it's useful to document unsoundness instances that type checkers agreed are not worth fixing, i.e. are by design

trim tangle
oblique urchin
#

nice example though, I'll merge it once the branch is clean

trim tangle
#

Yeah rebasing again worked for some reason, thanks

#

I un-drafted it

oblique urchin
#

put two small comments on GH

trim tangle
#

well well well, my UPS failed and now I have a buggy repo

(jelle-unsoundness) [df@duckpond jelle-unsoundness]$ git stash
BUG: diff-lib.c:622: run_diff_index must be passed exactly one tree
Aborted (core dumped)
oblique urchin
#

finding unsoundness not just in Python but in git, all for the price of one

trim tangle
#

not sure if git is going to accept this as a bug

#

presumably some filesystem nodes failed to sync to the disk due to my UPS failure

rough sluiceBOT
#

examples/cast/cast.py lines 4 to 5

def func(x: int) -> str:
    return cast(str, x)```
trim tangle
#

this does of course work around that

def _helper(x: object) -> str:
    return cast(str, x)

def func(x: int) -> str:
    return _helper(x)
oblique urchin
#

Somewhat dumb example but mypy has a mode that warns if you use Any anywhere, that would catch the simple Any example

#

Could have a summary table that shows e.g. mypy in standard mode, mypy in maximum strictness, etc. and lists whether each example succeeds in each case

#

I think the mutable_inplace one could be generalized without relying on the stdlib, the problem seems to be that it's unsound to add __ior__ in a subclass if the base class has __or__

cinder bone
oblique urchin
#

typeshed has a type ignore on set.__ior__

lunar dune
oblique urchin
#

but my repo wants to use this to create an unsafe type conversion (a function that is declared to return str but actually returns int)

#

I think you can leverage something like len() narrowing for that

lunar dune
#

oh yeah, I think neither of them ban overriding __len__ on fixed-length tuple subclasses

lunar dune
oblique urchin
lunar dune
#

oh, do they just fall back to the tuple.__new__ signature in typeshed for tuple subclasses?!

oblique urchin
#

But yeah, I think this is fixable and the fix is to special-case the constructor for tuple subclasses to only allow a compatible tuple

#

Speaking of constructors, I don't have any examples of unsound __init__ overrides yet

vague oar
#

I think I have something but I’ll check a bit later

vague oar
#
class SomeClass:
    x: int | str

    def func_a(self) -> None:
        self.x = "hi"

    def func_b(self) -> int:
        self.x = 0
        self.func_a()
        x: int = self.x
        return x

I’m not sure if you are taking about something like this, or that it works, but I think it does

gleaming goblet
#

question regarding type variables and generics

#

I'd like to be able to actually access the class object associated with a TypeVar at run time but this doesn't really work since its more of a type-check only construct.

#

The best way around this that I've found so far is something like:

from typing import TypeVar

T = TypeVar("T")

class MyGenericClass[T]:
    def __init__(self, also_my_type: type[T]) -> None:
        self.attribute_of_variable_type: T = also_my_type()

#

where I usually set T = TypeVar("T", bound=<some kind of Protocol or Abstract class which needs to be initialized>)

#

and actually where this comes from is that I have a class with a generator method which yields a dictionary which works just fine at runtime but I really dislike having to use the whole dict["key"] syntax and I also dislike the lack of type hinting on the yielded dicitonary.

The dictionary's keys are determined by the caller to said class although I suppose I could have the caller pass in a TypedDict as the type variable instead but that doesn't resolve the dict["key"] syntax which I dislike.

#

hmmm... let me try and spin up some example code of my current solution so yall can actually suggest improvements

trim tangle
#

Do you specifically need the class object for something later? Or do you just want some way of creating a T, and you happen to have types that have the same __init__ shape?

gleaming goblet
#
  1. I don't understand how the callable approach is "more flexible" necessarily... Does this do anything more than allow me to pass in arbitrary functions which also return T (but which also take no arguments).
  2. I've been working on Python 3.10 for the longest time and I take it the "type hint" on the T generic acts as a... bound? constraint? Either way that's a pretty neat (and somewhat amusing) syntax.
gleaming goblet
trim tangle
#
from abc import ABC, abstractmethod


class Foo(ABC):
    @abstractmethod
    def __init__(self, bar: int) -> None:
        ...

class Baz(Foo):
    def __init__(self, bar: int, surprise: str) -> None:
        ...
``` ```
Success: no issues found in 1 source file

trim tangle
# gleaming goblet 1. I don't understand how the callable approach is "more flexible" necessarily.....
  1. Yes, it lets you pass in an arbitrary callable. On the flip side, it doesn't leak the detail that your interface implementors must have the same __init__ (maybe you'll have a new implementor of a protocol that must have something passed in the __init__). If you're into acronyms, this is following the "Interface Segregation Principle" -- if you only need something to be callable and produce a T, specify that as the requirement.
    1b. And with the above, __init__ shape can be unreliable with type checking. You could make a classmethod if you really want to accept the class object
  2. That's the bound= argument, yes. I got a bit confused because you used both the 3.12 syntax and the old TypeVar() which was unused
#

With protocols it's even worse: type[SomeProtocol] lets you invoke a no-argument constructor because... reasons

from typing import Protocol


class Foo(Protocol):
    def bar(self) -> int: ...

class TheFoo:
    def __init__(self, required: int) -> None:
        self.whatever = required

    def bar(self) -> int:
        return 69

def baz(f: type[Foo]) -> None:
    print(f())

baz(TheFoo)  # boom
``` the whole notion of a `type[SomeProtocol]` seems a bit iffy to me, because a protocol is a structural type describing an instance, and it doesn't reliably signal what sort of things the parent has (e.g.: the protocol might specify `@property def foo(self) -> int`, but that doesn't mean that `Implementation.foo` exists at all -- maybe the instance has it as a plain attribute and not a property)
gleaming goblet
gleaming goblet
gleaming goblet
trim tangle
#

(well, it's required that it can be called with 0 arguments, it could have e.g. an optional argument)

gleaming goblet
trim tangle
#

e.g. with defaultdict: you can do defaultdict(int) (passing a class object), but you can also do defaultdict(lambda: defaultdict(int)) (passing a factory function)

gleaming goblet
trim tangle
#

the answer to your question is yes, it would stop working (as intended, I assume)

trim tangle
gleaming goblet
trim tangle
#

yes, it's just Callable[[], T]

gleaming goblet
#

question: how do I redirect from the Python docs to the associated implementation source code for CPython?

#

is there a good way to do this or do I just scour the CPython github?

trim tangle
#

what for in particular?

#

If there's a Python implementation, there's usually a link at the top of the doc page

#

If there isn't, it's a bit complicated. It could be in Objects/ (if it's a class like int (longobject.c for historical reasons)) or Modules/ if it's a module like math (mathmodule.c)

gleaming goblet
#

I'm looking for the defaultdict implementation

gleaming goblet
#

I assume they keep the type hints separate since they were tacked on later or...?

#

perhaps a backwards compatibility thing?

rough sluiceBOT
#

Lib/collections/__init__.py line 57

from _collections import defaultdict```
`Modules/_collectionsmodule.c` line 2198
```c
/* defaultdict type *********************************************************/```
trim tangle
gleaming goblet
#

aha, I also just realized that in VSCode I can also just import defaultdict and then right-click and select "go to definition" to quickly get the type hint

trim tangle
#

yep

#

you can also Ctrl+Click or press F12

#

I use Go To Definition and Go To References a lot

gleaming goblet
#

and that the separate stubs are more necessary for the standard library than anything else...?

trim tangle
#

in your own (new) code, there's almost never a good reason to have stub files

#

it's mostly for native extensions and for libraries that don't want to adopt annotations in the codebase

gleaming goblet
#

and native extensions are... Python code which directly wrap some "low level" code? (guessing based off a quick google search)

hallow flint
#

yes, low level code, typically written in a compiled language

trim tangle
#

like what numpy uses for fast processing of arrays

hallow flint
#

interacting with python via the python c api

trim tangle
#

sometimes they're called C extensions, but I want to be more inclusive for those 2.5 Rust extensions in the Python ecosystem πŸ™‚

#

okay, there's probably more than 2.5

#

oh and C++ extensions with pybind11, tensorflow for instance

gleaming goblet
#

the more you know 🌠

#

let me try and spin up an example of what I have now and you let me know if there's any part of it which you think doesn't make sense to have

gleaming goblet
gleaming goblet
#

feedback and recommendations are greatly appreciated

gleaming goblet
trim tangle
#

otherwise you have to use TypeVar from typing-extensions

gleaming goblet
#

TypeVar was removed from the usual typing module?

rare scarab
#

No.

gleaming goblet
#

Then why "TypeVar from typing-extensions" and not just "TypeVar from typing"?

trim tangle
gleaming goblet
#

... I'm pretty sure TypeVar from typing has had the default argument even in 3.10

#

Unless somehow I've been implicitly importing the typing-extensions version despite clearly using from typing import TypeVar

trim tangle
#

oh maybe

trim tangle
#

It's 3.13 and not 3.14 though as Jelle said

gleaming goblet
#

Hmmm maybe I'm remembering something incorrectly then. Got it

gleaming goblet
#

Actually before that, let me make one last revision

#

well that's just great now its working on one computer but not the other

#
F = TypeVar("F", default=dict)

class BagComboSelector(Generic[F]):
    # !! ERROR !!
    # Incompatible default for argument "bag_combo_factory" (default has type
    # "type[dict[_KT, _VT]]", argument has type "Callable[[dict[Any, Any]], F]") Mypy
    # (assignment)
    def __init__(self, *bags: Bag[Any], bag_combo_factory: Callable[[dict], F] = dict) -> None:
#

mypy isn't happy that I'm making my default factory method the dict class (which I believe should be a callabe which accepts a dict as its argument and returns a dict).

rare scarab
#

Fully type the dict. i.e. default=dict[str, list[object]]

gleaming goblet
#

wasn't necessary. I just updated my other laptop's VSCode profile to match the one without the error and the error went away

#

I don't see why typing the dict would help here. I believe all Python dictionaries conform to Callable[[dict], dict]

trim tangle
gleaming goblet
#

I don't see that link the in existing pins

gleaming goblet
#

oh jk found it

trim tangle
gleaming goblet
#

okay well my code did fail the mypy check although it supposedly "works" on both my computers now :P

#

that's just great

trim tangle
#

Maybe you don't have mypy running on both of your computers?

gleaming goblet
#

it should be? I'm using the Microsoft MyPy extension with the daemon disabled

#

here is the failing code.

trim tangle
#

so if you want this to be compatible with mypy, you need an @overload

gleaming goblet
#

...

#

I understand this conceptually but I definitely don't know how I would write it out

trim tangle
#
@overload
def __init__(self: BagComboSelector[dict], *bags: Bag[Any], bag_combo_factory: Callable[[dict], dict] = ...) -> None: ...
@overload
def __init__(self: BagComboSelector[F], *bags: Bag[Any], bag_combo_factory: Callable[[dict], F]) -> None: ...
gleaming goblet
#

I feel like the core of the issue is that the type variable and the factory argument are decoupled in the eyes of the type checker and idk how to make them coupled

gleaming goblet
trim tangle
#

yes

gleaming goblet
#

and so now BagComboSelector[int]() should give an error stating it doesn't match any function signatures?

trim tangle
#

Yep

#

I guess it should be something like ```py
@overload
def init[K, V](self: BagComboSelector[dict[K, V]], *bags: Bag[Any], bag_combo_factory: Callable[[dict], dict[K, V]] = ...) -> None: ...
@overload
def init[F](self: BagComboSelector[F], *bags: Bag[Any], bag_combo_factory: Callable[[dict], F]) -> None: ...

gleaming goblet
trim tangle
#

where?

gleaming goblet
#

in place of repeating BagComboSelector[dict[K, V]]

trim tangle
#

self: Self is redundant and it doesn't constrain self in any way

gleaming goblet
#

what about self: Self[dict[K, V]]

trim tangle
#

parameterizing Self is not allowed

#

E.g. if you do: py class IntComboSelector(BagComboSelector[int]): # no generic parameters class TwoParameterComboSelector[A, B](BagComboSelector[tuple[A, B]]): # new unrelated generic parameters what would Self[X] mean?

restive rapids
gleaming goblet
#

okay I am definitely missing something

#
F = TypeVar("F", default=dict)


class BagComboSelector(Generic[F]):
    @overload
    def __init__[K, V](
        self: "BagComboSelector[dict[K, V]]",
        *bags: Bag[Any],
        bag_combo_factory: Callable[[dict], dict[K, V]] = dict,
    ) -> None:
        pass

    @overload
    def __init__[F](
        self: "BagComboSelector[F]",
        *bags: Bag[Any],
        bag_combo_factory: Callable[[dict], F] = dict,
    ) -> None:
        pass
#

before even getting to the actual implementation this is already bleeding red

#

and admittedly I've lost sight of how this is meant to help

#

so I want to define two __init__ methods such that if someone calls BagComboSelector[<type that isn't dict>] then they will be required to use the __init__ which explicitly provides the new factory method, yes?

gleaming goblet
gleaming goblet
trim tangle
#

actually I think it's not needed, it should still work if you want a BagComboSelector[dict[str, int]]

#
    @overload
    def __init__(
        self: BagComboSelector[dict],
        *bags: Bag[Any],
        bag_combo_factory: Callable[[dict], dict] = ...,
    ) -> None:
        pass
```  should be fine
#

The second overload should be py @overload def __init__[F]( self: BagComboSelector[F], *bags: Bag[Any], bag_combo_factory: Callable[[dict], F], ) -> None: pass the entire point of the overload is that a factory should be required if you want something other than dict in F

gleaming goblet
trim tangle
#

(since it won't be used at runtime and doesn't impact the typing)

#

btw, you can use from __future__ import annotations so that you don't need to quote forward referenced types

gleaming goblet
trim tangle
#

(as in, you can remove that line once you don't need to support <=3.13)

gleaming goblet
#

also it appears to be incompatible with the fact that I'm already using F at the class scope

trim tangle
#

ah, maybe you need ```py
@overload
def init[T](
self: BagComboSelector[T],
*bags: Bag[Any],
bag_combo_factory: Callable[[dict], T],
) -> None:
pass

#

I think this should work too though ```py
@overload
def init(
self,
*bags: Bag[Any],
bag_combo_factory: Callable[[dict], F],
) -> None:
pass

gleaming goblet
#
F = TypeVar("F", default=dict)


class BagComboSelector(Generic[F]):
    @overload
    def __init__(
        self: BagComboSelector[dict],
        *bags: Bag[Any],
        bag_combo_factory: Callable[[dict], dict] = ...,
    ) -> None:
        pass

    @overload
    def __init__(
        self,
        *bags: Bag[Any],
        bag_combo_factory: Callable[[dict], F],
    ) -> None:
        pass

    def __init__(self, *bags: Bag[Any], bag_combo_factory: Callable[[dict], F] = dict) -> None:
        # implementation
#

mypy says this implementation isn't compatible with the two overloads

#

ah yeah wait

#
def __init__(
        self,
        *bags: Bag[Any],
        bag_combo_factory: Callable[[dict], F] | Callable[[dict], dict] = dict,
    ) -> None:

fixed :)

#

this is driving me crazy

#

do classes not conform to Callable based on their init method?

trim tangle
gleaming goblet
trim tangle
#

ah, if the latter works it should be fine

gleaming goblet
#

which is frustrating me because if bag_combo_factory is dict (default value) then calling bag_combo_factory() should cast dict back to dict anyways and it clearly works at runtime

trim tangle
#

You're already working around a mypy limitation, so # type: ignore seems like a decent choice. Just make sure to understand whether/why what you're doing is sound

gleaming goblet
#

question: is pyright in a better state than MyPy atm?

rare scarab
#

pyright is faster (to run and to adopt new features)

gleaming goblet
trim tangle
gleaming goblet
trim tangle
gleaming goblet
#

the issue is Callable[[dict], T] as opposed to Callable[[dict], dict]

trim tangle
#

yes

gleaming goblet
#

probably because T isn't bound by dict?

#

only defaults to dict

trim tangle
#

Just like if you had py class Box[T]: def __init__(self, value: T = 69) -> None: self._value = value 69 isn't a valid T so it's not allowed as a default

#

whereas pyright accepts this, but notes it and makes sure the default isn't used when it doesn't make sense

oblique urchin
#

generic parameters with defaults are somewhat dubious in general

gleaming goblet
oblique urchin
#

if your code works it's fine, just explaining why mypy might reject some patterns pyright accepts

gleaming goblet
#

which is the same as fix error suggested but I think I got my head turned around until I simplified it

indigo halo
gleaming goblet
#

thank you madduck, very cool

indigo halo
#

sorry πŸ˜‰

#

<enter> was too quick

#
    def fire(
        self,
        *,
        on_modified_cb: Callable[[FileSystemEvent, float | None], None]
        | type[Undefined] = Undefined,
    ) -> None:
        event = FileSystemEvent(str(self._path), is_synthetic=True)

        if on_modified_cb is self.Undefined:
            self.on_modified(event)

        else:
            reveal_type(on_modified_cb)     ● Pyright: Type of "on_modified_cb" is "((FileSystemEvent, float | None) -> None) | type[Undefined]"
            on_modified_cb(event, None)     ● Pyright: Expected 0 positional arguments

this is code I am struggline with. Why does it say "Expected 0 positional arguments"? Neither the Callable signature in the function defition, not a constructor of Undefined would take 0 positional arguments…

#

If I use None instead of Undefined (a subclass of this class), it works. But I try to avoid using None to convey meaning.

trim tangle
#

e.g. ```py
class MyList[T]:
def zip[U](self, other: MyList[U]) -> MyList[tuple[T, U]]:
...

gleaming goblet
trim tangle
#

no, it should be inferred

gleaming goblet
#

ah, the zip example makes it pretty clear. Thanks

gleaming goblet
#

next question from yours truly:
how do I get @property and @overload to play nicely together?

trim tangle
gleaming goblet
trim tangle
#

so you need a function like ```py
def is_undefined(x: object, /) -> TypeIs[type[Undefined]]

or a guard that your type checker understands (like `isinstance` or `subclass`)
indigo halo
indigo halo
gleaming goblet
#

ah I see now that your default argument is the class Undefined and not an instance of it

indigo halo
#

yeah, hacky me πŸ˜‰

gleaming goblet
#

... I'm not too familiar with singletons. Would an instance of a singleton class be the same as that class objects itself?

#

I know that there is both None and NoneType in Python although I don't fully understand the distinction. I also know that None works in the place of NoneType

indigo halo
indigo halo
gleaming goblet
#

so... None is None() returns true or is it invalid to even attempt to instantiate None?

#

time to find out

#

NoneType is not callable πŸ˜›

indigo halo
#

None is not callable, so None() should return AttributeError

gleaming goblet
#

TypeError but yeah

indigo halo
#

darn

trim tangle
gleaming goblet
trim tangle
gleaming goblet
#

I mean it should be in theory but MyPy isn't keeping up... I think I've found another workaround though. Thanks

#

I think that's it for me for the time being btw. Thank you very much @trim tangle. Kudos and five dollars to you.

feral wharf
#

[T = Default] is supported in 3.13 right?

gleaming goblet
feral wharf
#

3.14?

gleaming goblet
#

but I am told it is coming in 3.14

#

yes

feral wharf
#

ahh okay, ty!

trim tangle
#

@feral wharf @gleaming goblet it's in 3.13, I was wrong before

feral wharf
#

OH?

trim tangle
#

!e

class Foo[T = int]:
    pass
rough sluiceBOT
feral wharf
#

that's amazing

#

ty Love

gleaming goblet
#

I swear this was giving me an error earlier but yeah now its not 🀷

#

I also definitely suspect there is something wrong with my MyPy configuration in VSCode... its very inconsistent about reporting errors

errant tulip
#

How do I annotate type[subclass of BaseClass]?

mypy playground

terse sky
#

If I want a type for argparse, that represents ArgumentParser, ArgumentGroup and MutuallyExclusiveGroup - i.e. all the things with add_argument

#

is there any approach better than writing my own Protocol?

rare scarab
#

type[BaseClass]

terse sky
#

I could also use |, but the actual types of ArgumentGroup and MutuallyExclusiveGroup are "private"

rare scarab
terse sky
#

simple namespace?

rare scarab
#

Basically the same as argparse.Namespace

terse sky
#

yeah, that's not what I'm after though

#

I want a type for all the things that have add_argument

rare scarab
#

There isn't one in stdlib afaik

#

Oh wait, yes there is.

terse sky
#

So i guess writing my own Protocol is probably the cleanest solution, or I can use | if I want to be hacky?

#

ooh

rare scarab
#

argparse._SomethingContainer I forget the full name

terse sky
#

am I allowed to use it though? if it's _ prefixed

rare scarab
#

Sure

terse sky
#

_ActionsContainer, I guess

#

I thought that _ prefix meant that it was an implementation detail, "private", so it would be considered fragile to depend on it?

errant tulip
rare scarab
#

dynamic class creation isn't really compatible with static type checking

#

Maybe you'll want NewType instead

#

!d typing.NewType

rough sluiceBOT
#

class typing.NewType(name, tp)```
Helper class to create low-overhead [distinct types](https://docs.python.org/3/library/typing.html#distinct).

A `NewType` is considered a distinct type by a typechecker. At runtime, however, calling a `NewType` returns its argument unchanged.

Usage...
rare scarab
#

!e ```py
from typing import TypeVar, NewType

class MyMetaclass(type):
"""Simple metaclass that creates classes."""

def __init__(
    cls,
    name: str,
    bases: tuple[type],
    attrs: dict[str, str]
) :
    print("__init__")

class BaseClass(metaclass=MyMetaclass):
"""Base class."""

Runtime: Works perfectly

MyClass = NewType("MyClass", BaseClass)
instance = MyClass(BaseClass())
print(f"Created: {instance}")
print(f"Is subclass? {isinstance(instance, BaseClass)}")

rough sluiceBOT
rare scarab
#

It does need an actual instance of BaseClass though

#

it's usually used for primitive types like int or str, so I'm not sure if it's best for your usecase

rustic gull
#
def create_starlette_application(*args, **kwargs) -> Starlette:
    return Starlette(*args, **kwargs, routes=_routes)

#

how to hunt this

vale arrow
#

How can I add type hinting in a Lambda function?

#

And also how can I put a custom type hints?

spiral fjord
#

You can hint a lambda function if you assign it to a variable but that kinda defeats the point of a lambda

#

Type checkers might be able to infer what a lamba does though

#

!e

from typing import reveal_type

reveal_type(map(lambda x: x * 1.5, [1,2,3]))```
rough sluiceBOT
spiral fjord
#

(At static type checking time this say map[float])

stiff acorn
#
from typing import reveal_type

import ffmpeg # https://pypi.org/project/typed-ffmpeg/

out = ffmpeg.probe_obj("")
reveal_type(out)
assert out is not None
reveal_type(out)
reveal_type(out.streams)
assert out.streams is not None
reveal_type(out.streams)
reveal_type(out.streams.stream)
assert out.streams.stream is not None
reveal_type(out.streams.stream)
s = sorted(out.streams.stream, lambda s: s.index)
reveal_type(s)
❯ uv run --with mypy mypy --strict t.py
t.py:6: note: Revealed type is "Union[ffmpeg.ffprobe.schema.ffprobeType, None]"
t.py:8: note: Revealed type is "ffmpeg.ffprobe.schema.ffprobeType"
t.py:9: note: Revealed type is "Union[ffmpeg.ffprobe.schema.streamsType, None]"
t.py:11: note: Revealed type is "ffmpeg.ffprobe.schema.streamsType"
t.py:12: note: Revealed type is "Union[builtins.tuple[ffmpeg.ffprobe.schema.streamType, ...], None]"
t.py:14: note: Revealed type is "builtins.tuple[ffmpeg.ffprobe.schema.streamType, ...]"
t.py:15: error: No overload variant of "sorted" matches argument types "tuple[streamType, ...]", "Callable[[Any], Any]"  [call-overload]
t.py:15: note: Possible overload variants:
t.py:15: note:     def [SupportsRichComparisonT: SupportsDunderLT[Any] | SupportsDunderGT[Any]] sorted(Iterable[SupportsRichComparisonT], /, *, key: None = ..., reverse: bool = ...) -> list[SupportsRichComparisonT]
t.py:15: note:     def [_T] sorted(Iterable[_T], /, *, key: Callable[[_T], SupportsDunderLT[Any] | SupportsDunderGT[Any]], reverse: bool = ...) -> list[_T]
t.py:16: note: Revealed type is "Any"
Found 1 error in 1 file (checked 1 source file)

how come s is Any?

restive rapids
rough sluiceBOT
#

src/ffmpeg/ffprobe/schema.py line 295

index: int | None = None```
stiff acorn
#

such a stupid oversight from me lol

broken surge
#

bro wht

terse sky
#
@dataclass
class SubparserInfo:
    field_name: str
    nested_info: dict[str, tuple[type, 'SubparserInfo' | None]]
#

How would I make python happy with this?

restive rapids
# terse sky How would I make python happy with this?

put the entire type in the forwardreference quotes
because currently you're doing a bitwise or between a string and none, and since annotations are evaluated as normal expressions - thats an error
alternatively you could from __future__ import annotations and just not need quotes anymore

terse sky
#

yeah I figured as much

#

I didn't realize the quotes could just go around the whole thing though

#

thanks!

#

somehow I've never run into this case before

vague oar
#

is SQLModel a good library for typesafe sqlite in python?

autumn flint
#

I noticed in SqlAlchemy and PyCord libraries that there's runtime functionality being exposed through the type annotations, I get that this is because annotations are evaluated as normal expressions, but I'm confused about where this paradigm arose. I am using 3.11 and pylance hates this

novel notch
trim tangle
#

so is reversed, though confusingly calling reversed doesn't always produce an instance of it

novel notch
#

🧐

terse sky
#

I'm seeing mypy error out on commented out code

#

pretty wild

oblique urchin
#

type comments?

terse sky
#

i think so

#

yeah

gusty grotto
#

are dataclasses just classes of type hints?

oblique urchin
feral wharf
rare scarab
rough sluiceBOT
#

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

Protocol classes are defined like this...
gleaming goblet
gleaming goblet
#

so here's my conundrum

#

I want my calling code to look and behave as follows:

if __name__ == "__main__":
    my_combo = Combo(
        Selection("a", "b", "c", default="X"),
        Selection(1, 2, 3, default=0),
        Selection(0.125, 2.77, 3.14, default=math.nan),
    )

    for c in my_combo.combos():
        print(c)
#

hmm how to explain

#
class Combo[A, B, C]:
  def __init__(self, a: Selection[A], b: Selection[B], Selection[C]) -> None:
    ...
#

I want the caller to see an interface which looks like this ^

#

but I want the the code (and type checker) to also register a new dataclass (or similar) which basically looks and feels like:

@dataclass
class _ComboData:
  a: A
  b: B
  c: C

and have items of this type be yielded from the combos() method

#

I suppose I could just have a nested class definition...

#

but what I'd like even more is some way to get to this pattern without as much boilerplate

#

maybe a class decorator like

@dataclass_from_args_typevars
class MultipleContainers:
  def __init__(self, a: Container[A], b: Container[B], c: Container[C]) -> None:
    ...

which defines both the outer class and the dataclass with attributes of type A, B, and C.

gleaming goblet
#

not directly related, but any idea how I can condense isinstance(a, MyClass) and isinstance(b, MyClass) and ... into a single areinstance(a, b, ..., class_or_tuple = MyClass)?

#

I take it this just isn't supported yet?

#

...

#

Is it possible to make, say, a class decorator, which has access to the types over which the class which it is decorating is generic over? (during static type analysis, of course)

#
def dataclass_from_contained_typed[T: type](cls: T[A]) -> T:
  cls.new_attr: A = ...
#

have I gone too far?

Is this just type system brainrot?

#

or is what I'm looking for less of a class decorator and more of a metaclass... ?

oblique urchin
oblique urchin
gleaming goblet
#

Accessing an input variable's type variables

oblique urchin
#

Not in general, sometimes you can use e.g. types.get_original_bases

gleaming goblet
gleaming goblet
#

I definitely feel like I'm trying to overextend pythons type system though if I'm being honest

night wraith
#

Is it possible to type a dict that's keys are classes and values are instances of the key class

feral wharf
#

Sure?

#

dict[type[cls], cls]

oblique urchin
# feral wharf Sure?

that doesn't preserve the constraint that each value is an instance of its key

feral wharf
#

Oh true

#

I guess that's not really possible then

hallow flint
#

you can use a protocol that has a TypeVar in getitem

terse sky
#
from typing import overload, TypeVar, Literal, reveal_type
_T = TypeVar('_T')


@overload
def option(*, required=Literal[True], choices: list[_T] | None = ...) -> _T:
    ...

@overload
def option(*, default: _T, required=Literal[False], choices: list[_T] | None = ...) -> _T:
    ...

def option(*, default = None, required = False, choices = None):
    return default


x = option()
reveal_type(x)
#

I'm somewhat surprised here that x's type is Any

#

It seems to me like it should pick the narrowest type that works, which is None

#

is there any way to make this work other than adding an overload?

#

I'm writing a function that provides an API to argparse, to add an optional argument, and I'm currently at 7 overloads πŸ˜•

#
# Typing overloads:
# 1. required=True, no default, no const, nargs=None
# 2. required=True, no default, no const, nargs=multiplicity
# 3. required=False, no default, yes const, nargs=None or ?
# 4. required=False, no default, no const, nargs=None or multiplicity
# 5. required=False, default provided, yes const, nargs= None or ?
# 6. required=False, default provided, no const, nargs=None
# 7. required=False, default provided, no const, nargs=multiplicity
#

Actually I think I can merge the first two. Still, it's quite a bit.

#

The middle overloads seemingly have to be provided, because of the reduced example above

oblique urchin
terse sky
#

oops thanks

#

when i fix that mypy gives another confusing error, IMHO

#
scratch2.py:17: error: All overload variants of "option" require at least one argument  [call-overload]
oblique urchin
#

As for the behavior of option(), Any makes sense to me. It matches the first overload only, and because the choices argument isn't given there's no way to constrain the value of the TypeVar

oblique urchin
#

i.e., it should have a default for the overload that matches the runtime default

terse sky
#

do they have to have the same default, or just any default?

oblique urchin
#

though in that case I think the option() case should still throw an error, because the second overload still requires the default argument

terse sky
#

i.e. = ...

oblique urchin
terse sky
#

gotcha. okay, so I've fixed that - why is the first overload matched? required defaults toFalse

#

so the second overload is matched

oblique urchin
#

While the second had one required argument

#

So a call with no arguments can only possibly match the first overload

terse sky
#
from typing import overload, TypeVar, Literal, reveal_type
_T = TypeVar('_T')


@overload
def option(*, required: Literal[True] = ..., choices: list[_T] | None = ...) -> _T:
    ...

@overload
def option(*, default: _T = ..., required: Literal[False] = ..., choices: list[_T] | None = ...) -> _T:
    ...

def option(*, default = None, required = False, choices = None):
    return default


x = option()
reveal_type(x)
#

sorry, this is what it looks like now

#
scratch2.py:17: error: Need type annotation for "x"  [var-annotated]
scratch2.py:18: note: Revealed type is "Any"
oblique urchin
#

So I think you want to remove the = ... for the required param on the first overload. That overload should only be matched if the user actually writes required=True, not if they don't pass the argument

terse sky
#

I guess I wouldn't expect that to make a difference since the default is False, but alright

#

I did remove it, and it does not seem to make a difference

oblique urchin
oblique urchin
oblique urchin
terse sky
#

why can't it choose _T is None?

oblique urchin
#

Why would it?

terse sky
#

I guess this comes back to not using the default of th eimplementation

#

so, if I give overload 2, default: _T = None

#

I get this error

#
scratch2.py:10: error: Incompatible default for argument "default" (default has type "None", argument has type "_T")  [assignment]
scratch2.py:10: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
scratch2.py:10: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
scratch2.py:17: error: Need type annotation for "x"  [var-annotated]
scratch2.py:18: note: Revealed type is "Any"
Found 2 errors in 1 file (checked 1 source file)
#

This error seems wrong to me

oblique urchin
#

pyright does infer x as None in that case

#

yeah that mypy error is a bit confusing here

terse sky
#

it seems overzealous insofar as it assumes my intention here was implicit optional, which it wasn't

#

there's nothing wrong with this, it just constrains _T to be a type that allows None as a value

#

_T could be None or it could be int | None, etc

oblique urchin
#

That's indeed how pyright works here

terse sky
#

Gotcha. I mean I can see why the behavior here was useful but in hindsight, I think it would have been better if it was restricted to situations where the type was concrete rather than generic

oblique urchin
#

I've seen mention that defaults on parameters that use TypeVars can lead to some sketchy behavior, I haven't looked deeply into it though

#

But I believe that's why mypy is stricter than pyright here

terse sky
#

I suppose there is no way to opt out of this?

oblique urchin
#

Add more overloads I guess

terse sky
#
# 1. required=True, no default, no const, nargs=anything but ?
# 2. required=False, no default, yes const, nargs=None or ?
# 3. required=False, no default, no const, nargs=None or multiplicity
# 4. required=False, default provided, yes const, nargs= None or ?
# 5. required=False, default provided, no const, nargs=None
# 6. required=False, default provided, no const, nargs=multiplicity
#

this is where I'm at right now 😒

#
def option(
    *name_or_flags,
    group: WhichGroup = None,
    use_field_name=True,
    # default is something that occurs both in dataclasses and argparse, the handling is a bit nuanced
    default=None,
    # argparse arguments
    nargs: NargsType = None,
    const: Any = None,
    # skip default as we already handle that via dataclass
    type=None,
    choices=None,
    required=False,
    help=None,
    metavar=None,
    deprecated=False,
    # Other dataclass arguments
    init: bool = True,
    repr: bool = True,
    hash=None,
    compare=True,
    metadata=None,
    kw_only: bool = False,
):
#

and the function has quite a few arguments

oblique urchin
#

are you reimplementing argparse? πŸ˜„

terse sky
#
@dataclass
class MyArgs:
    first_arg: int = positional()
    glug: str | None = option()
    second: int | None = option()

    ex1: ClassVar[ExclusiveGroup] = ExclusiveGroup()

    foo: int | None = option(group=ex1, help="this is forwarded back to argparse")
    bar: int | None = option(group=ex1, metavar="so_is_this")

    sub: SubCommand1 | SubCommand2 | None = subparsers()
#

I'm aiming for a UI like this

#

so, I'm really trying to thread a sort of needle here; I want option to infer the most sensible defaults, but also give good type errors as much as possible

#

for example, you obviously want glug: str = option() to be a type error, that's one of the most common mistakes to make

oblique urchin
#

Is this what you want?

from typing import overload, TypeVar, Literal, reveal_type
_T = TypeVar('_T')


@overload
def option(*, required: Literal[True], choices: list[_T] | None = ...) -> _T:
    ...

@overload
def option(*, required: Literal[False] = ..., choices: None = ...) -> None:
    ...

@overload
def option(*, required: Literal[False] = ..., choices: list[_T]) -> _T | None:
    ...

@overload
def option(*, default: _T = ..., required: Literal[False] = ..., choices: list[_T] | None = ...) -> _T:
    ...

def option(*, default: object = None, required: bool = False, choices: object = None) -> object:
    return default


reveal_type(option())
reveal_type(option(choices=[1]))
reveal_type(option(default=42))
reveal_type(option(choices=[1], default=42))
#

gets me main.py:25: note: Revealed type is "None" main.py:26: note: Revealed type is "Union[builtins.int, None]" main.py:27: note: Revealed type is "builtins.int" main.py:28: note: Revealed type is "builtins.int"

terse sky
#

probably

#

I did have this working, and I left a note for myself that I needed this overload to make it work

#

but then I needed to add even more overloads to support nargs and lists, so I was hoping to cut some down

#

though I'm surprised you needed 4 overloads, I thought it would only require 3

oblique urchin
#

there might be a smaller solution

terse sky
#
from typing import overload, TypeVar, Literal, reveal_type
_T = TypeVar('_T')


@overload
def option(*, required: Literal[True], choices: list[_T] | None = ...) -> _T:
    ...

@overload
def option(*, required: Literal[False] = ..., choices: list[_T] | None = ...) -> _T | None:
    ...

@overload
def option(*, default: _T, required: Literal[False] = ..., choices: list[_T] | None = ...) -> _T:
    ...

def option(*, default = None, required = False, choices = None):
    return default


x = option()
reveal_type(x)
oblique urchin
#

Yeah was just trying that too, it does work but it doesn't force the right solution for _T so mypy could infer something else

terse sky
#

could? Based on what?

#

I do see what you mean that it doesn't force _T at all, but I guess if mypy behaves this way then it should always behave this way

oblique urchin
#

Solving TypeVars is necessarily based on heuristics to some extent

terse sky
#

I'd assume that if _T is totally unconstrained then it would default to object

#

that's the least constrained type, and object | None = None

oblique urchin
#

Mypy often seems to default to Never, which I think is why your example with fewer overloads works correctly

terse sky
#

hmm, wouldn't object as the default work too?

oblique urchin
#

the option() call matches def option(*, required: Literal[False] = ..., choices: list[_T] | None = ...) -> _T | None:

#

_T is Never, so we get Never | None = None

oblique urchin
terse sky
#

Yeah, I just meant that it would result in the type deduction we see above

#

Since object | None is also None

#

Actually I guess it's not

#

So much for that

#

Thanks for your help Jelle

low fox
#

@oblique urchin if u had to learn python from scrath rn how would u do it [sry for the ping ]

jolly cipher
# low fox <@783088578363523104> if u had to learn python from scrath rn how would u do it ...

i'll answer for myself here as someone with a background in teaching python
just do what you're interested in; explore, google things, try to figure your way out to the outcome you want, don't let lack of certainty hold you back
it's all about solving problems than anything else really. try to be clever, experiment, and just do things

i don't think i know of a better way

also, please use the right channel next time :)) this one is for the type hinting space specifically

indigo halo
rough sluiceBOT
#

Lib/asyncio/tasks.py line 56

class Task(futures._PyFuture):  # Inherit Python Task implementation```
rough sluiceBOT
#

stdlib/_asyncio.pyi line 52

class Task(Future[_T_co]):  # type: ignore[type-var]  # pyright: ignore[reportInvalidTypeArguments]```
indigo halo
#

_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)
#

fine. But what does_T_co even mean?

trim tangle
indigo halo
#

it's the return type of the coroutine of the task?

trim tangle
#

when you await the task (or call its result method) you should get a value of type _T_co

rare scarab
indigo halo
#

man, this is science

rare scarab
#

typing is a science

indigo halo
#

I kinda get it I think. Thank you for the link. This will take a few days to grok.

trim tangle
#

someone should make an easily digestible tutorial on generics

rare scarab
#

With a table comparing the features of each

trim tangle
#

of each what?

rare scarab
#

variance

trim tangle
#

ah

rare scarab
#

I just do what the type checker tells me to do

#

mypy: typevar should be covariant

#

me: ok

shy stirrup
#

hello guys! can you please recommend me some good resources to understand or master type-hinting.
Thank you! πŸ˜„

shy stirrup
slender basin
#

Hey. Maybe you guys have an idea? I tried to improve typing on a function argument from Iterable to ItemsView (as the function actually accepts some dict.items() and runs len on it), and started getting an error I don't understand.

from typing import Iterable, ItemsView, TypeVar, Any
from collections.abc import Callable

T = TypeVar('T')

def my_map_good(func: Callable[..., T], iterable: Iterable[Any], *args: object) -> list[T]:
    def _wrapped_func(func_and_args: tuple[Callable[..., T], *tuple[Any, ...]]) -> T:
        func = func_and_args[0]
        args = func_and_args[1:]
        return func(*args)

    func_and_args = [(func, it_arg, *args) for it_arg in iterable]
    result: list[T] = list(map(_wrapped_func, func_and_args))
    return result


def my_map_bad(func: Callable[..., T], iterable: ItemsView, *args: object) -> list[T]:
    def _wrapped_func(func_and_args: tuple[Callable[..., T], *tuple[Any, ...]]) -> T:
        func = func_and_args[0]
        args = func_and_args[1:]
        return func(*args)

    func_and_args = [(func, it_arg, *args) for it_arg in iterable]      # <=======
    # error: Argument 1 to "map" has incompatible type "Callable[[tuple[Callable[..., T], *tuple[Any, ...]]], T]"; 
    #                                         expected "Callable[[tuple[object, ...]], T]"  [arg-type]

    result: list[T] = list(map(_wrapped_func, func_and_args))
    return result
restive rapids
#

i'd imagine something like

def map_partial[First, **Rest, Result](fn: Callable[Concatenate[First, Rest], Result], iterable: Iterable[First], *args: Rest.args, **kwargs: Rest.kwargs) -> list[Result]:
  return [fn(arg, *args, **kwargs) for arg in iterable]

makes more sense
though tbh just inlining this avoids the complex typing thing

slender basin
# restive rapids if you do `ItemsView[Any, Any]` it goes away (on pyright, atleast) not sure what...

Thanks! On mypy ItemsView[Any, Any] doesn't change anything: https://mypy-play.net/?mypy=latest&python=3.12&gist=0e0234e5753bd351ad8d6b519261dc99
I realize this code snippet is weird - the real code does more than delegate the execution. This was just stripped to the bare minimum demonstrating the typing mystery.

#

(You'd need to 'Run' after clicking the link)

loud breach
#

Hey! Quick question:
In modern Python (3.10+), is it better to use from __future__ import annotations instead of if TYPE_CHECKING: to avoid circular imports and type hints?
Which one is considered more idiomatic or "clean" today?

jolly cipher
# loud breach Hey! Quick question: In modern Python (3.10+), is it better to use `from __futur...

these two things don't solve the same problem, so you can't use one instead of the other.

if TYPE_CHECKING means

  • if False at runtime
  • if True at type checking time (which does not exist, it's just a hint to your type checker to equally treat a set of statements that never execute because it contains extra info for typing)

(roughly)
therefore if you want to use module-level "circular-origin" annotations you need to (1) place all circular typing-only imports under if TYPE_CHECKING and (2) ensure these annotations aren't evaluated at runtime, because you will get a NameError (which is why i avoid if TYPE_CHECKING in modules with pydantic models)

up until Python 3.14 (PEP 749 target version), from __future__ import annotations is helpful with (1), so it complements (not replaces) if TYPE_CHECKING.

it is helpful because it turns all annotations in a module into strings, so they are effectively never truly evaluated as expressions (unless you use Pydantic, for example, or run typing.get_type_hints() etc.)

#

if you want to leverage if TYPE_CHECKING without PEP 563 (__future__.annotations) you either need Python 3.14+ that implements PEP 749 or quotes around circular annotations (foo: "Foo")

#

@loud breach does that help?

loud breach
# jolly cipher <@1360978102536245462> does that help?

I wasn’t very clear earlier, but yes, I’m specifically talking about circular imports, and your replies are really helping me understand things better.

Currently, I have classes like:

# manager.py
from .core import Workflow
from .history import HistoryLogger
from .serializer import WorkflowSerializer

class WorkflowManager:
    def __init__(
        self,
        max_active: int = 2,
        serializer: Optional[WorkflowSerializer] = None,
        history: Optional[HistoryLogger] = None,
    ):
        ...
# core.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .manager import WorkflowManager

@dataclass
class Workflow:
    manager: "WorkflowManager"
    ...
# serializer.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .manager import WorkflowManager

class WorkflowSerializer:
    def save(self, manager: "WorkflowManager") -> None:
        ...
# history.py
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .core import Workflow

class HistoryLogger:
    async def append(self, workflow: "Workflow") -> None:
        ...

With the TYPE_CHECKING, I don’t get any ImportError: cannot import name .. during test runs, and mypy doesn’t complain either, everything works.

From what I understand, using from __future__ import annotations would let me drop the quotes around class names, but it won’t solve the circular import itself if the modules depend on each other at runtime.

So if I got it right, combining both techniques (future annotations + TYPE_CHECKING) makes the code cleaner and avoids circular import issues entirely.

Is that correct?

Thank you so much for your response, it was really clear! πŸ˜‰

jolly cipher
#

From what I understand, using from __future__ import annotations would let me drop the quotes around class names, but it won’t solve the circular import itself if the modules depend on each other at runtime.
exactly

So if I got it right, combining both techniques (future annotations + TYPE_CHECKING) makes the code cleaner and avoids circular import issues entirely.
yes, but not entirely because if you evaluate those strings through the typing API, you're gonna get name errors because these things are never imported

i worked on something that could roughly solve this by delaying imports until first reference, maybe i'll go back to that hacky project

#

it could be nice if it's scoped only for annotations, or maybe it was not delaying but special types in place of type imports that would be treated properly

#

ideally python would have type imports like TypeScript at some point

#

@oblique urchin do you know of any past efforts in that area?

#

although idk, i think PEP 749 maybe solves some of this, will read deeper into it

oblique urchin
#

But no effort that's gotten far to get type imports specifically

loud breach
jolly cipher
#

typing.get_type_hints, typing._eval_type and typing.ForwardRef._evaluate

#

the typing API is used at runtime

#

commonly by libraries that give semantics to type annotations, such as Pydantic

#

(that one doesn't use typing.get_type_hints but it does wrap typing._eval_type afair)

pliant vapor
#

can you still manually specify variance with the [] typevar syntax

pliant vapor
#

i guess something like

class PageStrategy[R](ABC, Generic[Vd]):

doesn't work either

trim tangle
rare scarab
#

Isn't variance inferred?

pliant vapor
#

Vd is bound to PageVendor = PageSimpleVendor | PageMultiVendor, but it's invariant because it's in both the method arguments and return params

#

i'm working with frozen types so i really don't care that much

#

so i'd like them to be covariant

#

invariance means i can't do

SyncPageStrategy(CSLTrack) if client.is_sync() else AsyncPageStrategy(CSLTrack)

because it gets bound to PageMultiVendor or PageSimpleVendor specifically and then one of them conflicts (sync is simple and async is multi)

#

i'll probably just do

R = TypeVar('R')
Vd = TypeVar('Vd', bound=PageVendor, covariant=True)

class PageStrategy(ABC, Generic[R, Vd]):

like old times

cosmic storm
#

Is there any way for me to tell the type checker that it's not possible for y to be unbound or do I just type: ignore?

vague oar
trim tangle
cosmic storm
#

Another question, sorry
Is this the best way to do this or is there a standard way to tell it that it can't return None?
I think I actually do like this way but just wondering if there's a well established method

oblique urchin
#

but what you do is fine too

rare scarab
#

a flake8/ruff rule may suggest replacing it with raise AssertionError("unreachable")

oblique urchin
rare scarab
#

It's because -O

cinder bone
#

to be honest does anybody actually run python with -O?

brazen jolt
# cinder bone to be honest does anybody actually run python with `-O`?

Probably not, but it can improve speeds slightly, so... maybe. I think the ruff rule does make sense though, ruff is generally pretty strict, and raise AssertionError("unreachable") isn't that much harder to type compared to assert False, "unreachable" and since it is also more reliable, why not prefer it

#

it might even be more readable, even if someone isn't as familiar with asserts, they likely saw raise before

rare scarab
#

folks saying python is slow without using -O smh

jolly cipher
#

not silly question to a silly example:

from typing import Literal, TypeVar

T = TypeVar("T", bound=Literal[True])

def should_be_assignable() -> Literal[True]:
    return True

def parametrized(x: T) -> T:
    return should_be_assignable()

since T is bounded to Literal[True], which is a fully static type that represents a single runtime value

and therefore, since there is no consistent subtype of Literal[True] (as the upper bound of T) that isn't assignable to other consistent subtype of Literal[True] (as the upper bound of T), because Literal[True] represents a set with only one value

shouldn't any Literal[True] value be always assignable to T, where it's bound (to Literal[True], obviously)?

brazen jolt
#

technically yes, but I'm not at all surprised that most (if not all) type checkers can't figure that out, they just don't make the assumption of "this type can't have any subtype", even when if that is the case with literals.

#

also, that code is so cursed, lol

rare scarab
#

If the type checker were smarter, it wouldn't let you have a typevar bound for a single literal.

jolly cipher
mint inlet
#

mypy does have special cases for final bounds in some cases iirc

#

nvm i was confusing typevar constraints with bounds in my memories lol

jolly cipher
#

that's also maybe why you need at least two constraints... and bound maybe was assumed to always have further materializations

#

let's see what if i do TypeVar("T", bound=tuple[None, None])

#

yeah, same

#

it's also noted in the spec, section on literals, that it doesn't make sense to use literals as typevar bounds