#type-hinting

1 messages ยท Page 77 of 1

rustic lagoon
#

i'll give it a shot

#
MyBe[T]: TypeAlias = "Just[T] | Nothing"

like this?

soft matrix
#

Yep

#

Wait no

#
Maybe: TypeAlias = "Just[T] | Nothing"
#

Just don't try and subscript this at runtime

rustic lagoon
#
from typing import TypeAlias, TypeVar

from src.maybe import Just, Nothing

T = TypeVar("T")

MyBe: TypeAlias = Just[T] | Nothing


def main():
    def foo_or_bar(fbar: str) -> MyBe[str]:
        if fbar in ("foo", "bar"):
            return Just(fbar)
        return Nothing()

    match foo_or_bar("foo"):
        case Just(v):
            print(f"{v}!")
        case Nothing():
            print("Aw shucks!")


if __name__ == "__main__":
    main()

Ok, this works

#

I'm using MyBe cause technically there's a use for Maybe still

#

which i probably could just rewrite into a simple function instead of class method...

#

btw, can you import type aliases?

oblique urchin
rustic lagoon
#

well, pyright doesn't seem to want to use it for type checking

#

I can define it myself just fine though

summer berry
rustic lagoon
#

i'll give it a look tomorrow

twilit badge
#

Is there a generic version of enum? I'm looking for something where I could specify what type the enum values will be of

class MyEnum(Enum[str]):
    A = "some string"
    B = "some other string"
rustic lagoon
#

you can do

class MyEnum(str, Enum):
  A = "some string"
  B = "some other string"
twilit badge
#

oh, does that apply for any type?

#

like, including protocols?

rustic lagoon
#

oh, do know it applies for str

#

but you could probably do it with Generic[T]

#

well, do try it

#

I'm pretty sure the Enum has to go last in the parent class declarations though

twilit badge
#

hm, doing reveal_type(instruction.value) still gives any

#

(instruction being a specific enum value)

#

I wonder if just cheating it by ```py
class MyEnum(Enum):
value: ReadInstructionValue
A = ReadInstructionValue(...)
B = ...

soft matrix
#

Enum should use the literal values

#

That's weird

twilit badge
#

well i know, but i didn't want to use dict, since I wanted my keys to be specific, and yeah, I know about TypedDict, but I have a lot of keys (~100) and I'm not exactly eager to write a full typeddict specifying all of them, just to define the dict as a constant below

#

I also much prefer the x.A access then x["A"]

soft matrix
#

this works with pyright

#

and mypy

#

hmm

void panther
#

with paramspec is there some way to give the arguments names while keeping it tied to the paramspec, or do I have to just get it from args?
I have

def _cache_node_find(func):
    # type: (t.Callable[P, T]) -> t.Callable[P, T]
    def wrapper(*args, **kwargs):
        # type: (P.args, P.kwargs) -> T
        return func(*args, **kwargs)
    return wrapper

and want to do something like this

def _cache_node_find(func):
    # type: (t.Callable[P, T]) -> t.Callable[P, T]
    def wrapper(start_node, *args, **kwargs):
        # type: (P.args, P.kwargs) -> T
        return func(start_node, *args, **kwargs)
    return wrapper
brazen jolt
#

you could use Concatenate

#
def foo(func: Callable[Concatenate[int, P], R) -> Callable[Concatenate[int, P], R]:
    def wrapper(something: int, *args: P.args, **kwargs: P.kwargs) -> R:
        ...
#

but you can't do much for kw args

soft matrix
#

That's only part of the solution

#

Especially cause they want the names as well

brazen jolt
#

yes, but afaik there's no way to specify arg names with concatenate, I think it was a rejected proposal from the PEP

soft matrix
#

You might be able to do this with mypy using mypy_extensions.NamedArg but other than that you're out of luck

brazen jolt
brazen jolt
void panther
#

Will need to change a few things but it's better than nothing, thanks

tranquil turtle
#
reveal_type(int)

in mypy i get this:

Revealed type is "Overload(def (Union[builtins.str, builtins.bytes, array.array[Any], mmap.mmap, ctypes._CData, pickle.PickleBuffer, typing.SupportsInt, typing_extensions.SupportsIndex, _typeshed.SupportsTrunc] =) -> builtins.int, def (Union[builtins.str, builtins.bytes], base: typing_extensions.SupportsIndex) -> builtins.int)"
why there is single equals sign in first overload?
def (Union[...] =) -> builtins.int

#

isnt this a bug?

oblique urchin
tranquil turtle
#
from typing import overload

def f(x: int = ...) -> int: ...

reveal_type(f)
# Revealed type is "def (x: builtins.int =) -> builtins.int"
``` hmm
#

i expect to get def (x: builtins.int = ...) -> builtins.int

plain dock
#
testlst: list[int | set[int]] = [1, 2, {3, 4, 5}]
for n, _ in enumerate(testlst):
    if isinstance(testlst[n], set) and len(testlst[n])==1:
        pass

VSC's Pylance gives the testlst at the right (being len'd) a red type squiggle, saying Argument of type "int | set[int]" cannot be assigned to parameter "__obj" of type "Sized" in function "len", meaning that it doesn't know for sure that that testlst[n] is a set, despite the type check right before. I can replace the n with a literal number (e.g. testlst[2]), however, and it doesn't complain. Is there a way to fix this by making pyright realize the objects are the same?

pastel egret
#

It's technically correct, because it's possible testlist[n] returns a different object each time - it can't know for certain that it behaves like you want here.

#

Use the _ variable you're ignoring instead, it can type narrow local variables.

plain dock
pastel egret
#

In that case cache testlst[n] in a variable, you could use a walrus even:

if isinstance(testset := testlst[n], set) and len(testset)==1:
plain dock
#

i should post the original instead of the example

#
def _commit_candgrid(candgrid: candgrid_type, solngrid):
    for (r, c), _ in np.ndenumerate(solngrid):
        if solngrid[r][c]==0 and isinstance(candgrid[r][c], set) and len(candgrid[r][c])==1:
            candgrid[r][c] = candgrid[r][c].pop()
            solngrid[r][c] = candgrid[r][c]

    return candgrid, solngrid
#

candgrid_type is list[list[int | set[int]]],
solngrid is an numpy.ndarray, essentially list[list[int]]

pastel egret
#
def _commit_candgrid(candgrid: candgrid_type, solngrid):
    for (r, c), _ in np.ndenumerate(solngrid):
        if solngrid[r][c]==0 and isinstance(grid:= candgrid[r][c], set) and len(grid)==1:
            candgrid[r][c] = grid.pop()
            solngrid[r][c] = candgrid[r][c]

    return candgrid, solngrid
#

By doing the lookup only once, the type checker can be certain the other uses are the same object.

plain dock
#

walrus operator is a very nice solution. i should've thought of it. thank you @pastel egret

pastel egret
#

Your original is correct, but only because us humans know the collection is always going to give back one type, the type checker can't really know that.

rustic lagoon
rustic lagoon
#
from src.maybe import MH, MU, Just, Maybe, Nothing


def foo_or_bar(fbar: str) -> "Maybe[str]":
    if fbar in ("foo", "bar"):
        return Just(fbar)
    return Nothing()


@MU[str].bind
def i_bind(h: "MH[str]", s: str) -> "Maybe[str]":
    f1 = foo_or_bar(s)(h)
    return Just(f"{f1} and {s}")


def main():

    match foo_or_bar("foo"):
        case Just(v):
            print(f"{v}!")
        case Nothing():
            print("Aw shucks!")

    match i_bind("neither"):
        case Nothing():
            print("Expected that")
        case _:
            print("That's surprising!")


if __name__ == "__main__":
    main()

Does this look clean?

soft matrix
#

What are MH and MU?

rustic lagoon
#

MH is a type alias for Callable[[Maybe[T]], T]

#

it's supposed to stand for maybe handler

#

and MU is a subclass of unwrapper

soft matrix
#

Maybe have it be the full name

rustic lagoon
#

lemme show the code

sterile python
rustic lagoon
rustic lagoon
sterile python
#

great

rustic lagoon
#

the first argument of the decorated function must be a handler though

#

and you can either pass the maybe into the handler, or call the maybe with the handler

#

that will get you a value inside the maybe, or have the entire function return Nothing

sterile python
#

I see that you have inherited Just from Monad protocol, why you didn't make Monad as ABC?

rustic lagoon
#

I rewrote the Monad today, and I probably could just make it an abstract class

sterile python
#

ok

rustic lagoon
#

though i'm not exactly sure what the difference would be in having the interfaces inherit from ABC instead of being protocols

rustic lagoon
#

Ok, I've rewritten Result now as well. Now I just need to write tests for it

#

Other than that, I've noticed that there's hardly any difference between Result and Maybe in terms of code, and whether there's a way to reuse the logic and instead just change the type hints for them

sterile python
#

Structural typing is duck typing for static analysis

#

Abstract classes is nominal typing

rustic lagoon
#

oh, i get it

#

Yeah, ABC makes more sense

brisk hedge
#

can you hear them??

rustic lagoon
#

yeah, started doing that already :p

#

just trying to do it in a reasonable manner

trim tangle
#

so e.g. you can't make a function that turns a Result[int] into a Result[str] and a Maybe[int] into a Maybe[str]

rustic lagoon
#

yeah, i found that out early on. that's why i've been writing apply as an instance method

#

so that it follows the abstract, but the kind is inferred from the instance

#

it can be worked around, but it's more complicated than i thought it would be

brisk hedge
#

Result[A, B] <=> tuple[Result, A, B] twitchsmile

#

no problems arise from this whatsoever

cunning plover
#

is python 3.10 changed how tuples returns are typed?

#

or it is still same Tuple?

#

i saw mentions that some typing became easier in newer python versions ๐Ÿค” less requiring imports from typing lib

oblique urchin
cunning plover
# oblique urchin since Python 3.9 you can just use `tuple`

urgh. I tried replacing
Tuple[resources.ModelResource, str, int] with tuple(resources.ModelResource, str, int) and my VSCODE stopped recognizing it. VsCode did not provide syntax tips when using returned variables. It worked fine with Tuple

#

VsCode is correctly attached to Python 3.10

vast olive
#

brackets not parens

cunning plover
#

Ops. It works xD

#

thanks

#

xD I use typing only for the sake of IDE functionality

#

really useful to get code tips, reading docs to stuff on a fly

rustic lagoon
tranquil turtle
#
from __future__ import annotations
from typing import TypeVar, Callable, Generic, overload

TGet = TypeVar('TGet')
TGetNew = TypeVar('TGetNew')
TSet = TypeVar('TSet')
TSetNew = TypeVar('TSetNew')
TObj = TypeVar('TObj')

class prop(Generic[TObj, TGet, TSet]):
    fget: Callable[[TObj], TGet] | None
    fset: Callable[[TObj, TSet], None] | None
    fdel: Callable[[TObj], None] | None
    def __new__(cls,
        fget: Callable[[TObj], TGet] | None = ...,
        fset: Callable[[TObj, TSet], None] | None = ...,
        fdel: Callable[[TObj], None] | None = ...,
        doc: str | None = ...,
    ) -> prop[TObj, TGet, TSet]: ...
    def getter(self, fget: Callable[[TObj], TGetNew]) -> prop[TObj, TGetNew, TSet]: ...
    def setter(self, fset: Callable[[TObj, TSetNew], None]) -> prop[TObj, TGet, TSetNew]: ...
    def deleter(self, fdel: Callable[[TObj], None]) -> prop[TObj, TGet, TSet]: ...
    @overload
    def __get__(self, obj: TObj, type: type[TObj] = ...) -> TGet: ...
    @overload
    def __get__(self, obj: None, type: type[TObj] = ...) -> prop[TObj, TGet, TSet]: ...
    def __get__(self, *a): ...  # type: ignore[no-untyped-def]
    def __set__(self, obj: TObj, value: TSet) -> None: ...
    def __delete__(self, obj: TObj) -> None: ...


class X:
    x: int

    @prop
    def x_square_(self) -> int:
        pass

    @x_square_.setter
    def x_square(self, val: str) -> None:
        pass


reveal_type(X.x_square)
reveal_type(X.x_square)
reveal_type(X().x_square)
reveal_type(X().x_square)

x = X()
y = x.x_square
x.x_square = '123'


z: int = 5
print(z)
z: str


def f() -> None:
    x: int = 5
    print(x)
    x: str = ''
    print(x)

possible implementation of generic @property for typeshed

brisk heart
#

generic property please ๐Ÿ™

#

I'm forced to type Callable[[Callable[[], T]], property[T]] as type[property] and it hurts

soft matrix
oblique urchin
soft matrix
#

eric didnt like it cause it made error messages way worse

#

and it was kinda annoying to specify all the parameters

dull lance
#

Is there a better way to type hint proxies that only expose a few instance methods of the wrapped object without having to copy and paste the function signature? I have come up with something like this so far:

_TOwner = TypeVar('_TOwner')
_P, _R_co = ParamSpec('_P'), TypeVar('_R_co', covariant=True)
class UnboundMethodProto(Protocol[_TOwner, _P, _R_co]):
    @overload
    def __get__(self, obj: None, owner: type[_TOwner]) -> Self: ...

    @overload
    def __get__(self, obj: _TOwner, owner: type[_TOwner]) -> Callable[_P, _R_co]: ...

    def __call__(__self, self: _TOwner, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ...

_R =  TypeVar('_R')
def copy_signature(
    ref_fn: UnboundMethodProto[Any, _P, _R],
) -> Callable[[UnboundMethodProto[_TOwner, _P, _R]], UnboundMethodProto[_TOwner, _P, _R]]:
    return lambda f: f

# Example usage: only expose `MyClass.bar()` in `MyWrapper`
class MyClass:
    def foo(self, x: int) -> bool: ...
    def bar(self, y: float, *, z: int = 0) -> None: ...

class MyWrapper:
    def __init__(self, wrapped: MyClass) -> None:
        self._wrapped = wrapped

    @copy_signature(MyClass.bar)
    def bar(self, *args, **kwargs):
        return self._wrapped.bar(*args, **kwargs)
#

An issue arises when I try to make such proxies generic. I would like the signature of the exposed method to correspond to the actual type. For example:

class SupportsBar(Protocol):
    def bar(self, *args, **kwargs): ...

class MyClass(SupportsBar):
    def foo(self, x: int) -> bool: ...
    def bar(self, y: float, *, z: int = 0) -> None: ...

class MyOtherClass(SupportsBar):
    def bar(self, y: float, *, z: int = 0, k: int = 1) -> None: ...
    def baz(self) -> Self: ...

_TSupportsBar = TypeVar('_TSupportsBar', bound=SupportsBar)
class MyWrapper(Generic[_TSupportsBar]):
    def __init__(self, wrapped: _TSupportsBar) -> None:
        self._wrapped = wrapped

    # I want to do something like `@copy_signature(_TSupportsBar.bar)`, such that, for example, the signature of
    # `MyWrapper[MyOtherClass].bar` would be `(self, y: float, *, z: int = 0, k: int = 1) -> None`
    def bar(self, *args, **kwargs):
        return self._wrapped.bar(*args, **kwargs)
vast olive
#

does Python have something like the Partial type in TypeScript?

trim tangle
#

Why is slice not generic? ๐Ÿค”

tranquil turtle
#
>>> s = slice('', {}, [])
>>> s.indices(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: slice indices must be integers or None or have an __index__ method
``` because slices must support `.indices(n)`
trim tangle
#

Also, a method can be typed to only exist on slice[int|None, int|None, int|None]

acoustic thicket
#

i feel like having both a slice type and a range type is kinda redundant

buoyant swift
#

range is just numbers, though. if that was changed then i would agree

acoustic thicket
#

i like julia's approach, a:b always creates a range, and a_list[a:b] works the same as in python

#

additionally a and b aren't restricted to just ints

#

it could be floats, dates, etc

trim tangle
#

Why do you need it? Can you show an example maybe?

vast olive
#

sure. just gimme a sec

soft matrix
#

But also maybe it'd just happen without that if someone were to open an issue on python/cpython

vast olive
#

update_config should be a put and should not be a 201, but ignore that

past pumice
#

Logic wise, it looks like everything should work besides the type hint (and if fastapi performs any transforms for you). I've personally done this before by bypassing the framework, and setting the type hint to a request, then merging the raw data. Fastapi does have a section on doing this, which is basically this approach, but also setting every field in the model to Optional (manually) to allow you to use the correct type hint

#

Which behavior-wise does match what you'd do in TS I guess

vast olive
#

interesting. idk how I missed that in the docs. I'll check it out. thanks for the reply!

past pumice
#

I guess this is why the docs say most people use PUT

#

it avoids the issue

brittle socket
#

Can you typehint a tuple of exactly two sub-types?

#

Morally equivalent to tuple[int, int]

#

so that (1, 1, 1) fails

fervent sierra
brittle socket
#

Oh. Yeah, I had tried it with tuple instead of Tuple ๐Ÿ˜“

fervent sierra
brittle socket
#

I'm on 3.10.5

#

It's valid for list and dict

#

but not tuple yet apparently

oblique urchin
brisk hedge
#

You may be misinterpreting whatever problem you're having

hushed cairn
#

is it possible to add type hinting to attributes that have been defined with __slots__?

oblique urchin
hushed cairn
#

ok, thanks

brittle socket
past pumice
#

Could you perhaps provide a minimal reproduction?

#

I ran with a: re.Pattern[str] | tuple[list[re.Pattern[str]], list[re.Pattern[str]]] and it passed just fine

oblique urchin
#

or maybe not that specific one, that probably got fixed

brittle socket
past pumice
#

Oh, missed the type alias part

#

Seems the type here is irrelevant, just the combination of TypeAlias, union, and tuple generics

fair pawn
#

Oups:

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

def setUser(uid: UserId):
  ...

uid: int | str = getUid()
setUser(uid) # does not type check: Argument of type "int | str" cannot be assigned to...
#

I am not sure what's going wrong here...

oblique urchin
fair pawn
#

In particular, a type synonym for int | str

oblique urchin
#

UserId: TypeAlias = int | str

fair pawn
#

Nice, thanks, that's what I was looking for

#

I guess TypeVar is for generics, doh!

quasi spindle
#
from typing import Union, Literal
import numpy as np

T_IntOrNan = Union[int, Literal[np.nan]] 
# error: Parameter 1 of Literal[...] is invalid 
# error: Variable "numpy.nan" is not valid as a type

def f(x) -> T_IntOrNan:
    return x

f(3) # Should be valid
f(np.nan) # Should be valid
f(3.0) # Should not be valid

How do you type to allow ints and NaNs only?

sterile python
#
Literal[float('NaN')] | int
soft matrix
#

Literal[nan] isnt allowed

#

there isnt a way to type this

#

but also typing this doesnt make much sense, why is nan allowed but 3.0 isnt??

quasi spindle
#

In dask the shape of an lazy array can become undefined (nan) when masking with boolean arrays otherwise the shape values are ints.

soft matrix
#

ig it sort of makes sense to extend to nan

rustic gull
#

this is correct right?

a: None | str = None
soft matrix
#

yes

#

assuming you are on 3.10+

rustic gull
#

yeah because of the usage of pipes

#

thats all i needed to know thanks!

trim tangle
#

None | None doesn't work ๐Ÿ˜ฉ

#

my life is ruined

summer berry
#

My condolences.

#

Why did you want that to work

trim tangle
#

excuse my overdramatization...

#

I'd be terrified if that worked actually

grave fjord
#

people do None | Any and other weird stuff

reef nova
#

Hello! I'm tinkering with dataclass and I'm trying to understand a typing error that Pylance is reporting.```py
@dataclass
class EconomyEntry(LogEntry):
"""The EconomyTransaction object represents one exchange of currency."""
action_type: CurrencyActionType # This is just an IntEnum
currency: CurrencyType # So is this
amount: int
source_user_id: str
target_user_id: str | None
note: str | None = None
timestamp: datetime = datetime.utcnow()
record_no: str | None = None
doc: str = "economy"

def to_dict(self) -> dict[str, Any]:
    entry: dict[str, Any] = {
        "action_type": self.action_type,
        "currency": self.currency,
        "amount": self.amount,
        "source_id": self.source_user_id,
        "target_id": self.target_user_id,
        "note": self.note,
        "timestamp": self.timestamp,
    }
    if self.record_no is not None:
        entry["_id"] = self.record_no
    return entry

@classmethod
def from_dict(cls, **kwargs) -> EconomyEntry:
    return EconomyEntry(CurrencyActionType(kwargs["action_type"]),
                        CurrencyType(kwargs["currency"]), kwargs["amount"],
                        kwargs["source_id"], kwargs.get("target_id", None),
                        kwargs.get("note"), kwargs.get("timestamp"),
                                            ^^^^^^^^^^^^^^^^^^^^^^^
                        kwargs.get("_id", None))```
#
Argument of type "Unknown | None" cannot be assigned to parameter "timestamp" of type "datetime" in function "__init__"
  Type "Unknown | None" cannot be assigned to type "datetime"
    Type "None" cannot be assigned to type "datetime"```
#

What I don't really get is, why is it that it's raising a fuss about this field alone?

#

If it can't assign Unknown | None to datetime, then why can it assign Unknown | None to the other fields? Just because they're literals?

grave fjord
reef nova
grave fjord
reef nova
#

Ahh, sounds like inconsistent Pylance behavior is happening.

grave fjord
#
from typing import TypedDict
from typing_extensions import Unpack

class EconomyEntryDict(TypedDict):
    action_type: CurrencyActionType
    currency: CurrencyType
    ...
class EconomyEntry:
    @classmethod
    def from_dict(cls, **kwargs: Unpack[EconomyEntryDict]) -> EconomyEntry:
        return ...
reef nova
#

Oooh, I haven't seen Unpack before. This is great; I'll read up. Thanks, graingert!

grave fjord
#

But with your thing you can also just use keyword only arguments

#

But you'll be able to save time and reuse the TypeDict for your return type and kwarg type

brisk hedge
#

It's also very convenient for subclasses

reef nova
#

For anyone else who might wander in with this issue, apparently Pylance was specifically taking offense with kwargs.get("timestamp") (Unknown | None) not being assignable to datetime because I didn't include a default value. Providing a default as in kwargs.get("timestamp", datetime.utcnow()) changes the inferred parameter type to just (Unknown), which apparently is assignable to datetime.

#

I'm still keeping my cool new EconomyEntryDict, though.

brisk hedge
#

if you assume the key exists, why not just [index]? That would avoid the same issue

hasty hull
# reef nova For anyone else who might wander in with this issue, apparently Pylance was spec...

The reason it wasn't reporting errors for the other arguments was because for some of them you're use the [] syntax for some arguments which will error if the key isn't present and is inferred to be Unknown in your case. The other arguments that you're using .get for all contain None in their type signature, e.g. note: str | None = None which means Unknown | None is assignable to str | None.

In general though if a lot of your types are being inferred as Unknown that means you're missing type annotations somewhere and are doing things that might not be enforcing type safety. For example, with your original def from_dict(cls, **kwargs) -> EconomyEntry: method, someone using this API could pass whatever they want for any of the arguments and they wouldn't get a type error. You can see these errors if you enable strict mode.

chrome dust
#

How do you type a function that should take an Enum class with string values e.g.

from enum import Enum

class Foo(Enum):
    BAR = 'bar'
    BAZ = 'baz'

T = TypeVar('T', bound=Enum) # This is too weak

def func(enum: T) -> list[str]:
  return [e.value for e in enum]
brazen jolt
#

I don't think you can, enums aren't generic

#

you could make a protocol that specifies that value is of type str

#

but I don't think enums would match it (unless you explicitly specified it), you'd need to cast them

#

from python 3.11, there should be enum.StrEnum though

soft matrix
#

Enum can't be generic

rare scarab
#

Could you make a protocol that inherits Enum and str?

#
class StrEnum(Protocol, Enum, str): ...
#

an Intersection type would work best, but we don't have that.

trim tangle
#

You could have a Protocol with a @property def value(self) -> str

oblique urchin
rustic gull
#

How do you support recursive typing? I have a function that can return any type: str, bool, int, float, list, dict. And list can also have within itself lists that also contains any of these types, same for dict.
I looked and seems this is the only solution? https://stackoverflow.com/questions/53638973/recursive-type-annotations

#

(Which states Dict[str, Any] is the solution)

oblique urchin
sterile python
summer berry
#

What do you mean by a typechecker defining variance?

#

What would make sense to me is to say "a programmer defines variance" or "a typechecker verifies/infers variance"

#

That PEP has a section on inferring variance, which it is suggesting to do over requiring programmers to define variance.

#

Hence lack of syntax to define variance.

green gale
#

A while ago I think I heard mentions of a new TypeVar syntax (proposal?). Am I imagining things or is that a thing?

summer berry
#

That's what the PEP linked above is

green gale
#

Oh I didn't even see that

#

Thanks!

summer berry
#

np

sterile python
summer berry
#

Yeah it outlines the algorithm in the PEP.

dim trail
#

why should __eq__ work with any object?

Instinctively, ive written something along these lines as typehints for a __eq__ check:

from typing import Union


class A:
    def __eq__(self, other: Union["A", int]) -> bool:
        return True

now, with this, mypy complains:

error: Argument 1 of "__eq__" is incompatible with supertype "object"; supertype defines the argument type as "object"
note: This violates the Liskov substitution principle
note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
note: It is recommended for "__eq__" to work with arbitrary objects, for example:
note:     def __eq__(self, other: object) -> bool:
note:         if not isinstance(other, A):
note:             return NotImplemented
note:         return <logic to compare two A instances>

This seems odd to me, almost like telling me "you cant typecheck __eq__". Am i supposed to not provide any information as to which types my eq implementation actually supports?

how would i typehint eq so that the typechecker is able to warn me when comparing a incompatible type?

acoustic thicket
# dim trail why should `__eq__` work with any object? Instinctively, ive written something ...

This violates the Liskov substitution principle
object has a default implementation which supports eq with any other object. if a child class overrides a parent class's function, then the new function must accept whatever the old one did, if not more. since object.__eq__ accepts any object, A.__eq__ must also accept any object

consider this function:

def equals_hi(o: object) -> bool:
    return o == "hi"

this function is correctly typed, so it should never error, but it will if you did equals_hi(A())

dim trail
acoustic thicket
#

i think so

#

comparing being only ==, not <, > etc

pastel egret
#

The reason is that equality is in the short list of things that should work between any objects.

dim trail
#

aight, guess i'll have to live with that

acoustic thicket
#

are eq and neq the only two such operations pithink

brisk hedge
#

the only binary ops perhaps

acoustic thicket
#

yeah thats what i meant

brisk hedge
#

all the other operators are in some tp_as_xyz slots I believe

pastel egret
#

On the C level, actually all the comparison operations (==, !=, <, >, <=, >=) are the same function pointer, with an enum parameter for the op. Not really relevant to type checking though, conceptually object does not implement the others and it wouldn't be in the type stub.

brisk hedge
#

yes, they're in the dir but they simply raise

#

so for all intents and purposes they aren't implemented

summer berry
#

mypy documentation shows ```py
tp: Type[object] # "tp" is a variable with a type object value
if random() > 0.5:
tp = A
else:
tp = B # This is OK

def fun2(x: tp) -> None: ... # Error: "tp" is not valid as a type

So if I need to do this, how do I avoid the error?
#

Just # type: ignore?

#

In my case, A is a subtype of B so it'd be nice if I could at least tell mypy to be conservative and assume B

fervent sierra
summer berry
#

I have a model with pydantic. I want to use EmailStr if the email-validator dependency is installed. If it's not (i.e. I get an ImportError), then I want to fall back to using str as the field.

#

But mypy does not like the type of the field being dynamic (I get a valid-type error)

#

I could live with # type: ignore[valid-type] but I'm wondering if there's a better way.

oblique urchin
#

You can do something with if TYPE_CHECKING

summer berry
#

That's a good idea. I did not consider that. I can set it to str inside that.

soft matrix
#

try except ImportError should work right?

summer berry
soft matrix
#

no like mypy should be able to figure out the type right?

#
try:
    import the_email_validator
    tp: TypeAlias = email_validator.Type
except ImportError:
    tp: TypeAlias = str
#

this works with pyright iirc

summer berry
#

No, it will not work with mypy

fervent sierra
summer berry
#

As explained in what I linked above.

fervent sierra
#

won't this make mypy think that FieldType is a str throughout the code?

summer berry
#

Yeah, but I believe that is okay for my use case. The fact that the field is actually a EmailStr in some cases is only relevant for pydantic, not for end users.

blazing nest
#

...wait does PEP 695 mean that type will now be a soft keyword?

soft matrix
#

yes

blazing nest
#

As in, similar to match

soft matrix
#

thats whats suggested

summer berry
#

But if that was a concern, then I think my only option would be to subclass EmailStr and override the methods to not raise an importerror

summer berry
blazing nest
#

Hmm so far I haven't read about multi-line classes

#

Will I be able to do: ```python
class Container[
T
](
list, dict
):
...

#

Not really that I would want to do that

oblique urchin
cedar sundial
#

Is there a way to type a parent class' self as a subclass?

class Parent:
    def some_fn(self, ...) -> None: ...
    # self here to be typed as one of ChildA or ChildB

class ChildA(Parent):
    ...

class ChildB(Parent):
    ... 
fervent sierra
#

I think that would not be enforceable, because somewhere else in the code you could have a ChildC that would violate that typing. Maybe you could have some_fn be a standalone function or a static function, that instead of using self just uses some_child: ChildA | ChildB ?

soft matrix
#

whats are you actually doing in some_fn that requires knowledge of whether its A or B?

cedar sundial
#

I'm using functions from those specific classes that are not found in the Parent class so I would like the types and autocompletion

brazen jolt
#

maybe consider adding those functions as abstract functions

#

here's a small example of how that may look like```py
from abc import ABC, abstractmethod

class Foo(ABC):
@abstractmethod
def write(self, data: bytes) -> None:
...

def write_string(self, data: str) -> None:
    bytes_data = data.encode("utf-8")
    self.write(bytes_data)
cedar sundial
#

Ok, would this work the same way for class attributes instead of methods?

brazen jolt
#

with class attributes, just give them a type-hint without any values

#
class Foo:
    MY_VARIABLE: str

    def foo(self):
        return self.MY_VARIABLE
soft matrix
#

if you want checking of that however, you need to use a property

brazen jolt
#

it's not quite the same and the ABC metaclass won't prevent you from initializing, but I'm not aware of any better approach there

soft matrix
#

with @abstractmethod

cedar sundial
#

This works well. Thank you all

cedar sundial
#

Why does mypy not like variable name reassignment?

#

I know what I am doing it probably bad practice to cause these errors but I would think it makes sense to get the type of a variable in it's current context rather than when it is first defined

#

Quick example:

def fn():
    data = 1
    # some other stuff
    for names, data in some_dict.items():
        ...
        reveal_type(data) # int
brisk hedge
#

if you are allowed to reassign types, that defeats the purpose of type checking assignments

#
x = 1 # x is definitely an int
x = "ohoho" # well I guess x must be str now! 
trim tangle
#

It's just that x has different types in different parts of the code block

sterile python
#

pyright works fine, mypy not

#
name = "Oleksii"
name = 0
main.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str")
Found 1 error in 1 file (checked 1 source file)
verbal spoke
#

Okay, this has been driving me bonkers.

#

How the crap do you even start to type hint a JSON from an api request?

#

Like, cleanly I mean

#
PAYLOAD = dict[str, str | int | list[dict[str, str | int]]]
PARSED_CHANNELS = list[dict[str, str | int]]

Like....

#

That's about the best I have, and I don't think that fully covers all the cases I have now

buoyant swift
#

either a typeddict, dataclass, or give up and use any

verbal spoke
#

Yeah the Any is looking more and more attractive

#

I guess I could just do dict

#

Just hurts to do either

#

Feels like I'm not being thorough enough. But at the same time.... API JSON

buoyant swift
#

have you tried typeddict?

trim tangle
#

otherwise, well, the response is any JSON

acoustic thicket
soft matrix
#

There is _typeshed.JSON FWIW

cold ridge
#

Why does mypy give the error that "Name {typename} is not defined" when you're importing types from other files?

rare scarab
#

is the other file in a package?

#

If so, it needs a py.typed file

old dagger
#

Can somekne provide any resources to learn more about python type hinting. Also im not sure if this is typehinting but in lots of official python modules there are empty functions like def x(a) -> List[str | int]: ...

soft matrix
#

check the pins for some links

oblique urchin
old dagger
#

Alriht

old dagger
#

Is type hinting making your code more idiomatic in any way

sterile python
sterile python
#

Also type hints are important thing for auto-complete

old dagger
#

Yeah

#

Uh more stupid question. In 3.10 the pipe was introduced to replace Union[..., ...] although since Im new to type hihting i dont know what these do. My silly guess is that List[str | int] is a list of strings and ints? Lmfao

soft matrix
#

Yeah that's correct

#

A union says it can be one of these types

pastel egret
#

Note there's a big difference between list[str | int] and list[str] | list[int] - the first is a list with any combination of strings/ints, the latter is a list with only one of the types.

solid light
#

(to be super explicit as to what it means)

#

But then I suppose that's what @pastel egret was referring to with the difference between them so maybe what I said is wrong

#

I think it could technically still be all strings/ints

pastel egret
#

It can be yes.

#

What I meant is that list[str | int] accepts ['a', 1, 'b', 2], but list[str] | list[int] does not.

solid light
#

Yeah, exactly

visual vigil
#

When using typing.overload should the final implementation be type hinted. The docs and pep 484 suggest that type checkers should ignore it:

The @overload-decorated definitions are for the benefit of the type checker only, since they will be overwritten by the non-@overload-decorated definition, while the latter is used at runtime but should be ignored by a type checker.

But mypy still complains about them when running under strict mode. Is there any advantage to adding type hints (which I guess would always be just the unions for each possible parameter/return)?

oblique urchin
#

they are not useful from the perspective of callers though

visual vigil
oblique urchin
visual vigil
#

ah true

#

thanks

fervent sierra
#

I'll add my two cents here and say that overloads in python are a detriment to your type safety; It's completely on you, the developer, to properly dispatch to the correct implementation of your overloaded function; If the type checker can't determine for sure what the types of the parameters of the final implementation are, then it's easy for you to forget to dispatch for one of the overloads, specially if you add more later

#

This is different from a language like Java, which will determine which version of your function should be called during compile time

cold ridge
#

Hello, I wanted to annotate an attribute which would be initialized later. But, I'm not sure as to what placeholder value I should use in a way that doesn't make the rest of the code scream.

#

If I do Optional[Data], initializing it with None will work, but accessing this in the methods gives errors

rare scarab
#

don't initialize it

#

put data: Data as a class attr

#

i.e. ```py
class Cls:
data: Data
def init(self, other: str) -> None:
self.other = other

def set_data(self, data: Data):
self.data = data

soft matrix
#

i think initialising it with None is the most type safe thing to do as annoying as it may be

#

what if i accidentally do self.data.foo before ive called set_data or w/e?

rare scarab
#

Then you'd get a TypeError instead of AttributeError

soft matrix
#
class Cls:
  def __init__(self, other: str) -> None:
    self.other = other
    self._data: Data | None = None

  @cached_property
  def data(self) -> Data:
    assert self._data is not None
    return self._data
#

this is what id generally recommend

cold ridge
#

Thanks

runic sleet
#

not sure if this is a mypy bug or if I'm doing the toml config wrong?

[[tool.mypy.overrides]]
module = "tests.*"
ignore_missing_imports = true
disallow_untyped_defs = false
#

the ignore_missing_imports for tests doesn't work with this config

old dagger
soft matrix
#

yes exactly like an or

old dagger
#

Yeah thx

dull lance
#

I am trying to annotate type constraints for a class decorator, but pyright seems to be unable to enforce them for some reason... is this a bug, or am I just not doing this correctly? Also, I have no idea why dec0 behaves differently from dec1 in this respect

#

In case you want to copy and paste the code:

from typing import Protocol, TypeVar, Type

class MyProto(Protocol):
    def foo(self, x: int) -> int: ...

TMyProtoMeta = TypeVar('TMyProtoMeta', bound=Type[MyProto])
def dec0(t: TMyProtoMeta) -> TMyProtoMeta:
    return t

def dec1(t: Type[MyProto]):
    return t

@dec0
class Good0:
    def foo(self, x: int) -> int:
        return x

dec0(Good0)

@dec1
class Good1:
    def foo(self, x: int) -> int:
        return x

dec1(Good1)

@dec0           # Failed to detect error
class Bad0:
    pass

dec0(Bad0) 

@dec1           # Failed to detect error
class Bad1:
    pass

dec1(Bad1)      # Failed to detect error
#

mypy is able to infer the incorrect usage of the decorator for the Bad classes, so this seems like a pyright issue

brisk hedge
#

What are the semantics of type[Proto]? Perhaps you can substitute the typevar inside the type[] call?

dull lance
trim tangle
#

Maybe mypy treats untyped/partially typed decorators as identity functions

dull lance
#

with or without the TypeVar, it behaves the same as the corresponding case when I don't specify the return type

oblique urchin
#

mypy basically ignores class decorators

void panther
#

Is typing a function's args/kwargs impossible without also passing the function around at runtime? I have a class that stores a function call's arguments, but it doesn't store the function itself

oblique urchin
void panther
#

I have a class

class A:
    def __init__(self, args, kwargs):
        self.args = args
        self.kwargs = kwargs

and want to type args and kwargs to be the corresponding args and kwargs of a function. That function is known where A is instantiated, but A itself doesn't keep track of it

old dagger
#

Hey uh stupid question butshould i typhint everything.

soft matrix
#

typehint as much or as little as you want, its the benefit of gradual typing

#

i personally try and typehint everything that i think i could come back to and stuff thats a one off i dont normally bother

oblique urchin
#

but note that you mostly should not type hint local variables

sterile python
old dagger
#

Wdym

sterile python
#

For example if you are doing library you would like to typehint its all functions, classes that would be used by someone other

old dagger
#

Yeh

#

Aight ty

rare scarab
#

just for generics though

soft matrix
#

i prefer x = list[str]()

rare scarab
#

Well that doesn't work in python 3.8 (using annotations works if using from __future__ import annotations)

sterile python
buoyant swift
#

they can easily be inferred when you first assign them

brittle socket
#

So, it's not as much about local variables as it is about situations where type inference is obvious?

rustic gull
#

Guys, anyone uses pylint on vscode? Basically I'm doing this

def x() -> str:
 return "hello"

def y(foo: int) -> str:
  return str(foo)

y(x())

Shouldn't y mark an error? Because it's hinted it receives an int but gets a str

void panther
#

I believe you need to turn on pyright somehow in pylint for type checking

#

ah no that's pylance, curse the similar naming

#

pylint just doesn't do type checking afaik

rustic gull
#

Thank you a lot

void panther
#

vscode provides integration with pyright through pylance

sinful oxide
#

is typehinting a float to specify range a good idea (e.g. float[0, 1] to mean a float between 0 and 1)

dim trail
vernal sigil
#

Can I typehint a float/int to be between a specific range (i.e. 0 and 1.0 and 0 and 100)

#

I tried googling it but I get something about number types

pastel egret
#

Not with the base type system, since it's intended to mainly constrain types not values.

vernal sigil
#

Ok

pastel egret
#

There's libraries though that add the ability to do things like that - see phantom-types for instance.

#

You can use typing.Annotated and/or typing.NewType to create type-checker-only types with the constraints, and then have a converter function which checks the range, then casts it to that new type.

zinc hatch
#

i'm using the websockets module and noticed that VSCode does not offer any type hints for it
is that a vscode issue or does the module not offer any kind of type hints ..

#

?

pastel egret
#

Looking at the repo, it should have type hints. You might need to change some Pyright configuration to get it to be able to find the module properly.

zinc hatch
dull lance
dull lance
rustic gull
#

Hi guys, I'm using vscode and pylint

It just show errors when I save the file, is this OK? I think it's kind of annoying, can it show it in real time?

dull lance
rustic gull
#

I've pylance too, but I doubt it's affecting it

dull lance
#

I have default Pylance settings, didn't really change any settings regarding autosave

#

just checked, my autosave is off

rustic gull
#

Seems odd, I doubt this is it, but I'll restart my laptop

#

Thank you

dull lance
#

np

soft matrix
#

pylint probably isnt capable of running as you type

#

pyright has parse error recovery

rare scarab
warped lagoon
#

is this not a valid TypeVarTuple usage?

Ts = TypeVarTuple("Ts")

# ... mapping Tuple[*Ts] to Union[*Ts]```
fervent sierra
warped lagoon
#

yeah I'm basically implementing something funny like ```python
@final
@frozen()
class WrapResult(Generic[E]):
error_type: E

@classmethod
def create(cls, error_type: F) -> WrapResult[F]:
    return cls(error_type)  # type: ignore

def __getitem__(self, error_type: F) -> WrapResult[F]:
    return self.create(error_type)``` and I'm thinking of allowing tuples of error types as well
soft matrix
#

unfortunately no

glacial pollen
#

When does mypy look at stub files? It doesn't seem to be reading them if they're in the same directory, or even if they're in a directory pointed at by MYPYPATH. I have a very basic test.py and a test.pyi, but a type conflict that should be shown is not unless I directly annotate test.py

#

If I try mypy ., it tells me I have a duplicate module in stubs\test.pyi... I feel I'm missing something obvious!

fervent sierra
#

I think .pyi files must be inside modules to work (at least in pyright) which means you'd need to have those __init__.py files to make your folders into modules

glacial pollen
#

I just reread the docs, and it does say

Use the normal Python file name conventions for modules, e.g. csv.pyi for module csv.
I had misread that as "for csv.py" previously.

#

No idea. With or without __init__.py, changing the .pyi to the directory ("module") name, it just says "no issues found in 2/3 source files" :(

fervent sierra
#

what's your file structure?

glacial pollen
#

Flat directory structure. All files in single project dir.

#
pyi_test
|- __init__.py
|- pyi_test.pyi
|- test.py```
fervent sierra
#

ah, rats. I guess I was thinking of the py.typed file, not .pyi files

#

So, if I understand correctly, though, you'd use .pyi when you don't control the source code for the library you're annotating. If you do, then you should put the type annotations directly into the code

glacial pollen
#

I'm trying to emulate that situation.

#

Although keeping the typing in a separate file feels like a good way to avoid unnecessary bloat in the source file, too. (Especially to avoid confusing other devs who don't necessarily understand what the type hints are)

fervent sierra
glacial pollen
#

I think that depends on how complex they get.

#

When it's just def fn(a: int) -> None: ... that's one thing. When you start putting a load of optionals in... I guess you create a type (at which point, someone says "what's this weird variable at the top of the file?" ;) )

#

But anyway. No idea why the basic layout's not working.

fervent sierra
#

So your tree structure there should probably have a test.pyi file instead of a pyi_test.pyi file. They also have a __init__.pyi, in which they seem to reference the other .pyi files.

So i guess you either put all your typing inside __init__.pyi or, if you split it off, maybe they have to be referenced inside __init__.pyi ?

glacial pollen
#

I did initially have a test.pyi, but after reading the docs I quoted above I switched to <module>.pyi... It certainly would seem more natural to have a <file>.pyi

rustic gull
rustic gull
rare scarab
#

Yes

brittle socket
#

Why would you use a generic like this instead of just str | int or even a TypeAlias for it?

T = TypeVar("T", str, int) 
oblique urchin
brittle socket
#

As in fn(a: list[T]) -> T iiuc, can't a TypeAlias do the same?

soft matrix
#

no

brittle socket
#

T: TypeAlias = str | int

soft matrix
#

fn there with a type alias is equivalent to ```py
@overload
def fn(a: list[str]) -> str: ...
@overload
def fn(a: list[int]) -> int: ...
def fn(a): ... # some actual implementation

#

with a type alias you have the same input types but your output will be different

#

cause youll widen the type to str | int rather than str if a is list[str] or int if a list[int]

brittle socket
#

Oh. So, with that type alias, a str input can have an int output

soft matrix
#

yep

brittle socket
#

And the constrained typevar doesn't allow that?

soft matrix
#

yep

brittle socket
#

Because T is constrained to resolve to one type at a time, hmm

#

Interesting, thank you

#

Also, I read about this typevar constraint notion in an article on python generics, why is it wrong to call it a generic?

soft matrix
#

a generic is generally a type that can be subscripted (ie you use []) with another type and acts as a container (in a very broad definition of the term)

#

type vars arent types and currently cant be subscripted

brittle socket
#

Oh, interesting

#

So list and Union are generics

oblique urchin
#

Union is not, it's its own special thing

brittle socket
#

Hm

#

Alright, thank you :)

runic tapir
#

Is there a way to annotate that a function accepts instances of some type, but not instances of subtypes of that type? I've got an API that requires a dict instance, not an instance of a subclass of dict, and I'm wondering if there's some way to explain that via typing

brazen jolt
#

!d typing.Final

rough sluiceBOT
#

typing.Final```
A special typing construct to indicate to type checkers that a name cannot be re-assigned or overridden in a subclass. For example:

```py
MAX_SIZE: Final = 9000
MAX_SIZE += 1  # Error reported by type checker

class Connection:
    TIMEOUT: Final[int] = 10

class FastConnector(Connection):
    TIMEOUT = 1  # Error reported by type checker
```...
runic tapir
#

(Yes, it's probably not a good idea to have such an API in the first place; we're only accepting dict for good reasons, I swear)

brazen jolt
#

wouldn't Final[dict] do the trick?

oblique urchin
#

no

runic tapir
oblique urchin
#

that would mean the variable cannot be assigned to later

brazen jolt
#

oh

#

I see

oblique urchin
runic tapir
#

indeed. The spot where I'm doing it is probably pretty close to why the stdlib is doing it - performance, and to avoid the possibility of reentrancy

#

(since I know that the accesses I'm performing on a dict can't call back into me, but I couldn't know that about arbitrary dict subclasses)

trim tangle
#

that's the same issue as "iterable of strings but not a string"

runic tapir
#

true enough, that example makes sense.

#

it wouldn't really be enforceable at compile time in any statically typed languages I can think of, either

acoustic thicket
#

its probably possible in languages which do not support subtyping

#

wait

#

thats obvious nvm

rustic gull
#

i need help with type hints

#

does type-hinting slow down performance?

mortal cipher
#

it shouldn't, at least not enough to make any difference

soft matrix
#

depends

mortal cipher
#

if you're instantiating a lot of type hints in the runtime with function calls that are somehow expensive, it may ๐Ÿค”

#

personally i just use strings whereever i can. i still get syntax highlighting within them because my editor understands that those are type hints, and the typechecker also understands them

#

pyright will also complete inside type strings

soft matrix
#

theres really no need to use strings everywhere

#

just import future annotations

rustic gull
#

Python should add a flag where it removes all typing stuff, annotations, etc

#

would this be a good solution?

#

atleast optimize out typing stuff

trim tangle
#

solution to what?

#

it's a really minor cost, and you only pay it on startup (in almost all cases)

rustic gull
#

eg: use of typing.cast()

trim tangle
#

That's not really possible to optimize out

rustic gull
#

why not

trim tangle
# rustic gull why not

Python has a lot of dynamic behaviours, which break otherwise sensible optimizations. For example, you might think that this: ```py

module_a.py

foo = 1

def get_foo():
return foo
can be optimized topy

module_a.py

def get_foo():
return 1
But nothing is stopping some other module from doingpy
import module_a
module_a.foo = 42
```, in which case get_foo should return 42.

rustic gull
#

if i can remove a typing.cast call then even the optimizer can

trim tangle
#

An optimization, by definition, does not change the behaviour of a program.

rustic gull
#

hmm

#

what if it was opt-in only

trim tangle
#

If calling typing.cast is such a big performance penalty you can replace it with a # type: ignore

rustic gull
#

which is ugly

trim tangle
#

isn't typing.cast also ugly? lemon_pleased

rustic gull
#

atleast its better

#

kinda makes sense

fossil nest
#

why doesnt python support type hinting for more complex objects like coroutines by default?

#

or it does and i just havent discovered it ๐Ÿ˜„ ?

#

to clarify, i mean not importing any modules such as typing or collections

brittle socket
#

Mypy:
Item "None" of "Optional[Match[str]]" has no attribute "group" [union-attr]

if found := marker.search(paragraph):
    current = int(found.group(1))

Python 3.10.5, mypy 0.961

So...from context, "Item" is clearly never None. How can I solve this mypy warning?

void panther
void panther
fossil nest
oblique urchin
#

maybe type narrowing for the walrus isn't implemented properly

void panther
#

ah sorry I misread that error

brittle socket
void panther
fossil nest
#

has generics for python's abcs
what do you mean

#

i assume its an acronym

brittle socket
#

Welp, guess I'll just # type: ignore[union-attr] on that line until they fix that

void panther
# fossil nest > has generics for python's abcs what do you mean

e.g. collections.abc.Iterable is any iterable, most objects don't actually subclass it but it's understood that anything that implements __iter__ is iterable and falls under Iterable. It's abstract and only tells you what should be implemented (and may give some useful defaults for methods)

brittle socket
#

https://mypy-play.net/?mypy=latest&python=3.10&gist=153d5af78532766fc59af0b2e11c15a2
Argument 1 to "map" has incompatible type "Callable[[T], T]"; expected "Callable[[object], T]"

T = TypeVar('T', str, int)
StrOrInt: TypeAlias = str | int

def fn(p: T) -> T:
    if isinstance(p, int):
        return p
    
    return p * 2

def bn() -> Iterator[StrOrInt]:
    res = map(fn, ['a', 1, 'b', 2])
    print(res)
    
    return res

I wanted fn to express that it returns the same type it receives (i.e. int -> int and str -> str) so I used a constrained TypeVar there.
But mypy isn't happy with the map line. Not sure I understand why ๐Ÿ˜•

rustic gull
#

try using typing.T?

brittle socket
#

I'm not familiar with that. from typing import T?

rustic gull
#

yes

brittle socket
#

main.py:1: error: Module "typing" has no attribute "T"

rustic gull
#
>>> def f(p: T) -> T:
...     if isinstance(p, int):
...          return p
...     return p * 2
>>> def bn() -> Iterator[str | int]:
...     return map(fn, ['a', 1, 'b', 2])
void panther
brittle socket
#

Yeah...I came to suspect something like that but then didn't know how to fix it here

#

@rustic gull I don't see anything different from how I did it?

rustic gull
#

using typing.T

#

Python 3.10.5

brittle socket
rustic gull
#

the site's issue

brittle socket
#

Ok...I'll try in my actual code. Also having a hard time finding doc on typing.T since T is kinda hard to search

void panther
#

any T in typing is going to be their own typevar, it's not supposed to be used externally

rustic gull
#

oh

brittle socket
#

Ok...how would you approach that problem?

#

fn outputs the same type it takes, but it's normal for bn to return an iterable of both types ๐Ÿ˜•

rustic gull
brittle socket
#

Yeah, I actually read that example last night

#

Not sure how it applies here

rustic gull
#

T = TypeVar('T') instead of what you had

void panther
#

Only bound on the union of the two came to mind but that also seems to be problematic

rustic gull
#

this is too hard for me to understand

void panther
#

a plain typevar would make their fn invalid

rustic gull
#

can we make it like C++ templates

brittle socket
#

Well, if you're having trouble with this, maybe I should think about changing the design instead

#

I'll try overloading fn instead of branching on types with if

rustic gull
#

can we do that?

brittle socket
#

Never used @overload, about to

rustic gull
#

wouldn't it just replace the whole function

#

ah

#

@overload would just do what you did but hide the if else right?

brittle socket
#

I mean...I'm not doing anything crazy, why is the typing system giving me a hard time here ๐Ÿ˜•

rustic gull
#

would you consider this a bug in mypy?

brittle socket
#

I don't think so, because TypeVars do expect to resolve a single type and none other (can't be int | str), but that's what fn is doing. There shouldn't be a problem with bn returning an iterator of both types

#

A function returns a map object over int | str, and the mapper returns an int given an int, and a str given a str. Nothing too crazy here ๐Ÿคทโ€โ™‚๏ธ

#

It doesn't seem to me like TypeVars or mypy are broken, but I feel like there is a mechanism lacking for expressing something this simple

rustic gull
#
Argument 1 to "transform" of "Transformer" has incompatible type "Union[Any, Tree[Any], None]"; expected "Tree[Any]"mypy(error)
#

why

brittle socket
#

TypeVars seem to just not like playing nice with unions ๐Ÿคทโ€โ™‚๏ธ

oblique urchin
#

this is join-v-union territory: mypy infers the list as list[object] inappropriately

#

try explicitly declaring lst: list[str | int] = [...]

brittle socket
#

I'm wrong, it's not the "same", it was Callable[object] before:
Argument 1 to "map" has incompatible type "Callable[[T], T]"; expected "Callable[[object], T]"

oblique urchin
#

that one is correct. you cannot pass str | int to a function that is generic over a constrained TypeVar

#

(except it starts complaining about fn instead)

brittle socket
#

(You gave me back my same link, you need to click on the Gist button right next to Run to generate your own link)

oblique urchin
brittle socket
#

Interesting

#

So, as a TIL, just never pass a union type to a constrained TypeVar, that's not how they're supposed to be used

#

Hm. I think I'll just drop the TypeVar and only use TypeAlias here. I wanted to express that fn returns the same type it takes, but that seems too problematic here ๐Ÿคทโ€โ™‚๏ธ

rustic gull
#

If someone has a nice .pytlintrc and is willing to share it, I would apreciarte it

hallow flint
mortal cipher
#

anyone know why mypy would say error: Variable "typing_extensions.Self" is not valid as a type?

void panther
#

I'm not sure whether the support is there yet

mortal cipher
#

aw. :^(

#

at least pyright seems to understand it

#

mypy not understanding that causes a bunch of other failures to cascade

#

core/models.py:735: note: Revealed type is "Type[core.models.TranslationModelBase[Self?]]"
since it doesn't know what Self is, it doesn't let me use this type as an actual Type

fervent sierra
#

You can try this style for the Self type:

MYSELF = TypeVar("MYSELF", bound="MyClass")
class MyClass:
    def foo(self: MYSELF):
        ...

I think this style even prevents some of the variance unsoundness that you'd get by using Self

soft matrix
#

You get variance unsafeness using Self?

rare scarab
#

Self should be mainly used in classmethods to return an instance of the class.

#

i.e. ```py
class Foo:
@classmethod
def create(cls) -> Self:
return cls()

#

It can also be used for method chaining where each method returns self

#

i.e. x().y().z()

sonic fern
#

Hello!
Anyone knows what's the closest thing to

def get_const(img_type: type[Item], key: str) -> [WHATEVER TYPE tp[key] IS]:
    tp = item[img_type.as_string()]
    return tp[key]

?

leaden oak
#

where is the item mapping / dictionary coming from?

sonic fern
#

Same file as the function, as an example:

still: Consts = {
    "RESULTS_DIR": "Stills",
    "LIST_URL_TEMPLATE": "",
    "URL_TEMPLATE": "",
    "JSON_FILENAME": "stills.json",
    "PARSER": html_parser.StillParser,
    "ORGANIZER": organizer.StillOrganizer,
}
#

Consts is a TypedDict

#

I could return an union for sure.

oblique urchin
sonic fern
#

Ah, ok. I'll just return a cast to Any then.

#

However I'm having another issue

#

To solve a cyclic import, I put an if TYPE_CHECKING around the from classes import Item.
But now Python yells at me that NameError: name 'Item' is not defined. Did you mean: 'item'?

#

I'm not using Item in any way here other than type checking.

oblique urchin
#

annotations are still evaluated at import time. Put "Item" in quotes or use from __future__ import annotations

sonic fern
#

Ah, thanks.

brittle socket
#

Argument 1 to "join" of "str" has incompatible type "List[_S]"; expected "Iterable[str]"

def tables_to_text(paragraph) -> str:

    values_of = partial(map, itemgetter("VALUE"))

    extracted_text = []
    for row in values_of(paragraph):
        for cell in values_of(row):
            for cell_content in values_of(cell):
                for text in values_of(cell_content):
                    extracted_text.append(text)

    return ' '.join(extracted_text)

https://mypy-play.net/?mypy=0.961&python=3.10&gist=29175e885b7b464698564de8a5ebdb85
Not sure how to fix this error. What is this _S thing? It's supposed to just be a str ๐Ÿ˜•

rare scarab
#

you should complete the types in your function args

#

anyway, try annotating extracted_text with list[str]

brittle socket
storm python
#

Hello. What's the effective difference between @overload and @dispatch? I understand that the syntax between them in defining overloaded functions differs.

#

do they complement or substitute for one another?

oblique urchin
storm python
#

like mypy, ok

storm python
brittle socket
#
def tables_to_text(paragraph: list[dict[str, Any]]) -> str:

    values_of = partial(map, itemgetter("VALUE"))

    return ' '.join(
        text
        for row in values_of(paragraph)
        for cell in values_of(row)
        for content in values_of(cell)
        for text in values_of(content))

Generator has incompatible item type "_S"; expected "str"

brittle socket
oblique urchin
brittle socket
#

The _S it was complaining about was actually within partial's return type. I figured Callable with its implicit [[Any], Any] would subsume that

mortal cipher
#

i had asked this before in a help channel but didn't really get anywhere. i want to get essentially the following type structure:

from typing import Self, TypeVar, Type, Generic, TYPE_CHECKING
from django.db.models import Model
from django.db import models

if TYPE_CHECKING:
    from django.db.models.manager import RelatedManager

class Language(Model): ...

_TranslatedM = TypeVar("_TranslatedM", bound="TranslatedModel")
_TranslationM = TypeVar("_TranslationM", bound="TranslationModelBase")

class TranslationModelBase(Generic[_TranslatedM], Model):
    language: "models.ForeignKey[Language, Language]"
    parent: "models.ForeignKey[_TranslatedM, _TranslatedM]"

class TranslatedModelMeta(models.base.ModelBase):
    "Metaclass that generates a TranslationModelBase subclass as the Translation property of its classes."
    ...

class TranslatedModel(Generic[_TranslationM], Model, metaclass=TranslatedModelMeta):
    Translation: "Type[TranslationModelBase[Self]]"
    translations: "RelatedManager[_TranslationM]"

    @property
    def translation(self) -> _TranslationM:
        current_locale = get_current_locale_from_request()
        return self.translations.get(language__code=current_locale)

this would allow me to write the following and have it fully type hinted (and automatically linked in django!) without having to manually specify things:

class Foo(TranslatedModel["FooTranslation"]):
    ... # fields, etc.
    ## Expected:
    # Translation: Type[TranslationModelBase[Foo]]
    # translation: FooTranslation
    # translations: RelatedManager[FooTranslation]

class FooTranslation(Foo.Translation):
    name = models.CharField[str, str](...)
    description = models.CharField[str, str](...)
    ## Expected:
    # parent: models.ForeignKey[Foo, Foo]

however currently mypy doesn't seem to understand Self, and pyright refuses the code stating "Class definition of Foo depends on itself". is there a way to properly type this?

#

use in actual code would be something like

foo = get_object_or_404(Foo, pk=pk) # foo: Foo
foo.translation # Expected: FooTranslation
foo.translation.name # Expected: str (via CharField.__get__)
#

right now the best compromise i have is specifying translation and translations's type manually in the Foo class definition and not giving it the FooTranslation type parameter, but i hope that there's a better way to do it.

stray summit
#

im wondering - anyone aware of a painless way to tie lazy loading and type hinting so that i can declare certain descriptors on objects that will bascially act like cache properties

if TYPE_CHECKING:
   from ._heavy import Helper

class Obj:
  @cached_property
  def helper(self) -> Helper:
    from .heavy import Helper
    return Helper.from_obj(self)

## "wanted"
with lazy_import():
  from . import _heavy

class Obj:
   helper: _heavy.Helper = magic_demand_create_needs_sane_name()
fervent sierra
fervent sierra
# mortal cipher i had asked this before in a help channel but didn't really get anywhere. i want...

At first glance, I don't think you could do what you want via metaclasses. It sounds more like what you want is what "code generation" or "monomorphization" would do, which would actually create new types in compile time, and which would make the type checker aware of them. Even if you achieve that in your code, I don't think any type check would ever be aware of the classes that get generated.

Still, I don't think you need this level of magic to do what you want

mortal cipher
mortal cipher
#

the Foo.Translation model points to Foo via a foreign key (which is added in the metaclass). i want this foreign key's type to be automatically figured out. i also want to be able to tell Foo about the reverse relation via a type parameter.

#

i'll come up with a pure python version and post it to give an idea of what i want

fervent sierra
#

Great =)
The thing is, there's a lot that works in runtime but that the type checkers can't be aware of, because they don't execute the code. Whatever you come up with has got to be declarative, so to speak, not imperative.

knotty wren
#

how can i get generic type in a method of generic class?
like if i have a = generic_type[int]() i want to call int() in a.create()

void panther
#

I don't think you can get that information, but you could pass it into the normal args and have the generic resolve to that through a typevar

fervent sierra
# knotty wren how can i get generic type in a method of generic class? like if i have `a = gen...

That's kinda hard to do because you don't have runtime access to that generic. You can somewhat work around it with something like this:

from typing import TypeVar, Generic, Type

T = TypeVar("T")

class MyClass(Generic[T]):
    def __init__(self, your_type: Type[T]):
        self.your_type = your_type
        super().__init__()

    def create(self) -> T:
        return self.your_type()

Notice that the create method isn't completely safe, though. If you pass in a type in your_type that can't be instantiated without arguments, things will break in runtime. You might want to consider having T have a bound to a Protocol that specifies how it can be called/instantiated

knotty wren
#

thank you!

#

how about creating generic function?

#

i have a func that has no params so i cant use typevar

#

but i want to have its return value

#

generic_func[int]() # returns int

soft matrix
#

You can

#

Access self.orig_class

#

And then it should be args[0]

trim tangle
#

can you provide more details maybe?

mortal cipher
knotty wren
#

I'm constructing a pgsql query here
I defined everything that starts with Query till the last fetch

#

fetch looks like this:

#

I want to substitute the "Any" with something that the user can specify
so that I get the exact type instead of "Unknown" or "Any"

trim tangle
#

records: Sequence[PaginationResult[YourThing]] = await ...

knotty wren
#

that might just work

#

haha

mortal cipher
#
from typing import TypeVar, Generic, Sequence, Type
from typing_extensions import reveal_type, Self

_TranslatedM = TypeVar("_TranslatedM", bound="TranslatedModel")
_TranslationM = TypeVar("_TranslationM", bound="TranslationModelBase")

class Language: ...

class TranslationModelBase(Generic[_TranslatedM]):
    language: "Language"
    parent: "_TranslatedM"


class TranslatedModel(Generic[_TranslationM]):
    Translation: "Type[TranslationModelBase[Self]]"  # Generated by a metaclass
    translation: "_TranslationM"
    translations: "Sequence[_TranslationM]"


# Usage:


class Foo(TranslatedModel["FooTranslation"]):
    pass


class FooTranslation(Foo.Translation):
    name: str # CharField[str, str]


reveal_type(Foo.Translation)  # Type[TranslationModelBase[Foo]]
reveal_type(Foo().translation)  # FooTranslation
reveal_type(Foo().translations)  # Sequence[FooTranslation]
reveal_type(Foo().translation.name)  # str
reveal_type(FooTranslation().parent)  # Foo
#

@fervent sierra ^

#

right now mypy doesn't understand typing_extensions.Self, so i get the following output:

typing_test.py:15: error: Variable "typing_extensions.Self" is not valid as a type
typing_test.py:15: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
typing_test.py:27: error: Variable "typing_test.TranslatedModel.Translation" is not valid as a type
typing_test.py:27: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
typing_test.py:27: error: Invalid base class "Foo.Translation"
typing_test.py:31: note: Revealed type is "Type[typing_test.TranslationModelBase[Self?]]"
typing_test.py:32: note: Revealed type is "typing_test.FooTranslation"
typing_test.py:33: note: Revealed type is "typing.Sequence[typing_test.FooTranslation]"
typing_test.py:34: note: Revealed type is "builtins.str"
typing_test.py:35: note: Revealed type is "Any"
#

welp, i had the type vars reversed

#

fixed above, but still the same errors

soft matrix
#

If you want you can try my branch for it

#

It's a little bit broken for things that aren't Self but it should work for this

mortal cipher
#

thank you, i'll give it a try :^)

#

@soft matrix

typing_test.py:27: error: Variable "typing_test.TranslatedModel.Translation" is not valid as a type
typing_test.py:27: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
typing_test.py:27: error: Invalid base class "Foo.Translation"
typing_test.py:31: error: Access to generic instance variables via class is ambiguous
typing_test.py:31: note: Revealed type is "Type[typing_test.TranslationModelBase[Any]]"
typing_test.py:32: note: Revealed type is "typing_test.FooTranslation"
typing_test.py:33: note: Revealed type is "typing.Sequence[typing_test.FooTranslation]"
typing_test.py:34: note: Revealed type is "builtins.str"
typing_test.py:35: note: Revealed type is "Any"

the Self error went away but the other errors seem to persist

#

ah, one problem seems to be that Translation is seen as an instance variable

#

let me see how i can make it seen as a class variable

#

i wonder what a good way of approaching this would be... PEP amendment?

soft matrix
#

probably asking on typing-sig

#

i dont really understand why this isnt allowed

mortal cipher
#

but it seems like had ClassVar accepted that, mypy would be okay with that code

#

which is what i was going for ;^) so i guess time to bug the typing people about it

#

but i think i should first PoC a generic ClassVar

fervent sierra
mortal cipher
#

sure, i don't expect it to generate a type for me. i just want it to understand that it's there

#

the type is generated by the metaclass and placed on that property

#

seems like some of the other mypy errors i've been getting is caused by me not making Translation a ClassVar, and if i do that i get the error that ClassVars can't be generic (which seems to be an oversight in PEP 526)

fervent sierra
#

could you maybe get the Translation also as a generic arg?

mortal cipher
#

hmm, good point... i suppose something like this could work:

class TranslatedModel(Generic[_TranslatedM, _TranslationM]):
    Translation: "ClassVar[Type[TranslationModelBase[_TranslatedM]]]"
    translation: "_TranslationM"
    translations: "Sequence[_TranslationM]"

# ...

class Foo(TranslatedModel["Foo", "FooTranslation"]):
    pass
#

i'll try that later

#

it seems like pyright just can't cope with circular types so i'll ignore it for now

#

but then again i'm hitting the issue of classvars not being able to be generic

#

even though subclasses can fully specify the type of the class var

fervent sierra
mortal cipher
#

which i think should be fine

fervent sierra
mortal cipher
#

yes