#type-hinting
1 messages · Page 32 of 1
With the decorator, the type hint should be:
(date: Sequence[DateT]) -> Sequence[DateT]:
are you on 3.12?
Yes
hmh, i think they would need to "apply" Sequence to each arg in the paramspec which is not possible ? maybe it should only work for 1 argument functions?
What if the original function has two parameters?
Then only use Sequence[] for the params in "target"
And the return
you cant do anything with that target in the typehint
Ah... that's not going to be possible
Then you can consider only one target
But this still applies
Can't do it then? 😦
Yeah, you can't match string literals with parameters in this way
How many functions do you want to decorate with this?
maybe [A, *B, C](Callable[[A, *B], C]) -> Callable[[Sequence[A], *B], Sequence[C]] to apply it to the first arg
Yeah, you can probably do that
Seems like you're looking for a TypeGuard/TypeIs function of some kind. Those constructs allow you to tell the type checker how a custom function might narrow a particular kind of input, but it won't verify your narrowing logic for you.
And type checkers can't verify that your overloads are correct
Can you show an example? Haven't met any issues with overloads yet. Pyright seems to complain if an overload doesn't fit in the original function signature
A typeguard turning a list[int | None] into list[int] is incorrect as I explained
Ah, I didn't read down far enough. *My bad. I know it's unsafe, but I figured it might work with some fiddling depending on the use case.
Depending on what spawned the list, where it's passed, etc.
I meant that it doesn't check that the implementation follows the logic of the overloads: ```py
@overload
def double(x: str) -> str: ...
@overload
def double(x: None) -> None: ...
def double(x: str | None) -> str | None:
if random.random() > 0.5:
return str(x) + "!"
else:
return None
The overall signatures make sense, but the actual implementation doesn't follow "str maps to str and None maps to None"
When you use overloads, the typechecker trusts you that something like this won't happen
oh, I've met this one indeed 😄
is there a techincal reason for this why typecheckers just trust in that case instead of running type check on implementation using each of the overloads?
or they just didn't progressed to this point yet?
The point of overloads is to explain some complex conditional behavior that is not possible to express with ordinary constructs. If it was possible, you would use those other ordinary constructs
(in my example it's possible to use a MaybeStr = TypeVar("MaybeStr", str, None), but I'm not too sure)
but if it would type check something like this it would help to spot an error
def double(x: str) -> str:
if random.random() > 0.5:
return str(x) + "!"
else:
return None
This would be incorrect because you're returning str | None while the annotation says you're returning a str
I mean that in theory it could type check those two for issues and tell the user that both overloads are wrong.
Though no idea how it would work in terms of UI 😄
def double(x: str) -> str:
if random.random() > 0.5:
return str(x) + "!"
else:
return None
def double(x: None) -> None:
if random.random() > 0.5:
return str(x) + "!"
else:
return None
Here's a valid implementation: ```py
def double(x: str | None) -> str | None:
if double is None:
return None
else:
return x + x
in this case you can just replace it with a TypeVar as I mentioned
sorry, still don't understand why type checker wouldn't ensure that running implementation with any of the available overloads wouldn't produce type errors.
It essentially reduces to predicting the behaviour of arbitrary code
python/pyspark/sql/classic/dataframe.py lines 509 to 519
@overload
def repartition(self, numPartitions: int, *cols: "ColumnOrName") -> ParentDataFrame:
...
@overload
def repartition(self, *cols: "ColumnOrName") -> ParentDataFrame:
...
def repartition( # type: ignore[misc]
self, numPartitions: Union[int, "ColumnOrName"], *cols: "ColumnOrName"
) -> ParentDataFrame:```
It's also common to specify different mutually exclusive ways to call a method https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/translation/azure-ai-translation-text/azure/ai/translation/text/models/_models.py#L73-L91
I wonder if we should use Unpack[] for that
Well... this is a loving "abomination". I understand that a lot of Python functions just have complex interfaces
and 3.11 adds another argument :(
yeah, with Unpack[] we can at least avoid repeating the long list of parameters that don't affect the return type 🙂
Can someone please test this one in Python 3.12, does numpy array have a typing error for you too?
from typing_extensions import Buffer
class ClassA:
def accept_buffer2(self, buffer: Buffer):
pass
a = ClassA()
from array import array
import numpy as np
array_buffer = array("l", [1, 2, 3, 4, 5])
np_buffer = np.array([1, 2, 3, 4, 5], dtype=np.int32)
print(memoryview(array_buffer))
# typing issue
print(memoryview(np_buffer))
a.accept_buffer2(array_buffer)
# Argument of type "NDArray[signedinteger[_32Bit]]" cannot be assigned to parameter "buffer" of type "Buffer" in function "accept_buffer2"
# "ndarray[Any, dtype[signedinteger[_32Bit]]]" is incompatible with protocol "Buffer"
# "__buffer__" is not present
a.accept_buffer2(np_buffer)
numpy/__init__.pyi lines 1462 to 1463
if sys.version_info >= (3, 12):
def __buffer__(self, flags: int, /) -> memoryview: ...```
Are there any good convenience libraries that would allow the syntax require(foo).x.y such that if x could nominally be Bar | None it will be assumed to be non-null in the typing, such that .x evaluates to Bar and an exception is thrown during runtime if it is not?
should not be hard to make it yourself
I don't think you can make that yourself in a generic fashion
problem with it is you can't bind a type variable to "not none" and you cant negate None directly either
Yeah, it seems like it could be done in runtime fine, just not with correct typing
and you also want to return a wrapper around foo which "has" all of its attrs, but what return type is that?
I guess it would be more of a hypothetical macro, where the only thing that happened was that it was asserted that the segments parts would not be none
formally? It's a projection of the attributes of the input type when the input type has non-None attributes, and an error for any attributes which are None, which would require either HKTs and type negation, or algebraic effects to describe
Seems like it would need to be a language feature maybe
This kind of structure is often better handled by defining the non-null structure you want (often with something like msgspec or pydantic because this is usually incoming structured data being turned into a python object) and attempting to validate that the original object has the attributes you want with the types you expect
then after that validation, just use it
In this case the data is already modeled in pydantic, but later there are cases where I know that certain paths must be available - so having the ability to do something like foo.x!.y or foo.x?.y would be very convenient
im not sure what would hkts do without having some sort of attribute introspection, i think typescript has keyof, maybe something like that
!pep 505
deferred 😭
505 would be a nice solution for this kind of problem too
at the language level, you just know the resulting type might have short circuited to None
Thanks! Yeah, that's the one. Maybe another time 🙂
type NotNone[T] = T & Not[None]
type Require[T] = MapAttrs[T, NotNone]
maybe something like that, which needs a couple of things currently inexpressible indeed (Not, intersection, hkt's)
I'm kind of on the fence about this, because if you have foo?.bar?.baz?.quack and you get a None, you might get a pretty bad error, since it won't tell you which of these was a None
doesn't need to have any narrowing behavior in the type system at all.
foo?.bar?.baz?.quack
is just None | T where T is the type of foo.bar.baz.quack if foo is not None, bar is not None, and baz is not None.
Yeah I mean from the runtime perspective. You get a None... why?
it's hard to understand which one of them is actually none
the whole point of this is you don't actually care which was none if you're using that
and presumably any good use of that is followed by handling None
x = foo?.bar?.baz?.quack or default_x()
yep, it seems like the whole point of pep 505. wanna handle every is not None mannualy? just don't use ? then.
probably not actually using or but split across lines, but point is sometimes you just want the nested object if it exists, otherwise do something else
You could also have foo!.bar!.baz which would raise an exception on None so you could identify during runtime
I tried this in https://github.com/hauntsaninja/useful_types/pull/7 but couldn't get something to work in both mypy and pyright when Any was involved. Haven't been too motivated to try to fix it since at work we have our own version internally
That works for if you want to ensure foo isnt None (And breaks in the presence of Any as you said), but not for wrapping all of the attributes of foo
is there a way to type something like this?
# If limit is 1 return a single object
def something(limit: int = 1) -> str:
# If limit is >1 return a single object
def something(limit: int = 2) -> list[int]:
# Implementation
def something(limit: int = 2) -> int | list[int]:
assert limit >= 1
if limit == 1:
return "a"
else:
return ["a"]*limit
How would you call this function? Would you always know whether you have a limit of 1 or more than 1?
You could theoretically do this:
from typing import overload, Literal
@overload
def something(limit: Literal[1]) -> str: ...
@overload
def something(limit: Literal[2, 3, 4, 5, 6] = ...) -> list[str]: ...
@overload
def something(limit: int) -> str | list[str]: ...
def something(limit: int = 2) -> str | list[str]:
# implementation
``` but I would change the interface of this function. How it would change depends on how you're planning to use it
it's a function that queries a site and returns all the results with a limit parameter to avoid http 429
It's better to have 2 functions, get_one or get_all
APIs that auto unpack single length lists are the worst
Yep
thanks, i'll do just that then
Or just have one function and always return a list, even if it has 0 or 1 elements
If you know that you want one thing, you can do [thing] = get_things(1)
You can always do this anyway. ```py
result, = something(limit=1)
^ will fail if it returns an empty list
I wasn't certain how I want to make it but i guess that does give me some idea
thank you once again, appreciate the help
is there any way to indicate the range of a variable (i.e say you have an integer input that has to be between 0 and 255) with type hinting or anything similar to it?
i assume not
send me a site to do beginner programs and exercices
https://github.com/annotated-types/annotated-types has helpers for this but most static type checkers (except pyanalyze) will ignore them
Reusable constraint types to use with typing.Annotated - annotated-types/annotated-types
interesting
honestly ive started type hinting mainly just for the little highlight that IDEs have, i dont think ive used type checkers at all
does this seem alright for typehinting btw?
def rgb_to_ycbcr(r:float,g:float,b:float) -> tuple[float,float,float]:
"""
Converts a RGB color to a JPEG Y'CbCr color.
https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
"""
y = (0.299*r) + (0.587*g) + (0.114*b)
cb = 128 - (0.168736*r) - (0.331264*g) + (0.5*b)
cr = 128 + (0.5*r) - (0.418688*g) - (0.081312*b)
return y,cb,cr
def ycbcr_to_rgb(y:float,cb:float,cr:float) -> tuple[float,float,float]:
"""
Converts a JPEG Y'CbCr color to a RGB color.
https://en.wikipedia.org/wiki/YCbCr#JPEG_conversion
"""
r = y + 1.402 * (cr-128)
g = y - 0.344136 * (cb-128) - 0.714136 * (cr-128)
b = y + 1.772 * (cb-128)
return r,g,b
i guess i could hint the internal variables
you usually don't need to hint local variables, type checkers can infer them
kk
If you wanted to be fancy, you could do
RGBColor = NewType("RGBColor", tuple[float,float,float])
YCbCrColor = NewType("YCbCrColor", tuple[float,float,float])
def rgb_to_ycbcr(rgb: RGBColor) -> YCbCrColor:
oh neat
at some point i might just make a proper module for color conversions and have it all in classes but that's a neat one
metaprogramming, f"Literal[{', '.join(map(str, range(256)))}]" and put somewhere in the code as a typealias
i dont think it will give you any safety
it probably will cause more problems
It might work if you have a runtime type-checker actively validating input against the type annotations, e.g. typeguard or beartype or whatever, but yeah, a regular type-checker using just static analysis won't do anything with that.
whats turtle mean
tuple
tuples are like (1, 2, 3) (like lists but with () and immutable)
oh so like they cant be changed what not?
In the sense that you can't insert/remove/assign elements from tuples
!e
a = (1, 2)
a[0] = 3
:x: Your 3.12 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 2, in <module>
003 | a[0] = 3
004 | ~^^^
005 | TypeError: 'tuple' object does not support item assignment
!e
a = (1, 2)
del a[0]
:x: Your 3.12 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 2, in <module>
003 | del a[0]
004 | ~^^^
005 | TypeError: 'tuple' object doesn't support item deletion
!e
a_tuple = b_tuple = (1, 2)
a_tuple += (3, 4)
print("Tuples:", a_tuple, b_tuple)
a_list = b_list = [1, 2]
a_list += [3, 4]
print("Lists:", a_list, b_list)
:white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | Tuples: (1, 2, 3, 4) (1, 2)
002 | Lists: [1, 2, 3, 4] [1, 2, 3, 4]
Is there a way to make it work without defining TEST_VALUES = ("A", "B", "C") explicitly?
from typing import get_args, Literal, assert_type
TEST = Literal["A", "B", "C"]
TEST_VALUES = get_args(TEST)
# this would fix it
# TEST_VALUES = ("A", "B", "C")
def literal_compatible(a: str):
if a not in TEST_VALUES:
raise Exception
# "assert_type" mismatch: expected "Literal['A', 'B', 'C']" but received "str"
assert_type(a, TEST)
oh, I guess I can just
from typing import get_args, Literal, assert_type
TEST = Literal["A", "B", "C"]
TEST_VALUES: tuple[TEST, ...] = get_args(TEST)
def literal_compatible(a: str):
if a not in TEST_VALUES:
raise Exception
assert_type(a, TEST)
Why is a a str and not a TEST?
actually evaluating that expression and putting the result in source code works (python as a python preprocessor style deal) but the approach doesn't scale. you can't handle for isntance, cffi interfaces that take a uint64 with that.
mhm, a newtype wrapper works better i guess, with a constructor that validates the value
i dont know how to do that correctly in python though, in haskell i would make a module which exports the type but not the constructor (they're separate in it, and the constructors that are declared with the type are like dataclasses, just using the thing you gave it), and the constructor that i export would do the validation thingy
maybe just
class UInt64:
def __init__(self, value: int):
if fits_in_u64(value):
self.value = value
else:
raise ValueError
maybe do it in __new__
refinement types aren't really supported in python, and numerics being infinitely sized by default is an intentional design choice that dates back to pep237
you could use numpy's numeric types if you needed sizedness though
There's also the curiousness that some people would want overflow to wrap, some would want overflow to saturate, and some would want overflow to error, when looking at various uses for machine types, but that + doesn't give choices there.
make UInt64.add_{wrap, saturate, error} obviously
i think thats the rust approach
Could also have different derived types with differing behavior there
there's also widening as an option, which zig allows (As long as the assignment is to a type it could fit in)
Why is this not allowed? It works at runtime:
class Movie(TypedDict):
name: str
year: int
Movie([("name", "idk"), ("year", 2024)]) # misc - Expected keyword arguments, {...}, or dict(...) in TypedDict constructor
Missing feature, should probably be allowed
Movie(name="idk", year=2024) should work though
Yeah, so does passing a dict. I was just wondering if it's reserved for a future thing or something, or if I'm missing something obvious.
I guess since you have to pass all the keys as literals there isn't much reason to use the pairs-of-tuples approach
Why does pylint not like you hinting with functools.namedtuple but is fine with collections.namedtuple?
Non-working:
from functools import namedtuple
FileCoverage = namedtuple('FileCoverage', ['filename', 'vote', 'coverage', 'required_coverage'])
class ParseCoverageMessage:
...
@classmethod
def _get_warnings(cls, message: str) -> FileCoverage:
Vs collections.namedtuple working.
functools.namedtuple isn't where the object exists. It may exist by accident if functools.py happens to import it but you shouldn't use it
Ah, ok. Looks like it's owned by collections since functools just imports it to use it. I'm not sure I understand why pylint would be upset though. Either way, good to know! Thanks for the quick response.
https://github.com/python/cpython/blob/main/Lib/functools.py#L18
Lib/functools.py line 18
from collections import namedtuple```
pylance, not pylint. very different tools
I'd like to have a type marker for serialization, and the best type hint I've found is Runtime = Annotated[T, True] where T is a TypeVar. However in code the test field.type is Runtime is always false (field comes from dataclasses.fields()), and isinstance(field.type, Runtime) fails with TypeError. How may I test runtime that the type is the Runtime type?
The reason is that with from __future__ import annotations the .type attribute from fields contains str and not the actual type object
So now I need to figure out how to translate that str into actual objects
my logic of typing systems thinks u wish basically creating Generic class capable to hold as its element any T
Generic Struct/Class essentially
Serialized[T] essentially
The problem where not the "container" type Annotated[], but the implications of using from __future__ import annotations which alters how types are stored in dataclasses. I'm simply comparing the string and it works.
!d typing.get_type_hints
typing.get_type_hints(obj, globalns=None, localns=None, include_extras=False)```
Return a dictionary containing type hints for a function, method, module or class object.
This is often the same as `obj.__annotations__`, but this function makes the following changes to the annotations dictionary:
Hey so I have something like so:
class SomeTestClass:
TYPE_ATTR: type[SomeBaseClass]
@classmethod
@pytest.fixture
def foo(cls: type['SomeTestClass']) -> SomeBaseClass:
c = cls.TYPE_ATTR()
yield c
and I'm trying to figure out how to type hint this fixture such that i knows what the type of cls.TYPE_ATTR is as the return type, is this possible?
I think the following code with generics and TypeVars should work (btw foo's return type is a Generator if you yield):
from typing import Generator, Generic, TypeVar
T = TypeVar("T", bound=SomeBaseClass)
class SomeTestClass(Generic[T]):
TYPE_ATTR: type[T]
@classmethod
@pytest.fixture
def foo(cls) -> Generator[T, None, None]:
c = cls.TYPE_ATTR()
yield c
oh I didn't realize it was a generator, thanks! yeah I actually ended up doing something exactly like that. but I had to also add c: T = cls.TYPE_ATTR() to make pycharm happy
oh I don't need that, that was a separate problem actually, variable shadowing, but yes I think the bound typevar works. Thanks!
hmm question I just noticed. Whats the purpose of putting Generic[T] as a subclass?
to make SomeTestClass parametrizable, like list[T] but SomeTestClass[T]
in 3.12 it could be done as
class SomeTestClass[T: SomeBaseClass]:
...
more like in other langs with generics
I guess I'm a little confused about what that gets me, when I theoretically need to set the class variable TYPE_ATTR anyway, to create an instance of it in foo
I don't think I could just create an instance of T? rather than needing to set that attribute
it gets you a bound (by the SomeBaseClass type) type variable that you can use as a type in like the typehints inside the class
i also suppose you'd want TYPE_ATTR to be a classvar? or it is actually per instance
its a classvar yeah
but its defined in the subclasses
this is essentially a baseclass for other pytest test classes
and the subclasses will define the TYPE_ATTR so that the foo fixture returns an instance of what they define
mh, sadly classvars cannot have typevars, so ClassVar[type[T]] wont worky
weird limitation
ah interesting
so SomeTestClass is not meant to be used directly too?
i suppose it could have uh.. an abstract class method returning the type then, to play around the classvar limitation
that is,
from abc import ABC, abstractmethod
class SomeBaseClass:
...
class SomeTestClass[T: SomeBaseClass](ABC):
@classmethod
@abstractmethod
def TYPE_ATTR(cls) -> type[T]:
raise NotImplementedError
If you need an abstract classmethod, consider a metaclass
yeah typecheckers tend to work with that better
hmm I haven't looked too much into metaclasses before I'll need to do some diving
you just create a class class AMeta(type), then make another class with class A(metaclass=AMeta)
isinstance(A, AMeta) will be True
ok I've filled this in a bit more to give more context https://paste.pythondiscord.com/23SA
if that changes any answers/advice
using python 3.11 too so I don't think I can use some of the syntax you've suggested
hmm looking this up more, I don't think what I wan't is necessarily possible, but this seems fine for now.
It's sad that vscode doesn't fully support the 3.12 syntax yet.
what doesn't it support?
It's not that it fails to parse the new generic syntax. It just ignore it.
I read it was from an unmaintained upstream library they use for syntax highlighting.
Oh that might be true. The type checker (pyright) definitely supports the syntax fully
Yeah. It's just weird how the generics are the only part not highlihgted
microsoft/pylance-release#5824
^ you've commented on it
yeah what you said made me think that the new syntax was ignored in type checking, not syntax highlighting
especially since we're in #type-hinting 🙂
¯_(ツ)_/¯
This method works fine with a string as the only argument but it sells my static type checker that it shouldn't be a string. How can I fix it? Its from a package so idk how it works but I just want the type checking to not falsely go off
def from_conf(self, *args, **kwargs): # real signature unknown
"""
Construct a sender from a :ref:`configuration string <sender_conf>`.
The additional arguments are used to specify additional parameters
which are not present in the configuration string.
Note that any parameters already present in the configuration string
cannot be overridden.
"""
pass
Changing it to
def from_conf(self, *args: Any, **kwargs: Any) -> None: # real signature unknown
Didnt fix it so I think its getting overridden by something
Can you show the error from the type checker?
Where did you get this stub from?
and what typechecker gives you the error?
Is the error also coming from pycharm? Or from a mypy or pyright plugin?
its from pycharm's build-in "Incorrect type" inspection
then it seems like a pycharm bug
args / kwargs are so annoying
like seriously, the function does nothing but pass, why even write comments if you're going to make me turn into a detective to track down what it actually does
It doesn't do nothing but pass, that would mean it always returns None. It's the stub that pycharm generates when it can't find the code, or something like that (see the # real signature unknown)
It works fine though... I think I'm going to try debugging and stepping through
So I found the actual function, written in cython:
@staticmethod
def from_conf(
str conf_str,
*,
str bind_interface=None,
str username=None,
str password=None,
str token=None,
str token_x=None,
str token_y=None,
object auth_timeout=None, # default: 15000 milliseconds
object tls_verify=None, # default: True
object tls_ca=None, # default: TlsCa.WebpkiRoots
object tls_roots=None,
object max_buf_size=None, # 100 * 1024 * 1024 - 100MiB
object retry_timeout=None, # default: 10000 milliseconds
object request_min_throughput=None, # default: 100 * 1024 - 100KiB/s
object request_timeout=None,
object auto_flush=None, # Default True
object auto_flush_rows=None, # Default 75000 (HTTP) or 600 (TCP)
object auto_flush_bytes=None, # Default off
object auto_flush_interval=None, # Default 1000 milliseconds
object init_buf_size=None, # 64KiB
object max_name_len=None): # 127
We figured it out in https://discord.com/channels/267624335836053506/1257020579006124073
Is there some workaround for bytearray and memoryview popping in when trying to process just bytes?
from typing import IO, Union, Any
def extract(ifc_file: Union[IO[bytes], IO[str]]):
line = next(ifc_file)
reveal_type(line) # bytes
if isinstance(line, bytes):
line = line.decode("utf-8")
else:
# Type of "line" is "bytearray | memoryview | str"
reveal_type(line)
reveal_type(line) # str | bytearray | memoryview
# Cannot access attribute "startswith" for class "memoryview"
a = line.startswith("test")
I guess assert isinstance(line, str) will do as a workaround but doesn't looks pretty
which typechecker are you using
works fine in pyright
I use Pyright
maybe it's some older version problem, I'll check it out
can you please test it in your VS Code?
actually it does fail for pyright test.py too for some reason
>pyright test.py
test.py
test.py:5:17 - information: Type of "line" is "bytes | str"
test.py:10:21 - information: Type of "line" is "bytearray | memoryview | str"
test.py:12:17 - information: Type of "line" is "str | bytearray | memoryview"
test.py:13:14 - error: Cannot access attribute "startswith" for class "memoryview"
Attribute "startswith" is unknown (reportAttributeAccessIssue)
test.py:13:25 - error: Argument of type "Literal['test']" cannot be assigned to parameter "prefix" of type "ReadableBuffer | tuple[ReadableBuffer, ...]" in function "startswith"
Type "Literal['test']" is incompatible with type "ReadableBuffer | tuple[ReadableBuffer, ...]"
"Literal['test']" is incompatible with protocol "Buffer"
"__buffer__" is not present
"Literal['test']" is incompatible with "tuple[ReadableBuffer, ...]" (reportArgumentType)
2 errors, 0 warnings, 3 informations
🤔
pylance v2024.6.1
type checking mode strict
if i set typechecking mode to off then i do get this

doesnt work for off, basic, standard but works with strict
no idea how that works lol
🤨
can you reproduce
oh, you're right
it works fine in strict mode
but breaks in standard for me
hmm
no idea why you get those errors with typechecking = off
😄
actually can reproduce by disabling the "strict" in pyright playground
looks like a bug in pyright
if default value is false it seems it should break in both strict and standard modes
its true in strict
It's not as bug, it's correctly executing the wrong design 🙂
How can I get mypy happy here
https://mypy-play.net/?mypy=latest&python=3.12&gist=919104b663c16ec0fdc8df143f2d6327
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
It does not seem to like the overload, works just fine if you remove tht.
You need to use a "callable protocol".
If you define a Protocol class with just a __call__ method, that's treated the same as Callable, except this way you can specify keyword only arguments, generics, overloads, things like that.
from typing import Protocol
class MyFunc(Protocol):
@overload
def __call__(self, x: None = None) -> int:
...
@overload
def __call__(self, x: str) -> str:
...
Use that as the return value of foo().
Ah okay thanks :]
So I tried taking this example a little farther and, I crashed mypy xD
from typing import TYPE_CHECKING, Any, TypeVar, Self, Protocol, cast, overload
V = TypeVar("V")
class SimpleAttributeFunc(Protocol[V]):
# getter
@overload
def __call__(self, new: None = None, **kwargs: Any) -> V: ...
# setter
@overload
def __call__(self, new: V, **kwargs: Any) -> Self: ...
class Test:
@property
def color(self) -> SimpleAttributeFunc[str]:
@overload
def foo(new: None = None, **kwargs: Any) -> str: ...
@overload
def foo(new: str, **kwargs: Any) -> Self: ...
def foo(new: str | None = None, **kwargs):
if new is None:
return str(["hi"])
else:
print(f"Setting to {new} with {kwargs}")
return self
return foo
x: str = Test.color()
y: Test = Test.color("#FF00", recurse=True)
EDIT: I got it working https://gist.github.com/mypy-play/62fad45174312b2b1ec0c17e555d9ece
Would you mind sharing the stack trace on the issue you just opened? You may need to run mypy from master (so it's not mypyc-compiled) to get a more informative stacktrace
I did just that :)
https://github.com/python/mypy/issues/17474
no worries :) It was a really long stack trace lol - I still had to cut a lot of it out to fit in GitHubs requirements for maximum length
Is there a way for me to make my "own" Iterable type (list/set/tuple) which I can then use it likeMyIterable[int]
I'm trying to work around this issue https://github.com/pydantic/pydantic/issues/9541
What I'm thinking is to make my own union of list/set/tuple and then use that for typing various iterables
Yes, you need to make a generic class
from collections.abc import Iterator
class Triple[T]:
def __init__(self, a: T, b: T, c: T, /) -> None:
self.a = a
self.b = b
self.c = c
def __iter__(self) -> Iterator[T]:
yield self.a
yield self.b
yield self.c
Not sure how it will interact with Pydantic, maybe you need something else entirely
(you can't convert something into another type without knowing what that type is, so you'll need to write a converter somehow)
don't need to convert it really, all im doing is for i in x so Iterable would suffice but Iterable is currently broken in pydantic
looks like this worked?
from typing import TypeVar, Union, reveal_type
T = TypeVar("T")
Iterable = Union[list[T], set[T], tuple[T, ...]]
def test(i: Iterable[str]) -> None:
print(reveal_type(i))
test(["a", "b"])
test({"a", "b"})
test(("a", "b"))
although vscode added a blue underline so I'm doing something wrong?
❯ mypy .\test.py --strict
test.py:16: note: Revealed type is "Union[builtins.list[builtins.str], builtins.set[builtins.str], builtins.tuple[builtins.str, ...]]"
Success: no issues found in 1 source file
I don't understand what you're trying to do
Maybe you want Collection instead of Iterable?
Pydantic is a serialization library, it wouldn't make sense to use a lazy iterator inside a pydantic model
well, apparently pydantic thinks it does make sense and wraps the result with a lazy validator
why is that broken for you?
blue line is not an issue
it's just saying there is some information you can inspect there with intellisense
when you hover over it it will show you the type in this case
Pydantic is also validation library, it validates the input based on the type hints. In this case it's turning my collections.abc.Iterable to an iterator hence the bug as acknowledged by pydantic devs in the issue. So I'm trying to replace collections.abc.Iterable with something that still accepts the common iterables and see if pydantic works with that. Basically re-inventing collections.abc.Iterable so I can use it with pydantic
Well, the only thing you can do with an Iterable is iterate over it, at most once.
If you want something re-usable, you need a Collection, not an Iterable
I wasn't aware of Collection, lemme read up on it
!d collections.abc has information about these classes
Added in version 3.3: Formerly, this module was part of the collections module.
Source code: Lib/_collections_abc.py
This module provides abstract base classes that can be used to test whether a class provides a particular interface; for example, whether it is hashable or whether it is a mapping.
An issubclass() or isinstance() test for an interface works in one of three ways.
- A newly written class can inherit directly from one of the abstract base classes. The class must supply the required abstract methods. The remaining mixin methods come from inheritance and can be overridden if desired. Other methods may be added as needed:
Slightly related, where do the commonly used list/set/tuple fall in? Are they sequences? iterables? collections?
I find these a bit confusing so bear with me here
obj is an iterable if you can call iter(obj)
An iterable obj is a collection if you also call len(obj)
A collection obj is a sequence if you can also call obj[i] where i is in range(len(obj)), and has some methods described in the above page
So list and tuple are sequences, but set is only a collection
ah that clears somethings up, thank you
is it a gurantee that I can do for i in x for every instance of Collection?
Yes, because every collection is iterable
Is there a way to configure Ruff / Pylance to give a clear warning on cases with match foo: case Bar() as bar: where the evaluated type for bar is Never, i.e. when there is some issue with the code such that a match is performed that can never occur?
I have a class inheriting dict: py class MyDict(dict): ... and a Typehint defined:py class MyDictTypeHint(TypedDict): key1: int key2: str How do I typehint MyDict to MyDictTypeHint? I tried inheriting from both:```py
class MyDict(dict, MyDictTypehint):
...
pylance (via pyright) handles this under the rule "reportUneccessaryComparison", which I was surprised to see is only on in strict or with that rule specifically enabled
a typeddict class is already a dict subclass isnt it? you should just MyDict(MyDictTypeHint) (or, well, dont separate them, why would you)
here's the problem:
#1258718663780077610 message
Thanks! reportUnnecessaryComparison did the trick
Hey, what should I say here? because i can't put the class name
tbh I would use composition + MutableMapping to reduce boilerplate
Inheritance just feels like the wrong approach here.
Is there a way to make type hint for a db? i already made some kind of tree to represend my sqlite db, it was for another thing, but could I use that to tell my ide what var i am working with ?```py
class DBType(Enum):
TEXT = "TEXT"
JSON = "TEXT" # JSON is stored as text
INTEGER = "INTEGER"
BOOL = "INTEGER" # 0 or 1 but stored as integer
ID = "INTEGER" # ID is stored as integer
DATETIME = "DATETIME"
LIST = "TEXT" # List is stored as text
@dataclass
class Row:
name: str
type: DBType
def __str__(self) -> str:
return f"{self.name} {self.type.value}"
@dataclass
class Table:
name: str
rows: list[Row]
def __str__(self) -> str:
return f"CREATE TABLE IF NOT EXISTS {self.name} ({', '.join(str(row) for row in self.rows)[:-2]})"
Cause now, i a type Row, but that is not very specific
Try making Row a generic
Something like
D = TypeVar("D", bound=DBType)
class Row(typing.Generic[D]):
...
type: D
this looks sick but it will also means i will have to rewrite it
what does a generic do?
It allows you to parameterize it, like you can write list[int] because list is a generic
So in your case you could write like
list[Row[DBType.TEXT]] as a typehint
It was a choice I made
I just used the first letter in your enum because I was lazy and didn't feel like thinking
does mypy have an api i can use to do type checking manually
You can write a plugin: https://mypy.readthedocs.io/en/stable/extending_mypy.html
I have an example but don't quote me https://github.com/decorator-factory/mypy-plugin-attempt
ah neat, ty!
how do you hint that the argument should be a subclass?
class A: ...
class B(A): ...
class C(A): ...
def f(a: A) -> None: ...
f(A()) # ok
f(B()) # ok
f(C()) # ok
if you mean an actual class and not an instance of a subclass:
class A: ...
class B(A): ...
class C(A): ...
def f(a: type[A]) -> None: ...
f(A) # ok
f(B) # ok
f(C) # ok
what if the class is not implemented yet?
class Abc(object):
@classmethod
def foo(cls: Abc):
cls.subMethod()
return ...
what I did is the following
class Abc(object):
def subMethod():
raise NotImplementedError()
@classmethod
def foo(cls):
cls.subMethod()
return ...
If you want an abstract base class, then you can just use abc.ABC and abc.abstractmethod
in class methods, you should not annotate cls
noted
just like you don't annotate self in normal methods
btw, you don't need to inherit from object
ik
from abc import ABC, abstractmethod
class MyClass(ABC):
@classmethod
@abstractmethod
def sub_method(cls) -> str:
raise NotImplementedError
@classmethod
def foo(cls) -> int:
print(cls.sub_method())
return 42
I appreciate it
can I overload a abstractmethod?
from a subclass
You mean override?
Yes, that's the point of abstract methods
!d abc
Source code: Lib/abc.py
This module provides the infrastructure for defining abstract base classes (ABCs) in Python, as outlined in PEP 3119; see the PEP for why this was added to Python. (See also PEP 3141 and the numbers module regarding a type hierarchy for numbers based on ABCs.)
The collections module has some concrete classes that derive from ABCs; these can, of course, be further derived. In addition, the collections.abc submodule has some ABCs that can be used to test whether a class or instance provides a particular interface, for example, if it is hashable or if it is a mapping.
This module provides the metaclass ABCMeta for defining ABCs and a helper class ABC to alternatively define ABCs through inheritance:
I created a parent class called TagAbc, and made an overload for an abstract method (dump) when I create a child of the TagAbc the overload is not shown because the method has been overwritted by the child, how do I keep the sig of the overloaded dump method?
Can you show the code?
yeah sure
Seems like you should only put abstractmethod on the actual implementation https://github.com/python/mypy/issues/11488#issuecomment-966187657
isn't it possible to move the type-hinting to the sub class?
what do you mean?
I think you will need to repeat the overload in the child class
I think you shouldn't have a single method that's overloaded. Have a single dump method that accepts a file-like object
seems like it, I could create a private method to handle the functionlity and keep the overload in the subclasses
You can add a normal method to the base class that will handle the case when you just want the bytes:
def as_bytes(self) -> bytes:
file = io.BytesIO()
self.dump(file)
return file.getvalue()
``` (just like there's `json.dump` and `json.dumps`)
hm
Overloads are a pain and you should avoid them if possible
I will go drink something and look at this again, I feel frustrated
why?
I am not good at expressing but its hot and was coding for hours
back, and it sounds great.
one function to serialize and the other to write to a file (basically: dumps, dump)
def dump(self, data: io.BytesIO) -> int:
return data.write(self.dumps())
def dumps(self) -> bytes:
return self._type_.pack(self.value)
that's much cleaner now
Is get_type_hints is the correct way to get annotations if I want to use them at runtime or there is some other caveat?
The problem with __annotations__ that with from __future__ import annotations values of annotations dict become strings instead of types.
from __future__ import annotations
import typing
class A:
a: str
an = A.__annotations__['a']
# without future annotations:
# <class 'str'> <class 'type'>
# with future annotations:
# str <class 'str'>
print(an, type(an))
# always appears to be
# <class 'str'> <class 'type'>
an = typing.get_type_hints(A)['a']
print(an, type(an))
It is yes, since it will handle that for you, also doing a few other convenient things like unwrapping Annotated.
Q: I have defined this TypeMeta dataclass as a generic for the default parameter. Is there some way to define this such that if I call TypeMeta(size=2) it won't complain that I haven't set a type? I only need to set T when default is set such as TypeMeta[int](default=4). Would making this a frozen dataclass help?
T = TypeVar("T")
@dataclass
class TypeMeta[T]:
size: int = 1
default: T | None = None
like some kind of default type if [T] isn't set when creating a class
oh apparently there is a PEP that fixes this issue in python 3.13. https://peps.python.org/pep-0696/ such that T = TypeVar("T", default=int) # This means that if no type is specified T = int
backported in the third-party typing_extensions package* if you want it on Python <3.13!
*(Disclaimer: maintained by a bunch of people that includes me)
is it really third-party if it's maintained by the same people who maintain the first-party version
i can be in multiple parties at once
i'm that cool
oh very cool! I might just use that for now then, thanks!!
# setup typing for editor autocompletions
from typing import TypeVar
from another_file import class_type1
from another_file import class_type2
# create the types
reference_class_type1 = TypeVar('reference_class_type1', bound=class_type1)
reference_class_type2 = TypeVar('reference_class_type2', bound=class_type2)
class another_type():
# type these class variables as the class types
class_type1_instance: reference_class_type1
class_type2_instance: reference_class_type2
# now . completion will show the methods of the types as needed
# eg. when you type "class_type1_instance." at the "." the class methods appear
The above code gives myself editor completions correctly, it is what I want. I'm very new to typing though - is there a way to accomplish this same result with less code. I need the imports because the other classes are in other files, and I need the TypeVar's to bind the reference. Is there any way to combine those two items?
And I'm using Visual Studio Code with pylance.
And the class instance references for another_type are in the class variable scope, I'm using them to give units a reference to a unit manager. And setting them during initialization wherever needed.
It's not clear that you need the TypeVar. Why can't you use class_type_instance: class1?
I did what you said, works a charm. Thank you!
I'm using pyusb, which creates attributes based on the "external" information dictated by the USB spec. For example, for USB interfaces, it does this:
_set_attr(
desc,
self,
(
'bLength',
'bDescriptorType',
'bInterfaceNumber',
'bAlternateSetting',
# ...
Is there a brief way in pyright, or in general, to say that "yes, the bInterfaceNumber field exists on usb.Interface objects"? So far all I can think of is to use typing.Protocol, but having to do that for every different kind of USB "thing" I use is a lot of boilerplate.
(oh, I also know about # pyright: ignore [reportAttributeAccessIssue], but that is perpetually getting broken by black or ruff's reflowing)
unrelated question: how can I type check a hatch_build.py build hook? it uses build time dependencies, which pip installs into an ephemeral environment and removes
anyone familiar with using callable protocols for method definitions - unlike Callable, there isn't yet a working hack for knowing the difference between method descriptor invoked or not (i'm hitting https://github.com/python/mypy/issues/16200)
Optional static typing for Python. Contribute to python/mypy development by creating an account on GitHub.
correctly specifying the method descriptor is a utter pain
i arrived at
class HasApplication(Protocol):
@property
def application(self) -> Application: ...
R = TypeVar("R")
P = ParamSpec("P")
TM = TypeVar("TM", covariant=True)
HAS_APPLICATION = TypeVar("HAS_APPLICATION", bound=HasApplication, contravariant=True)
class ApplicationEntityMethod(Protocol, Generic[HAS_APPLICATION, P, TM]):
def __call__(protocol_self, self: HAS_APPLICATION, *k: P.args, **kw: P.kwargs) -> TM: ...
@overload
def __get__(
self, instance: HAS_APPLICATION, owner: type[HAS_APPLICATION]
) -> Callable[P, TM]: ...
@overload
def __get__(self, instance: None, owner: type[HAS_APPLICATION]) -> Self: ...
def __get__(
self, instance: HAS_APPLICATION | None, owner: type[HasApplication]
) -> Callable[P, TM] | Self: ...
it was most frustrating to figure that one - i wish there was a builtin
how would I typehint something like list[BaseClass] where the list contains subclasses of BaseClass?
right now I'm thinking something like
T = TypeVar("T", bound=BaseClass)
class MyC(Generic[T]):
attr: list[T]
but I'm not sure if that's a good idea nor if it will work
Cause in my case saying MyC[something] doesn't really make sense
probably like:
list[Base | SubA | SubB | ...]
If you just have list[Base], you're not allowed to do anything that would need one of the subclasses
well, unless you narrow it
class A: ...
class B:
def m(self): ...
def f(xs: list[A]):
for x in xs:
if isinstance(x, B):
x.m()
this is fine
Yeah, that's fine.
the benefit to spelling out the specific expected subclasses sometimes comes up if you would exclude the base that only exists for shared behavior or ADT-like class use
don't quite understand the ins and outs of type hinting... what does this mean
H and V are string objects
can someone help me. i want to make something but dont know what to type to make something in python
it doesn't mean anything in the standard type system
there must be from __future__ import annotations in that file?
because the str class is not subscriptable, which means it would have to be skipping evaluation in some way
Can you show the rest of your code? Do you type check it?
There's too many subclasses 😅
is there another (equivalent) data structure that's covariant (I'm guessing not)?
I guess I could always cast it 
fully equivalent? no, if you're already needing to do isinstance or only used shared behavior, you can just do list[Base], and then match out when you need to know specifics
collections.abc.Sequence is covariant, but that comes with potential problems by being covariant. If you can use it, it might be appropriate.
Well 99% of the time I don't need to know the specifics - I just need to be able to add something to the list
Like
def add(self, *subclasses: Base) -> None:
self.items: list[Base]
self.items.extend(subclasses)
The problem with Sequence is that it doesn't have the MutableSequence stuff 😅
yeah, that's why it can be covariant
well yeah but then you end up with issues like this not working
or should I just type: ignore these?
I'd probably go with list[Base] here. you can see up here #type-hinting message an example of still being able to narrow when needed (you'd probably want case match instead for a large number of classes though)
wait how would that even work from a typecheck perspective, shouldn't a list[A] only contain As?
no, it can contain subclasses of A
yes. that means that a list[Sub] isn't assignable to list[Super]. It does not mean the list can't contain instances of Sub at runtime
Another question:
I have some code (that I can't really change) like this
self.play(Square().animate.shift(UP))
where .animate returns an _AnimationBuilder that's a proxy for the Square. In order to get autocomplete for the methods of Square, I have the typehint of the property as
@property
def animate(self) -> _AnimationBuilder | Self: # Self for autocomplete
...
However, I only want the self.play call to accept _AnimationBuilders, which now isn't possible because of the union type of animate.
I also want it so that the users IDE would warn them if they do something like
self.play(Square())
so I can't just change the typehint of play to accept Square
Is there anything I can do? If not, what's the way you would suggest approaching typing this?
it sounds like _AnimationBuilder should be generic, so e.g. an _AnimationBuilder[Square] is a proxy for a square. However, the type system doesn't support general proxies that delegate all methods.
I'll try out the generics, do you know if there's a PEP or something I can hit like on for adding proxies?
thanks ❤️
Having some issues with pyright. My editor is just filled with errors that don't affect run time, ie: these errors do not prevent code from running or failing. They're picked up by pyright. One example is a module not being exported. Here's a snippet that would result in that error
# foo.__init__.py
from foo.bar import Baz as baz
Then in another project that uses the package foo
# test.py
import foo
with foo.baz(...): # error here, "baz is not exported from module foo"
# do stuff
I know that I can get rid of the error by using one of the following
# foo.__init__.py
from foo.bar import Baz
baz = Bazb
or
# foo.__init__.py
from foo.bar import Baz as baz
__all__ = [..., "baz", ...]
was able to reproduce your reportPrivateImportUsage warning by installing the package into a .venv and deleting the source - looks like the rules for whether an import is considered private is documented here:
https://microsoft.github.io/pyright/#/typed-libraries?id=library-interface
- Imported symbols are considered private by default. If they use the ... “from X import A as A” (a redundant symbol alias) ... symbol “A” is not private unless the name begins with an underscore.
...- A module can expose an
__all__symbol at the module level that provides a list of names that are considered part of the interface. ... All symbols included in the__all__list are considered public even if the other rules above would otherwise indicate that they were private.
pyright considered it a private member because you renamed the symbol, so you have to add it to__all__to explicitly make it public
if you're curious, mypy has similar rules with --no-implicit-reexport
https://mypy.readthedocs.io/en/stable/config_file.html#confval-implicit_reexport ```py
This won't re-export the value
from foo import bar
This will re-export it as bar and allow other modules to import it
from foo import bar as bar
This will also re-export bar
from foo import bar
all = ['bar']```
The re-export won’t apply for my example. It’s from foo.bar import Baz as baz. Notice the case.
Oh I misread this. So bc I renamed the variable from Baz to baz it’s considered private.
I don’t see the renaming referenced in the link though.
it's implied in the phase "redundant symbol alias" that renames don't count, but yeah it feels like both docs deserve a short note clarifying that
Kk. So I’ll have to add the all keyword.
I just don’t understand why my editor yells at me for all this whereas my teammates see zero errors.
They’re using PyCharm. Not sure what checkers / linters etc it uses. I assume it would use Pyright LSP but maybe not?
^ @weak oriole i believe pycharm uses an in-house type checker which supposedly isn't well known for its correctness
So my group works in an internal module that passes settings from some BaseSettings component.
We run into a case of needing to often transform Enums we define into their string name to do some magic later on.
We're a bit stuck on the proper type signature, and this is where we landed. Can anyone confirm if it's clear and good
from enum import Enum
from typing import Type, TypeVar
_T_Enum = TypeVar("_T_Enum", bound=Enum)
class BaseEnumSettings(BaseSettings, abc.ABC):
def str_to_enum(self, attr: str, cls: Type[_T_Enum]) -> None:
"""Transform an attribute into an enum value, if it is a string.
This assumes the attribute exists.
Args:
attr: Name of the attribute to update.
cls: Enum class used to create the enum value.
"""
...
It feels like overkill, I think it could just be cls: Type[Enum] but my colleague disagrees, and we can't find any good resources with clear opinions
This is an invalid use of a typevar
The correct way would be ```py
def str_to_enum(self, attr: str, cls: type[Enum]) -> None:
I don't know why type checkers don't show an error here, but here they do:
T = TypeVar("T")
def func(x: T) -> None:
# TypeVar "T" appears only once in generic function signature
# Use "object" instead
print(x)
class Foo(abc.ABC):
@abc.abstractmethod
def bar(self, value: T) -> None: # same diagnostic
print(value)
The purpose of a typevar is to link two types together:
T = TypeVar("T")
def choose(first: T, second: T) -> T:
return random.choice((first, second))
If it's used only once in a signature (and it's not one of the type variables in a Generic), that's not right
Thank you, this makes a lot of sense tome
can't you use the typevar in the body of the function tho?
why though
I'm annotating a list of callbacks that are invoked with named arguments (yeah, I know, but I can't change that yet). Mypy complains about Unexpected keyword argument "arg1" [call-arg]. The annotations have to support Python 3.8 (for now). Any tips to how to best fix this? (I'm far from a typing expert.)
a typing.Protocol with a __call__, perhaps?
from typing import Protocol
class Callback(Protocol):
def __call__(self, /, arg1: int) -> None:
...
def callback(arg1: int) -> None:
print(arg1)
callbacks: list[Callback] = [callback]
callbacks[0](arg1=42) # fine
Thanks, I'll give it a try!
@grand vault It should be *, not /
/ is the opposite, it means that self can only be a positional argument
well, yeah, i want the callable class to be able to name the first positional arg whatever, doesnt have to be self (i dont like enforcing conventions)
the arg1 will not match something like arg2 anyways, the * would be wanted if all the callbacks only accept it by keyword yeah
ah
Using Protocol was a nice solution; thanks again!
does the type checker relate to how imports are handeled? I just setup PyCharm and VSCode. PyCharm allows autocompletion of private modules whereas VSCode & Vim do not.
uhh i think autocompletion is more of the LSP's responsibility, i.e. pylance in vscode
doesnt look like pylance offers a setting for that
It looks like pycharm has some deep learning model that handles the auto complete (in addition to dependency stuff and hieristics i'm sure)
full line completeion is the name?
all 3 pycharm, vscode and vim (my setup at least) are all using pyright for lsp
the difference being pycharm adds more completions... I'm actually not sure at all, I turned off the full line completion stuff so just w/e is default then is still adding private modules such as those not exported and _method / __methods
ah right, pyright comes with a more basic lsp too
hmm, it seems to autocomplete private modules for me on vsc
oh it does?
oh sry, it does for me too but it's not including that module I listed above... the from foo.bar import Baz as baz, baz is shown as an autocomplete option with PyCharm but not VSCode or Vim.
Is the left in the same directory?
When I’m in the same directory as my package it autocompletes the baz. If I’m in a different project that imports the package it won’t see the baz.
hey chums, i've got a bit of a weird one for you. Is there a way for me to update a class's typehints from an inherited class in such a way that mypy will see them?
More concretely, i've got a class B that inherits from A. For reasons, i do some magic to make sure any of A's methods that are called from an instance of B do not actually call those A methods, just store that method call for later.
However, i've got an issue with MyPy complaining that methods on A which are type-hinted to return A are now actually returning B, so when i try to use B's methods on the returned object, MyPy thinks it's an A not a B. I'll reply with an example.
class A:
def some_fun_func(self) -> "A":
# do something fun
return self
class B(A):
# magic with __getattribute__ to store calls to A's methods
def cool_magic_trick(self) -> Self:
# does a neat trick
return self
b = A()
b.some_fun_func().cool_magic_trick() # <-- here's where MyPy doesn't like it
is there a way for me to tell mypy that actually it's totally cool for me to call .cool_magic_trick()?
class A:
def some_fun_func(self) -> B:
...
sorry, i forgot one other constraint—i actually don't control A, and would like B to update as A does without me having to do anything
there are currently about 20 methods that i'd need to update this way
If you don't control A, how does it know about B?
yeah, it doesn't. I was hoping i could just tell MyPy through some sort of dynamic manipulation that these inherited functions from A actually return B instead.
I think this is because the people maintaining A didn't use Self like i did up there, they actually used "A"
Well I mean, what makes it have those attributes?
i'm not sure i follow what you're asking. Do you mean the class B(A) line?
Oh wait, I didn't see the updated version
yeah sorry, i fixed it.
If you are certain that the method always returns a B, then maybe add
b = B()
b2 = b.some_fun_func()
assert isinstance(b2, B)
b2.cool_magic_trick()
(and contribute a fix to the original library 🙂 )
the extra wrinkle is that the code i'm writing for B is a library used by people other than myself, and MyPy would complain for all of them as well 😩
but yes i will submit a PR to Microsoft to fix their typehints in Playwright, haha
thanks for the suggestions and discussion @trim tangle !
i think typing.cast makes more sense if you're certain that it returns a B
That is also possible
a friend is suggesting i generate a stubfile for my B class that just changes the values in there
does this sound like a tree worth barking up?
this seems like it worked! It can be a bandaid until that PR gets accepted, merged, and published.
OK, new but slightly related—if i have a class C that inherits from A and B, but i'm overriding the typing for a method that only A has, how do i do that without mypy complaining?
The error mypy gives is override and seems to be complaining that one of the classes C inherits from does not have the method that i'm typing.
Mypy wouldn't complain about that. Not sure what your issue is but it's got to be something different
it did seem odd. I'll try to figure out if it's something i missed.
ah ha, i missed that this was a @property'd method, not a method. Problem fixed!
thanks for challenging my assumption @oblique urchin!
is there a generalised way to type hint 'iterable of strings other than str' nowadays?
Not a good one.
This is the best current option:
type CommonSeqStr = list[str] | tuple[str, ...]
type CommonIterableStr = Generator[str, Any, Any] | dict[str, Any] | set[str] | frozenset[str] | CommonSeqStr
there's ongoing current discussion about this on discourse: https://discuss.python.org/t/removing-sequence-str-as-base-class-of-str/56907
The typeshed builtins.pyi stub indicates that the str class derives from Sequence[str]. This is somewhat consistent with runtime behavior in that Sequence is a registered virtual base class for str (and hence isinstance(str, Sequence) evaluates to True), but it’s also somewhat inconsistent because Sequence is not a real base class of str and doe...
what are some of the bad ones? im still wrapping my head around TypeGuard, but was wondering if it's doable using that? my understanding is that it only works when the type guard is explicitly called inside a function, is that right?
ah ok thanks
if you're willing to use runtime things like typeguard, you can just do:
def takes_iterablestr_not_str(x: Iterable[str])
"""
...
Parameters
----------
x: Iterable[str] &~ str
note: due to limitations in python's type system, type checkers will not
catch this isn't meant to accept `str` instances, use a container of strs
"""
if isinstance(x, str):
raise TypeError(...)
# use as intended
since typeguard/typeis can't result in a denotable type for your user facing api either
i see, hmm this could work for my use case. there's basically two main public functions in the module, so that should be relatively ok i think. cheers.
I feel like I read something like this yesterday, but why doesn't pyright like this?
def testfn(arg: list[int | float | bool]) -> None:
pass
testfn([True, False])
It says "list[bool]" is incompatible with "list[int | float | bool]" Type parameter "_T@list" is invariant, but "bool" is not the same as "int | float | bool"
it should allow that because the below is fine:
def testfn(arg: list[int | float | bool]) -> None:
pass
x: list[int | float | bool] = [True, False]
testfn(x)
but this would require pyright to do more complicated things. (it does do those, see below)
That particular example is fine, because of a concept called bidirectional inference
(And pyright shows no errors for it)
However, if you pass a variable of type list[bool], you will get errors, because list is invariant. https://mypy.readthedocs.io/en/stable/common_issues.html#invariance-vs-covariance is a good explanation
You're right. Go figure that if I give a simplified example and don't test it I don't have problems.
I should have checked the example, I took the error at face value and thought for some reason pyright had reduced where it uses bidirectional inference
And because lists are mutable, right? So a fn that accepts lists with other stuff could append non-bool stuff to a list of bools.
yes, that's the general idea
Thanks. Context is I'm working on all the possible ways to provide keys to __getitem__ and __setitem__ for netCDF variables. 🙂 It's kind of awful because you're allowed do something like this for a 3D variable:
var[0, 2.0, "-1"] = 2
for _ in range(2):
v = 1
v # Unbound | Literal[1]
Shouldn't type checkers know this will always be bound?
That would require some special casing for range
I want to type hint a param which has these requirements:
- param must be subclass of
tkinter.Tk - it has a property called
app
How do i do it?
from typing import Protocol
from tkinter import Tk
class Thing(Tk, Protocol):
app: ... # whatever
maybe?
That would require intersection types, which unfortunately are not available yet
this is invalid because a protocol can't inherit from a non-protocol
I have alredy tried it aint works with type checkers
so its better to use type: ignore then
That's a strange requirement, why do you want this?
I am writing chat app where each sub major component of ui require refrence to the app so i just pass app to the child and mypy then says that it do not has attribute called app
app is like tkinter.Tk + app property
class App(tkinter.Tk):
@property
def app(self) -> Self: ...
Maybe you want some base class that inherits from Tk and has an app property, or something like thaT?
yah
Then the parameter annotation will just be App, or something like that
Guys, how can I make pylance understand generic types? So, I have a generic container called Collection that basically allows the user to pass either list or ndarray. However, pylance's type checker doesn't interpret list as a Collection.
Here's my code for Collection:
from typing import (Iterator, overload, Protocol, TypeVar, Type,
TypeAlias, Self, runtime_checkable)
T = TypeVar("T")
@runtime_checkable
class Collection(Protocol[T]): # pragma: no cover
def __len__(self) -> int:
...
def __iter__(self) -> Iterator[T]:
...
@overload
def __getitem__(self, idx: int) -> T:
...
@overload
def __getitem__(self, idx: slice) -> Self:
...
@overload
def __setitem__(self, idx: int, value: T) -> None:
...
@overload
def __setitem__(self, idx: slice, value: Self) -> None:
...
def __add__(self, other: Self) -> Self:
...
def __mul__(self, other: int) -> Self:
...
Before you ask, there is reason for making this generic container. Yes, I have tried all of collections.abc containers. No, none of them was a good fit for my usecase. Yes, I am aware I can just do a union over list and ndarray and call it a day but it's not sth I consider good practice.
Here is the error I am getting.
def _controlled_qubit_gate(self,
gate: Literal["I", "X", "Y", "Z", "H", "S", "T", "RX", "RY", "RZ"],
control_indices: int | Collection[int],
target_indices: int | Collection[int],
angle: float=0) -> None:
control_indices = [control_indices] if isinstance(control_indices, int) else control_indices # HERE
target_indices = [target_indices] if isinstance(target_indices, int) else target_indices # And HERE
# Define the gate mapping for the non-parameterized controlled gates
gate_mapping = {
"X": qml.PauliX(0).matrix(),
"Y": qml.PauliY(0).matrix(),
"Z": qml.PauliZ(0).matrix(),
"H": qml.Hadamard(0).matrix(),
"S": qml.S(0).matrix(),
"T": qml.T(0).matrix(),
"RX": qml.RX(angle, wires=0).matrix(),
"RY": qml.RY(angle, wires=0).matrix(),
"RZ": qml.RZ(angle, wires=0).matrix(),
}
# Apply the controlled gate controlled by all control indices to each target index
for target_index in target_indices:
self.circuit.append(
qml.ControlledQubitUnitary(
gate_mapping[gate],
control_wires=control_indices,
wires=target_index
)
)
And this is the error I am getting for the marked lines:
Expression of type "list[int] | Collection[int]" is incompatible with declared type "int | Collection[int]"PylancereportAssignmentType
Meaning it doesn't understand that list itself should be interpretted as Collection.
I would deeply appreciate some guidance regarding this.
It is a bit unfortunate, but it is technically possible for an int to also be a Collection[int]
Did I go at it the wrong way you think?
I would just accept control_indices: Collection[int]
if the user has just a single int, they can put square brackets around it
But the problem remains. Issue is that it doesn't understand list is a Collection.
It's not the int.
Ah I see
Wait, ignore my explanation here, it's not relevant
I think pylance does not like it when you redefine an argument with a different type
def f(x: int) -> None:
x = str(x)
I think my approach whilst starting on the right track is quite flawed. Firstly because if the user for instance passes a numpy array of ints, it's not actually int. It's np.int32 and so on. So, then you'd have to make a new type alias for int itself. Feels like a weird thing to do.
Am I going about the generalization the wrong way?
Why though?
Casting is a very standard thing.
I don't know, ask Pylance developers 🙂
So, what should I do now?
You can rename the variable to control_indices2 or something more creative
I mean, I can't get any better than just control_indices.
Wait, so now I have to rename my vars?
The argument name is not very accurate then. 42 is not indi_ces_
There should be a better way, no?
The part about an object being both int and a Collection[int] is not relevant
As a library user/maintainer, I'd much prefer a simpler interface, even if it means I have to write [42] instead of 42
I mean the whole point of allowing int is to make it easier for the user.
Also, I wanted to be smart about this and not do a ton of overloads.
[42] is not harder to do than 42
but the parameter is indi_ces_
Yeah because I allow for both int and collection of int.
I don't know a better way of saying index/indices.
The harder thing, in my view, is understanding a very flexible function interface. Later you'll add more cases later and end up with a monster like this
https://github.com/aio-libs/aiohttp/blob/master/aiohttp/typedefs.py#L41-L49
If a function accepts a dict[str, BaseCookie[str]], then you immediately know how to call it. If it accepts LooseCookies... you'll need to read the type alias definition and figure out what you could have
aiohttp/typedefs.py lines 41 to 49
LooseCookiesMappings = Mapping[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]]
LooseCookiesIterables = Iterable[
Tuple[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]]
]
LooseCookies = Union[
LooseCookiesMappings,
LooseCookiesIterables,
"BaseCookie[str]",
]```
Argument of type "list[int]" cannot be assigned to parameter "qubit_indices" of type "int | Collection[int]" in function "Identity"
Even with just list it doesn't work.
Can you show the code?
If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/
After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.
you need to make the parameters in __getitem__ and other dunders positional-only, probably
Why?
Is there a rule in pylance for retyping a variable?
I can just turn that off.
The protocol says: it should be legal to call the_thing.__getitem__(idx=0)
It's erasing the problem, which I hate more than anything...
It is.
I allow for both int and slice.
But this call won't work if the_thing is a list, its __getitem__ only accepts a positional argument
Is it maybe it needing T = TypeVar("T")?
hm?
Doesn't seem so. You could start a discussion on https://github.com/microsoft/pyright/discussions, perhaps. It's probably just an opinionated rule
I believe I tested my Collection before and it would pass for both list and ndarray.
How did you test it?
x: Collection = MyCls()
this is probably the simplest way
Yes, and pyright doesn't like it #type-hinting message
I think I can narrow my question a bit more. How can I have pylance interpret list[int] as a Collection[int]?
Alternatively, and I hate to say this, I could make Collection a type alias for list and ndarray? For now at least.
Would that be just
Collection: TypeAlias = List[T] | # NDArray type? I tried NDArray but it didn't accept it. Also tried np.ndarray, same issue
Mark the parameters in dunders as positional-only
Let me try it, one moment...
i think the problem is list.getitem doesn't return Self for a slice
(on a phone so can't check)
stdlib/builtins.pyi line 1026
def __getitem__(self, s: slice, /) -> list[_T]: ...```
Like so?
T = TypeVar("T")
@runtime_checkable
class Collection(Protocol[T]): # pragma: no cover
def __len__(self) -> int:
...
def __iter__(self) -> Iterator[T]:
...
@overload
def __getitem__(self, idx: int, /) -> T:
...
@overload
def __getitem__(self, idx: slice, /) -> Self:
...
@overload
def __setitem__(self, idx: int, value: T, /) -> None:
...
@overload
def __setitem__(self, idx: slice, value: Self, /) -> None:
...
def __add__(self, other: Self, /) -> Self:
...
def __mul__(self, other: int, /) -> Self:
...
It removed the errors, but I don't understand why.
If you're wondering why on earth I added / to all of the methods, the errors didn't go away with just __getitem__.
I'll check sir, one moment...
Ohh I missed that, my apologies.
But wouldn't that be same as Self?
It's a list, and __getitem__ with slice is returning a list.
with a protocol, you're allowed to only specify the parts of the protocol you personally use and care about, being more permissive that other behaviors exist too
if you dont index with a slice, you don't need to specify the interface you expect when that happens
Yeah, I need all of these. At a glance it may not seem like it since I'm using them as index containers, but with other parts of the code it becomes needed.
ah, then yeah, you've got more to specify here
I do.
control_indices[:]
if slice was genenric, you could model only that case.
Still this error remains:
Argument of type "list[int] | Collection[int]" cannot be assigned to parameter "qargs" of type "Sequence[QubitSpecifier] | None" in function "append"PylancereportArgumentType
For this line
# Apply the controlled gate controlled by all control indices to each target index
for target_index in target_indices:
self.circuit.append(gate_mapping[gate], control_indices[:] + [target_index])
Which is odd given this method takes lists and ndarrays, so why not Collection?
A Collection is a Sequence.
I mean a Collection can be treated as a Sequence but a Sequence can't be treated as a Collection.
not for subclasses of list
Sequence has fewer methods supported iirc.
So, what would be the correct thing to put there given I'm using it as a generic type?
there's a lot of missing capabilties people want when it comes to structural typing, you might be able to do:
type alias = Sequence[int] | MutableSequence[int] | numpy.typing.NDArray[Any]
might also be able to just use numpy.typing.ArrayLike
!e
import numpy as np
from numpy.typing import ArrayLike
a = [1, 2, 3]
b = np.array(a)
print(isinstance(a, ArrayLike))
print(isinstance(b, ArrayLike))
:x: Your 3.12 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 7, in <module>
003 | print(isinstance(a, ArrayLike))
004 | ^^^^^^^^^^^^^^^^^^^^^^^^
005 | File "/lang/python/default/lib/python3.12/typing.py", line 1564, in __instancecheck__
006 | return self.__subclasscheck__(type(obj))
007 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
008 | File "/lang/python/default/lib/python3.12/typing.py", line 1568, in __subclasscheck__
009 | if issubclass(cls, arg):
010 | ^^^^^^^^^^^^^^^^^^^^
... (truncated - too many lines)
Full output: https://paste.pythondiscord.com/4FB4VPYE3767OKJC6RLOU3YAAM
TypeError: Subscripted generics cannot be used with class and instance checks
it's not a runtime checkable option here
Right.
What's the proper way of doing what I'm trying to do? Doing union of a bunch of types feels weird.
I feel duck typing is the way to go, but as you can see it's still not working.
If you need it to be runtime checkable, you may want to check out https://github.com/beartype/numerary/
I personally would not reccomend runtime checking for this for a multitude of reasons involing some currently existing soundness holes, but there are some libraries that exist to try and make numeric types work well with typing
I feel if I want to do runtime checking I can just do with my Collection.
I honestly still don't understand why it's not interpreting a list as a Collection.
the methods of list[T] don't return Self, they return list[T]
Right, it's returning the same type as itself.
Am I misunderstanding it?
If you have this:
class A:
def foo(self) -> Self:
...
class B(A):
...
it means that B().foo() returns B, but here: ```py
class A:
def foo(self) -> "A":
...
class B(A):
...
``` B().foo() returns A
If you subclass a plain list and get a slice out of an instance of your subclass, you will get a plain list, not an instance of your subclass
Hmm
So, I can't use the generic container with what you're saying, since if I set it to list, then it's gonna have issues with NDArray, and vice versa.
In my head, I understood it as list return a subset list, and ndarray returns a subset ndarray.
Hence Self.
You can set the return type of __getitem__-with-slice to Collection[T]
Isn't that what Self is doing?
Self is not just a shortcut to "the class where the method source code is", it's like a type variable:
S = TypeVar("S", bound="A")
class A:
def foo(self: S) -> S:
...
Look at this example again
I see.
I got this one, still trying to extrapolate it though.
So like this?
T = TypeVar("T")
@runtime_checkable
class Collection(Protocol[T]): # pragma: no cover
def __len__(self) -> int:
...
def __iter__(self) -> Iterator[T]:
...
@overload
def __getitem__(self, idx: int, /) -> T:
...
@overload
def __getitem__(self, idx: slice, /) -> Collection[T]:
...
@overload
def __setitem__(self, idx: int, value: T, /) -> None:
...
@overload
def __setitem__(self, idx: slice, value: Collection[T], /) -> None:
...
def __add__(self, other: Collection[T], /) -> Collection[T]:
...
def __mul__(self, other: int, /) -> Collection[T]:
...
Yes
Hmmm
Actually, __add__ might require Self
I am really sorry for being slow.
Why?
Isn't it following the same logic?
You can only add a list to a list, not an arbitrary collection to a list
So maybe ```py
def add(self, other: Self, /) -> Collection[T]:
But, uh, hmmm...
If you don't use list addition, I'd leave it out. binops are not easy to type properly, and in this particular case, it's actually impossible to fully express in the type system
I use it.
But how do you add two things if you don't know if both of them are lists?
Do you have an example of where you use it?
lists also add differently from numpy arrays, if you are accepting both of those 🤔
oh yeah
One moment...
the method signature would fit, but not the meaning
like, straight up different results here, numpy arrays add element wise, lists create a new list that's one concatenated with the other
for target_index in target_indices:
self.circuit.append(gate_mapping[gate], control_indices[:] + [target_index])
Full code
def _controlled_qubit_gate(self,
gate: Literal["I", "X", "Y", "Z", "H", "S", "T", "RX", "RY", "RZ"],
control_indices: int | Collection[int],
target_indices: int | Collection[int],
angle: float=0) -> None:
control_indices = [control_indices] if isinstance(control_indices, int) else control_indices
target_indices = [target_indices] if isinstance(target_indices, int) else target_indices
# Define the gate mapping for the non-parameterized controlled gates
gate_mapping = {
"X": XGate().control(len(control_indices)),
"Y": YGate().control(len(control_indices)),
"Z": ZGate().control(len(control_indices)),
"H": HGate().control(len(control_indices)),
"S": SGate().control(len(control_indices)),
"T": TGate().control(len(control_indices)),
"RX": RXGate(angle).control(len(control_indices)),
"RY": RYGate(angle).control(len(control_indices)),
"RZ": RZGate(angle).control(len(control_indices))
}
# Apply the controlled gate controlled by all control indices to each target index
for target_index in target_indices:
self.circuit.append(gate_mapping[gate], control_indices[:] + [target_index])
What semantics are you expecting from an __add__ on your collection?
Append.
then you can't accept numpy arrays
!e
import numpy as np
xs = np.arange(6).reshape(3, 2)
print(xs)
print(xs + [42])
:white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | [[0 1]
002 | [2 3]
003 | [4 5]]
004 | [[42 43]
005 | [44 45]
006 | [46 47]]
I see.
Actually, why does numpy allow this 🤔
It's mostly for tensor operations.
Not for container representation.
Yeah I mean, adding a 3x2 with a 1x1
broadcasting addition is fine, I actually have more of an issue with python having list addtion
1x1 is considered a scalar if I understand correctly.
Try with 1x4.
ah, it treats a 1x1 as a scalar
which saves you for needing to do .item on certain reductions
So, I can't pass ndarrays to begin with...
not if you expect addition to be concatenation, no
Should I make a class to represent indices?
Everything would be so much simpler if you accepted a single concrete type 😛
And define dunders there?
I know, but feels very noob-ish to just type hint as list instead of a general container.
Although, if I can't pass numpy directly then there's no point anyways, I could just use sth from collections.abc.
You could accept an ndarray if you indent to work with those
Issue with collections.abc was it would never fit both.
you could also just not use + for this and isntead do the concatenation/alternative indexing yourself, based on what types you have, but you're increasing the amount of work you're doing
I mean, that's not the issue anymore is it? It's that I completely forgot that __add__ in numpy does element wise addition not concatenation.
You can still append to it in other ways
Should I just make a class called IndexSet and just convert list and ndarray there to a IndexSet instance and define dunders for it?
rather than +, you can do : [*control_indices, target_index]
if you really want to retain the capability you have on that line
Lemme try it, one moment...(though pylance is complaining for unpacking as well)
I mean yeah, otherwise the code won't work.
Actually, + on list is pretty stinky, considering that a + b is already expressible as [*a, *b]
Also it allows people to do atrocities like sum(lists, start=[])
yeah, I'm not a fan of + being concatenate, but we'd need a python4 to fix that and some other things, let alone agreement on what the things justifying a python4 would even be.... it wont happen
?
I can't change python, but I can make a class Hehehe.
It'd be generic enough, but also a concrete class.
Actually no, then people would have to use my container to make lists.
Do you have the project on github somewhere?
I'm not sure what you're even using this Collection for
It's private, I can make it public,
Literally a generic container.
I just wanted to allow people to pass list or ndarray.
Or anything that behaves like that.
Let me push the changes and make it public, one moment...
You don't have to use the same protocol every time you want something that smells like a list. In this function you only really need a collections.abc.Collection, since you need to iterate over it and get its len. So use that for the parameter
I need __len__, __getitem__, __setitem__, __contains__, and __iter__.
Not for this method
Other methods will call these methods, and they need the additional methods. I tried it before, it didn't work because it's quite interconnected, so I need the smallest common denominator.
And that needs those dunders.
Is there more code in this method?
In this specific one no, I sent the full method.
Would you like to see the full codebase?
sure
Ok, one moment...
If you require so many specific operations with specific semantics (e.g. that __add__ is concatenation), then you don't really gain anything from generality. You can use this class with a list or with a custom class that has exactly the same interface as list (at which point maybe you just want a list anyway)
Just gonna run pytest to ensure I didn't break anything, and I'll push to the repo and make it public and send the link here.
Personally, as a noob, I like to just use list. However, from other packages that I use I've seen they never type hint it as a concrete class. They have very generic types which I've seen allows for ndarray and list to be passed. I just wanted to have that in my package as well.
It's good to program against an interface and not an implementation when you can plausibly have several implementations for an interface. Most of the time you want to do some limited operations on a collection. If you just want to iterate over it, you can accept Iterable, and your code will work with list, tuple, range, map, generators and so on.
But if you some specific needs, it's alright to accept a concrete type. You don't have a protocol for Literal["I", "X", ...] or int or float, right?
What you could do is accept any collection/iterable in your class's __init__ (assuming this is where you get them from), and then convert them to list for use by the class
Can you kindly clone the repo and let me know so I can make it private again please?
Why do you want to make it private?
Right.
Competitors.
And that I don't like to put unfinished code on my organization repo.
So you're never going to release this code?
I am when it's done for free.
Well, big projects are never really done
True, but still haven't reached the MVP stage.
You should see the backlog of tasks on my SCRUM board.
I still haven't added proper backend support, still adding additional support for other packages, fixing a bug with Mottonen, and figuring out the Bra-Ket interface.
It's currently a very tiny percentage of what I aim for as the alpha release.
By the time I'm done, it'll likely be the new go-to SDK for gate-based quantum computing.
Did you clone it?
Two things you'll need. One, open it in WSL remote window (I use vscode). This is because cuda-quantum only works on Ubuntu. Two, use python 3.11.9, that's the only one that's compatible for making the venv.
Apologies if this already came up during the convo, but are the places that you’d like to use your protocol exposed to users, or is this all internal?
Sinbad can you please clone it as well?
I mean, users would know they can pass anything like a list or an ndarray.
But right now as explained above, I have not done this, so it needs fixing, or rather complete rewriting.
Gotcha.
@trim tangle May I ask if you cloned it?
I could, but I'm unlikely to spend more time on this in terms of specific reccomendations, the general ones I'd give here:
- Start with determining what interfaces you need (how you use it)
- determine if making it generic actually allows more types
- determine if expressing what is neccessary is possible in a way that allows more than just a union of a few types
type hint appropriately from there.
there's an implicit 0. there of just start with specific types you will use it with before generalizing it for others
if you want to
Did you clone it?
I think I've looked at the relevant bits
I would choose one of these options:
- Accept a concrete type (like
list) - Accept a very general type like
Iterableat interface boundaries, and then turn it into alistfor actual use (if you need to mutate it and such. actually mutating a parameter is pretty stinky, so don't do that) - Accept a very general type like
CollectionorSequenceand then only do operations on it that you know you can do. You can do[*collection, new_element]instead of using+andlist(collection) * 3instead of using*on the object directly
Did you see the Circuit class?
If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/
After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.
Sorry, had to delete the methods at the end (you don't need them) to make it fit.
So, have a look at these two methods:
.vertical_reverse().horizontal_reverse()
I believe these are instances of where I mutate sth.
So, the indices are changed (which are of type Collection).
Yes, you mutate some internal state of the object. The user shouldn't care whether it's an ndarray or a list or some quantum magic
Or the angles (which can be of type Collection[float),
Ok, but here's my point. If I pass sth that doesn't support __setitem__ for instance, then this won't work.
There's more methods than I can remember to be frank, spread across the codebase. I've noticed that they need all of the ones I mentioned before to work, so I can't go with anything from collections.abc for a generic interface. That would mean it would only really let list or MutableSequence for instance to pass.
How does the user of the library add some collection to the circuit? With the add method?
Great question. Whenever users call a gate method (like X, Y, Z, etc.) these are recorded in the self.circuit_log. The way this is done is by simply recording the variables passed. If the indices are of a type that can't be mutated for instance, then when .vertical_reverse() is called it's gonna scream.
You can see this in gatemethod which is a static method at the top.
All the nasty changes, checks, etc., are inside this static method. However, I don't change the type from ndarray to list, or from list to ndarray, so if you pass sth that doesn't support all of the dunders I mentioned, it'll break then.
I need __len__, __getitem__, and __iter__ and even __add__ in some cases for gate methods themselves. Afterwards when I call sth like .vertical_reverse() I need __setitem__.
With .horizontal_reverse() I need __mul__.
So, overall I need all of those dunders for the entirety of the codebase to run correctly.
Mutating parameters is generally a bad idea. It's very surprising behaviour, especially if it happens far into the future. It's like quantum entanglement
If you want to mutate some data, you should save a copy of it, not the original one
And in that case you can accept any iterable/sequence/collection and just turn it into a list before saving it
I mean how would you do .vertical_reverse()?
So, in gatemethod you want me to strictly make everything into a list?
Yes, for example
You're already converting range to a list
Wouldn't that make my gate methods become list only too (type hint I mean) given how decorators affect the signature of the decorated?
True.
I'm not sure what you mean
Instead of int | Collection[int] it would be list[int].
So if someone hovers over the method to see what it is they'll think it has to be a list.
Which basically makes all of this just useless.
You accept a Collection[int] (or Iterable[int], or Collection[int] | int) from the user. Then you convert it to a list for internal use.
Yes, but when I convert it inside gatemethod, then what I'm passing to gate methods (like X, Y , Z) in reality is just list not int | Collection[int].
At that point, I should just change my Collection to MutableSequence, and kiss ndarray support goodbye.
In that case yes, seems fine
Other way is to do overloads, like what TKET does (qiskit uses generics as far as I can tell) which is veryyyy messy to read.
Or you can convert everything to ndarray. I have no idea about the domain, so it's up to you
Well then when you hover it's gonna tell the user to pass a list.
You don't need to know anything about quantum.
FWIW it's just a generic type for mutable containers.
You can see everything now, I only need the dunders I mentioned.
Preferably I'd like to support as many interfaces as possible.
Yes, that's unfortunate. There's no way to express argument conversions
You could use a generic type all the way until you actually save it. And then the thing you save will always be converted to a list
So, choices are:
- Make conversion to list internally (this effectively causes the gate method documentation to be purely list[int] for indices which is sth I'm trying to avoid with inclusion of
intto begin with) - Change to
int | MutableSequence[int]but ndarray can't be supported anymore. - Perform the conversion within the gate methods, which seems messy and would appear across all subclasses of
Circuit, but in turn would allow a flexible type hint with the mentioned generic.
Third one seems like the best out of the bunch, huh...
Third one is an option, yes
It would allow me to keep int | Collection[int], pass ndarray if I want, and perform the casting to list internally.
At the cost of a few honestly dirty lines.
I don't think accepting a list from a user and then mutating it later is a good idea. It's very surprising when someone else touches your data in the future. (or if the user mutates the list, and now things inside your Circuit class break)
Actually, hmm...pylance would technically still scream at me since it doesn't interpret list nor ndarray as a Collection.
User should do
circuit.X(0)
# Or
circuit.X([0, 1])
# Or
circuit.X(np.array([0, 1]))
But why not circuit.X((0, 1))?
Because I didn't think of it?
well my point was, you don't need to require it to be mutable, if you want to mutate something, make a copy
Technically it'd be allowed.
That is IF I cast it to list internally.
Or make a copy like you said and set that to my var internally.
Wait...
F*ck...
If I make the casting internally, the data recorded is still what the user passes (I'm recording the signature and param values that user passes).
Which means I have to do it in gatemethod staticmethod one which would again make the type hint all wrong.
Goddamit.
user passes values -> values go through decorator first, where decorator has to convert them to list -> which forces the gate methods themselves to be type hinted as lists only as well in turn.
Alternatively
user passes values -> values go through decorator first, where decorator makes a copy and records that -> this allows the gate methods to be int or collection int, but would need another copy and cast inside the method definition
So, second one would need to repeat the conversion and in return would allow me to keep the signatures like I want it (int or collection int, or whatever generic container, but allowing int).
Seems like an ok deal?
It would be a justified duplication as it pays back with a simpler user interface.
What do you think?
Am I getting colder?
Ok, so I think this would make some sense:
- Type hint the indices as
int | Collection[int]. This needs some fixing since pylance doesn't accept list as aCollection. - Copy the indices value in the decorator, and convert to list and record that for
self.circuit_log, but let the non-converted value go to the decorated method. - Redo the conversion inside the decorated method, and always write the dunder uses assuming a list is being used.
Oh my god, I killed him with my stupidity!
wdym pylance doesn't accept list as a Collection?
no error here
Wrong Collection
The PT is asking for a custom class called Collection
rlymad
I wanted to provide a generic container compatible with both list and ndarray. Nothing from collections.abc fit both, so I had to make my own.
Issue now is that I neglected to remember that ndarray and list have different dunders, so I'd have to cast to list internally, and was showing fix error my ideas.
I'd appreciate feedback on that.
This is how qiskit has type hinted it.
I'm going to see what the type of Qubit is.
This is the src for Qubit and QuantumRegister (the latter is usually how you would express a container of qubits).
class Qubit(Bit):
"""Implement a quantum bit."""
__slots__ = ()
def __init__(self, register=None, index=None):
"""Creates a qubit.
Args:
register (QuantumRegister): Optional. A quantum register containing the bit.
index (int): Optional. The index of the bit in its containing register.
Raises:
CircuitError: if the provided register is not a valid :class:`QuantumRegister`
"""
if register is None or isinstance(register, QuantumRegister):
super().__init__(register, index)
else:
raise CircuitError(
"Qubit needs a QuantumRegister and %s was provided" % type(register).__name__
)
class QuantumRegister(Register):
"""Implement a quantum register."""
# Counter for the number of instances in this class.
instances_counter = itertools.count()
# Prefix to use for auto naming.
prefix = "q"
bit_type = Qubit
Here's Bit and Register.
https://paste.pythondiscord.com/I7LA
@trim tangle So, qiskit has done it like you said (a concrete class). But it's their custom concrete class.
So, pylance screams at me when I pass ndarray to qiskit (saying NDArray is not a match for QubitSpecifier, however, it works if I run it).
So, not sure how to think about that.
Either pylance doesn't understand that it needs to look at the interface of the container passed (here it's ndarray) to assess the situation, or it's just being nit picky.
Likely the prior.
TKET just uses Sequence with lots of overloaded options.
(method)
def add_gate(
Op: Op,
args: Sequence[int],
**kwargs: Any
) -> Circuit: ...
def add_gate(
Op: Op,
args: Sequence[UnitID],
**kwargs: Any
) -> Circuit: ...
def add_gate(
type: OpType,
args: Sequence[int],
**kwargs: Any
) -> Circuit: ...
def add_gate(
type: OpType,
args: Sequence[UnitID],
**kwargs: Any
) -> Circuit: ...
def add_gate(
type: OpType,
angle: Expr | float,
args: Sequence[int],
**kwargs: Any
) -> Circuit: ...
def add_gate(
type: OpType,
angle: Expr | float,
args: Sequence[UnitID],
**kwargs: Any
) -> Circuit: ...
def add_gate(
type: OpType,
angles: Sequence[Expr | float],
args: Sequence[int],
**kwargs: Any
) -> Circuit: ...
def add_gate(
type: OpType,
angles: Sequence[Expr | float],
args: Sequence[UnitID],
**kwargs: Any
) -> Circuit: ...
So the indices are either Sequence[int], or Sequence[UnitID].
So, I think this could be a good solution. In my decorator, I record the params right? And only the recorded params need to be iterable, not the actual params of the decorated.
What I can do is to convert any container to a list and record that, but only pass a Sequence (from collections.abc) to the decorated.
What do you guys think?
Although, I don't know how to ensure what's being sent to the decorated (from the decorator part) would be a Sequence. If I indiscriminately cast containers to list, then isn't pylance going to scream at me saying it wants a list?
Only the param sent to self.circuit_log needs to always be a list. The param sent to the decorated just needs to be a Sequence (I would need __len__, __getitem__, and __iter__ as far as I can tell).
hmm, why does it use args with no *?
Bad naming I suppose. It's just the qubit indices.
Does anybody know why mypy rejects this?
from numbers import Real
def foo(a: Real, b: Real, c: float) -> Real:
return c * (a - b)
saying:
main.py:4: error: Incompatible return value type (got "float", expected "Real") [return-value]
main.py:4: error: Unsupported operand types for * ("float" and "_ComplexLike") [operator]
Why is float not compatible with Real?
Why is the subtraction of two Reals a _ComplexLike?
Why can’t a _ComplexLike be multiplied by a float?
is there something wrong here?
from typing import reveal_type
def greet(name: str | bytes, encoding: str = "utf-8") -> None:
name = name.decode(encoding) if isinstance(name, bytes) else name
reveal_type(name)
print(f"Hello {name}!")
I just want to accept either bytes or string and convert it to string internally
bytes gets expanded by type checkers into bytes | bytearray | memoryview when in parameter annotations (and possibly elsewhere), so your code only accounts for one of those three possibilities.
okay, thank you
Another question, what's the difference between bound and constraints in a TypeVar
I want a TypeVar that allows only two types, i.e, str or None
But note this behavior is deprecated and mypy will eventually change it
Right, due to collections.abc.Buffer existing now, iirc. Is there a timeframe on that deprecation, e.g. 3.11 being EOL?
It's type checker behavior and doesn't depend on the Python version. I think we may want for mypy to do a major version bump though.
Fair enough. Makes me a bit curious, because I imagine there's some implicit dependence on python version if only to avoid immediately invalidating a ton of <3.12 code with bytes annotations that depends on this promotion since it hasn't transitioned to Buffer yet. 3.11 being EOL would provide justification since Buffer will always available then, even without typing_extensions. Is there any explicit coordination between type-checkers for this kind of thing, or could e.g. mypy deprecate this promotion a month from now and pyright a year from now?
No explicit coordinates. I think the promotion never made a ton of sense (and you can always write bytes | bytearray to be explicit), so there's no reason to depend on the Python version
I agree, but backwards incompatibility is a thing. I guess the possible impact on user code and package releases (older ones, really, but also current) depending on this was gauged and judged insufficient to force type checkers to sync over this, which, y'know, fair enough. I wasn't there, but I imagine plenty of discussion was had.
Well it's more that you can't really "force" type checkers
In PEP 688 I simply wrote that the special case should be removed
Right, "force" doesn't convey what I wanted it to there. "encourage" is more like it, I suppose, for the sake of potentially transitioning the user bases at once.
thank you, that was very informative!
Having issues with code I'm creating a number guessing game project to get more familiar I've added the quit() command so if the user inputs a string it should say "please pick a number next time" and then quit I'm getting error code NameError: name 'quit' is not defined. Any thoughts on what I'm doing wrong? Running on Spyder
Lemme know if this needs to be put elsewhere as well
You should probably open a help channel in #1035199133436354600, post your entire code and the error message there
I will do so tomorrow when I have more brain power, thank you
i have a generic class A[X, Y]
is there a way to make A[X] equivalent to A[X, X] ?
You could have Y default to X thanks to PEP 696.
!pep 696
X here is a typevar
i want A[int] to be the same as A[int, int] and A[str] to be the same as A[str, str]
how do i do that?
would class A[X, Y = X]: ... work?
pep 696 is very vague about what can be a default type of typevar
Is it?
i see
this is the most cryptic text i have seen in a while
seems clear to me 😐
anyhow, for your case:
from typing_extensions import TypeVar # pre 3.13 backport for default= in TypeVar
X = TypeVar("X")
Y = TypeVar("Y", default=X)
class A(Generic[X, Y]):
...
class A[X, Y=X]: ...
?
yes, in 3.13
nice!
thank you both
Is there a way to type hint this, maybe with overloads+generics on __init__? Or am I stuck with the Callable[..., object]?
@dataclass(frozen=True, slots=True, eq=False)
class Updater:
_updater: Callable[..., object]
dt: bool = False
def __call__(self, mob: OpenGLMobject, dt: float) -> None:
if self.dt:
self._updater(mob, dt)
else:
self._updater(mob)
I think you can probably get away with generic + overloads on __init__ and maybe __call__ to make this work. It’ll be a bit verbose, though.
AFAIK, there’s no way currently other than overloads to represent a “relationship” between two sets of types, which is what it looks like you’re going for.
Why do you need this to be a dataclass?
I don't, I was just protyping and wanted a nice __repr__ lol
type Updater = Callable[[OpenGLMobject, float], object]
If you don't want the dt, you can ignore it in the callback
The problem is that it needs to be a class because it'll store lots of other data
(in this case like whether the function is going to modify the colors, etc. so that it can be optimized)
Also forcing a float parameter is not semvar minor happy :(
ah
If you're in a deep pickle, maybe```py
class Updater:
@overload
def init(self, Callable[[OpenGLMobject, float], object], dt: Literal[True]) -> None: ...
@overload
def init(self, Callable[[OpenGLMobject], object], dt: Literal[False] = False) -> None: ...
hmm good idea
Thanks!
Another question: if I wanted something like
Callable[[Mobject], object]
where the first parameter is any subclass of Mobject, how would I do it? I don't know why the above works because parameters are (to my understanding) contravariant, but at this point I'm not really sure how variance works anymore 🙃
If you have a fn: Callable[[Animal], None], you can call fn(cat)
Variance doesn't matter here, because you can always do animal: Animal = cat and then do fn(animal)
Variance matter when you need to figure out whether one of (Callable[[Organism], None], Callable[[Dog], None]) is assignable to fn. Callable[[Organism], None] does work: if it works for any organism, it must work for animals (which is only a smart part of that). However, Callable[[Dog], None] doesn't work, because Callable[[Animal], None] needs to be able to work with cats, birds etc., not just dogs
So in general variance is only for assigning to something?
Are you familiar with the notion of "subtype"? i.e. that int is a subtype of int | str
yeah conceptually
Variance describes how subtyping works for a generic type. For example, list is invariant, so even if A is a subtype of B, list[A] and list[B] aren't subtypes of each other. tuple is covariant, so if A is a subtype of B, then tuple[A, ...] is a subtype of tuple[B, ...]. And functions are contravariant in the parameters but covariant in the return type, so if C is also a subtype of D, then Callable[[B], C] is a subtype of Callable[[A], D]
Use TypeAdapter
from typing import Any, Generic, Protocol, TypeVar
_ST = TypeVar("_ST", bound=Any)
_ST_co = TypeVar("_ST_co", bound=Any, covariant=True)
_DT = TypeVar("_DT", bound=Any)
_DT_co = TypeVar("_DT_co", bound=Any, covariant=True)
class ArrayProtocol(Protocol[_ST_co, _DT_co]):
def count(self, value: Any, /) -> int: ...
class Array(Generic[_ST, _DT]):
def __init__(self, shape: _ST, dtype: _DT) -> None:
self._shape = shape
self._dtype = dtype
def count(self, value: Any, /) -> int:
return 2
@property
def shape(self) -> _ST:
return self._shape
def test_pass_typevars(a: ArrayProtocol[_ST, _DT]) -> ArrayProtocol[_ST, _DT]:
return a
arr = Array(shape=(3,), dtype=int)
reveal_type(arr) # information: Type of "arr" is "Array[tuple[Literal[3]], type[int]]"
# array: ArrayProtocol[tuple[int, ...], int] # Works correctly uncommented
array = test_pass_typevars(arr)
reveal_type(array) # information: Type of "array" is "ArrayProtocol[Unknown, Unknown]"
I struggle to understand why test_pass_typevars does not pass through the typevars from arr, with pyright I get Unknown, and Any with mypy. Any ideas why?
Why should array be ArrayProtocol[tuple[int, ...], int] and not ArrayProtocol[bool, socket]?
ArrayProtocol does not use any of its typevars, so anything with a count method with the appropriate signature satisfies ArrayProtocol[???, ???]
from typing import Any, Generic, Protocol, TypeVar
_ST = TypeVar("_ST", bound=Any)
_ST_co = TypeVar("_ST_co", bound=Any, covariant=True)
_DT = TypeVar("_DT", bound=Any)
_DT_co = TypeVar("_DT_co", bound=Any, covariant=True)
class ArrayProtocol(Protocol[_ST_co, _DT_co]):
@property
def dtype(self) -> _DT_co: ...
@property
def shape(self) -> _ST_co: ...
class Array(Generic[_ST, _DT]):
def __init__(self, shape: _ST, dtype: _DT) -> None:
self._shape = shape
self._dtype = dtype
@property
def shape(self) -> _ST:
return self._shape
@property
def dtype(self) -> _DT:
return self._dtype
def count(self, value: Any, /) -> int:
return 2
def test_pass_typevars(a: ArrayProtocol[_ST, _DT]) -> ArrayProtocol[_ST, _DT]:
return a
arr = Array(shape=(3,), dtype=int)
reveal_type(arr) # information: Type of "arr" is "Array[tuple[Literal[3]], type[int]]"
array = test_pass_typevars(a=arr)
reveal_type(array) # information: Type of "array" is "ArrayProtocol[tuple[Literal[3]], type[int]]"
aha, forcing the protocol to use the typevars does indeed do the trick, thanks @trim tangle !
btw, bound=Any is not necessary
Yeah, the more advanced version will have a proper bound eventually.
@tranquil turtle @restive rapids 👀 I need some help with a localization review
https://github.com/DetachHead/basedpyright/pull/507
| Type annotations | Аннотации тип**а** |
нот аннотации тип**ов**? https://fastapi.tiangolo.com/ru/python-types/ uses that for example, and most articles on types in russian language.
perhaps same with type variables but im not sure
аннотация/аннотации
each annotation declares one type, so "типа"
Well, in the context of an error messages it usually refers to the annotation of some specific single type
Type annotation in English is more like "Типоаннотация", it refers to both cases
Typanmerkung
Type annotation (noun)
Type annotation (verb) ?

if you share invite to your private server, we can discuss there without cluttering this channel)
github pr messaging is very.. not live
| positional-only | чисто позиционный | lol
"чисто" is kinda jargon, "только" (only) makes more sense
that is true
from dataclasses import dataclass
from typing import Protocol
@dataclass(frozen=True)
class D:
x: int
class P(Protocol):
x: int
def f(a: P) -> None:
pass
f(D(1))
Argument of type "D" cannot be assigned to parameter "a" of type "P" in function "f"
"D" is incompatible with protocol "P"
"x" is writable in protocolPylancereportArgumentType
What do? Make x a property?
Remove frozen?
The equivalent of f in the actual code takes a mutable D1 or an immutable/frozen D2 (see above). It doesn't mutate anything.
yeah, the property solution seems to work and is whats recommended
is (vartype,vartype) syntax valid in any case, like maybe in older versions or smth? noticed it in a module someone else made; my vscode doesn't like it
i dont think so, and they have 2 of them in the "typehint" but in the returning tuple only 1 value?
It's never been part of the type system, but pycharm allows it I believe
The runtime lets you put whatever you want in annotations
basedmypy also allows it i think, but it allows.. a lot of weird stuff. although it wouldnt be valid here either as the amount of values doesnt match
it gets decorator'd (or something like that) i think
that confused me as well
the type of a function that has a decorator applied to it is determined by the decorator's return type. you typehint the original function the way the original function works
huh odd the Tuple[var] syntax is used in other bits of this code
Sometimes people write type annotations without running a type checker, and then the annotations can end up all over the place
yeah thats me basically lol (though i didnt write this)
i mainly just do it for the ide highlighting
btw why does typing have things for List and Dict but you can just do tuple[var]
in older versions the builtin types werent subscriptable, now they are so its mostly a compatibility thing
ah
oh mfw theres also one for tuple
i guess i just never checked to see if there was one lmao
yup. list[T], dict[K, V], set[T], tuple[T, ...]
is there a way to have the type checker enforce that an attribute in an abstract class must be overwritten?
A property with @abstractmethod is the best approximation you can get for this, but it's not perfect
in the example below, you’ll see an error if you try to assign attribute foo to the wrong type. but if you leave it off entirely, no error is shown
class AbstractFoo:
foo: str
class Foo(AbstractFoo):
foo = 5 # shows an error
class Bar(AbstractFoo):
bar = 5 # no error for not assigning foo
can’t get that to work for my usecase unfortunately
I think pyre may check for this
I think attributes on Protocol classes that are not initialised are also treated as abstract by at least some type checkers
Guys why i getting this mypy error even I have type hinted it
class FileHandler(logging.FileHandler):
_lastEntry: datetime.datetime = datetime.datetime.today()
def __init__(self, *, ext: str, folder: Path | str = "logs") -> None:
self.folder = Path(folder)
self.ext = ext
self.folder.mkdir(exist_ok=True)
super().__init__(
self.folder / f"{datetime.datetime.today().strftime('%Y-%m-%d')}-{ext}.log",
encoding="utf-8",
)
self.setFormatter(Formatter(name=ext))
def emit(self, record: logging.LogRecord) -> None:
if self._last_entry.date() != datetime.datetime.today().date():
self._last_entry = datetime.datetime.today()
self.close()
self.baseFilename = (
self.folder / f"{self._last_entry.strftime('%Y-%m-%d')}-{self.ext}.log"
).as_posix()
self.stream = self._open()
super().emit(record)
src/utils/logger.py:68: error: Cannot determine type of "_last_entry" [has-type]
_lastEntry or _last_entry?
this is a pytorch function
def _foreach_sub(
self: Tuple[Tensor, ...] | List[Tensor],
other: Tuple[Tensor, ...] | List[Tensor],
*,
alpha: Number | _complex = 1
) -> Tuple[Tensor, ...]: ...
how to annotate a Callable to accept functions like this??
I say Callable[[TensorSequence, TensorSequence, PyNumber], TensorSequence] but it screams at me because alpha is keyword only
I used proto call
class TorchForeachAlphaFn(Protocol):
def __call__(self, __self: TensorSequence, __other: TensorSequence, *, alpha: PyNumber) -> TensorSequence: ...
Type parameter "_T@list" is invariant, but "Parameter" is not the same as "Tensor"
Consider switching from "list" to "Sequence" which is covariant
why is list invariant
this is even dumber how to fix
Type parameter "_T@list" is invariant, but "Parameter" is not the same as "Tensor | Parameter"
mutable things tend to be invariant, imagine you have a list[int], and you could pass it to something that expects a list[str | int] -> it could add strings to your list of ints
what if I don't intend on mutating it
because pytorch has List[Tensor] | Tuple[Tensor], so if I say Sequence[Tensor], it says Sequence is incompatible