#type-hinting
1 messages · Page 40 of 1
maybe mypy just ignores the decorator
sounds like both type checkers exhibit some amount of buggy behaviour here
seems to be it, I'm running mypy in ci so i'll go with that to atleast get typechecking in spots that use func() syntax
What even happens to other decorators in overload? Do they get executed? I would assume they don't affect runtime?
@overload is not reserved Python syntax, there's nothing special about it
!e
from typing import overload
counter = 0
def decorator(fn):
global counter
print("called decorator on", fn)
counter += 1
c = counter
def wrapped(*args, **kwargs):
print("called wrapped", c)
return fn(*args, **kwargs)
return wrapped
@overload
@decorator
def foo(a: int) -> int: ...
@overload
@decorator
def foo(a: str) -> str: ...
@decorator
def foo(a: int | str) -> int| str:
return a
print(foo(42))
:white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | called decorator on <function foo at 0x7f42c2309580>
002 | called decorator on <function foo at 0x7f42c21ce660>
003 | called decorator on <function foo at 0x7f42c2309580>
004 | called wrapped 3
005 | 42
That's incredibly helpful, thank you
i need some kind of combine(Foo[T], Foo[U], Foo[V]) -> CombinedFoo[T|U|V] - is anyone aware of a example of something like that?
Intersection? Nope but basedpyright has support for it #type-hinting message
hmm, i kinda wish i could define something like def combined[*Ts](inputs: *(Foo[T] for T in Ts) -> CombinedFoo[Union[*Ts]: ...
that's not possible
Actually, depends on what Foo is. Is it covariant or invariant?
Pyright understands this: playground this
# `Box` is covariant
def f[T](*boxes: Box[T]) -> Box[T]:
return random.choice(boxes)
strbox = Box("foo")
intbox = Box(42)
lbox = Box([1, 2, 3])
reveal_type(f(strbox, intbox, lbox))
``` ^this works because, since `Box` is covariant, `Box[int]` is assignable to e.g. `Box[int | str]`, and so pyright just resolves `T` to be `int | str | list[int]`
simpler example: ```py
def pick[T](a: T, b: T, /) -> T: ...
thing = pick(42, "hello")
``` this call is allowed, and thing is inferred as int | str by pyright
(this won't work with mypy because of "union vs join" (https://github.com/python/mypy/issues/12056))
i'm gathering context on https://github.com/bswck/ctnss. do you remember if it was ever intended to be possible to subclass a typed namedtuple?
have you ever done something like that?
I say use a @dataclass(frozen=True) instead
this looks wrong immediately
it shouldn't be allowed since they don't share a base type
they share many base types, including object and int | str
I'd say it's wrong that T is inferred int | str with how it's used
T should be str OR int, not either or in the same call site
if it's observed as object or Any, then it's pretty meaningless
It would be weird if py x: int = 42 y: str = "hello" x1: int | str = x y1: int | str = y pick(x1, y1) was allowed, but ```py
pick(x, y)
I disagree, tbh
int | str seems like a pretty reasonable inferred type in @trim tangle 's example above
They aren't "different types". They are both instances of str | int
ehhhhhh this feels like a bad semantic argument
What would you define as different types then?
We could benefit from something like NoInfer in TypeScript: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html#the-noinfer-utility-type
well one, the python type system itself does not identify the union as a supertype
You mean, at runtime?
correct
Well, int | str is not a class, so you cannot have an instance of the class int | str
There's a difference between "Python class" and "type-system type", just like in TypeScript
Out[22]: True
you mean like this?
that's cheating
yeah, this is a bit frustrating from a tooling respect since you're now forced to rely on another system
I've never really dug into "what" int | str is before, and now I'm seeing it's actually a types.UnionType and not a typing.Union
I fixed that in 3.14
I didn't even know types exists
neat!
I think my disagreement with the conversation stems with the lack of native ADTs in Python so you can't express what StrOrInt is
~~In a type system with subtyping, you generally want this: if A is a subtype of B, and an operation f is allowed on x: A, then doing ```py
y: B = x
f(y)
or rather, if f(x) is not allowed, then f(y) shouldn't be allowed either
Wait, I got it backwards.
Yeah, if f(A) is not allowed, it doesn't make sense for f(B) to be allowed
i think it makes more sense for f to be taking B (the supertype) in the example?
class B:
...
class A(B):
...
x: A = ...
y: B = x
# if f(x) is not allowed, so shouldnt be f(y)
I think y: B = x isn't valid since you aren't constructing whatever extension B is doing
it would have to be the other way around
Yeah, I wanted to say this actually
well, that's not strictly related to the inference of type variables
you could define a type checker where pick(42, "hello") would not be allowed, requiring you to explicitly do pick[int | str](42, "hello")
(well... the latter is not a thing in Python)
unless you make pick a class and make its __new__ funny
yeah, I just think the union inference shouldn't be allowed for generic positions and should be required to be explicit?
that is valid syntax, no?
im mostly in favor of the union strategy except in cases where i actually want the types to have a common concrete base (thats not just object)
its just practical, idk
It's valid syntax, but functions don't support subscripting at runtime
!e
def f[T]():
pass
print(f[int])
:x: Your 3.13 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File [35m"/home/main.py"[0m, line [35m4[0m, in [35m<module>[0m
003 | print([31mf[0m[1;31m[int][0m)
004 | [31m~[0m[1;31m^^^^^[0m
005 | [1;35mTypeError[0m: [35m'function' object is not subscriptable[0m
oh sad
also this will never work with functions whose types are in stubs only
but you can do that with classes, which is weird
It would work like this ```ts
function pick<T>(first: T, second: NoInfer<T>): T {
return Math.random() < 0.5 ? first : second
}
const _test1 = pick("Foo", 42) // ok: Argument of type '42' is not assignable to parameter of type '"Foo"'.
const _test2 = pick("Foo", "Bar") // not terribly helpful: Argument of type '"Bar"' is not assignable to parameter of type '"Foo"'
const _test3 = pick<string>("Foo", "Bar") // allowed
const _test4 = pick<string | number>("Foo", 42) // allowed
`_test2` is an example of unhelpfully inferring a literal type
Actually, typescript seems to have a weird opinion on this inference. ```ts
function pick<T>(first: T, second: T): T {
return Math.random() < 0.5 ? first : second
}
const _test1 = pick("Foo", 42) // error: Argument of type '42' is not assignable to parameter of type '"Foo"'.
const _test2 = pick("Foo", "Bar") // _test2: "Foo" | "Bar"
it unionizes literals if they don't include the bottom type unknown in the shared type heirarchy
I've heard Amazon banned the use of | in their TypeScript code. They don't want any unions
or I guess the simpler way to put it is if they return the same typeof
I kind of sympathize with that, but does TypeScript have ADT emulation?
||/s||
The way you make a tagged union in TypeScript is with a union ```ts
type Color =
| { kind: "rgb", r: number, g: number, b: number }
| { kind: "theme-color", name: string }
(like in Python)
I don't know what the benefit of adding tagged unions to a language that already has unions would be
because the unions aren't real at runtime
but rust unions can be values, structs, or tuples
how are those real but objects aren't?
Rust unions are supported natively via enum in the type system.
you cannot ignore variants
you can't ignore kind's if you are switching on them in a function which is supposed to return something non-void either
So the problem you have is with lack of exhaustive matching?
yeah, but the type system in python isn't enforcing that
it is with match-case
kind of? I think it's around the lack of ergonomics
I'm sure there's a ruff rule to enforce exhaustive match statements
maybe it's in redknot
match doesn't enforce exhaustive checks
sure it does
maybe not in mypy
typecheckers are not runtime
i literally have exhaustiveness errors from pyright until i finish writing all the cases every day
which is a large part of my problem with the bolted on types semantics in Python
at least typescript forces you to go through a compiler
why are you pushing without passing CI anyways
because I'm not the only person on my team writing code and there's a bad culture surrounding code quality
match x {
MyEnum:X => {},
_ => {
println!("hehe, non-exaustive");
}
}
except _ => makes it exhaustive
it's explicitly non-exaustive
and it's enforced byt he compiler
pyright does not allow that if you enable reportMatchNotExhaustive (which is enabled in strict mode)
make a encoding that gives match a default _ branch that raises an error
I so wish I could crank pyright up to strict without all my installed packages reporting errors
sqlalchemy is especially painful
You can enable this one rule selectively
mypy unfortunately doesn't have such a rule
I dropped mypy from my set of tools since it was always lagging on new Python features
so I had a fight with my coworkers over this and I'm curious what people think. for each table, I create a NewType over its id: pk attribute and that is kind of annoying since each table = newtype = type DbId gets longer, but it gives me nice ergonomics that I can't accidentally pass the wrong ids across relationships
and as a result, I also need to create this giant type_annotation_map since sqlalchemy doesn't resolve newtypes natively
do you think this is a sound idea? is there a way to make it more ergonomic?
something I was thinking about was rather than using NewType, create my own newtype abstraction using inheritance, but I'm not sure if there's a solution for the type_annotation_map
Are you sure the type_annotation_map is required for that?
the docs in .37 say so. it wouldn't work without it. let me get a reference
MyTableId = NewType("MyTableId", int)
class MyTable(Base):
id: Mapped[MyTableId] = mapped_column(primary_key=True)
from codecs import register, CodecInfo, utf_8_encode
from collections.abc import Buffer
from ast import Match, NodeVisitor, match_case, MatchAs, Raise, Call, Name, Constant, parse, unparse, fix_missing_locations
class Transformer(NodeVisitor):
def visit_Match(self, node: Match) -> None:
if not isinstance(node.cases[-1].pattern, MatchAs):
node.cases.append(match_case(
MatchAs(),
body=[Raise(Call(
Name("TypeError"),
[Constant(f"Non-exhaustive match-case (@{node.lineno})")]
))]))
def decode(buf: Buffer, errors: str = "strict") -> tuple[str, int]:
txt = buf.__buffer__(0).tobytes().decode(errors=errors)
tree = parse(txt)
Transformer().generic_visit(tree)
txt = unparse(fix_missing_locations(tree))
txt += "\n"
return txt, len(txt)
@register
def search(name: str) -> CodecInfo | None:
if name == "strict": return CodecInfo(
name=name,
encode=utf_8_encode,
decode=decode,
)
Now publish it to pypi
I recently learned about encoding hacks and they disgust me
very cool, but disgusting
exhaustive-match-at-home
smh its just a file-wide rust procedural macro
so check the link and see
reading
By “loose matching” does it mean types without being defined in type_annotation_map?
well sqlalchemy has an internal, default type_annotation_map
so things like datetime and int, etc. resolve natively
is there a DateTimeUTC type in sqla? I haven't checked yet
I made a TypeAdapter for it.
from datetime import UTC, datetime
from sqlalchemy import TIMESTAMP, Dialect, TypeDecorator
class UTCDateTimeType(TypeDecorator[datetime]):
impl = TIMESTAMP
cache_ok = True
def process_result_value(
self, value: datetime | None, dialect: Dialect
) -> datetime | None:
if value is None:
return None
# datetime is stored internally as a naive UTC timestamp. It's safe to make it
# tz-aware.
return value.replace(tzinfo=UTC)
this is my id definition, and everything works flawlessly in practice, but working in the tables can be kind of annoying because of the mix of what's allowed since Mapped["Type"] and relationship("Type") etc. are eval()'d while it seems this inherited column is not
neat. do you ever use onupdate=func.current_timestamp() and does it mesh nicely? for example an updated_at col
once or twice
Done reading. NewType for each table does seem to create a bit of boilerplate.
ideally, sqlalchemy should be able to infer the internal type of the NewType
Maybe you can reduce it with a UserDict object implementing _missing_
NewType.__supertype__/TypeAliasType.__value__
Hello, is there any way how to get correct type hints on this?
class Decoder[T]:
# I would like this to support also something like TypeUnion[T]
def __init__(self, t: type[T]) -> None:
pass
def decode(self, v: bytes) -> T:
raise NotImplementedError("")
NON_UNION_DEC = Decoder(int) # works fine
UNANNOTATED_DEC = Decoder(str | int) # need type annotation
ANNOTATED_DEC: Decoder[int | str] = Decoder(int | str) # incompatible type "UnionType"; expected "type[int] | type[str]
if __name__ == "__main__":
reveal_type(NON_UNION_DEC.decode(b"1")) # correctly inferred as "builtins.int"
reveal_type(UNANNOTATED_DEC.decode(b"1")) # inferred as "Any" (should be str | int)
reveal_type(ANNOTATED_DEC.decode(b"1")) # correctly inferred as "Union[builtins.int, builtins.str]"```
There's a PEP for this: https://peps.python.org/pep-0747/
pyright has experimental support for it
from dataclasses import dataclass, field, MISSING
def named(
*,
default=MISSING,
default_factory=MISSING,
init=True,
repr=True,
hash=None,
compare=True,
metadata=None,
kw_only=MISSING,
):
return field(default, default_factory, init, repr, hash, compare, metadata, kw_only)
@dataclass
class MyArgs:
first_arg: int
second_arg: int = named(default=0, init=False)
a = MyArgs(5, 7)
how should I annotate named so that mypy correctly gives an error here?
hmm, when I specify that named returns a Field, I get other errors
ah, I see
actually, yeah, I don't 😂
I guess what I need is something like dataclass_transform but for field
I may be missing something, but can't you just declare the return value of named(...) to be Field?
for metadata you might want Optional[dict[str, Any]] = None or something? Not sure what shape that arg has in your mind
i tried, but it does not type check
metadata is just forwarding an argument that field already requires
I mean I could just leave it as th edefault I suppose
okay, I realize there were some unrelated errors. but here's the most boiled down version:
def named(
*,
default=MISSING,
) -> Field[int]:
return field(default=default)
@dataclass
class MyArgs:
first_arg: int
second_arg: int = named(default=0)
a = MyArgs(5)
this "should" type check but does not
it seems like the way to go is just to make named generic function and return _T itself, and do # type ignore inside the body
i assume I'm missing something stupid, but why does this not type check
_T = TypeVar("_T")
class Factory(Generic[_T]):
def __init__(self, func: Callable[[], _T], /) -> None:
self.func = func
def f() -> int:
return 5
def glug(x: _T | Factory[_T]):
pass
glug(Factory(f))
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
this looks like some sort of bug to me
If you add a return type annotation, it works out somehow ```py
def glug[_T](x: _T | Factory[_T]) -> tuple[_T, _T]:
assert False
x: tuple[int, int] = glug(Factory(f))
reveal_type(x)
though in general, this function signature is potentially... unclear
hmm, I guess the _T itself could be Factory[_T]
yeah
so, to explain why I'm doing this weird stuff
I am working on this function that forwards to dataclasses.field
i got it working but it's pretty horrifying
because you have to use @overloaded, to handle how default vs default_factory is done
I want to define a bunch of these helper functions, and make it easy for users to do so too, so having to write every function signature four times is pretty bad
I think the solution here is to wrap both sides of the union
which I didn't really want to do; I wanted to keep the common case nice
class Factory(Generic[_T]):
def __init__(self, func: Callable[[], _T], /) -> None:
self.func = func
class Value(Generic[_T]):
def __init__(self, val: _T, /) -> None:
self.func = val
def f() -> int:
return 5
def glug(x: Value[_T] | Factory[_T]):
pass
glug(Factory(f))
this works, but now people would have to write e.g.
@dataclass
class MyArgs:
first_arg: list[int] = argparse_field(default=Value(0), ...)
i guess one alternative is to just give up on statically enforcing that exactly one of default and default_value are passed
how much would you hate if a library had you write, instead of
@dataclass
class foo:
x: int = field(default=0)
y: list[int] = field(default_factory=list)
, rather
@dataclass
class bar:
x: int = my_field(de=fault(0))
x: list[int] = my_field(de=fault_factory(list))
Taking inspiration from attr.ib?
a little
Maybe you get away with just default_factory? like ```py
class bar:
x: int = my_field(default=int) # or lambda: 0
x: list[int] = my_field(default=list) # or lambda: []
tbh I find it weird that int supports a 0-argument call producing 0
yeah, that is pretty weird
Hmm I could also just support default because... that's what argparse does
In [8]: ap = argparse.ArgumentParser()
In [9]: ap.add_argument("--foo", type=list, default=[])
Out[9]: _StoreAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default=[], type=<class 'list'>, choices=None, required=False, help=None, metavar=None, deprecated=False)
In [10]: ns1 = ap.parse_args([])
In [11]: ns2 = ap.parse_args([])
In [12]: ns1
Out[12]: Namespace(foo=[])
In [13]: ns1.foo.append(5)
In [14]: ns1
Out[14]: Namespace(foo=[5])
In [15]: ns2
Out[15]: Namespace(foo=[5])
😂
why?
every primitive data type has a default empty arguments constructor
bool() == False also feels weird
!e print(bool() == int(), bool() == str())
:white_check_mark: Your 3.12 eval job has completed with return code 0.
True False
from dataclasses import fields
...
def parse_args(t: type[_T]) -> _T:
for f in fields(t):
...
if I have code like this, mypy complains
scratch2.py:108: error: Argument 1 to "fields" has incompatible type "type[_T]"; expected "DataclassInstance | type[DataclassInstance]" [arg-type]
Found 2 errors in 1 file (checked 1 source file)
What's the most appropriate way to resolve this? I found this definition
_DataclassT = TypeVar("_DataclassT", bound=DataclassInstance)
but _DataclassT and DataclassInstance (imported from _typeshed) both don't seem to be for public usage
it seems like maybe I should be importing from _typeshed?
I see, I guess _typeshed only has an underscore as a sort of "warning" that they are type checking only types
You should be able to use dataclasses.is_dataclass() to narrow the type to one mypy will allow to be passed into dataclasses.fields()
Is there a way to use a boolean function in a bound for a generic?
can you elaborate? I'm not sure I fully understand what you want, like restricting a type-var to accept functions with any parameters that return a bool?
just the example above to work
I want a generic function that accepts a type, and for the type to be constrained such that it's a dataclass
alex suggested using a function that returns a bool
I'm asking how I can incorporate that into a generic bound
from typing import TYPE_CHECKING
from dataclasses import fields
if TYPE_CHECKING:
from _typeshed import DataclassInstance
def parse_args(d: type[_D]) -> _D:
for f in fields(d):
...
currently the code looks like this and it works
so I'm asking in response to Alex's suggestion, if there's a way to use is_dataclass instead of DataclassInstance and have it work the same way
Protocol?
what would that look like
would this not work then?
def parse_args[T: DataclassInstance](d: T) -> T:
...
that's what I already have?
more or less
.
and what's wrong with using DataclassInstance?
class LikeDataclass(Protocol):
is_dataclass: Literal[True]
def func[T: LikeDtataclass](dc: T) -> T: ...
Not sure if that's what you want but basically, that LikeDataclass can have the same stuff as the class that fields() wants
nothing - alex just suggested something else, and I asked how to make it work, lol
well, the is_dataclass check just returns a TypeIs[DataclassInstance]
so it can be used to narrow down the type in a condition
Does this actually work? this seems to be checking if there's an is_dataclass field
that doesn't answer my question 🤷♂️
Yes
well yeah, because this doesn't help you to solve the issue
If dt is a class with a is_dataclass attribute
if you wish to use is_dataclass, I guess you could like do this:
def func[T](dc: type[T]) -> T:
if not is_dataclass(dc):
raise TypeError
but that's not ideal
just using DataclassInstance for the type is much more type safe
In [1]: from dataclasses import dataclass
In [2]: @dataclass
...: class Foo:
...: pass
...:
In [3]: Foo.is_dataclass
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[3], line 1
----> 1 Foo.is_dataclass
AttributeError: type object 'Foo' has no attribute 'is_dataclass'
I wouldn't go with a protocol here
Oh my bad, it's just a function in the module
would static type checkers even actually catch this?
then yeah, not very useful
that's why I find that suggestion weird
and why I was confused for why you're avoiding DataclassInstance
yeah, I didn't think that python's type system could do that
most constrained generics systems cannot just use arbitrary boolean functions that way
I mean, the function acts similarly to an isinstance check
you could still do the thing I said as last
Not really trying to avoid it, like I said, just trying to understand Alex's suggestion.
It would be a little nicer to avoid using _typeshed and needing the if TYPE_CHECKING thing but eh
not a big deal
inside of an if is_dataclass(x) a type-checker can assume that x is a DataclassInstance
yeah, that's a branch inside, but what I need is to actually constrain a generic function
so yes, in here, a type-checker would be able to infer that after the if, you're working with a dataclass
which there isn't a way to do in python's generic system (or most constrained generics systems)
but it doesn't restrict the function calls to just dataclasses
yeah
I was also thrown off at first by it being _typeshed - now that I read it, I understand the motivation but initially it's a lot more confusing that way
(that is, the leading underscore made me assume it was package internals)
well, it is generally a good idea to try and avoid _typeshed imports if you can, but in this case, there isn't really anything better, dataclasses don't have any common parent class, they're built through dynamic code generation, and there's no easy way to distinguish between a dataclass and a regular class.
The is_dataclass actually does this in the back: ```python
def is_dataclass(obj):
"""Returns True if obj is a dataclass or an instance of a
dataclass."""
cls = obj if isinstance(obj, type) else type(obj)
return hasattr(cls, _FIELDS)
`_FIELDS` being `'__dataclass_fields__'`
and since the dataclass.fields method expects a DataclassInstance, you need to convert that type into it somehow
even if you defined a protocol with __dataclass_fields__ on your own, it wouldn't work
interesting; why not?
because that's not what dataclass.fields is expecting as an input, it expects DataclassInstance which afaik isn't a protocol
okay, it actually is
Ah, ok
in that case, a protocol should actually work too
class DataClassType(Protocl):
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
but the protocol has the downside that _FIELDS is an implementation detail, strictly speaking
you can do this, if you really wish to avoid the _typeshed import
that import will give you that same thing
that seems a little worse to me on balance since I'm accessing implementation details directly now, no?
I'd probably only do this if I really badly wanted to avoid the typeshed dependency
well, you're kinda also doing that through _typeshed, which on it's own could be considered an implementation detail
but I'd probably go with _typeshed too
yeah, I figure if dataclass changes something then the typeshed authors will too 😛
precisely
Pyright and Mypy have the exact opposite behaviour
The mypy one strikes me as a wrong annotation on the return value
not sure why pyright is being funky
I think Pyright is correct here. When you call NamedTuple, you get a class, which is a subclass of tuple
!e
from typing import NamedTuple
Foo = NamedTuple("Foo", [])
print(Foo)
:white_check_mark: Your 3.13 eval job has completed with return code 0.
<class '__main__.Foo'>
Hmm, does it want NamedTuple[T] where T is a TypeVar?
i have a function that returns a NamedTuple without complaint, but it's the class form instead
What would NamedTuple[int] mean?
Hmm, yeah, I guess it doesn't make sense. I'm just trying to parse the pyright error
and isn't it saying it's expecting an instance of a type, and getting that type instead?
but how is NamedTuple(...) not an instance of NamedTuple?
NamedTuple is not a class, it's a function that returns a class
are you familiar with collections.namedtuple?
!e ```py
from typing import NamedTuple
print(NamedTuple)
Foo = NamedTuple("Foo", [])
print(Foo)
:white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | <function NamedTuple at 0x7f07022f7f60>
002 | <class '__main__.Foo'>
I get it now, cheers
Lib/typing.py lines 3025 to 3031
_NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {})
def _namedtuple_mro_entries(bases):
assert NamedTuple in bases
return (_NamedTuple,)
NamedTuple.__mro_entries__ = _namedtuple_mro_entries```
hacks
but you can subclass NamedTuple
!e
from typing import NamedTuple
class Foo(NamedTuple):
x_1: int
x_2: float
print(f"{Foo(x_1=10, x_2=2.9)!r}")
:white_check_mark: Your 3.12 eval job has completed with return code 0.
Foo(x_1=10, x_2=2.9)
!e
It creates a subclass of tuple that doesn't involve NamedTuple though (since NamedTuple is not a class):
from typing import NamedTuple
class Foo(NamedTuple):
x_1: int
x_2: float
print(Foo.mro())
:white_check_mark: Your 3.12 eval job has completed with return code 0.
[<class '__main__.Foo'>, <class 'tuple'>, <class 'object'>]
I just want dependent types ):
Huh, I didn't know this existed either https://typing.python.org/en/latest/spec/generics.html#typevartuple
what is pyright smoking
im doing
y = x if isinstance(x, Sequence) else [x]
where x: float | Sequence[float]
where is it getting the int from
im sorry what
This might be a minor bug, but Self only makes sense when subclassing is possible
this also happens
or rather, when you don't plan to have a subclass, just use the class name directly
class Bar(Foo):
pass
x = bar.get_foo()
``` `x` is supposed to be a `Bar` here
right, the implementation doesn't match the signature, so pyright is complaining
In the enum example, it's not possible to subclass Foo, so technically the Self return type will never be violated, but it doesn't put the two pieces of knowledge together
ok
is type hinting an art form
it certainly looks that way sometimes
Type hinting is helpful but you can go down rabbit holes when using certain frameworks. Luckily most popular Python frameworks have proper type hints or stubs made b my other developers, but doing type hinting with an unmaintained framework is so much pain
@overload
def foo(x: Literal[True] = ...) -> str:
pass
@overload
def foo(x: Literal[False] = ...) -> int:
pass
def foo(x = False):
pass
reveal_type(foo())
the revealed type here is str
how would I do this correctly?
obviously, I believe the revealed type here should be int
actually I think I get it. I have to remove the default value from the first overload
Is there any substitution for @override decorator in py < 3.12?
I can't use typing_extensions as well
that's what typing-extensions is for. why can't you use it?
I am writing an extension for a certain application.
Application uses python 3.11 so no go for typing.override()
For the sake of typing_extensions it is quite weird.
Since I am writing an extension, I cannot just pip install packages, however, according to the API I can install .whl binaries. And also, software doesn't come with typing_extensions as site-package either.
For automated binary installation I have a class, that ... installs them lol. I also have a logger class.
logging.Formatter class has method format that I then override in my custom formatter.
The problem is that I use my logger inside of module installation class and thus cannot install typing_extensions module.
Hope it makes sense!
you should be able to declare install-time dependencies
What do you mean by that?
the packaging tools you use are likely able to declare install-time dependencies, e.g. in pyproject.toml. I'm not sure exactly what tools you are using but what you're saying doesn't convince me that it's impossible to install third-party dependencies.
Though an alternative is to use a TYPE_CHECKING hack, something like ```from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing_extensions import override
else:
def override(f): return f
Haven't mentioned that it is impossible.
I said that I can't use them within my logger class/file due to dependcies being loaded after my logging is being executed . It is a circular import in some way
does anyone have any examples on using typevar tuples to take in a sequence of types and having multiple methods in turn be returning unions of the types
typing_extensions does not import your project, there's no circular dependency issues with using it
I'm not sure what you mean, do you have some example?
You cannot get a union from a typevartuple
It was initially allowed to do Union[*Ts], but it is now disallowed for some reason
So if you run e.g. ```py
class Foo[*Ts]:
def init(self, *items: *Ts) -> None:
self._items = items
def get_first(self):
return self._items[0]
def f[*Ts](foo: Foo[*Ts]):
reveal_type(foo.get_first())
``` through pyright, it will reveal the type as Union[*Ts], but it will not allow you to name this type explicitly. A bit silly
To be fair, this is also true for this ```py
class Foo: pass
class Bar: pass
def f(x: Foo):
assert isinstance(x, Bar)
return x
f inferred as: (x: Foo) -> <subclass of Foo and Bar>
i have something like
class Loader[*Ts]:
loader_types: Sequence[type[Ts]
def parse(self, input) -> Union[*Ts]
but no idea how to do the mapping
What does the Ts represent?
list of types i pass in as valid to receive
class Loader[T]:
def __init__(self, loader_types: Sequence[type[T]]):
self.loader_types = loader_types
def parse(self, input: str) -> T:
...
``` would work on pyright because it unifies types (so, in `Loader([int, str])`, `T = int | str`) (not on mypy because it joins them so you'd probally end up with `object` or something)
yup - pyright gets it, mypy botches it
You could make an atrocious API like this ```py
class Loader[Uni, *Ts]:
def init(self: Loader[Never]) -> None: # type: ignore
self._items: tuple[Any, ...] = ()
def add(self, new_type: type[T]) -> Loader[Uni | T, *Ts, T]:
loader = Loader()
loader._items = self._items + ()
return loader
def parse(self, input: str) -> Uni:
...
loader = Loader().add(int).add(str)
You could require an explicit type annotation in mypy: Loader[int | str]([int, str]) and then link this mypy issue in the docs https://github.com/python/mypy/issues/12056
Is there a way of defining literals as "any combination of the options"?
Best example is tkinter direction constants:
from typing import Literal
W = LEFT = "w"
E = RIGHT = "e"
N = TOP = "n"
S = BOTTOM = "s"
NSEW = ALL_DIRECTIONS = N + S + E + W
Direction = Literal[LEFT, RIGHT, TOP, BOTTOM, LEFT+RIGHT, TOP+BOTTOM, TOP+LEFT, TOP+RIGHT, BOTTOM+LEFT, BOTTOM+RIGHT, ALL_DIRECTIONS]
afaik, the answer is no. What you could do is define the combinations manually as actual separate literals, or take a sequence of the literals (or a set to ensure each option is only present at most once)
Maybe this? But it's too verbose.
Direction = Union[Literal[LEFT, RIGHT, TOP, BOTTOM, ALL_DIRECTIONS], tuple[DirectionHorizontal, DirectionVertical]]
and tuple would need be mutually exclusive based on type options
but you had LEFT+RIGHT as an option too, a tuple of (horiz, vert) can't represent that
I'd just go with set[Direction] or write them out explicitly
depending on your use-case
in this case, writing them out doesn't look too bad
I could validate if len(tuple) == 2 and each element is unique as being either horizontal of vertical
I wanted to have this behavior at the type checker level as well
Agreed. Still...
Thanks
and btw, I'd recommend an enum for this, if you need the str representations, you can use StrEnum
it's generally a better idea than a union Literal
I'm writing a wrapper around tkinter, which uses literal strings as enums because that's how TCL works
so it's better to keep these zero-cost simple things as is
class Direction(StrEnum):
LEFT = "w"
RIGHT = "e"
...
def my_wrapper(d: Direction):
tkinter_method(d.value)
worrying about 0 cost abstractions when you're using python IMO isn't all that important
I'd generally prefer a clean design choice over something that might save you a few bytes of memory
I'm constraining the data structures as well. So no Enum, no @property, no decorators.
Keeping it simple is the main concern
what do you mean?
Agreed. What I mean is: prefer the simplest.
The less hidden control flow, the better.
I'd personally much rather see an API like Direction.UP than one where I'm passing "n" as a literal
I wouldn't say this harms how hidden the control flow is, but I suppose it depends on the kind of wrapper you're making
That's how it works. Procedural constants. Import the library as a namespace and use the .UP. That's how people who would be using my wrapper would have been doing the whole time. These things aren't the point of my efforts.
oh, I see
If it isn't broken, don't fix it.
yeah, if you're passing these into tkinter functions directly, this makes more sense
I thought by wrapper you meant more like a complete one, supplying your own functions that call tkiner internally
rather than just exposing some things to the user but still have them use tkinter calls too
No, no. Makes sense to think that. tkinter is just a python-ized version of the thought process behing the C API. It's a lot nicer, but simple things are verbose and can turn spaghetti real fast. Plus, many quality of life things that you barely touch after writing the first time require more ugly glue code, or a new dependency just to do very little, if your glue code is use-case based.
I want to make all these static things explicit and declarative. Keep it simple and leave what already is good as is. Improve customizability by abstracting and turning declarative the things you can only learn by reading 2 books, 3 documentations and the C implementation.
Explicit method calls if your property needs to be calculated. Regular properties if they are calculated before you're able to interact with them, or if they are constant/final.
dicts, tuples, NamedTuples, TypedDicts, literals, dataclasses sometimes. That it.
and an awful lot of type hinting
yeah, that makes sense, the only annoying thing with it is that the tkinter method signatures won't direct you to your abstractions, which can make it hard to know which one to use from your lib
I wasn't joking when I said 2 books, 3 documentations and the C API. Python has so much magic that the library developers expect you to read their minds. If I expect people to read my code, the v1.0 documentation will not allow them to feel my pain, I promise.
I wish I had a counter of the amount of "find text" operations I did on those two PDFs.
Yet to finish so many more to go.
Yeah, what I meant was that, if your lib just provides types for tkinter, but not wrapper methods, those types will indeed need to be compatible with what the tkinter built-in methods already take. That however means that even if the built-in tkinter methods have some type annotations for the variables, those annotations won't direct the devs towards your types.
And while your docs can be good, in pretty much all cases, devs prefer just following func signatures, since it's hard to remember everything, and and cross-referencing docs, even when it's good docs, can still be very annoying. With signatures, it'd be type enforced (at least with a type checker)
You would be surprised by how many people would download my library if all I did was write proper type stubs for tkinter.
you wouldn't even be able to define tkinter stubs to direct the user towards your types, since the types are just string literals
but yeah, if the built-in tkinter doesn't even have stubs, and your goal is just providing them, then this does make sense
I haven't used tkinter in a long time (not for like 4 years), so I've got no idea on how type-complete it is
Define stubs for the standard tkinter, I mean. To replace the unreadable, missing, or impossible to import types.
yeah, that makes sense then
Type-incomplete enough to gaslight me into writing a wrapper. 😆
lol
is there a way to annotate with something that ends up as an Annotated, but to restrict the annotated values in relation to the type
Like, say I have some Config[T] class
I want users to be able to annotate with an Annotated[T, Config[T]] - i.e. enforce that the Config value that is passed to the Annotated has the same type parameter as the T
!pep 746
Which is kind of stuck
interesting, thanks for the info
if I'm understanding this correctly, then it means that I can currently do strictly more type checking with
@dataclass
class Foo:
x: SomeType = my_field(...)
Than with
@dataclass
class Foo:
x: Annotated[SomeType, MyInfo(...)]
but if this PEP were to be accepted, then the situation would be reversed
I could do strictly more type checking with the latter than the former
attrs could suffice
sorry, not sure I follow
I was looking at two approaches where I wanted to add extra metadata to fields of a dataclass
One was using Annotated, one was writing my own my_field function. I was comparing the type checking possibilities of the two approaches
hi
i have
empty = object()
Wrapped = TypeVar("Wrapped", Settings, UserSettingsHolder)
class LazyObject(Generic[Wrapped]):
_wrapped: Wrapped | None | object = None
def __init__(self):
self._wrapped = empty
def _setup(self) -> None:
...
self._wrapped = Settings()
def configure(self) -> None:
...
self._wrapped = UserSettingsHolder()
so at the start of the class self._wrapped is None, then it's empty
but when using, self._wrapped is guaranteed to be either Settings or UserSettingsHolder
how can i tell python and mypy that the type will be one of these two?
the problem happens here:
def register(self, settings_module: ModuleType) -> None:
if self._wrapped is empty:
self._setup()
self._wrapped.register(settings_module)
def clear(self) -> None:
if self._wrapped is empty:
return
self._wrapped.clear()
where mypy errors saying empty object doesn't have a method register(), or clear()
from typing import TypeGuard
def is_settings_thingy(obj) -> TypeGuard[Settings | UserSettingsHolder]:
return (isinstance(obj, Settings) or isinstance(obj, UserSettingsHolder))
``` is one way I guess
maybe you could just say isinstance(obj, Wrapped) there? Not sure if TypeVars let you do that.
Would removing the default value of _wrapped work?
I don't think so, because I don't think mypy can figure out that it can't be None
class LazyObject(Generic[Wrapped]):
_wrapped: Wrapped | object
...
Why not just Wrapped? What other types do you want it to be able to be?
I just copied what he wrote here ^
Oh sure, yeah so we don't know yet.
I guess that's my main question; why does this need to be a union type at all?
it's first assigned to None because some infinite recurssion would happen without that, it has some discussion behind it in django
then it's assigned to empty, because there is nothing to wrap when the instance starts
it needs to be provided
I’d suggest changing empty to be a 1-value enum or literal string, that way it can be narrowed by comparisons.
mm
am not sure what you mean by narrowed by comparisons
The checker knows enums are singletons, so an is check can remove it from the variable type. Same with string equality.
btw, X | object is the same as object
Any updates on PEP 728?
can someone help me understand how to use sockets
You want #networks
i have package which uses type stubs for type definitions where a particular class is generic and the generic type parameters aree defined in the pyi files using default= for TypeVar
we can also however provide the generics ourselves in user side code like A[MyType] is there a way to determine the source of these generic type params whether its stub defined or user defined
!pep 728
it's in typing-extensions 4.13
currently my class aren't really typeddicts its just a generic class inheriting from t.Generic, for reference this is the issue I am facing
https://github.com/typeddjango/django-stubs/pull/2590#issuecomment-2812769225
Hello, why does calling _get work but get doesn't?
from typing import TypeVar, reveal_type
from collections.abc import Callable
class Base: ...
class Derived(Base): ...
T = TypeVar("T", bound=Base)
def _get(cls: type[T]) -> T:
return cls()
def func(get: Callable[[type[T]], T]) -> None:
reveal_type(get(Derived)) # incompatible type
reveal_type(_get(Derived)) # Derived
Callable[[type[T]], T] is not the type of a generic function. It might be easier to see with new-style generics:
from collections.abc import Callable
def func[T](get: Callable[[type[T]], T]) -> None:
reveal_type(get(Derived)) # incompatible type
reveal_type(_get(Derived)) # Derived
``` this means that you can call this function with e.g. `Callable[[type[int]], int]`, and `T` will be bound to `int`. `T` is bound to a specific type, which isn't necessarily `Derived`. In your case it's more like ```py
def func[T: Base](get: Callable[[type[T]], T]) -> None:
reveal_type(get(Derived)) # incompatible type
reveal_type(_get(Derived)) # Derived
If you want to express the type of a generic function, you need a callable protocol
from typing import Protocol
class GetFn(Protocol):
def __call__[T: Base](self, cls: type[T], /) -> T: ...
def func(get: GetFn) -> None:
...
thanks for responding, what I don't understand is doing something like func(_get) is fine, if my type hint was wrong, this should be flagged though?
You can try this: ```py
def func(get: Callable[[type[T]], T]) -> T:
assert False
x = func(_get)
reveal_type(x)
``` it's probably sneaking in an Any somewhere in there
if you are in this position: py def func(get: Callable[[type[T]], T]) -> T: # here you can't really call get in a type safe way, because you don't know what T is. (you can call it with an Any argument)
I think I am following, how would you suggest I go about this if I want type hints?
If you want to accept a generic function, you can use this
thank you, I will use that then
Why it is saying it is posibaly unbound
because pyright is not smart enough to recognize that it's not unbound there
If that if is_main is not in the finally then there is no problem but the problem is just when it gose in the finnaly block.
seems like it doesn't matter ```py
import random
x = random.choice((False, True))
if x:
hello = "world"
print("message")
if x:
print(hello) # "hello" is possibly unbound
Can you show the code where it doesn't show the error?
you could open an issue on github if it hasn't been brought up yet
but I see it many times detect it in complex code
whatever
Hey I found the problem
the variables are global
not local
so other thread can change it
yea
they can do like get_all_gloabl_vars["is_main"] = True
I forgot what how we vchange the global vars
An alternative would be keeping the things that depend on is_main as a singular entity, something like ```py
@contextlib.contextmanager():
def app_resources():
chat_history_file = ...
chat_history.load_from_json(chat_history_file)
...
try:
yield
finally:
chat_history.load_from_json(chat_history_file)
...
if is_main:
ctx = app_resources()
else:
ctx = contextlib.nullcontext()
with ctx:
socketio.run(app, ...)
I need to do research on it
Hi, how can I solve the following typing error on 'arg' in the following code? Pylance says Type of "arg" is unknown
def func(data: dict[str, Any]):
args = data.get("args")
if args is None:
raise ValueError("ERROR: Arguments not found")
if not isinstance(args, list):
raise ValueError("ERROR: Arguments must be a list")
if not all(isinstance(arg, str) for arg in args):
raise ValueError("ERROR: All arguments must be strings")
You can annotate the args as a list of string (my first thought)
args: list[str] = ...
I assume you're going to process it as a str right after
What I would probably do is just write your for loop that processes each arg, and put your check and raise there
Then the narrowing to str type should work
Type narrowing cannot work with arbitrarily complex logic even if it's obvious to a human
for arg in args:
if not isinstance(arg, str):
raise ...
# arg should be treated as a str here
If you add more details about what you want to do, a better recommendation can be made though
Thanks, but I still get the Type of 'arg' is unknown. The updated code:
def func(data: dict[str, Any]):
args = data.get("args")
if args is None:
raise ValueError("ERROR: Arguments not found")
if not isinstance(args, list):
raise ValueError("ERROR: Arguments must be a list")
for arg in args:
if not isinstance(arg, str):
raise ValueError("ERROR: All arguments must be strings")
the code is intended to validate some data from a JSON. The full code contains other checks for values coming from other keys
and indeed, if I add after the for loop for example len(args) as a separate statement, I get:
Argument type is partially unknown
Argument corresponds to parameter "obj" in function "len"
Argument type is "list[Unknown]"
You can use a typeguard to narrow the type of args
from typing import Any, TypeGuard
def is_string_list(lst: list[object]) -> TypeGuard[list[str]]:
return all(isinstance(ele, str) for ele in lst)
def func(data: dict[str, Any]):
args = data.get("args")
if args is None:
raise ValueError("ERROR: Arguments not found")
if not isinstance(args, list):
raise ValueError("ERROR: Arguments must be a list")
if not is_string_list(args):
raise ValueError("ERROR: All arguments must be strings")
len(args)```
nice, thanks! Now what is left is to solve the error:
Argument type is partially unknown
Argument corresponds to parameter "lst" in function "is_string_list"
Argument type is "list[Unknown]"
updated code:
from typing import Any, TypeGuard
def is_string_list(lst: list[Any]) -> TypeGuard[list[str]]:
return all(isinstance(ele, str) for ele in lst)
def func(data: dict[str, Any]):
args = data.get("args")
if args is None:
raise ValueError("ERROR: Arguments not found")
if not isinstance(args, list):
raise ValueError("ERROR: Arguments must be a list")
if not is_string_list(args):
raise ValueError("ERROR: All arguments must be strings")
len(args)
from dataclasses import dataclass
@dataclass
class Foobar:
baz: int | None = None
def is_baz_int(self) -> bool:
return self.baz is not None
obj = Foobar(20)
reveal_type(obj.baz)
if obj.is_baz_int():
reveal_type(obj.baz)
is it possible to narrow down obj.baz like that?
Mypy reports both as int | None
thanks, guess I'll do without it
You used list[Any] and he used list[object]
changing the type of lst to list[object] reports the same issue

I mean you're not showing the entire code because this type checks just fine
and using reveal_type shows that the type of arg inside the loop is str, like I said
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
If you can add some representative code that actually errors in a mypy playground it'll be easier to help
I also wouldn't use a type guard unless it's necessary or you really want to reuse some bit of code. If you can just make use of type narrowing in the function body easily, then that's preferable
Oh huh, TIL: https://rednafi.com/python/typeguard_vs_typeis/
The handful of times I’ve reached for typing.TypeGuard in Python, I’ve always been
confused by its behavior and ended up ditching it with a # type: ignore comment.
For the uninitiated, TypeGuard allows you to apply custom type narrowing1. For example,
let’s say you have a function named pretty_print that accepts a few different types and
p...
yeah as a casual type hinting person I never really understood why we would want TypeGuard if we have TypeIs, and the documentation isn't really helpful for this lol
TypeGuard came first, then we discovered some problems with how it was specified. There was a proposal to make TypeGuard work differently, but we rejected that for compatibility reasons and instead I proposed adding TypeIs.
I wrote https://typing.python.org/en/latest/guides/type_narrowing.html about when to use each
does the type system have a special way to indicate that a function has a 'divergent' or 'terminal' type? like, something that makes the program exit?
or do we just say -> None for that?
like in Haskell you can say your return type is ⊥
aha typing.Never
y'all thought of everything, lovely
Hello, have anyone here used pylance in VSCode. I am working on a project that relies heavily on "imgui" and we get literally 100's of "errors" that are incorrect.
Writing # type: ignore at every single line will imho only bloat the code. Can I whitelist e.g. "imgui" somewhere?
This error occurs for every single use of imgui.X everywhere. I want the linting to be on by default, but want to whitelist all imgui.X usage. Is it possible?
i suppose Pylance is just stupid here, is imgui a builtin?
otherwise, a quick fix that should work for all situations is doing #type: ignore for the whole file, after your done with coding (so normal errors still get shown)
Also maybe look at the answers here https://stackoverflow.com/questions/67931905/suppress-pylance-type-annotation-warning-in-vs-code
I think you need extraPaths configured for pylance maybe, to tell it where imgui is?
Like, your site-packages path probably?
This is bad advice, it should be possible to configure pylance in a way that ignores the issues from this one module while still allowing you to type check the rest of the file
As i said, only temporarlly
Hi folks,
I am trying to annotate a function with particular types -
@overload
def func(a: int) -> int = ...
@overload
def func(a: float) -> float = ...
Now the issue here is that an element of type int can be used to construct an element of type float, ie, b = float(int(0)) is valid. Now this above snippet shows me an error saying overloads overlap due to incompatible return types. But I believe that these return types are compatible due to float being constructible from int. Is there any way around without having to use # type: ignore throughout?
Is that the exact set of overloads you're checking? What you show should be accepted https://mypy-play.net/?mypy=latest&python=3.12&gist=14589c2f8735d6b6d6d632becd0ae98f
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
Interestingly, if I change this to -
from typing import final, overload, Union
@final
class mpz:
@overload
def __add__(self, other: Union[int, "mpz"] ) -> "mpz": ...
@overload
def __add__(self, other: Union[float, "mpfr"]) -> "mpfr": ...
def __add__(self, other):
return other
@final
class mpfr:
pass
This seems to work with mypy but pyright (or whatever is the vscode default, i ain't sure) gives the overload overlap error. Note that mpz and mpfr are gmpy2 types represting int and float respectively.
Could it be a pyright bug??? 🤔
I suppose that is because it accepts the Union (and the return-types) as they are in strings, which tells type-checkers that they should not check the actual type, and just belive the annotation?!
No, the strings shouldn't make a difference here. You can check though by using a top-level function instead of a method and removing the quotes
Nope, same thing. Also being discussed here https://github.com/microsoft/pyright/discussions/10365
Oh didnt know that, i suppose then its because of using Unions where Pyright thinks that mpz could be anything, as its not defined yet?
No
Eric explained it: https://github.com/microsoft/pyright/discussions/10365#discussioncomment-12938787
Yeah that is an amazing explanation. You guys are superb! 
Don't start annotating without using a checker in CI
Start Annotating EVERYTHING and also be sure to never forget that ... exists
Elaborate
Like running mypy in GitHub actions
Why is that necessary tho? Aren't you running the checker locally anyways
No people forget
I've seen a senior coworker annotate every function lacking a return statement with a -> NoReturn annotation in a project, because PyCharm didn't complain apparently.
(that project in particular was lacking mypy in CI for some reason)
I always install it as a pre-commit hook.
People then forget to run pre-commit in CI
We're hoping to have an alpha release out in a few weeks or so to demonstrate some of its capabilities and features that other tools don't have. But the alpha will still be very far from feature-complete. It'll be at least a few months before it's something approaching parity with mypy or pyright
That's understandable. I'll be looking out for the release!
Must've thought NoReturn meant void
it was probably interpreted literally as "no return"
like you know, it's not a generator function if it doesn't have yield
which is the same as void in other languages
You can return early with no value
def foo():
if bar():
return
func1()
func2()
That should be annotated as -> SomeReturn (joking)
I've also seen this confusion about the meaning of NoReturn though, another reason to move to Never
MaybeReturn
I think Python docs are somewhat missing a formal notion of "soft deprecation". Like, X is not recommended anymore, but we won't delete it to break thousands or programs.
(maybe it's just not explicit/formal enough?)
We do have that notion but maybe we need to apply it more to typing
Hi, I would like to specify clearly a new “soft deprecation” status in PEP 387: Backwards Compatibility Policy for functions which should be avoided for new code since these functions will not be developed further, but it’s ok to keep them in old code since their removal is not scheduled. Over the least years, whenever I saw anything mar...
ah, it has been discussed
I was thinking of %-formatting as an example. There are at least two (three?) newer ways of formatting strings now, but % is not going anywhere, and on occasion can be used
https://docs.python.org/3.14/library/typing.html#typing.Never here we don't actually say anything about Never being preferred
Emit PendingDeprecation in the code
That seems to have a bit of a different vibe
Yeah he walked away from that
I think we ended up with soft deprecated meaning docs-only
https://peps.python.org/pep-0387/#soft-deprecation is the current policy
here's an example in the wild https://docs.python.org/3.14/library/mimetypes.html#mimetypes.guess_type
opened https://github.com/python/cpython/issues/132941 to use soft-deprecation for typing
A while ago we introduced the concept of "soft deprecation" in PEP 387 (https://peps.python.org/pep-0387/#soft-deprecation): for things that we no longer recommend using, but which we are...
Why does the docs require a soft deprecation to say a newer syntax is preferred?
It doesn't, but I think it's more clear
Clearer, like collecting into a "soft deprecation" list of some sort?
I think I was basically looking for this statement when reading the PEP.
hello everyone. I am using unittest and mypy together.I find that unittest's self.assertIsInstance cant narrow the type correctly,how can i solve it?
like this. I have to write many redundant isinstance to stop the error message.I am using vscode's Mypy Type Checker
extension.
i really hate to be that guy but maybe consider using pytest?
pytest works with the built in assert and gives equally good error messages without remembering all this stuff
Hello, looking for help/advice.
When decorating a function with functools.lru_cache, the signature is lost
I mean:
@lru_cache(None)
def myfunc(arg1: Type1, arg2: Type2):
....
becomes
myfunc(*args, **kwargs)
How can we have it back again ?
I tried to use functools.update_wrapper but then the cache_clear, cache_info and cache_parameters are not available anymore
I simply came up with
@lru_cache(None)
def _myfunc(arg1: Type1, arg2: Type2):
...
def myfunc(arg1: Type1, arg2: Type2):
_myfunc(x, y)
but I am not really satisfied as you need to call _myfunc.cache_clear() instead of myfunc.cache_clear()
I could assign myfunc.clear_cache = _myfunc.clear_cache but such dynamic assignements are not recognized by pylance
I finally came up with
from functools import lru_cache as _lru_cache
from typing import Any, Callable, ParamSpec, TypeVar
P = ParamSpec("P")
T = TypeVar("T")
def lru_cache(size: int | None = 128, typed: bool = False):
def cache_wrapper(function: Callable[P, T]) -> Callable[P, T]:
class CachingWrapper:
@_lru_cache(size, typed)
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
return function(*args, **kwargs)
def cache_clear(self):
self.__call__.cache_clear()
def cache_info(self):
self.__call__.cache_info()
def cache_parameters(self):
self.__call__.cache_parameters()
return CachingWrapper()
return cache_wrapper
Which I think have all expected behaviors but Idk it feels too complicated
Now for some reason pylance do not know the cache_clear, cache_info and cache_parameters methods
Thanks for your reply! I will try that later.
Hello, I've recently found that the order in which you overload a function/method actually matters? Especially with ones having default arguments. So I have this method-
@overload
async def get_account(
self, auto_create: Literal[False] = ...
) -> None | User: ...
@overload
async def get_account(self, auto_create: Literal[True] = ...) -> User: ...
async def get_account(self, auto_create=True):
async with BaseData.make_session() as session:
get_user_query = select(User).where(User.id == self.id)
result = await session.execute(get_user_query)
data = result.fetchone()
if data is None:
if auto_create:
await self.post_account()
return await self.get_account()
return None
return data[0]
Now even though the default value of auto_create is True, when I try calling the method like so-
player_data = await player.get_account()
VSC indicates the type of player_data as User | None.
But if I interchange the places of the overloads to like so-
@overload
async def get_account(self, auto_create: Literal[True] = ...) -> User: ...
@overload
async def get_account(
self, auto_create: Literal[False] = ...
) -> None | User: ...
async def get_account(self, auto_create=True):
async with BaseData.make_session() as session:
get_user_query = select(User).where(User.id == self.id)
result = await session.execute(get_user_query)
data = result.fetchone()
if data is None:
if auto_create:
await self.post_account()
return await self.get_account()
return None
return data[0]
Everything becomes alright and player_data does indeed turn out to be just User and not User | None
I wanna know if this is how overloads are meant to be or is there some bug with vsc or am i tripping hard on smt
Yes, the order of overloads matters, it's like an if-elif-elif-elif chain
In your case the correct overload is
@overload
async def get_account(self, auto_create: Literal[False]) -> None | User: ...
@overload
async def get_account(self, auto_create: Literal[True] = ...) -> User: ...
``` or ```py
@overload
async def get_account(self, auto_create: Literal[True] = ...) -> User: ...
@overload
async def get_account(self, auto_create: Literal[False]) -> None | User: ...
Now even though the default value of auto_create is
True
overloads do not peek at the actual signature, it is considered an implementation detail as far as overload resolution is concerned
is there a type for attribute that may or may be not defined (without None)?
Basically smth working for hasattr level of class attribute
Optional is supposed to be value that is still defined, it is just going to be None?
Is there smth fitting the case, working with hasattr case?
oh well, i can be just using like it has normal attributes
and not forgetting to make checks for hasattr 😄 may be even going for just Optional, and remembering to do hasattr instead of check for None
optionally NotRequired type could be a nice hint, even if it is not typeddict may be
Here's my dilemma: I want a typed data structure with all of the attributes optional, I want it to be fast, I don't want it to eat a lot of memory, and I want to be able to address it without literal keys. TypedDict is failing at this last part
No, there is no support. Optional means it may be None, not that it may not exist. NotRequired is only for TypedDict.
dataclass_transform?
There have been some discussions around adding some feature like this, but it's difficult to create a sound spec for it
i think there is a good solution at the level of... wrapping my tricky variable into class that has defined getters for better defined types
Minimizing scope of typeless data to a single class, letting the rest code interacting with more predictable thing
Yes, that sounds like a good strategy
addressing attributes properly in data structs is possible at least with
from dataclasses import dataclasses- and
typing.NamedTuplecan work too - As ultimate hacky option it can be smth like
SimpleNamespacethat has defined types throughProtocol(we could use smth likefrom typing import castto cast our simplenamespace into protocol, i say here already very hacky stuff)
a thing to try which one works nicer here
ultimately under the python hood arbitary in amount of atttributes class instance is dictionary somewhere inside
so question is only which out of those variables will work better
sorry, my requirements were wrong, would like for the structure to be non-total, not for the values to be optional, as in attributes not set
SimpleNamespace it is, by default it does not have any attributes set
Yet its variables are accessed as class attributes if defined
this was why i was using TypedDict instead of dataclass or NamedTuple
that does sound good
also need to be able to iterate over the attrs pretty quick
and we can't defined types for not set attrs, i guess it will be impossible to type 🤔 unless u define typed getters in it as i noticed just a moment above
Are there any docs on this "typed getters" that you're talking about? Sounds interesting
https://www.geeksforgeeks.org/getter-and-setter-in-python/
getters in python are stuff like this
class Geeks:
def __init__(self):
self._age = 0
# using property decorator
# a getter function
@property
def age(self):
print("getter method called")
return self._age
# a setter function
@age.setter
def age(self, a):
if(a < 18):
raise ValueError("Sorry you age is below eligibility criteria")
print("setter method called")
self._age = a
U just add to them types as from
https://docs.python.org/3/library/typing.html
We can inherint SimpleNamespace (highly likely)
and define getters to it
Ahh I see. Thank you!
!e
from typing import Iterator
class PropStruct:
__slots__ = (
"a",
"b",
)
a: int
b: str
def has(self, name: str) -> bool:
"""Check if the attribute exists."""
return hasattr(self, name)
def __iter__(self) -> Iterator[tuple[str, int | str]]:
"""Iterate over the attributes."""
for name in self.__slots__:
if hasattr(self, name):
yield name, getattr(self, name)
s = PropStruct()
print(s.has("a"))
s.a = 1
s.b = "test"
print([(k, v) for k, v in s])
:white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | False
002 | [('a', 1), ('b', 'test')]
mypy doesn't choke on it, might work 👍
iter is probably gonna be really slow unless i do some more work
and mypy probably gonna have issues with iter output
feeling like going down this road gonna be expensive, when there's 29 possible attrs and usually 0 are set..
maybe i'll just make a kwarg factory for the TypedDict for users
and then maybe just use this approach for the other end.. it's message properties for a networking lib
and keep using TypedDict internally
Just use SimpleNamespace 😏
it is just Dictionary with wrapping to have attrs accessed in class way
ultimate if u have 29 Possible attrs which are may or may not defined, i think hashmap does have point but
but... to be fair i prefer always having defined explicitely Data Structs 😄
There is always a room to optimize memory usage in other ways than hacking hashmaps
- using smartly bufferization principles usually. making sure that at the moment of a time in a program at maximum N instances of smth
Each time when it reached N, the buffer is flushed, to free the memory
and when u finished working with big set of data, u callflushone last time (it can be done automatically in python by context manager)
thing is i don't mind so much about memory and cost for the end user interface, internally i'll probably always just use dict
- just using actually manual GC to clean up memory faster if necessary
user might have 1 or 2 of these alive at a time, library might have 65000
- Also when u work with relational databases, u can be not storing all data at the same time in memory
U can be using cursors and reading chunks in portions and handing data as u go i think?
At least i used this trick in django orm with queryset.iterator
https://docs.djangoproject.com/en/5.1/ref/models/querysets/#iterator
it allowed me handling data memory well even if having half of millions objects in the query without memory problems
may be your database engine (if u are going to do it with databases) supports similar feature / and code logic fits such thing
same as bufferization but at db engine client basically
i don't expect the persistence will be anything fancier than sqlite, just persisting state of MQTT session
ended up using a dataclass with optional fields on the user side, shipping it
anyone aware of a way to blend typeddict and normal dicts - i'm sorting out some legacy code that shoehorns some data into a different mapping and i'd like to defer refactoring it to something correct without foregoing better typing
a minimal example would be something like
Data = dict[str, DataItem]
class ErrorMess(TypedDict, total=False):
_errors_parsing: list[ErrorRecord]
DWIM = magic[Data, ErrorMess]
I wonder if intersections will support that
btw, you mentioned you care about memory usage - so you should also use slots=True
on the dataclass
if you can use that internally, then that will save memory relative to a dict
It's not supported in mypy yet
Because I need fast iteration over non-empty attrs (for encoding) I really need to have access to a dict in the library. That same iteration is required to create a TypedDict from the dataclass. When I benchmarked this, a non-slots dataclass was much much faster to produce a dict, because I don't have to call getattr twice to check for the None sentinel value 29 times (which is required to solve this problem with a stock dataclass, which is always total). It was also faster than a custom non-total slotted class checking with hasattr.
Maybe there's some kind of slotted class I can make that's faster than non-slotted dataclass, which has all the iteration performance and typing I need, but I haven't found it yet. Using more memory (by a lot! but never retained by the library) and marginal cost (only when this optional feature is used) to complete the typing story in the user interface seems like a pretty good win over forcing users to use TypedDict which has some obnoxious limitations.
This is the only dataclass use case in this project where slots weren't preferable!
empty TypedDict is 64B
slotted dataclass with 29 None is 264B
non-slotted dataclass with 29 None is 2048B
is there no way to iterate a slots dict?
also, a named tuple should be both fast and have fast iteration
sorry, should both be small and have fast iteration
I think, at any rate
how would you iterate over a slots dataclass without iterating over __slots__?
fields
sure, but practically the same thing, you get a list of attr names and you need to check if the value is None
and need to use getattr to do anything in that iteration
non-slotted dataclass:
return {key: value for key, value in self.__dict__.items() if value is not None}
slotted dataclass:
return {key: getattr(self, key) for key in self.__slots__ if getattr(self, key) is not None}
custom slotted class:
return {key: getattr(self, key) for key in self.__slots__ if hasattr(self, key)}
i may still eliminate the TypedDict and just use the dataclass, since it's just gonna get encoded to bytes up front 
In general I see TypedDict as mostly for adding typing to legacy code thats using dicts where it should have used a dataclass, while having the guarantee that you aren't changing the runtime behavior
but I admit these kinds of microoptimizations generally aren't on my radar
this pep is really neat. getting closer to typescript is realer
I have what is equivalent to the following code:
if sys.platform == "win32":
from my_pkg._pty.windows import PtySession
else:
from my_pkg._pty.unix import PtySession
On my current platform Windows, Mypy continues to perform checking inside my_pkg._pty.unix and when I import PtySession from this module going to the declaration in VS Code for some reason takes me to the implementation inside my_pkg._pty.unix. Why is this happening?
pyright is what provides "go to definition" features in vs code
maybe something about it is misconfigured
Mypy is behaving in the same (incorrect?) manner as well
if sys.platform == "win32" only works as literally that you can't use equivalents
sorry, what do you mean?
If you do;
WINDOWS: Final = sys.platform == "win32"
if WINDOWS: ...
It doesn't work as you night expect
btw mypy supports treating some vars as always true, so you can do if WINDOWS:... else:... and mypy --always-true=WINDOWS (or something like that) on windows and mypy --always-false=WINDOWS on non-windows
I think that would work, but that is clunky
It's annoying it won't propagate them to other finals
class Foo:
num: int
Is it possible to type that num will always be greater than or equal to 1
it can't be negative, it can't be 0
no
why though? and what would you do with that information?
(Could a TypeIs do this?)
you could make a "newtype wrapper" int subclass but it would just be a separate symbol and the typechecker couldnt really do anything with it
i guess you could override __gt__(Literal[0]) -> Literal[True]
int: Literal[0,1,2,3,4,5,6,7,8,9,10,<all numbers in the world>] 
maybe a range(1, max_int) would work?
No, a range object is not valid as a type
Imagine
Not much other than more accurately reflecting what it'll be at runtime
If it's not possible then int does the job too
I don't understand exactly what you're hinting at, in my experience sys.platform == "win32" is working properly in other circumstances
Do you have a link to the code in question?
You said you have something "equivalent" to that code, so it makes sense to wonder if your sys.platform check actually looks like that or something else
It's important because type checkers recognize only specific patterns
I haven't pushed my branch yet but the full file is dda.utils.platform._pty.session:
from __future__ import annotations
import sys
if sys.platform == "win32":
from dda.utils.platform._pty.windows import PtySession
else:
from dda.utils.platform._pty.unix import PtySession
__all__ = ["PtySession"]
and other files only import from this file that exposes the PtySession. dda.utils.platform._pty.__init__ is empty, and the only part of my code that imports that is dda.utils.process:
class SubprocessRunner:
def __run(self, ...) -> tuple[int, str]:
from dda.utils.platform._pty.session import PtySession
isn't that weird/unexpected behavior?
could it be that the private method importing from a private package is somehow to blame?
no I'd expect this to work, not sure what's wrong
I guess the thing that comes to mind is that you run with something like a --platform argument
mypy --install-types --non-interactive src/dda tests
config can also be in mypy.ini/pyproject.toml
(also unrelated but --install-types is evil)
[tool.mypy]
disallow_untyped_defs = true
disallow_incomplete_defs = true
enable_error_code = ["ignore-without-code", "truthy-bool"]
follow_imports = "normal"
ignore_missing_imports = true
pretty = true
show_column_numbers = true
warn_no_return = true
warn_unused_ignores = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
check_untyped_defs = false
no_implicit_optional = false
strict_optional = false
warn_return_any = false
warn_unused_ignores = false
❯ hatch run types:uv pip freeze | rg mypy
Using Python 3.12.7 environment at: C:\Users\ofek\AppData\Local\hatch\env\virtual\dda\6xDqlXVy\types
mypy==1.15.0
mypy-extensions==1.1.0
Is there anyway to overload *args: Enum?
Why does this work for pyright
- pyright
from __future__ import annotations
import typing as t
T = t.TypeVar("T", default=str)
class A(t.Generic[T]):
@t.overload
def __new__(cls, *, null: t.Literal[True]) -> A[T | None]:
...
@t.overload
def __new__(cls, *, null: t.Literal[False] = False) -> A[T]:
...
def __new__(cls, *, null: bool = False) -> A[T] | A[T | None]:
return super().__new__(cls)
def __init__(self, *, null: bool = False) -> None:
super().__init__()
a = A(null=False)
reveal_type(a)
b = A(null=True)
reveal_type(b)
c = A()
reveal_type(c)
c:\Users\Triyan Mukherjee\VsCodeProjects\imc\test.py
c:\Users\Triyan Mukherjee\VsCodeProjects\imc\test.py:63:13 - information: Type of "a" is "A[str]"
c:\Users\Triyan Mukherjee\VsCodeProjects\imc\test.py:65:13 - information: Type of "b" is "A[str | None]"
c:\Users\Triyan Mukherjee\VsCodeProjects\imc\test.py:67:13 - information: Type of "c" is "A[str]"
0 errors, 0 warnings, 3 informations
but not for mypy
- mypy
test.py:54: error: "__new__" must return a class instance (got "A[T] | A[T | None]") [misc]
test.py:63: note: Revealed type is "test.A[builtins.str]"
test.py:65: note: Revealed type is "test.A[builtins.str]"
test.py:67: note: Revealed type is "test.A[builtins.str]"
the __new__ overload and return type both seem to be inferred weirdly
whats wrong with just using @typing.overload?
edit: nvm doing a union causes the args tuple to expand to all possible enum members
maybe try out enum.IntFlag with | and ~& for multiple options or settings depending on your use case
i was wondering why json.loads returns Any type instead of some better typing?
for example something like
Json = dict[str, Json] | list[Json] | str | int | float | bool | None
Because of json.JSONDecoder
Plus annotating with a typeddict you expect it to be is usually good enough. Other libraries can provide validation like pydantic
i was wrong about dataclass, it doesn't have to be total! you just write your own init and conditionally set the attrs
You can have default values.
Conditionally setting attributes will lead to AttributeErrors
Mypy didn't even support recursive types until relatively recently
And improving the type would be a pretty big breaking change - I'm actually dealing with that at work now
And probably the value isn't high enough, as unalive said
Typically people should anyway be pumping the json into a data class and doing validation there and then you have the data class which is more strictly typed
@oblique urchin @grave fjord I pushed my branch https://github.com/DataDog/datadog-agent-dev/pull/83 and the error is reproducible, you can see the failing Windows job
yes, but default values use memory when non-slotted, and slots are slower to iterate than __dict__
Then use a different type
what would i use?
A type that doesn't define those attrs
the thing i want is a typed data structure that can quickly iterate over all non-default key: value pairs, and not storing the default values in an internal dict
Default value instances are shared
they are shared when they are __slots__
No, always
Anyway, this is #type-hinting and typing doesn't support a "maybe defined" attribute.
Outside of TypedDict of course.
is this the most cursed thing that could possibly do what i want?
class PropertiesDict(TypedDict, total=False):
one: int
two: str
three: bytes
four: float
@dataclass(init=False, repr=False)
class PropertiesDataclass:
one: int
two: str
three: bytes
four: float
def __init__(
self,
one: int | None = None,
two: str | None = None,
three: bytes | None = None,
four: float | None = None,
) -> None:
if one is not None:
self.one = one
if two is not None:
self.two = two
if three is not None:
self.three = three
if four is not None:
self.four = four
def __getattr__(self, name: str) -> Any:
return self.__dict__.get(name, None)
def __repr__(self) -> str:
return f"PropertiesDataclass{self.__dict__}"
def to_dict(self) -> PropertiesDict:
return cast(PropertiesDict, self.__dict__)
dataclasses.asdict(obj, *, dict_factory=dict)```
Converts the dataclass *obj* to a dict (by using the factory function *dict\_factory*). Each dataclass is converted to a dict of its fields, as `name: value` pairs. dataclasses, dicts, lists, and tuples are recursed into. Other objects are copied with [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html#copy.deepcopy).
Example of using `asdict()` on nested dataclasses...
thing is i'd rather incur the cost of filtering out None in __getattr__ than when iterating over all the key value pairs
Might as well use types.SimpleNamespace
is there a way to get that to type check, and reject unspecified attrs? this passes mypy strict:
class PropertiesNamespace(SimpleNamespace):
one: int
two: str
three: bytes
four: float
def __repr__(self) -> str:
return f"PropertiesNamespace{self.__dict__}"
def to_dict(self) -> PropertiesDict:
return cast(PropertiesDict, self.__dict__)
PropertiesNamespace()
PropertiesNamespace(one=1)
PropertiesNamespace(one=1, two="two")
PropertiesNamespace(one=1, two="two", three=b"three")
PropertiesNamespace(one=1, two="two", three=b"three", four=4.0)
PropertiesNamespace(one=1, two="two", three=b"three", four=4.0, five=5.0)
import types
import typing
@typing.dataclass_transform()
class Namespace(types.SimpleNamespace): pass
class PropertiesNamespace(Namespace):
one: int | None
Make a base class decorating with @typing.dataclass_transform()
it's also inexplicably slower to render the dict using the same method as the cursed dataclass
class types.SimpleNamespace```
A simple [`object`](https://docs.python.org/3/library/functions.html#object) subclass that provides attribute access to its namespace, as well as a meaningful repr.
Unlike [`object`](https://docs.python.org/3/library/functions.html#object), with `SimpleNamespace` you can add and remove attributes.
[`SimpleNamespace`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) objects may be initialized in the same way as [`dict`](https://docs.python.org/3/library/stdtypes.html#dict): either with keyword arguments, with a single positional argument, or with both. When initialized with keyword arguments, those are directly added to the underlying namespace. Alternatively, when initialized with a positional argument, the underlying namespace will be updated with key-value pairs from that argument (either a mapping object or an [iterable](https://docs.python.org/3/glossary.html#term-iterable) object producing key-value pairs). All such keys must be strings...
NamedTuple can also be useful for immutable data. It's much faster than dataclasses
i put @dataclass_transform() on it and it still passes type check, nothing is jumping out at me in the docs as a way to fix that?
Works fine for me.
import types
import typing
@typing.dataclass_transform()
class Namespace(types.SimpleNamespace): pass
class PropertiesNamespace(Namespace):
one: int | None
PropertiesNamespace(one="one") # Argument of type "Literal['one']" cannot be assigned to parameter "one" of type "int | None" in function "__init__"
oh ok, put it on a parent class
works in mypy too
i get this though
bench/prop_struct.py:71: error: Missing positional arguments "one", "two", "three", "four" in call to "PropertiesNamespace" [call-arg]
bench/prop_struct.py:72: error: Missing positional arguments "two", "three", "four" in call to "PropertiesNamespace" [call-arg]
bench/prop_struct.py:73: error: Missing positional arguments "three", "four" in call to "PropertiesNamespace" [call-arg]
bench/prop_struct.py:74: error: Missing positional argument "four" in call to "PropertiesNamespace" [call-arg]
bench/prop_struct.py:76: error: Unexpected keyword argument "five" for "PropertiesNamespace" [call-arg]
with kw_only_default it causes "Missing named argument" instead
!e this should work. ```py
import types
import typing
@typing.dataclass_transform()
class Namespace(types.SimpleNamespace): pass
class PropertiesNamespace(Namespace):
one: int | None = None
PropertiesNamespace(one=1)
_ = PropertiesNamespace()
print()
print(.one)
_ = PropertiesNamespace(one=1)
print()
print(.one)```
:white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | PropertiesNamespace()
002 | None
003 | PropertiesNamespace(one=1)
004 | 1
Note that PropertiesNamespace().__dict__ is empty
Also experiment with namedtuple
@rare scarab thanks, that's worth about 14% more throughput when properties are used 😂
I get that using another language is annoying but dang it kind of hurts to see this level of micro optimization in python when simply writing the code in any mainstream statically typed language will give you a 10x speedup
(or more)
i don't think using another language is annoying, i just want to have the fastest pure python implementation
and this SimpleNamespace trick is really very clean
most of the optimizations i'm doing are resulting in cleaner python, because the less python you write the faster it runs
in 3.13 the differences are more marginal than 3.10, so things are moving in the right direction
in fact pybind11 is really very pleasant
follow-up from last night with more context:
I have what is equivalent to the following code:
if sys.platform == "win32":
from my_pkg._pty.windows import PtySession
else:
from my_pkg._pty.unix import PtySession
On my current platform Windows, Mypy continues to perform checking inside my_pkg._pty.unix and when I import PtySession from this module going to the declaration in VS Code for some reason takes me to the implementation inside my_pkg._pty.unix. Why is this happening? I can reproduce this on CI.
- PR (notice Windows job): https://github.com/DataDog/datadog-agent-dev/pull/83
- private package: https://github.com/DataDog/datadog-agent-dev/tree/ofek/pty/src/dda/utils/platform/_pty
- only place it's imported: https://github.com/DataDog/datadog-agent-dev/blob/ofek/pty/src/dda/utils/process.py#L256
Pybind is nice but mixing languages is significantly more involved than not is what I meant
I'm positive this a bug and VS Code's pylance has the same issue, it makes no sense otherwise
I guess the question is whether a static type checker can actually run sya.platform to see which branch to take
In that sense I don't actually find this that surprising
they do, it works in other circumstances
can I get a hand working out this into a minimal reproducer? https://mypy-play.net/?mypy=latest&python=3.12&gist=ff40e78844fa3c037ee3b05c2e317676
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
this program should typecheck, but I get:
main.py:49: error: An overloaded function outside a stub file must have an implementation [no-overload-impl]
main.py:65: error: Name "walk_breadth_first" already defined on line 49 [no-redef]
Found 2 errors in 1 file (checked 1 source file)
but the thing is, if I delete the first function; walk_depth_first - this issue goes away
okay I created an issue with a very small reproducible example https://github.com/python/mypy/issues/19009 cc @oblique urchin @grave fjord in case you're still interested or may have new ideas
they can check sys.version_info, seems like an oversight if they can't check sys.platform too
why did you put that behind if TYPE_CHECKING ?
it should work without it
I didn't it was willmcgugan
imo overloads inside a type_checking block shouldn't need an implementation, just be defined or imported somewhere in the file.
It makes you need a reference to typing.overload at runtime
is there a better way to type annotate a function that accepts a string of certain length than just str ?
!pep 736 didn't realize this got rejected.
rust has this for struct initialization and it's really nice
syntactic sugar? In MY language? It's more likely than you think.
I opened a feature request based on the issue I opened earlier https://github.com/python/mypy/issues/19013
ok MRE for my thing https://mypy-play.net/?mypy=latest&python=3.12&gist=9aa50566df450052503989e5bba6a3c7
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
and I filed it https://github.com/python/mypy/issues/19015
Bug Report TYPE_CHECKING overload with implementation just outside flags second usage, but not first To Reproduce https://mypy-play.net/?mypy=latest&python=3.12&gist=9aa50566df450052503989e...
anyone know of a way to make this work properly with basedpyright?
from typing import TypedDict
import httpx
class Params(TypedDict):
foo: str
bar: int
params: Params = {
'foo': '',
'bar': 1,
}
httpx.get('/', params=params)
right now basedpyright is giving me
Argument of type "Params" cannot be assigned to parameter "params" of type "QueryParamTypes | None" in function "get"
on the last line
i would paste this here but idk how to copy it
That is because the values of a TypedDict is considered to house objects
mostly because typeddicts are allowed to have extra items:
class Params1(TypedDict):
foo: str
bar: int
class Params2(TypedDict):
foo: str
bar: int
baz: socket.socket
def f(p: Params1):
...
def g(p: Params2):
f(p) # allowed
There's a new PEP728 that allows one to create closed typeddicts (which is what most people want most of the time probably, but for some reason has not been added yet)
However, pyright still doesn't think that Params is assignable to Mapping[str, str | int] when Params is closed=True. I don't know if it's a bug or not. You can file an issue on the pyright repo when PEP728 is accepted
Can anyone help me ignore certain warnings from basedpyright?
Diagnostics:
1. Return type is Any [reportAny]
2. Type `Any` is not allowed [reportExplicitAny]
This is the default config I used from the basedpyright website.
require("lspconfig").basedpyright.setup {
settings = {
basedpyright = {
analysis = {
diagnosticMode = "openFilesOnly",
inlayHints = {
callArgumentNames = true
},
}
}
}
}
[Resolved] #type-hinting message
What's the correct way to bind the generic type of Node to its parent class DoublyLinkedList? I've read in mypy docs that the same type var T used in nested classes does not mean its type has to follow the parent T, but there's nothing saying why or how to make this happen.
from __future__ import annotations
from typing import Generic, TypeVar
T = TypeVar("T")
class DoublyLinkedList(Generic[T]):
class Node(T):
value: T
def __init__(self, value: T):
self.value = value
self._prev: DoublyLinkedList.Node = None
self._next: DoublyLinkedList.Node = None
head: Node[T] = None
tail: Node[T] = None
Inheriting from a type variable does not make sense. Did you mean to inherit from Generic[T]?
yup that's what i meant to do
I think you just want class Node:. That way, it inherits the typevar T that is already in scope.
ah hmm
But this may be easier if you don't make it a nested class and make it independently generic
class DoublyLinkedList(Generic[T]):
class Node:
value: T # error: Type variable "__main__.T" is unbound
yeah I definitely wouldn't nest classes
pyright accepts ```class DoublyLinkedList[T]:
class Node:
value: T
I think this issue hasn't been caught because people don't generally use nested classes in Python 😛
this sort of thing works differently in different languages, as well
If someone is used to C++ or Rust, vs if they're used to Java or Kotlin, they will expect very different behavior from the nested class
in python if you don't want anyone except DoublyLinkedList to use Node, you would just define it as _Node as top level and call it a day
Yup this was my intention for nesting it. But perhaps it is better to unnest it as you suggested.
yeah, for sure
just use module/package level encapsulation
_Node is the idiomatic way to do this
really, _Node is "more" encapsulated than what you wrote, because anybody can use that Node type anyway and you haven't indicated that it's an implementation detail with a leading underscore
Alternatively you can use two different type variables ```py
class DoublyLinkedList[T]:
class _Node[U]:
value: U
def __init__(self, value: U):
self.value = value
self._prev: DoublyLinkedList._Node[U] | None = None
self._next: DoublyLinkedList._Node[U] | None = None
head: _Node[T] | None = None
tail: _Node[T] | None = None
does this force T and U to be the same type?
This may be unexpected if you're coming from a different language with nested classes, but in Python, nested classes are more or less completely independent
essentially you're defining two standalone classes and then doing DoublyLinkedList._Node = _Node
this is how it works in Java/Kotlin, fwiw
that's kind of what I expected tbh
this is the more "type erasure" kind of model
I thought there was some impact on access to private fields in Java
there is, I'm just talking about the generic aspect of it
in Java/Kotlin nested classes of generic classes don't have any kind of menaingful access to the type parameter
so if you were doing something similar to this, this is how you would do it
!e
actually, I wonder
class Foo:
class Bar:
def __init__(self):
self.__baz = 42
bar = Foo.Bar()
print(bar.__dict__)
:white_check_mark: Your 3.12 eval job has completed with return code 0.
{'_Bar__baz': 42}
makes sense
thanks for the input, this seems to work for me:
from __future__ import annotations
from typing import Generic, TypeVar
T = TypeVar("T")
class Node(Generic[T]):
value: T
def __init__(self, value: T):
self.value = value
self._prev: Node[T] | None = None
self._next: Node[T] | None = None
class DoublyLinkedList(Generic[T]):
head: Node[T] | None = None
tail: Node[T] | None = None
Like, can you access _Node as DoublyLinkedList[int]._Node ? is that even valid syntax?
btw, if None is an option, you need to explicitly specify it as a possibility
class DoublyLinkedList(Generic[T]):
head: Node[T] | None = None
tail: Node[T] | None = None
!e
class Foo[T]:
class Bar:
pass
print(Foo[int].Bar)
:white_check_mark: Your 3.12 eval job has completed with return code 0.
<class '__main__.Foo.Bar'>
interesting, it's allowed, but I wonder if type checkers will actually distinguish
Also this ```diff
-
self._prev: Node = None -
self._next: Node = None
-
self._prev: Node[T] = None -
self._next: Node[T] = None
``` because Node on its own silently means Node[Any]
is there a way to run mypy jobs here
!mypy
we do not have mypy or pyright playgrounds
class Foo[T]:
class Bar:
pass
x: Foo[int].Bar = Foo[float].Bar()
mypy doesn't even like this as a type annotation
I was thinking of making a side-by-side type checker thing, but I'm too lazy
the UX of my pyright playground was so bad that microsoft had to make their own
at any rate I haven't fully understood how it works but yeah the bottom line I think is just to really really avoid nested classes in python.
in the era of type checking more than ever
trying to make a decorator for a class instance method, what's wrong with this picture?
P = typing.ParamSpec("P")
R = typing.TypeVar("R")
def wrapper(
func: typing.Callable[typing.Concatenate[C, P], R],
) -> typing.Callable[typing.Concatenate[C, P], R]:
"""A decorator to wrap a function with additional functionality."""
@functools.wraps(func)
def wrapped(c: C, *args: P.args, **kwargs: P.kwargs) -> R:
print(f"Wrapped function called with {c}")
return func(c, *args, **kwargs)
return wrapped
class C:
@wrapper
def f(self, a: int, b: str) -> None:
print(f"Function f called with {a}, {b}")
bench/wrapper.py:17: error: Incompatible return value type (got "_Wrapped[[C, **P], R, [C, **P], R]", expected "Callable[[C, **P], R]") [return-value]
bench/wrapper.py:17: note: "_Wrapped[[C, **P], R, [C, **P], R].__call__" has type "Callable[[Arg(C, 'c'), **P], R]"
Found 1 error in 1 file (checked 1 source file)
friend or footgun?
R = typing.TypeVar("R")
def wrapper(
func: typing.Callable[..., R],
) -> typing.Callable[..., R]:
"""A decorator to wrap a function with additional functionality."""
@functools.wraps(func)
def wrapped(c: C, *args: typing.Any, **kwargs: typing.Any) -> R:
print(f"Wrapped function called with {c}")
return func(c, *args, **kwargs)
return wrapped
class C:
@wrapper
def f(self, a: int, b: str) -> None:
print(f"Function f called with {a}, {b}")
Footgun.
make the typevar bound Callable[..., Any]
TFunc = TypeVar("TFunc", bound=Callable[..., Any])
def wrapper(func: TFunc) -> TFunc:
@functools.wraps(func)
def wrapped(*args: Any, **kwargs: Any) -> Any:
print(f"Wrapped function called with {args[0]}")
return func(*args, **kwargs)
return wrapped
class C:
@wrapper
def f(self, a: int, b: str) -> None: ...
reveal_type(C.f) # C.f is (self: C, a: int, b: str) -> None
reveal_type(C().f) # C().f is (a: int, b: str) -> None
With your solution, C.f would be (c: C, *args: Any, **kwargs: Any) -> None
C().f would be (*args: Any, **kwargs: Any) -> None
That's wrong, wrapper(int) is not a class for example
ParamSpec is the right approach, but mypy might be confused about methods
This was the way to go before paramspec.
also paramspec involves more boilerplate, and if you're extracting the first argument, you also have to use Concatenate
but I don't think that works with P.args
Unless...
Do you have to treat self specially? Can you show the real decorator perhaps?
import functools
from typing import Callable, Concatenate, TypeVar, ParamSpec
C = TypeVar("C")
R = TypeVar("R")
P = ParamSpec("P")
def wrapper(func: Callable[Concatenate[C, P], R]) -> Callable[Concatenate[C, P], R]:
@functools.wraps(func)
def wrapped(self: C, *args: P.args, **kwargs: P.kwargs) -> R:
return func(self, *args, **kwargs)
return wrapped
I'm actually not getting any of these errors on mypy. What version and settings are you using?
wtf, I'm only getting an error in strict mode
Add a / after self
Oh right
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
Yeah, I just realized that as well
the error is extremely unhelpful
main.py:15: error: Incompatible return value type (got "_Wrapped[[C, **P], R, [C, **P], R]", expected "Callable[[C, **P], R]") [return-value]
main.py:15: note: "_Wrapped[[C, **P], R, [C, **P], R].__call__" has type "Callable[[Arg(C, 'c'), **P], R]"
things wrong with this error:
- unclear what
_Wrappedis and what module it's even from Argis some internal mypy entity whose representation is not explained
well, 1 is a minor complaint
It's weird to see it talk about Arg though, given that I thought that was internal
What's missing from this error is a link to some explanation on assignability of callables, specifically that you need to remember how keyword arguments work. If you have a function like py class SomeClass def foo(self, *args, **kwargs): ... you cannot call SomeClass().foo(bar=1, baz=2, self=42) because the self argument (which is both positional and keyword) is already supplied. But this would accept a self kwarg: ```py
class SomeClass
def foo(self, /, *args, **kwargs): ...
The wrapper function promises that it will transform a function with any shape whatsoever, including those shapes that expect c as a keyword argument. So it must not reserve any particular name for the first parameter
In retrospect, Callable[[Arg(C, 'c'), **P], R] is not assignable to Callable[[C, **P], R] is kind of saying that, but in a way where you need to know the above to understand it
is it possible to specify "I'll return the same size of tuple you gave me but with these types instead" with the current typing spec?
while also specifying that the input tuple must hold values of some type
def exclusive_true(
*set_funcs: Callable[[bool], None]
) -> tuple[Callable[[bool], None], ...]:
return tuple(
(
lambda pressed: (
None
if not pressed
else consume(func(i == my_index) for i, func in enumerate(set_funcs))
)
)
for my_index in range(len(set_funcs))
)
It's basically a function that when a radio button is set to true, it sets all the others false
it returns the functions you can then pass as button press event handlers
consume here is more_itertools.consume
I was thinking of using TypeVarTuple, but that doesn't allow you to bind the values to some type
so it'd just accept any tuple
Current solution is to use overloads
for each number of arguments?
Yes
yeah, i thought using wraps would Just Work, but the vibe i got from the error was that wraps returns something incompatible with what it is wrapping
this is working for me
i bound C to the class since that seems correct
since it's meant to be a base class
it's a class that lets you acquire its lock in a with block and use a decorator to guarantee a method is called with that lock
and when applying it to thread-safe code, i find all the places i wasn't locking access to resources 😂
You should still use a typevar. If needed, make it bound to your baseclass
I'm trying to build some type checking for my config parser. It's relatively simple, but I'm wondering how I should get this type checked properly:
from typing import TypedDict
class InnerConfigBase(TypedDict):
foo: str
class ConfigBase(TypedDict):
inner: InnerConfigBase
class ExpandedInnerConfig(InnerConfigBase):
bar: str
class Config(ConfigBase):
inner: ExpandedInnerConfig
To me it feels this should work, but mypy complains about overwriting the inner field in Config.
This is due to invariance. You may want to use ReadOnly for the TypedDict fields
I've just tried adding it, and that doesn't appear to change anything
I don't know too much about type hinting though 😅
That seems to be a bug in mypy
Using a generic over InnerConfigBase may be better.
class InnerConfigBase(TypedDict):
foo: str
class ConfigBase[T: InnerConfigBase](TypedDict):
inner: T
class ExpandedInnerConfig(InnerConfigBase):
bar: str
class Config(ConfigBase[ExpandedInnerConfig]): pass
unless your config parser doesn't support generics this way.
How do a I do a generic type where it could be a list, dict, or any json value, and I process it different for each type? (simplifying my code a little bit)
def diff_json_data[T](old: T, new: T) -> tuple[T, T]:
if isinstance(old, dict) and isinstance(new, dict):
olddiff_dict: T = {}
newdiff_dict: T = {}
for key in list(old.keys()) + list(new.keys()):
diff_value = diff_json_data(old.get(key), new.get(key))
if diff_value[0] or diff_value[1]:
olddiff_dict[key], newdiff_dict[key] = diff_value
return olddiff_dict, newdiff_dict
if isinstance(old, list) and isinstance(new, list):
olddiff: T = []
newdiff: T = []
oldstart, newstart, oldend, newend = 0, 0, len(old) - 1, len(new) - 1
while oldstart <= oldend or newstart <= newend:
diff_value = diff_json_data(
old[oldstart] if oldstart <= oldend else None,
new[newstart] if newstart <= newend else None,
)
if diff_value[0] or diff_value[1]:
olddiff.append(diff_value[0])
newdiff.append(diff_value[1])
oldstart += 1
newstart += 1
return olddiff, newdiff
if old != new:
return old, new
return None, None
is there a way to use typing.overload with contextlib.contextmanager? something like
from collections.abc import Iterator
from contextlib import contextmanager
from typing import overload
@overload
def f(x: int) -> Iterator[int]: ...
@overload
def f(x: str) -> Iterator[str]: ...
@contextmanager
def f(x: int | str) -> Iterator[int | str]:
yield x
with f(1) as y:
print(y)
doesn't seem to work
The overloads should have the "post-decorator" return type, i.e. ContextManager[int]
ah thx
I like the type anootations since they provide me better understanding via editor supoort for arguments and their types. I am feeling that I am using it wrong.
I have a seperate folder named typing for type annotation imported all over the application. Can you please provide useful suggestions to better approach for using type annotation in my project.
Project: https://github.com/sparrowsurya/whisper
Yay congrats @oblique urchin !
https://discuss.python.org/t/pep-749-implementing-pep-649/54974/66
Wooooe
https://peps.python.org/pep-0749/
(at the end of Abstract)
I believe there is a word missing: If annotations are accessed in a partially executed module, ...
otherwise I can't parse that
Thanks, fixed in a pending PR
other than not naming your folder the same name as the stdlib module I think it's fine
having X.typing submodule is a common practice
Well I dont think it goona have some side effects cuz the place main app is running do not have typing folder. But I will change the name since someday it might crate issue
wasn't there a x: str @ doc('docstring for x') proposal? I don't see it being mentioned in the pep 727
I want to add a typeshed TypedDict type for asyncio.Task's kwargs (available at loop.create_task, asyncio.create_task and asyncio.TaskGroup.create_task somewhat on 3.13.3 and completely on 3.14b1)
how do I add it so it's available to typeshed to import but it's private outside typeshed?
if I call it asyncio.tasks._TaskKwargs can I still import it in asyncio.base_events ?
Put it in asyncio._types
oh cool
If only we had a ParamsOf(func) utility
base class?
?
how so? kw_only is about __init__ of the class, here I am invoking a parameter
no, ignore that.
Try using Calable[[Unpack[RecordType]], T]
Yeah, you might want cls: Callable[..., T] | None = None
I'm not sure Unpack is legal with Mapping
A limitation of the current typing system is we can't separate ParamSpec.args from ParamSpec.kwargs
Since this is impossible to strictly type, might as well do the YOLO option (... in Callable)
Do you have an example of how you'd use this? Maybe there's a type-safer way
yeah
thanks so much, this was very helpful. I stared at this 2 hours and you guys took 5 minutes 😉
typing is the foxhole of python
maybe even a rabbit hole
And at the bottom are punji sticks
the good thing is that it remains optional. Compare to C++ where I remember spending nights over pre-processer errors.
Maybe you want something like this? py TPAdapter([ lambda: Foo(bar=1), lambda: Foo(bar=1, baz=5), lambda: SpecialFoo(bar=1, debug=True), ]) or just ```py
TPAdapter([
Foo(bar=1),
Foo(bar=1, baz=5),
SpecialFoo(bar=1, debug=True),
])
def unfun[T](*fns: Callable[[], T]) -> Iterator[T]:
return (fn() for fn in fns)
TPAdapter(unfun(
lambda: Foo(bar=1),
lambda: Foo(bar=1, baz=5),
lambda: SpecialFoo(bar=1, debug=True),
))
Just a second, booting up VSCode.
Issue being:
if div := response.find(
"div",
{"class": "sessions-new system session region"},
):
tag = div.find("p")
if isinstance(tag, Tag) and "This work is only available to registered users" in tag.get_text(strip=True):
raise RuntimeError(
"The page which you are trying to reach is restricted to authenticated sessions; use `session.authenticate`"
)
# I have NO clue what Pyright is complaining about.
elif div.find("div", {"class": "flash alert"}): # type: ignore ???
raise RuntimeError(
"The authentication process has failed. Are you certain you inputted the correct credentials?"
)
The elif gives the following type error:
The argument of type "dict[str, str]" cannot be assigned to the parameter "start" of type "SupportsIndex | None" in the function "find"
"dict[str, str]" cannot be assigned to type "SupportsIndex | None"
"dict[str, str]" is incompatible with the protocol "SupportsIndex"
"index" is not present
"dict[str, str]" cannot be assigned to "None"
Pylance(reportArgumentType)
type of response is BeautifulSoup, div being Tag
At runtime both end up being Tag though.
@trim tangle
Is there any more information that I could provide you here?
you make my head spin 😉
Well, my main question is why not ```py
TPAdapter([
Foo(bar=1),
Foo(bar=1, baz=5),
SpecialFoo(bar=1, debug=True),
])
I'm getting this ```py
from bs4 import BeautifulSoup
def f(response: BeautifulSoup) -> None:
if div := response.find(
"div",
{"class": "sessions-new system session region"},
):
tag = div.find("p")
# ^^^^
# Cannot access attribute "find" for class "PageElement"
# Attribute "find" is unknown
(with basedpyright)
Oh yeah. I'm using types-beautifulsoup4 to mitigate the worst of it
[dependency-groups]
lint = [
"pre-commit==3.6.0",
"pyright==1.1.353",
"types-beautifulsoup4==4.12.0.20250204",
"ruff==0.2.2",
]
From the PyPI page
Note: The beautifulsoup4 package includes type annotations or type stubs since version 4.13.0. Please uninstall the types-beautifulsoup4 package if you use this or a newer version.
I replaced that line with ```py
assert isinstance(div, Tag)
tag = div.find("p")
presumably response.find can return either of PageElement | Tag | NavigableString, but here I'm assuming you're sure that it's a Tag
Yeah, I am
Both their runtime types end up being Tag
Lowkey strange. But oh, well. That fixed it
Thank you
So whenever I encounter this type of error, just asserting with isistance does the trick. Good to know
In general, if you have an object that's inferred as being one of several types, you are only allowed to perform an operation that makes sense in all cases. e.g.: ```py
foo: str | bytes = ...
len(foo) # ok
foo + "bar" # not ok
Sometimes you can eliminate this by refactoring the code, e.g.: ```py
before
class Widget:
def init(self):
self.parser: Parser | None = None # multi-step initialization
def run(self):
assert self.parser #???
self.parser.parse(self.things())
widget = Widget()
widget.parser = MyParser()
widget.run()
after
class Widget:
def init(self, parser: Parser):
self.parser = Parser # single-step initialization
def run(self):
self.parser.parse(self.things())
widget = Widget(MyParser())
widget.run()
or alternatively ```py
using the Null Object pattern
class Widget:
def init(self, parser: Parser | None = None):
self.parser = parser or NoOpParser()
def run(self):
self.parser.parse(self.things())
but sometimes you can't avoid it
If beautifulsoup wanted, they could have an assertive method like find_tag(...) or find(Tag.DIV, ...) that narrows down the possibilities -- that would be the refactoring route
I see. That clarifies a lot, actually.
Thank you very much for helping me squash out this error. I appreciate you
My runs of mypy . and mypy as part of pre-commit (using https://github.com/pre-commit/mirrors-mypy) differ. Is it possible to get the pre-commit version to use pyproject.toml like the other one?
