#type-hinting
1 messages · Page 21 of 1
not sure what you mean
I believe type[type[Arg]] is illegal. type[] can only holds class names, unions of class names, TypeVars, or Any according to PEP 484
!e ```py
class Foo: ...
print(type(Foo())) # => type[Foo]
print(type(type(Foo()))) # => type[type[Foo]]
@soft matrix :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | <class '__main__.Foo'>
002 | <class 'type'>
pyright doesnt seem to have any problems with nested type[...]
this doesn't seem to make sense? if for example x: type[str] means x = str or class SubClassOfStr(str): pass; x = SubClassOfStr then surely x: type[type[str]] must still mean that x can only be set to str or a SubClassOfStr?
oh ok i see, so then is this the correct way to go about annotating this?
class SomeClass:
pass
T = TypeVar('T', bound=Type[SomeClass])
def patched_class(cls: T) -> Type[T]:
# take in a class that is SomeClass itself or one of its suclasses, then return a subclass of that
class PatchedClass(cls):
...
return PatchedClass
no. that suggests it returns the metaclass of your class
there should be type[] either both in the argument and return type or neither
because you're both taking and returning a type
so then what exactly is the meaning of bound in TypeVar?
it means the TypeVar must be solved to a subtype of the bound
so it's a naming issue? as in, i should change the name of T to something clearer? otherwise, isn't the actual type hinting correct?
no. you're saying that you're taking a T and returning the type of T
but in fact you're both taking and returning a type
from typing import NewType
PolicyARN = NewType("PolicyARN", str)
some_arn = PolicyARN("bla")
str_arn = "bla2"
def some_func_with_arn(arn: PolicyARN) -> None:
pass
def some_func_with_str(arn: str) -> None:
pass
some_func_with_arn(some_arn) # Mypy verifies OK
some_func_with_str(some_arn) # Mypy verifies OK :see no evil:
some_func_with_arn(str_arn) # Mypy screams Error
some_func_with_str(str_arn) # Mypy is ok
NewType works like this in mypy
Can i have it working like
some_func_with_arn(some_arn) # Mypy verifies OK
some_func_with_str(some_arn) # Mypy verifies Error
some_func_with_arn(str_arn) # Mypy screams Error
some_func_with_str(str_arn) # Mypy is ok
? forbidding automated casting back to parent type
please correct me if im wrong, im just going off of what it says here: https://docs.python.org/3/library/typing.html#the-type-of-class-objects
my understanding so far is: if T itself is bound to Type[SomeClass], that means that a name with annotation T must be set to SomeClass, or a subtype of SomeClass, right? So then, why would a name of annotation Type[T] have to be set to a metaclass of T, rather than T itself or one of its subtypes?
I don't think you can
because NewType creates a "subtype"
any alternative to achieve such result?
some_func_with_arn(some_arn) # Mypy verifies OK
some_func_with_str(some_arn) # Mypy verifies Error
some_func_with_arn(str_arn) # Mypy screams Error
some_func_with_str(str_arn) # Mypy is ok
trying to find may be there is some plugin or smth that forbids implicit casting to parent back
thinking of may be possible to write such mypy plugin 😄
maybe some other linter exists that forbids it
from typing import TypeVar, Callable, NewType, TypeAlias
T = TypeVar('T')
class PolicyARN:
def __init__(self, name: str):
self.name = name
def __str__(self) -> str:
return self.name
# PolicyARN = NewType("PolicyARN", str)
some_arn = PolicyARN("bla")
str_arn = "bla2"
def some_func_with_arn(arn: PolicyARN) -> None:
pass
def some_func_with_str(arn: str) -> None:
pass
some_func_with_arn(some_arn) # Mypy verifies OK
some_func_with_str(some_arn) # Mypy screams Error
some_func_with_arn(str_arn) # Mypy screams Error
some_func_with_str(str_arn) # Mypy is OK
I did it 😄
yeah... additional casting costs. I don't care 🙈
there are magic method for all simple types. (__int__, __float__ and etc) 😄 exactly what i wish
yeah... not super performant 🙈 but lets say performance was not a goal anyway.
at the end seeing new bugs appearing from this, easier to use NewType. because it is more friendlier to code that is not typed 😐
well
pycharm seems to understand this correctly:
T = TypeVar('T', bound=type[SomeClass])
def patched_class(cls: T) -> T:
class PatchedClass(cls):
...
return PatchedClass
but neither mypy or pyright like the class PatchedClass(cls): line
mypy says:
main.py:12: error: Variable "cls" is not valid as a type [valid-type]
main.py:12: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
main.py:12: error: Invalid base class "cls" [misc]
main.py:15: error: Incompatible return value type (got "type[PatchedClass]", expected "T") [return-value]
where line 12 is class PatchedClass(cls): and line 15 is return PatchedClass
and according to https://pyright-playground.decorator-factory.su/ pyright says Expected type expression but received "T@patched_class" about the cls in class PatchedClass(cls):
Is it possible to remove kwargs from a function definition using ParamSpec? 🤔
E.g. something like
def remove_kwargs(func: Callable[P, R]) -> Callable[[*P.args], R]:
return func
I don't think so
Maybe you can use TypeVarTuple?
Ts = TypeVarTuple("Ts")
def remove_kwargs(func: Callable[[*Ts], R]) -> Callable[[*Ts], R]:
return func
I don't think that would work 
I guess I need to write a mypy plugin
Ultimately I want to strip all Annotated[..., Inject] from function definition, but for my limited usecase leaving only first argument of a function would have worked:
@inject
async def load_users(
keys: Sequence[int],
session: Annotated[AsyncSession, Inject],
) -> Sequnece[User | None]:
pass
@trim tangle Do you think a plugin is the way to go there?
Yeah any manipulation with Annotated is not possible to express in the type system
Well, using * + kwargs would have worked too for my case
How can I declare a CALLBACK: Callable[..., Any] = None, as None?
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/upload_fileobj.html in this function, callback parameter is optional, and i want do take the parameter from my function, but leave it as optional too
like how can I point that callback to nothing?
Expression of type "None" cannot be assigned to parameter of type "(...) -> Any"
Type "None" cannot be assigned to type "(...) -> Any"
| None
Union callable and none?
Yes
Argument of type "((...) -> Any) | None" cannot be assigned to parameter "Callback" of type "(...) -> Any" in function "upload_fileobj"
Type "((...) -> Any) | None" cannot be assigned to type "(...) -> Any"
Type "None" cannot be assigned to type "(...) -> Any"
uhhhhhhhhhhhhhhhhhh
it doesnt accept none, like i have to point it to something void
sounds like the typings for boto3 are fucked
Oh, i misunderstood you. I dont think it is possible without calling function in teo different ways in if
probably, service and resource was fucked too
is the library even typed?
Pep8 is also violated...
here ise the stub file in unofficial typings
def upload_fileobj(
self,
Fileobj: FileobjTypeDef,
Bucket: str,
Key: str,
ExtraArgs: Dict[str, Any] = ...,
Callback: Callable[..., Any] = ...,
Config: TransferConfig = ...,
) -> None:
"""
Upload a file-like object to S3.
[Show boto3 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.upload_fileobj)
[Show boto3-stubs documentation](https://youtype.github.io/boto3_stubs_docs/mypy_boto3_s3/client/#upload_fileobj)
"""
Yeah this is wrong
It contradicts this: S3.Client.upload_fileobj(Fileobj, Bucket, Key, ExtraArgs=None, Callback=None, Config=None)
yep
exactly
I would file an issue with the stub repo
All optional args are wrong
will do
thanks boys
i cant even find my way where the heck is source code in github lmao
thank you thank you
if you wrap a typed function with an untyped decorator type checkers assumes its args Any, kwargs Any -> Any (I think right?).. is there a way to say "im not going to change the function signature with this decorator"
you could do this:
from typing import TypeVar
from collections.abc import Callable
F = TypeVar("F", bound=Callable)
def my_deco(f: F) -> F:
...
!pep 612
or this: ```py
from typing import TypeVar, ParamSpec
T = TypeVar("T")
P = ParamSpec("P")
def my_deco(f: Callable[P, T]) -> Callable[P, T]:
def wrapper(*a: P.args, **kw: P.kwargs) -> T:
...
return wrapper
^ for more advanced variations
yeah, that's actually the linked pep
generally though id recommend the first example until hkt is a thing because it will preserve types better
and its easier to do
how so? why would this need hkt?
because to preserve the type of the callable used you need to do F[P, T] or something
otherwise it gets erased on the return type to just a generic callable when it could have been a functiontype or methodtype etc
oh, yeah that's true I suppose, though I never really needed to preserve FunctionType/MethodType, but yeah, that makes sense
For more advanced code for the same effect
Paramspec is more useful if you actually want to change the params
it also avoids a cast() in the implementation for decorators that don't change the signature
Does pycharm support decorators that change the signature yet?
from typing import NewType, TypeVar, Type
ExcType = NewType("ExcType", Type[Exception])
class PanicException(Exception):
pass
abc: ExcType = PanicException
Trying to make a type that allows any subclassed exception. help 😄 not sure how yet
class Notificator(metaclass=ABCMeta):
@abstractmethod
def panic(self, msg: str, exc: Optional[Exception] = None, error_cls: types.ExcType = PanicException) -> None:
"""
Critical problems.
"""
target, to use there
That's not possible
okay, then i need to figure out Generic usage for Notificator, then it should be possible
Well, it will allow using BaseException
But I don't see problems with that
Dammit. it was all possible 😅
from typing import Type
ExcType = Type[Exception]
https://docs.python.org/3/library/typing.html#the-type-of-class-objects
instruction is over there, with subclassing provided
Use bound= on the typevar
Your typevar has no meaning in your function
You gotta use it all least twice
And typevars don't behave when used with a default arg. You'll need an overload.
mypy ate it. everything is fine
Suppose I have a decorator that adds a regular parameter to the function it’s decorating, between the args and kwargs. Is there a way to indicate that the parameter has been added to the function when working in the context of type stubs, where (AFAIK) one can’t actually use custom decorators and has to collapse the return type of the decorator into the function’s return type? I have relevant code but can’t post it atm, so I’m curious about general mechanisms for this.
I’ve managed to nail down a callable protocol that describes the decorator return type such that if the extra parameter is or isn’t specified, my IDE indicates alternate return values for the function. Beyond that though, I have no idea how to apply that kind of typing to every function that the decorator wraps, or if that’s even a relevant approach.
this is very difficult and usually doesn't work, esp since there's no Concatenate for kwargs like things (afaik). I ran into this a bit ago and while decorators can do really fancy things, if u want to fully type ur code, u ought to be careful with how fancy u get.
i think there's a way to use @typing.overload, but it's pretty restrictive
im not sure what you mean by collapsing the decorator return type, but it sounds like a generic callable protocol with paramspec (3.10) is what you want
Every type feature is available in pyi files
Hmm. Technically this decorator gives every function it decorates two overloads and a new arg. I guess I’m asking, is there an obvious clean way to indicate these functions now have overloads (in type stub files) without going through the work of showing the overloads for every function that’s been decorated? If that makes sense.
Maybe. I tried using Concatenate and ParamSpec before to no avail. Might have to revisit that.
oh didnt know that paramspec didnt allow appending positional args, only prepending
^^ Yeah, that was an issue.
The more I think about it, the less possible it seems. Might just be easier to bite the bullet and type out the overload definitions.
Are you unable to use the decorator in the stub?
By collapsing, I mean this:
https://typing.readthedocs.io/en/latest/source/stubs.html#decorators
The behavior of other decorators should instead be incorporated into the types.
I kinda just . . . made an assumption based on the link above, which says that decorators in stubs should be limited to those in the typing module and a few general ones like property and abstractmethod, lol.
That's a fair assumption, and probably correct
I believe most type checkers will support other decorators
Huh. If so, sick. I’ll give it a go tomorrow and see what happens.
The decorator, for the record.
# Some of these imports are for other things in the same file.
from threading import Thread
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Protocol, TypeVar, Union, overload
if TYPE_CHECKING:
from typing_extensions import TypeAlias
T = TypeVar("T")
T_co = TypeVar("T_co", covariant=True)
class ThreadableCallable(Protocol[T_co]):
@overload
def __call__(self, *args: Any, threaded: Literal[False] = ..., **kwargs: Any) -> T_co:
...
@overload
def __call__(self, *args: Any, threaded: Literal[True] = ..., **kwargs: Any) -> Thread:
...
def __call__(self, *args: Any, threaded: bool = False, **kwargs: Any) -> Union[Thread, T_co]:
...
def threadable(func: Callable[..., T]) -> ThreadableCallable[T]:
"""Allows the function to be ran as a thread using the 'threaded' argument"""
@overload
def wrapped(*args: Any, threaded: Literal[False] = ..., **kwargs: Any) -> T:
...
@overload
def wrapped(*args: Any, threaded: Literal[True] = ..., **kwargs: Any) -> Thread:
...
def wrapped(*args: Any, threaded: bool = False, **kwargs: Any) -> Union[Thread, T]:
if threaded:
thread = Thread(target=func, args=args, kwargs=kwargs)
thread.start()
return thread
return func(*args, **kwargs)
wrapped.__module__ = func.__module__
wrapped.__name__ = func.__name__
wrapped.__qualname__ = func.__qualname__
wrapped.__doc__ = func.__doc__
wrapped._threadable = True # type: ignore
return wrapped
Probably quite a bit wrong with it, but this was my attempt at typing it.
(Not quite sure how functools.wraps interacts with overload and I didn’t look too deep into it at the time.)
Overload doesn't exist, so wraps doesn't do anything
I'm not sure if I am being dumb, but I am trying to type hint something which is a ctypes.POINTER.
I tried the naive
data: ctypes.POINTER(DataStructure)
(data type being pointed to for the most part here doesn't matter, but I am generally using other ctypes.Structures)
I have also tried googling and followed this: https://github.com/python/mypy/issues/7540#issuecomment-845741357 to no avail:
data: ctypes.pointer[DataStructure]
In both cases vscode just thinks that data is Any, so I get no code completion...
I'd like to have it so that data is correctly understood to be a pointer, and thus has the contents attribute, and the type of centents is DataStructure.
Also, what is weird is that if I do
data = ctypes.POINTER(DataStructure)
It works fine, but the issue is that I am just defining the types as properties to a ctypes.Structure, so I can't set it equal like that...
I don't think ctypes.POINTER is generic
notice that pyright uses a _Pointer type, not the ctypes.POINTER class directly
this is probbaly comming from a stub
yeah, I did notice that...
typeshed apparently has a stub for it
but I wasn't sure how to use that info...
typing it with _Pointer also didn't seem to work...
lol, ok, I got it...
x: ctypes._Pointer[ctypes.c_int] works
thanks for helping to nudge me in the right direction
yeah, but it might fail on runtime
if TYPE_CHECKING:
from ctypes import _Pointer
I'd do this
with future annotations, the above would work too
from __future__ import annotations
from typing import TYPE_CHECKING
from ctypes import c_int
if TYPE_CHECKING:
from ctypes import _Pointer
x: _Pointer[c_int] = ...
this should work on runtime
alternatively, if you really don't want __future__.annotations, you could also put an else under that if TYPE_CHECKING, and make the _Pointer variable hold something that supports class getattr
it's still gonna be lying to the type checker though
why do I need to import annotations from future?
it's a special import, that affects how python treats type-hints, it basically converts every type hint into a string
so it's the same as if you did: ```py
x: "_Pointer[c_int]" = ...
which would work without that import too
list is invariant so that should be an error yes
try changing searches to a Sequence
read the link i sent
oh youve snuck an int at the end of your union
yeah that also makes sense still
you cant put subclasses into a list and expect it to work
try changing searches to a Sequence
https://decorator-factory.github.io/typing-tips/tutorials/generics/variance/
why would you cast a parameter like that?
yes
well open a pr to fix it
or for now cast it yeah
unless they actually mutate the list
what are you doing?
oh no you need to cast to the exact same type
Hello
is there a way to "downgrade" a dict[str, str] to a Mapping[str, str] without cast?
here's my example that mypy doesn't like:
from collections.abc import Mapping
from python_dotenv import dotenv_values
ENV: Mapping[str, str | None] = dotenv_values() # returns dict[str, str | None]
oh never mind.. i forgot the None on the left hand side 😅
it'd be nice if mypy was clearer about which part of the type was incompatible
Hey guys, how do I type hint a str with exactly three chars?
@jade viper you can't unfortunately
tuples are special in that they can know their own size in some sense. strings however are opaque to the type checker
you can use newtype wrappers to emulate it, but it's going to be hard to work with and it's probably not worth the trouble
there are some programming languages with something called "dependent types" where types can encode arbitrary information about the object, but they're fairly complicated and mostly used for proof assistants and other research work. the only one that's practical for regular programming is Idris 2, and it's very very different from python
:/
I really do only need it to be a hint
Not pass type checks
str[3] seems like a obvious type hint but my linter only recognizes str
maybe Annotated and a linter plugin if that's a possibility?
something like
from typing import TypeVar, Annotated, Generic
I = TypeVar('I', bound=int)
class Length(Generic[I]):
pass
# then
example: Annotated[str, Length[3]] = ...
theres a pypi project for reuseable annotateds so you could use that as a basis
is it this one? https://pypi.org/project/annotated-types/
i see a Len there
yeah
Why though?
You can't put everything in type hints
If you want to be damn sure you have 3 characters you can use tuple[str, str, str]
and how would you know those are one-character strings
yeah good question
I fully expected you to start typing out Literal["a", "b", ...]
Why are you starting with lowercase letters
got to start somewhere
Please think of the \x00!
Since they're currency codes I may or may not have created an enum with every currency in the world and type hinted the enum...
you type hinted an enum?
If you need a currency code, you probably need much more validation than len(s) == 3
"Ж\x00&" is probably not something that you can trade for US dollars
You cannot encode every possible constraint in the type system, as I said. Runtime checks are perfectly valid, especially if you can build a "boundary" that encapsulates some data that's definitely valid.
class CurrencyCode:
def __init__(self, raw: str, /) -> None:
if len(raw) != 3:
raise ValueError("Expected currency code length to be 3")
if not all(char in string.ascii_uppercase for char in raw):
raise ValueError("Only expected uppercase Latin letters in currency code")
self._value = raw
def value(self) -> str:
return self._value
It's the type hint of the return type actually, I can guarantee that the response will always be an existing currency code
Well then, why do you need to encode the length in the type?
Is it possible to get functools.singledispatch to work nicely with intellisense? I know this isn't strictly type hinting, but it's kinda close...
For example, I have a test class like this:
class Test():
def __init__(self, x: int):
self.x = x
@singledispatch
def function(self, y: int) -> int:
""" This is a docstring... """
return self.x + y
@staticmethod
@function.register
def _(this: int, y: int) -> int:
return this + y
This all works perfectly, but the issue is that I'd quite like to be able to type
t = Test()
t.function(
And be able to see the possible arguments (kind of like the overloads of something like a ctypes.protocol where it shows mutliple options...)
what I see:
what would be nice:
lmao, just realised I should look at how they do it internally and there is an overload decorator...
Ok, actually looking at that and some other links such as this on github: https://github.com/python/mypy/issues/8356
I still can't figure it out.
I think I am a bit stuck because I need one of the methods to be a staticmethod since I am basically wanting to have the same method name for both a static method and a normal method...
# this is inside class
def method1(self):
return 'hello world'
def method2(self, x_method):
result = x_method()
return result
what is the type hint for method inside that class?
What is x_method?
a variable
I want x_method to have type hint of method in that class
that sounds strange, can you explain more?
!d collections.abc.Callable
class collections.abc.Callable```
ABC for classes that provide the `__call__()` method.
You mean self.method2(self.method1)?
oh... yeah that
Why does it have to be a method and not just any function?
from collections.abc import Callable
from typing import TypeVar
T = TypeVar("T")
class Foo:
def method2(self, x_method: Callable[[], T]) -> T:
result = x_method()
return result
I think any function also work I think
could you give a more realistic example maybe?
class ScannerWidget:
def __init__(self, x: int, y: int, h: int = 100, w: int = 100, pad: int = 20) -> None:
self.win1 = Toplevel()
self.win2 = Toplevel()
self.win3 = Toplevel()
self.win4 = Toplevel()
self.win1.geometry('%dx%d+%d+%d' % (w-pad, pad, x+pad, y))
self.win2.geometry('%dx%d+%d+%d' % (w-(2*pad), pad, x+pad, y+h-pad))
self.win3.geometry('%dx%d+%d+%d' % (pad, h-pad, x, y+pad))
self.win4.geometry('%dx%d+%d+%d' % (pad, h-(2*pad), x+w-pad, y+pad))
self._win_prep(self.win1)
self._win_prep(self.win2)
self._win_prep(self.win3)
self._win_prep(self.win4)
self.left_top = Coordinate2D(x, y)
def _move(self, win: Toplevel, x: int, y: int) -> None:
win.geometry('+%d+%d' % (win.winfo_x() + x, win.winfo_y()) + y)
def _do4win(self, method):
pass
def _win_prep(self, win) -> None:
if platform == "darwin":
win.overrideredirect(1)
win.overrideredirect(0)
else:
win.overrideredirect(1)
label = Label(win)
label.pack(fill="both", expand=True)
label.bind('<Configure>', self._label_resize)
label.bind("<ButtonPress-1>", self._drag_start)
label.bind("<ButtonRelease-1>", self._drag_stop)
label.bind("<B1-Motion>", self._drag_motion)
def _label_resize(self, event):
width = event.width
height = event.height
# more code...
here as you can see I have lot's of repeated win1, win2, win3, win4. so I want to make method that take method
tkinter?
why use [[]. T] here?
Callable on its own means an object that can be called with any argument and has any return type
In your example you call the function with 0 arguments, so that's something the argument should satisfy
I see... ty 👍
this is old lol but like agree with this, building onto it i'd probably use a class to validate this, that seems like a graceful solution
the class could even derive from str, with its validation in __new__
obviously what you initialise the class with won't be type checked before runtime but like, it's beyond the scope of the type checker here imo
Is it possible to dynamically create a TypedDict (or akin) based on the value of a list at runtime? E.g.py wanted_info = [ "id", "user.username", "user.id", ]would create the {id: string, user: {username: string, id: string}} type (every bottom-level key always has a string value)
It wouldn't be useful at runtime would it?
with typescript maybe 😆
what they said
# non-recursive ver
my_dict = {k: str for k in wanted_info}
wanted_info_type = TypedDict("wanted_info", my_dict) # static type checker cannot read a dynamically created dict entry
x: wanted_info_type = {"id": "hi", "user.username": "hi", "user.id": "hi"} # fails due to above
(this is just a flattened example). code runs fine, but you won't get type supports for this
you probably want to use dataclasses (won't support recursion?) or pydantic to solve this kind of problem in a pythonic way
Hmm, okay. I know it's possible in ts so was hoping you could in python too, but guess not (easily, anyway)
i wonder whether typescripts advantages to python are largely just due to more work put into or or a fundamental difference between a typed superset and static type checking?
typescript is designed type system first and attempts to fit as many existing use cases of javascript as possible
targetPrice: Optional[int] = Field(default=0)
why in the tarnation pylance see this field as either a bool, integer, or none
i understand int or none, but where the fk bool comes from
Booleans are a subclass of integers.
so how would i write the signature of this ```py
def something(targetPrice: int) -> int
Originally for historical reasons, it does come in handy sometimes though.
Like that? It's actually valid to pass True in. Try True + 5 in the REPL :)
wadufuuuuug
Type "bool | int | None" cannot be assigned to type "int"
Type "None" cannot be assigned to type "int
```i am gonna ignore these errors then lmfao
pyright at strictest is most fun and most stubborn thing i have ever seen
well that's a different problem
Yeah, you need to check for none first.
Have a look at the PEP for more info - bools were added pretty late to the language, in Python 2.3!
https://peps.python.org/pep-0285/
Python Enhancement Proposals (PEPs)
How can I assert that a variable is bound?
if 'tile_data' not in locals():
raise Exception("No tile data found.")
this doesn't work, tile_data is still Unbound | NDArray[uint8]
I'm assuming there's a typing reason for that?
it will actually work with pyright
you could probably open a feature request if you want to float the boat but i highly doubt it would be accepted when setting it to None and just checking after the fact if its None already works
Yeah, conditionally defining locals is generally considered a bit weird
Hey guys
What would be the right way to type hint the translate_symbol_with_cache function?
from functools import lru_cache
from typing import Optional
def translate_symbol(field: str, source_name: str, destination_name: str = 'GS_VALUE', source_value: Optional[str] = None, destination_value: Optional[str] = None, primary_only: bool = False, return_none_on_not_found: bool = False, break_on_not_found: bool = False) -> Optional[str]:
...
translate_symbol_with_cache = lru_cache(maxsize=None)(translate_symbol)
My linter doesn't get anything from it
Just lrucachewrapper
it doesnt preserve types unfortunately currently
I think you can easily-ish make a wrapper over lru_cache that preserves types:
P = ParamSpec("P")
R = TypeVar("R")
def lru_cache_(maxsize: int | None = 128) -> Callable[[Callable[P, R]], Callable[P, R]]:
return functools.lru_cache(maxsize) # type:ignore
def f(x: int, y:str)-> dict:
return {}
fcached = lru_cache_(123)(f)
reveal_type(fcached) # Type of "fcached" is "(x: int, y: str) -> dict[Unknown, Unknown]"Pylance
It's really hard, since you need to handle function -> method descriptor behaviour. Someone tried to do it on typeshed, it's like a page of types and still doesn't work...
We have had... many... attempts to fix lru_cache typing at typeshed
One was actually merged, but was then reverted a few months later as it caused an unacceptable number of regressions for mypy users
I'm personally unclear about whether all of these problems are due to the fact that the specification is very unclear about how ParamSpec should work with the "function -> method descriptor behaviour". Some of the issues may be due to incomplete ParamSpec support at mypy
A lot of ParamSpec fixes have been made to mypy recently, so it's possible we could try again after mypy 1.6 is released and see what the fallout is
FWIW I think https://github.com/python/typeshed/pull/7771 has been the most promising attempt so far to fix lru_cache typing at typeshed. You can see from the primer diff that it would have fixed a bunch of issues in user code; it just would have caused a few regressions as well
This code fails in Pylance because argument y does not fit the type of parameter x. Why? ```py
def f(x: list[tuple[str]]):
print(x)
y = [("hello",)]
f(y)
Argument of type "list[tuple[Literal['hello']]]" cannot be assigned to parameter "x" of type "list[tuple[str]]" in function "f"
"list[tuple[Literal['hello']]]" is incompatible with "list[tuple[str]]"
Type parameter "_T@list" is invariant, but "tuple[Literal['hello']]" is not the same as "tuple[str]"```
Interestingly, removing the y variable and directly passing the value: f([("hello",)]) works completely fine
maybe this is just a bug with pylance?
this also passes ```py
def f(x: list[tuple[str]]):
print(x)
y = [("hello",)]
y: list[tuple[str]]
f(y)```
i'm struggling to think of a scenario in which "list[tuple[Literal['hello']]]" is incompatible with "list[tuple[str]]" makes any sense
list[some subclass of T] is not compatible with list[T], because lists allow mutating their elements
that causes problems where you could add an invalid element to a list. like if you pass list[MyInt subclass of int] to a function taking a list[int], and the function append()s a bool to it, then your original list now doesn't only have MyInt objects
you can avoid the error by explicitly annotating y: list[tuple[str]]
the part in the error (T is invariant) explains the distinction a bit
i believe this happens due to bidirectional inference
given a xs: list[tuple[str]] i should be able to do xs.append(('not hello',)), but that's not a valid operation on a list[tuple[Literal['hello']]]
I do agree that inferring y as list[tuple[Literal["hello"]]] is very inconvenient
Pyright for some reason tells me that staticmethod/classmethod are generic. Is it true?
What about property?
property isnt at all but the other 2 arent at runtime unless somethings changed
Are these 2 generic in typeshed?
Can we pls pin link to typeshed here?
Pin this VVV
typeshed
class classmethod(Generic[_T, _P, _R_co]):
@property
def __func__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ...
@property
def __isabstractmethod__(self) -> bool: ...
def __init__(self, __f: Callable[Concatenate[type[_T], _P], _R_co]) -> None: ...
@overload
def __get__(self, __instance: _T, __owner: type[_T] | None = None) -> Callable[_P, _R_co]: ...
@overload
def __get__(self, __instance: None, __owner: type[_T]) -> Callable[_P, _R_co]: ...
if sys.version_info >= (3, 10):
__name__: str
__qualname__: str
@property
def __wrapped__(self) -> Callable[Concatenate[type[_T], _P], _R_co]: ...
this is in my builtins.pyi locally
nah that's the right message
it's the right message
Ok -_-
I pinned this wdym
here you said "that"
I think we are having a politician moment
sounds like you got me
is there a python linter (mypy configuration?) that allows to enforce correct private/public vars/methods access?
as work around i could use probably Interface/protocols for this enforcement, but can i do it without Interfaces?
This is truly interesting and right reason to use always Interfaces over Abstract classes then 🤔 i heard there are some other reasons to prefer interface over abstract classes, but not knowing full list yet
Yup. Interface helps. hooray to interfaces. Abstract classes look like work too though, so not really matters as long as i make sure to input abstract class as a type of variable, in order to forbid access to any unappropriate private stuff
thanks @acoustic thicket @brisk hedge, now I understand why I get the error - is there a more convenient way of resolving it without having to explicitly specify the generic type?
in my actual code I would have to do
y: list[tuple[str, str, bool] | SomeClass] | None
which is pretty awkward
https://github.com/microsoft/pyright/issues/5706 funnily enough id like this feature for all classes :P
you can add an alias for tuple[str, str, bool] which may increase readability
that's better but I'd still have to import the alias or copy/paste it across 3 files which is kinda awkward still
I found out if I edit the function and type the parameter as: typing.Sequence[tuple[str, str, bool] | SomeClass] | None
then it accepts the Literal and seems to otherwise work fine
is this an okay solution or does it have other negative side effects?
yeah since Sequence is read-only it works
class A:
pass
class A2(A):
pass
class B:
def __init__(self, a: A):
self.a = a
class B2(B):
def __init__(self, a2: A2):
super().__init__(a2)
whats the least awkward way to indicate that a subclass attribute's type has changed? none of the checkers seem to infer that B2().a is an A2 rather than an A.
i tried:
class B2(B):
def __init__(self, a2: A2)
self.a: A2
super().__init__(a2)
which didn't change anything
and
class B2(B):
a: A2
which works but isnt great since now it implies a is a class attribute.
which works but isnt great since now it implies a is a class attribute.
a: A2here actually indicates an instance attribute, you'd needa: typing.ClassVar[A2]for a class attribute
then what does that mean for class attributes with a value set? e.g.
class A:
value: str = 'value'
my understanding was that ClassVar only added strictness.
for example mypy complains about the instance attribute assignment here:
class A:
value: ClassVar[str] = 'value'
A.value = 'new value'
A().value = 'new value'
but is fine with both the assignments here:
class A:
value: str = 'value'
A.value = 'new value'
A().value = 'new value'
and what i'm seeing is pycharm seems to think this:
class A:
a: str
implies the existence of A.a in autocomplete
yeah technically that's true and a good point. but the intent is that in the first example A.value is expected to be used like an instance attribute. pep 526 allows value: str to be assigned a value at class level specifically as an expedient for changing a shared default value for what is otherwise meant to be used as an instance attribute.
pycharm in particular is wonky unfortunately
it's unfortunate that this doesn't work
class B2(B):
def __init__(self, a2: A2)
self.a: A2
super().__init__(a2)
if that's how ClassVar is supposed to be used, then it would make more sense to have InstanceVar instead. making a: T = ... and a: T by itself mean two different things in a class body is pretty awful honestly.
but in any case, the a: A2 in the class body solves my problem at least, so thanks @fierce ridge 🙂
i don't know anyone who actually writes a: T = ... for use as a default. in fact i specifically avoid it
class Starship:
_default_captain: ClassVar[str] = 'Picard'
captain: str
but yes i agree
it made sense in 2016 back when this was all kind of new and they/guido didn't want to make things too strict lest they seem unpythonic
a: T = ... is a lot more intuitive and consistent with the rest of the language. wouldn't really expect anyone to know about this ClassVar special case off the bat.
alternatively you could say that you can't assign to a: T at class level, being an instance variable declaration, which follows typical idiomatic usage
most class-level attribute declarations are intended to be instance attributes in my experience
How to set a type hint that refers to an instance from a certain type/class? Callable is too generic.
: X
I need a reference to an instance not a type/class.
I think you need to provide context and code
Its also too generic, it says "Any".
^
Do : A
But that will ask for the type, not the instance.
No
y: type[int] = bool
(because bool is a subclass of int, if that's a sticking point)
Ok now i understand, thanks guys!
you don't understand actually
unless you are just naming your variables in a confusing way
Just an example.
You also have to pass argument of this type to this method
!e
class A: ...
class B(A): ...
x: A = A()
print(isinstance(x, A))
T1: type[A] = type(x) # value is `A`
print(issubclass(T1, A))
y: A = B()
print(isinstance(y, A))
T2: type[A] = type(y) # value is `B`
print(issubclass(T2, A))
@viscid spire :white_check_mark: Your 3.11 eval job has completed with return code 0.
001 | True
002 | True
003 | True
004 | True
Thanks! I thought the whole time that just : A was a reference to the type itself and that i had to do something else to ask for the instance. For example : object[A]. But that didn't work. In my opinion it would make more sense like that. But anyway, thanks for help again.
well, the way Python does it is the same as how every other language does it
Yeah i'm not very familiar with the other languages yet. Maybe that's why i made this mistake lol.
: should be read as "has type"
Python's TypeVar accepts either multiple constrained types, or a bound type, where the typevar represents any subclass of the bound type
wouldn't it just be easier to represent that using a single constrained type
like i assume uh
from typing import TypeVar
T = TypeVar('T', str, bytes)
class StrSub(str):
pass
def f(x: T):
print(x)
f(5) # doesn't like this
f(StrSub()) # fine
constained types also include subclasses
so it seems like bound is literally just a single type constraint
I think I've answered my question if someone can confirm:
T = TypeVar('T', str)is literally juststr(?). If this is true my question could be answered what's the difference between what's the difference betweenstrandS = TypeVar('S', bound=str), and the difference is that inf(x: str, y: str)x and y can be different subclasses of str, but inf(x: T, y: T)if x is a subclass, y cannot bestr, it must be x's subclass (or a subclass of x's subclass?)
T = TypeVar('T', str) is literally just str (?).
No, but it will only be different if subclasses ofstrare involved. Not sure I've ever seen one.
(actually... is a StrEnum variant a str subclass? might be important, then)
what's the difference
Consider:
def f(x: str) -> str:
return x
x = MyStrSubclass("yay")
reveal_type(f(x)) # str
T = TypeVar('T', str)
def f2(x: T) -> T:
return x
reveal_type(f2(x)) # MyStrSubclass
RIGHT
wait, so then why can't you have a single constraint? that seems useful in this context
that example errors bc of single constraint, which i assume was intentional to show the theory, but then what's the difference between your T and the bound T T = TypeVar('T', bound=str)?
Oh, I didn't realise you mean the constraint, I thought you were talking about TypeVar("T", bound=str), whoops.
Yeah, I believe TypeVar("T", str) is literally just str and hence not allowed due to being useless and confusing.
right, i think i understand then
A declaration like T = TypeVar("T", Foo, Bar, Baz) means that T resolved to exactly Foo, Bar, Baz.
For example:
import random
from typing import TypeVar
T = TypeVar("T", bound=int | str)
X = TypeVar("X", int, str)
def f(x: T) -> list[T]:
return [x, x, x]
def g(x: X) -> list[X]:
return [x, x, x]
if random.random() > 0.5:
wat = "yes"
else:
wat = 42
reveal_type(f(1)) # list[int]
reveal_type(f("foo")) # list[str]
reveal_type(f(True)) # list[bool]
reveal_type(f(wat)) # list[str | int]
reveal_type(g(1)) # list[int]
reveal_type(g("foo")) # list[str]
reveal_type(g(True)) # list[int]
reveal_type(g(wat)) # Error!
ah, so is T bound to Foo resolved to being the same thing as T is constrained to any subclass of Foo?
Yep
Well, to any "subtype" more broadly. For example int is a subtype of int | str, and so is Literal["yes", 42]
oh right yeah
ah okay! that helps me a lot, showing the conversion between them makes it rly intuitive
I'm getting "Any" back from creating a weakref proxy to an object. Is there a type that I can use instead that I can distinguish weakref proxies from the genuine article?
I want a dictionary to contain the "owned" version of objects, but for all outside interactions to be done through proxy objects or weak references.
Weak references can be made typesafe it looks like
however, i want to avoid accidental owning references to objects.
can you show the code maybe?
@dataclass
class Actor:
id: int
atype: str
pos_x: float
pos_y: float
bounds: pygame.Rect
dir: Dir
frame: int
def get_ref(self):
"""Obtain a proxy to the actor."""
return weakref.proxy(self)
Ah
weakref.proxy returns Any
yeah I don't think there's a good way to type weakref.proxy
I guess you could pretend that you're returning Actor form the method
I'd prefer for the proxy object type to have a unique type.
ActorRef was what I had in my head.
if you want a new type then create a protocol for it
class ActorRef(Protocol):
id: int
atype: str
pos_x: float
pos_y: float
bounds: pygame.Rect
dir: Dir
frame: int
@dataclass
class Actor:
id: int
atype: str
pos_x: float
pos_y: float
bounds: pygame.Rect
dir: Dir
frame: int
def get_ref(self) -> ActorRef:
"""Obtain a proxy to the actor."""
return weakref.proxy(self)
Well, Actor does match the protocol 🙂
ok maybe just dont make a protocol then 🤷
Hehe. Sorry, I have C++-brain.
though fwiw, I have found that reasoning about ownership, and even const correctness, leads to better code, even in a scripting language.
it send me to here
Well, that's what its type is
What file did it send you to?
it called base.py
oh... ty ❤️
Is it possible to overload a function with the same parameter type but depending on the value?
def extract(self, filename: str) -> pd.DataFrame | pd.ExcelFile: ...
I would like to make sure the typing is correct based on the filename extension, unless there is a different way from overloading?
Not currently like that no because constant string operations don't exist
But you can overload if you put suffix separately to the file name using a typing.Literal
Ah. I think I'll just assert instead rather than change that everywhere
this is what dependent types are for. it is possible to some extent in python with @overload and typing.Literal, but "literal" really requires you to pass in an actual specific string, not a variable that has that string in it, unless that variable itself is typed as a Literal
for example pandas uses this for their inplace=True kwarg which changes the return type to None
pandas-stubs/core/series.pyi lines 407 to 420
@overload
def drop_duplicates(
self, keep: NaPosition | Literal[False] = ..., inplace: Literal[False] = ...
) -> Series[S1]: ...
@overload
def drop_duplicates(
self, keep: NaPosition | Literal[False], inplace: Literal[True]
) -> None: ...
@overload
def drop_duplicates(self, *, inplace: Literal[True]) -> None: ...
@overload
def drop_duplicates(
self, keep: NaPosition | Literal[False] = ..., inplace: bool = ...
) -> Series[S1] | None: ...```
but no you can't do something like return ExcelFile if it's *.xlsx
is that correct to typehint varriable as dict[str]?
you need a parameter for both the key and value types, dict[KeyType, ValueType]
e.g. {"a": 1, "b": None} might be dict[str, int | None]
what am i doing wrong here? mypy says signatures 1 and 2 overlap with incompatible return types, which i guess they do.
but i see the exact same pattern in the official pandas stubs and those apparently work fine: https://github.com/pandas-dev/pandas-stubs/blob/v1.4.3.220822/pandas-stubs/core/series.pyi#L718-L753
@overload
def index_antijoin(df1: pd.DataFrame, df2: pd.DataFrame, inplace: Literal[True] = ...) -> None: ...
@overload
def index_antijoin(df1: pd.DataFrame, df2: pd.DataFrame, inplace: Literal[False] = ...) -> pd.DataFrame: ...
@overload
def index_antijoin(df1: pd.DataFrame, df2: pd.DataFrame, inplace: bool = False) -> pd.DataFrame | None: ...
def index_antijoin(df1: pd.DataFrame, df2: pd.DataFrame, inplace: bool = False) -> pd.DataFrame | None:
r"""Obtain rows from df1 for which the index is not in df2.
Equivalent to:
df1.loc[~df1.index.isin(df2.index)]
"""
return df1.drop(index=df2.index.intersection(df1.index), inplace=inplace)
i also tried adding the * to make inplace keyword-only as a wild guess, but that of course didn't help
only one of the two overloads should have = ...
so that if you don't pass that arg, exactly one overload matches
oh wow, i missed that in the Series type stub
i see
i'll have to try to wrap my head around that
not sure i fully understand but thank you because it works now 😆
_Indexable = TypeVar("_Indexable", bound=pd.DataFrame | "pd.Series[Any]")
@overload
def index_antijoin(
df1: _Indexable, df2: pd.DataFrame | "pd.Series[Any]", *, inplace: Literal[False]
) -> _Indexable: ...
@overload
def index_antijoin(
df1: _Indexable, df2: pd.DataFrame | "pd.Series[Any]", *, inplace: Literal[True] = ...
) -> None: ...
@overload
def index_antijoin(
df1: _Indexable, df2: pd.DataFrame | "pd.Series[Any]", *, inplace: bool = False
) -> _Indexable | None: ...
def index_antijoin(
df1: _Indexable, df2: pd.DataFrame | "pd.Series[Any]", *, inplace: bool = False
) -> _Indexable | None:
r"""Obtain rows from df1 for which the index is not in df2.
Equivalent to:
df1.loc[~df1.index.isin(df2.index)]
"""
return df1.drop(index=df2.index.intersection(df1.index), inplace=inplace)
interestingly now i get a return type problem, although i could just # type:ignore that one
pandas returns DataFrame | Series[Any] | None but i'm returning _Indexable | None which i guess is a tighter restriction
not sure if there's a way to assert that my version is actually correct, instead of casting or ignoring
I think your overloads are reversed? since your default is False but not passing the arg will match the True overload
you're totally right, yes
i also removed the = False from the 3rd one
i didn't have a clear sense of what ... really meant in a type annotation until now
the default itself is ignored, what matters is that there is a default
right, but in my mind i had it reversed, so that the overloads with ... indicated the use of non-default values
how should auto_variance interact with the suffix naming of type params?
you shouldn't use a suffix
at least, that's my opinion. The language won't enforce anything obviously
yet™️ 😉
(im not actually gonna change that, im just thinking for if they become public api then naming probably matters)
I think there was some issue about this and we don't want to change it now for compatibility reasons
python/cpython#103445
wild
the linked discuss thread is rather bad too
Hi. I have a function that takes in this type
dict[str, dict[str, str | set[str]]]
Issue is that I want to add something to the set, but python thinks I am adding it to the string rather
people[person_id]["movies"].add(movie_id)
Cannot access member "add" for type "str" Member "add" is unknown
So I came up with using isinstance, but that still failes. The full code I came up with is below
for row in reader:
person_id = row.get("person_id")
movie_id = row.get("movie_id")
if person_id and movie_id:
if isinstance(people_dict[person_id]["movies"], set) and isinstance(
movies_dict[movie_id]["stars"], set
):
people_dict[person_id]["movies"].add(movie_id) # still gives that message here
movies_dict[movie_id]["stars"].add(person_id) # and here
narrowing doesn't work if the key is not a literal
so you need to first put people_dict[person_id] in a variable and then check isinstance() on that_dict["movies"]
Thank you. That was clear
also this kind of sounds like you want to use a TypedDict
since the "stars" key should always be a set[str] and some other keys should always be a str
Never heard of a typed dict before.
!d typing.TypedDict
class typing.TypedDict(dict)```
Special construct to add type hints to a dictionary. At runtime it is a plain [`dict`](https://docs.python.org/3/library/stdtypes.html#dict).
`TypedDict` declares a dictionary type that expects all of its instances to have a certain set of keys, where each key is associated with a value of a consistent type. This expectation is not checked at runtime but is only enforced by type checkers. Usage...
Checking it out. That's nice that theres something better
So Eric Traut is going to keep working on pyright, right?
I saw a commit from him 4h ago
wait, he's quitting?
context?
Check his GitHub bio
Link?
https://github.com/erictraut "former Microsoft Technical Fellow"
😭
well that's interesting news...
Former starting this spring, for context. The bio has been there since at least May.
Who can answer me ? about my questions
can you have a TypedDict with optional keys? meaning that the key itself wont be present instead of its value being None...
you can use typing.NotRequired https://docs.python.org/3.12/library/typing.html#typing.NotRequired
if you dont ask you questions nobody will answer
damn, never seen it before, thanks
if you're using a version older that 3.11 you can use https://typing-extensions.readthedocs.io/en/latest/#NotRequired
haha I just got that error
I'll put it under if TYPE_CHECKING
do you guys define TypedDict's under if TYPE_CHECKING?
I don't, are you getting that error because you're using a version older than 3.11?
you need to import it from typing_extensions if that's the case
and typing_extensions itself must be >=4.0.0
yes
btw is there a way to type hint a list or tuple that takes ints, and the length is 1, 2, or 3. exactly like range()
technically you could tuple[int] | tuple[int, int] | ...
Yeah, you can only do it with tuples
I don't see him as part of the microsoft team, so...
unless you can hide what teams you're on
If he's no longer working on pyright, that's just the way things are in corporate projects. It's a double edged sword.
you can
I do
Eric has maintained pyright really well though. Most bugs are fixed within a few days.
anyone know why this generates an error in pyright? https://pyright-playground.decorator-factory.su/?gzip=H4sIABC_5GQC_12OQQ4CIQxF95ziZ9zAwguw8CpkMgLDAmZSqmESDy_VRMW_avv7fnvCflCKK1tUprSwUoG2DD72VCJS3jdizLV6Ylf83ZNSVx8QdLNIhfEQDgbnixRWoSvPvKxo70a0zNXLtjbfmYg836hgihuLPY1Az9Om30Zzn-g_8uWMlBsXf1_XzTwBeD0WtfAAAAA%3D
or closed as "not planned"
"Pattern will never be matched for subject type "Never""
that site never worked for me on my work network
It's not even a ssl or firewall issue. Just "The connection has timed out"
sorry here's the code
# pyright: strict
from typing import assert_never
def f(x: int | str ) -> str:
match x:
case int():
return "got int"
case str() as x_str:
return x_str
case _:
assert_never(x)
demo.py:11:14 - error: Pattern will never be matched for subject type "Never" (reportUnnecessaryComparison)
that's on the case _: line
In this case, you might need to add a # type: ignore
pyright considers the case impossible to reach since both int and str are covered
just looks like a bug
under normal operation, _ will never match
_ should be allowed as Never, this really does look like a regression still
it would be the same if it was without the match. ```py
def f(x: int | str) -> str:
if not isinstance(x, int | str):
raise TypeError("x")
...
not sure what the policy is for what contexts dead code is allowed in
why
i thought eric was slightly more ok with exhaustive code checks with match
maybe it's explicitly checking for a raise statement?
or assert?
not sure. Never messed around with assert_never
noticed bug in pyright: ```py
o: type = int
type(o).flags + 0 # works fine at runtime, but pyright think that lest thing is a property
can someone pls confirm it and create github issue (if it is not created yet)?
What does doing that even solve?
what if you do o: type[int]?
it fails in mypy too without type[int]
and tbh i don't see how it could work. the type checker can't know what __flags__ is, only that accessing it should be valid on a type[Any]
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
What would be the right way to type hint this?
return []
Just doing list acts as a catch all which is not what I want
__flags__ is a descriptor, so accessing this attr runs getter and return result
__flags__ is annotated as property, so this should work. I dont see why wouldn't this work
But typecheckers for some reason dont understand that type is an instance of type, so they dont execute getter, but return descritor itself
list[object] or list[Any]
That depends actually on other return statements
These are the other return statements list[tuple[str, str]] | None
How can you annotate only one return statement? That doesn't make sense
So there are different return paths based on what happens.
So
if a == 2:
return []
if a == 3:
return [("hello", "hello")]
return None
What is the problem?
Your function returns list or None.
list[?] | None
What your list can contain? Tuple of str and str. Does the empty list consist only of tuple[str, str]? Yes, because you can't prove that it doesn't.
So there is only one good way to annotate this function
Also i think that list[T] | list[Any] is the same as list[T]
Ok I think I am getting it. I was thinking there was a special way to tell python that there would be 3 different return types, an empty list, a list with tuples or None
Because an empty list is different from a list with tuples.
It isn't
There is no way to express length of container in type system
i'm not sure, maybe it has to do with needing to know something about the specific type
or maybe it has to do with how it handles descriptors
ah, maybe it's confused because type is the type of type so it can't tell if it's the type or the instance thereof?
list[Never] exists, doesn't it?
(Actually reading up on it, should substitute NoReturn in 3.11+ as well)
Is there a way to have a wrapper function that injects an argument but the base function definition still defines the argument as required (ie just not optional)
I have a situation where I am injecting an argument but because I am not actually providing that argument, the syntax is invalid and the return type gets ignored (shows up as Any).
An example of what I mean,
@inject_a
def foo(a: int) -> int:
return a
The problem here, is that if I call foo(), it will be displayed as Any. The "solution" seems to be making a an Optional[int] = None. But I don't like this solution because it implies that a is not actually required from within the function logic. When it is, it just gets handled in the case you don't provide it. (Obviously the actual case would be with a class of some type and not an int).
I wonder if there is a way to convey the intent. Concatenate seems to want to solve this purpose but it doesn't actually solve this issue. An example use case would be https://discord.com/channels/267624335836053506/1143885302503579708
When I think about it though, I can see a reason why this is imposible or really hard to fix. The most ideal situation is that the function signatrue would look like:
(method) def foo(
a: int | None = None
) -> int
But the actual definition would be:
@inject_a
def foo(a:int) -> int:
return a
This would be the most ideal implementation but I don't know that something like this exists or not
sooooo ... I half found a solution. If all i wanted was to make help work;
from inspect import Parameter, signature
def inject_a(function: Callable[Concatenate[P], R]) -> Callable[Concatenate[P], R]:
@wraps(function)
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R:
a = kwargs.get("a", None)
if a is None:
a = 10
kwargs["a"] = a
return function(*args, **kwargs)
sig = signature(function)
new_sig = list(sig.parameters.values())[:-1] + [
Parameter("a", Parameter.KEYWORD_ONLY, default=None, annotation=Optional[int])
]
sig = sig.replace(parameters=new_sig)
wrapped.__signature__ = sig
return wrapped
This will result in help producing:
Help on function foo in module __main__:
foo(*, a: Optional[int] = None)
And that is closer to what I would like. However, the signature on my "ide" is still
(function) def foo(
*,
a: int
) -> int
Not exactly ideal. However, if I remember correctly, this is a limitation of using vscode.
Concatenate will allow you to inject an argument into the first parameter, if that helps.
Something like
from typing import *
from functools import wraps
P = ParamSpec("P")
R = TypeVar("R")
A = TypeVar("A")
def inject_a(fn: Callable[Concatenate[A, P], R]) -> Callable[P, R]:
injected_a: A = ... # type: ignore (value of A obtained however you wish)
@wraps(fn)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return fn(injected_a, *args, **kwargs)
return wrapper
@inject_a
def function(a: int, b: int, c: str, d: float, **kwargs: bool) -> float:
print("hello", a)
return a / 2
reveal_type(function) # Type of "function" is "(b: int, c: str, d: float, **kwargs: bool) -> float"
(@livid plover)
That's—I believe—the standard way to inject values to a signature
Right, this would work in the case of just injection. But what I am talking about is an optional injection. In other words, I want a way having the signature not just remove a from the params but replace it with Optional[A]. And the function treats it as required.
I have been thinking of making a class which can store a list of callbacks and when you __call__ the class it will forward the call to all the callbacks. Is it possible to type hint this so that the callbacks are all the same and the __call__ of the class matches the callbacks?
This is what I have gotten so far: https://mypy-play.net/?mypy=latest&python=3.11&gist=d74cbbba49b699c08fa74a3817175ccd
Oh I think I got it: https://mypy-play.net/?mypy=latest&python=3.11&gist=39efa640017abf1780a2343130a5fd10
For the love of god is there a way to keep my linter with the original linting from the function when using a lru_cache decorator?
It's still technically correct
the answer hasnt changed since you last asked

don't use lru_cache
make your own wrapper with your "fixed" types
TFunc = TypeVar("TFunc", bound=Callable[..., Any])
def lru_cache(func: TFunc) -> TFunc:
return functools.lru_cache(func)
That worked perfectly.
Thank you so much!
If it's this easy, why hasn't functools been fixed yet? lol
because most people don't see it as a problem.
Wait but does this accept parameters for the decorator?
stdlib/functools.pyi lines 66 to 73
if sys.version_info >= (3, 8):
@overload
def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ...
@overload
def lru_cache(maxsize: Callable[..., _T], typed: bool = False) -> _lru_cache_wrapper[_T]: ...
else:
def lru_cache(maxsize: int | None = 128, typed: bool = False) -> Callable[[Callable[..., _T]], _lru_cache_wrapper[_T]]: ...```
Just copy/paste this?
that's the stdlib type definition.
If you copy it, you'll have the same types as before
Where #type:ignore?
Sorry, I'm not familiar with @overload at all
@overload
def lru_cache(*, maxsize: int | None = ..., typed: bool = ...) -> Callable[[TFunc], TFunc]: ...
@overload
def lru_cache(func: TFunc, /) -> TFunc: ...
def lru_cache(*args: Any, **kwargs: Any) -> Any:
return functools.lru_cache(*args, **kwargs) # type: ignore
The reason stdlib returns _lru_cache_wrapper is because you can call f.cache_info(), f.cache_clear() and f.cache_parameters() on it
stdlib/functools.pyi lines 54 to 64
@final
class _lru_cache_wrapper(Generic[_T]):
__wrapped__: Callable[..., _T]
def __call__(self, *args: Hashable, **kwargs: Hashable) -> _T: ...
def cache_info(self) -> _CacheInfo: ...
def cache_clear(self) -> None: ...
if sys.version_info >= (3, 9):
def cache_parameters(self) -> _CacheParameters: ...
def __copy__(self) -> _lru_cache_wrapper[_T]: ...
def __deepcopy__(self, __memo: Any) -> _lru_cache_wrapper[_T]: ...```
I'm not sure I understand the need to overload here?
it accepts a function or arguments, but not both
But is the function not an argument in this case?
the function is passed when used as a decorator
In fact, my type may be wrong because you may want to do @lru_cache(10) instead of @lru_cache(maxsize=10)
Hello I have 2 things that are annoying me.
Im on python 3.10.5
I am using the csv library that is built in (I think the abbreviation is stdlib?)
with open("data/Students.csv") as csv_file:
csv_reader = csv.DictReader(csv_file, delimiter=",")
for row in csv_reader:
row["X Coord"]
The "X Coord" is underlined with
Expected type 'int | slice', got 'str' instead
Also how do I except a exact list of strings as an argument to a function?
def __init__(self, house: Location, school: School, level: typing.Literal["E", "M", "H"]):
When passing a string to level I get
Expected type 'Literal["E", "M", "H"]', got 'str' instead
in response to the second question:
you need to pass exactly what is said in the Literal so you can pass either "E" or "M" or "H"
I have row[1] that is equal to "E" is that not exact enough?
however the first question no idea why its showing this error, even in the docs thats how they access the values from row
I should specify for the first one. It is not giving an error just a warning.
from the docs:
A short usage example:
>>> import csv
>>> with open('names.csv', newline='') as csvfile:
... reader = csv.DictReader(csvfile)
... for row in reader:
... print(row['first_name'], row['last_name'])
...
Eric Idle
John Cleese
>>> print(row)
{'first_name': 'John', 'last_name': 'Cleese'}
should be enough
this first error looks like a type checker bug. what type checker are you using?
Pycharm is the IDE not sure how to view the type checker
PyCharm has its own type checker, which is famously not very good
Ok, I will deal with it I guess. (No need to use a different type checker even if it is possible. This is just a small project.)
Could it be that there is no guarantee that row[1] is equal to one of those values? It is read from the CSV. I know that those are the only possible values for that column but python/typechecker would not
yes, that's right. The type checker can't see that the value is only ever one of those literals
yes pretty much thats the issue
Ah, makes sense. What is the recommended way to handle this? Instead of literal just use string and then throw an error if it is not in the list.
yes, or keep the literal type and do the check before you call that function
Thanks!
What is mypy yelling at me for here:
OriginalFunc = Callable[Param, RetType]
DecoratedFunc = Callable[Concatenate[str, Param], RetType]
Missing type parameters for generic type "OriginalFunc" [type-arg]
Missing type parameters for generic type "DecoratedFunc" [type-arg]
Missing type parameters for generic type "OriginalFunc" [type-arg]
error: Missing type parameters for generic type "DecoratedFunc" [type-arg]
You need to provide Param and RetType to your type alias usages
would TypeAlias help there?
what do people think about https://github.com/microsoft/pyright/issues/5830?
your last comment there matches my expectations, but you may want to add an example that includes something like this when it comes to pep 718
class Foo[T=int]:
def self(self) -> T: ...
def other[U](self, another: T, other: U) -> U: ...
for showing when a method should still be subscript able, while retaining other info from the class's generic
@trim tangle would you be able to update the pyright version for pyright playground please?
I wished you could configure the linter and use something like mypy instead 😞
There is a mypy extension
I have it installed, but you need to have a separate tab open and match the errors with the line numbers... not as convenient as having the warnings be displayed directly in the code
oh, the extension is that underfeatured?
what do u guys think of PEP 727? It does a good job of listing its counterarguments imo... verbosity is definitely a concern. but it seems like it could be a good way to formalise documentation patterns
!pep 727
Wow, this is... extremely verbose
yeah, even for like, simple type annotations, it's a lot
if you want to document all your parameters it feels like half of your type code will be the docs
Maybe a better solution would be something like argument annotations in Java
def hi(
@doc("the name of a user in the system")
to: str,
) -> None: ...
pass
``` or a new doc-string syntax ```py
def hi(
to "the name of a user in the system": str,
) -> None: ...
pass
Interesting approach but it seems rather, not so good. That would make the params pretty long..?
The proposed syntax is, with all due respect, a bit mad
When I though of something similar, editor support felt crucial to make it less in the way of everything, but that can't cover normal text editors
oh interesting ideas. and yeah, you're def justified about the verbosity
You can already do almost this with Annotated[str, "the name of a user in the system"]
I don't see why a new library symbol is even required
well it can't be mixed up with other things in Annotated
i was thinking that as well actually, unless there is any other use case for a string as metadata, which i guess there could be
I think everything in Annotated should have some unique wrapper obj around it because if there are multiple uses for the metadata everything will just get mixed up
i see your point. doesn't help for the verbosity, though. also, in a way, this proposal is kind of for introducing a new standard already
Nevertheless, this verbosity would not affect end users as they would not see the internal code using
typing.doc()And the cost of dealing with the additional verbosity would only be carried by those library maintainers that decide to opt-in into this feature.
I disagree. I often read library code, especially for libraries without an online reference (like FastAPI which the author of this PEP made, and starlette on which it is based)
Any authors that decide not to adopt it, are free to continue using docstrings with any particular format they decide, no docstrings at all, etc.
I thought the point of this PEP was to standardize argument docstrings, not add another convention?
I think the usability really just depends on everything fitting on one line, after that it's a lot of data in the signature. But still harder to ignore when reading if it's not in the docstring
If the annotation is large and complicated, I'm probably going to ignore it
agree again here, adding a new convention as a solution to existing ones is always a concern. there's a good xkcd for that
a lot of good points, ty for your input :)
Well, I don't have a good alternative tbh
I'm mostly just heckling from the back row
I think it could be nice but really would just have to see actual code using it to decide on that, but don't really think it has that big of a chance of going through when normal docstrings are somewhat standardised for params, google style is fairly low effort to parse visually and group all the docs together
one thing it'd make possible without additional work would be documentation for globals and other free standing vars instead of the current way that's not really standard
yeah, agree. i wonder why standardizing an existing format was rejected
oh, true, true
It's also accessible at runtime without having to write/import a complex parser.
I suppose, but the work for those is already done and I'd hope parsing data from docstrings isn't done that often to be much a bottleneck
I personally would like to: str | doc('...'), syntax
looking at a function definition and seeing symbol vomit that takes up 4 lines is awful. this only makes that worse.
surely just having a rigid format for the docstring and a flag that you're using the new rigid format gets you to the same place whilst keeping definitions looking like normal python?
Editors don’t have a way to support all the possible micro languages in the docstrings and show nice user interfaces when developers use libraries with those different formats.
pick/create one, make it the official standard, then this is a non-issue.
It wouldn’t solve any of the other problems, like getting editor support (syntax checks) for library authors, the distance and duplication of information between a parameter definition and its documentation in the docstring
if there was an actual standard, editors could/would totally be able to support it. duplication > syntax vomit. this is a problem solved in most other languages, why is python deciding to be a special fairy and do it a completely different way?
this 2nd one is the kind of thing id want to see, feels in like with how tooling detects variable documentation
ideally though there does need to be a way to get them at runtime
from a type system perspective this would have to be an intersection to make any sense
& doc(...) is also pretty good
no, hear me out
TheNameToSayHelloTo = NewType("TheNameToSayHelloTo", str)
def hello(to: TheNameToSayHelloTo) -> None: …
How would that work for int?
def hello(num: int("The number of people to say hello to")) -> None: …
better wrap it in quotes to prevent execution
type.__matmul__ for Annotated when 
def hello(num: int @ 'The number of people to say hello to') -> None: …
that's the nicest-looking one imo
def hello(the_number_of_people_to_say_hello_to: int) -> None: ...
adding @ support for all type objects plus all the type-like things sounds like a bad idea to me
Especially when it's not really necessary
you don't think it's bad?
sorry, I completely screwed up the sentence
How about support for __rmatmul__ on annotation types?
damn that's evil
class DocType:
...
def __rmatmul__(self, typ):
return Annotated[typ, self] # doesn't actually work for typing
def doc(doc: str) -> DocType:
return DocType(doc)
foo: str @ doc("this is a foo")
It would basically make it an opt-in feature
I'd rather we not overload operators for documentation. I don't even see a need for this, there are existing IDEs that can parse inline variable documentation already, so clearly it's already possible, why is this not just the responsibility of documentation tooling?
It doesn't seem to belong in the type system.
The intent is mostly to give easy runtime access
That's literally not in the rationale or motivation section, so I don't know why you think that's the intent.
and there's Already inspect.cleandoc, as well as sphinx's methods if you use sphinx to get runtime info
quoting the motivation "It’s not possible to programatically test these docstrings, for example to ensure documentation consistency for the parameters across several similar functions, at least not without implementing a custom syntax parser."
programtically doesnt necessitate "At runtime" and in context, it's talking about editors and consistency
so if that's actually the motivation, there should probably be a clarification that it is referring to runtime access as well
I would count a unit test that inspects the metadata as "runtime"
I would not. Tests are not runtime code. And saying this makes it possible is misleading, because this is already possible with existing documentation tools.
and in the full context, it's talking about in editors and the impact on maintainers of the seperation of the documentation from the variable:
Because the docstring is in a different place in the code than the actual parameters and it requires duplication of information (the parameter name) the information about a parameter is easily in a place in the code quite far away from the declaration of the actual parameter. This means it’s easy to refactor a function, remove a parameter, and forget to remove its docs. The same happens when adding a new parameter: it’s easy to forget to add the docstring for it.
Editors can’t check or ensure the consistency of the parameters and their docs.
It’s not possible to programatically test these docstrings, for example to ensure documentation consistency for the parameters across several similar functions, at least not without implementing a custom syntax parser.
Nothing here indicates at runtime, and this reads as "makes it easier to write an editor plugin"
!typehinting
A type hint indicates what type a variable is expected to be.
def add(a: int, b: int) -> int:
return a + b
The type hints indicate that for our add function the parameters a and b should be integers, and the function should return an integer when called.
It's important to note these are just hints and are not enforced at runtime.
add("hello ", "world")
The above code won't error even though it doesn't follow the function's type hints; the two strings will be concatenated as normal.
Third party tools like mypy can validate your code to ensure it is type hinted correctly. This can help you identify potentially buggy code, for example it would error on the second example as our add function is not intended to concatenate strings.
mypy's documentation contains useful information on type hinting, and for more information check out this documentation page.
#bot-commands
sorry
re: parameter documentation, how about bare strings below the argument?
def example(
arg_1: int,
"""documentation for arg_1"""
arg_2: int,
"""documentation for arg_2"""
) -> int:
...
Why below? This is not consistent.
First you read module docstring, and them module itself
Class docstring - class body
Function docstring - function body
Variable docstring - variable declaration
def example(
arg_1: int, # documentation for arg_1
arg_2: int, # documentation for arg_2
) -> int:
...

And it's in python already!
afaik it's consistent with attribute docstrings in other contexts per pep 224, usually done with a docstring below the attribute assignment/declaration
idk how many checkers actually pick those up though
or if it was ever made into an official thing
vsc does as does pycharm iirc
The reason why pep224 was rejected also applies to this
how so?
i can see the ambiguity for someone who'd be encountering these for the first time, yeah
Not just the first time. It's also just hard to remember
ive personally never had any problems with it
oops hit enter a bit too early. id say in comparison to the other proposed solutions it balances tradeoffs well.
also normally the doc string makes it obvious enough as it contains a reference to the name
same
To me doc-declaration order seems more logical, than declaration-doc order, and i always thought that rejected pep 224 proposed doc-decl, and not decl-doc
Referencing a name in docstring sounds redundant
the problem with that would be this:
def example():
"""is this a docstring for the function or the attribute below?"""
attribute = ...
maybe im just bad at writing docstrings but i think it becomes unavoidable
It makes sense to sweep all the documentation for a function and its attributes into a single docstring
eg```py
def parse_trade_url(url: StrOrURL, /) -> TradeURLInfo | None:
"""Parses a trade URL for useful information.
Parameters
-----------
url
The trade URL to search.
or even ```py
async def edit(self, *, name: str | None = None, tagline: str | None = None, avatar: Media | None = None) -> None:
"""Edits the chat group.
Parameters
----------
name
The new name of the chat group.
tagline
The new tagline of the chat group.
avatar
The new avatar of the chat group.
"""
No, this is ok
I thought you are talking about x: int """x: do X-related stuff""" where you prefix docstring with arg name
oh right understood
hmm, how about requiring some indentation as well?
def example(
a: int,
"""
does the extra indentation make it easier to
tell/remember which one this belongs to?
"""
b: int,
): ...
Indentation is not important in python (except for top-level things), so it will be inconsistent
Also, function and class docstring are placed at the same level as the thing they are describing
that's quite the a load-bearing 'except' there 😅
aren't they usually supposed to be placed one indentation level further, inside the thing? is that how it's supposed to be done?
You can move everything inside any brackets left and right, and nothing will change. Args are inside brackets, their placement is nor important. So making docstring indent-related is weird
They are placed at the same level, as the thing's body, so they are aligned with everything after it
seems like you're talking about indentation in the context of expressions, whereas docstrings are generally related to statements where indentation definitely does matter.
is the level of a function/class definition the indentation level of its body or its signature?
yeah it's technically possible to do something like this
def example(
a: test,
b: test,
):
pass
but why would anyone do this? what information is it supposed to convey? and how would it make using indentation to relate docstrings to attributes inconsistent? the rule would specifically be about an indented string following an argument, not arguments themselves.
for backward compatibility reasons, code like that (or with any other weird indentation style) would have to continue to work after your proposal
my thinking was that the new rule would only target indented strings after an argument, not other indented arguments, so nothing would change for any old code without the indented docstrings. would that be possible with the current parser?
It's probably possible, but I don't think the tokenizer currently even tracks indentation within parentheses
def example(
arg_1 "foo bar": int,
arg_2 "baz": int,
) -> int:
...
I'm still highly partial to rst docstrings 🤷♀️
rst is one of those things where it's just super annoying and hard to remember compared to markdown, but it's WAY more powerful and can be reasoned about programmatically much easier
What does a * mean in types?
Expression of type "DataFrame*" cannot be assigned to declared type "T@Imputer"
Type "DataFrame*" cannot be assigned to type "T@Imputer"
its inferred sort of
Looks like a pointer type
How do I get rid of the star then? Idk whether that is causing this error or not 😆
the star isnt causing the error
well probably
can you share the code
and the full error
T = TypeVar("T", pd.DataFrame, NDArray[np.float64])
if not isinstance(X, pd.DataFrame):
X = pd.DataFrame(X)
#
error: Expression of type "DataFrame*" cannot be assigned to declared type "T@Imputer"
Type "DataFrame*" cannot be assigned to type "T@Imputer" (reportGeneralTypeIssues)
is there a reason youre using constraints here?
That makes sense. You function is generic, type of X is T. You are assigning non-T to X
cause you should probably be using a union
Ahh, I don't understand these generics very well. I just looked at the docs and didn't want a bound, so used that instead
Wouldn't a union also be a constraint?
I think you dont need TypeVars there at all
A union is a looser constraint than a typevar
When using a typevar then all the uses of the typevar inside a function must be for the same type
whereas two instances of a union can be different types inside the union
You can try annotating X as DataFrame
Oh, its just
class Imputer(Generic[T]):
def __init__(self, method: Methods, features: list[int] = []): ...
def transform(self, X: T) -> pd.DataFrame: ...
This works. I keep forgetting that I shouldn't keep the same variable name if assigning a different type
How can I properly type annotate the following decorator function so that the type checker can identify both x and func as members of obj?
from __future__ import annotations
from typing import Protocol, TypeVar
class Decorated(Protocol):
def func(self) -> None: ...
T = TypeVar('T')
def decorate(cls: type[T]):
class _Decorated(Decorated, cls):
def func(self) -> None:
return None
assert issubclass(_Decorated, cls)
return _Decorated
@decorate
class MyClass:
def __init__(self, x: int) -> None:
self.x = x
obj = MyClass(1)
obj.x
obj.func()
Or is this simply impossible without explicit type intersections?
its not possible
mypy and pyright can infer intersections to some degree but they cant really escape the scope they are created in
I see, I have been trying to use the assertions to narrow the inferred output type of the decorator but it doesn't appear to work. this must be why
Oh well, thanks for the info
I have a func with three parameters that can either all be specified or all be None, is there a way with overloads to specify that? I could switch to one param with a tuple[a,b,c] | None setup but I would prefer to have them individual params
hmm I think I got it (yay for rubber ducking): https://mypy-play.net/?mypy=latest&python=3.11&gist=c6b8536b96e4924a8e68b40dd779828c correct?
overload with no params and overload with all params non-optional
Did you send the correct link?
If I remove the params I get Overloaded function implementation does not accept all possible arguments of signature?
Should be?
It has a fib function
weird, link works for me. This is what it should have:
from typing import overload
@overload
def a(a: int, b: str, c: float): ...
@overload
def a(a: None, b: None, c: None): ...
def a(a: int | None, b: str | None, c: float | None):
pass```
(The function only has one parameter)
Put default value in the implementation
yeah I realized I forgot that thanks!
that second overload says you have to pass a(None, None, None), you should probably add defaults there
(That will then allow an overload of def a(): ...)
Thanks
following django tutorial:
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
pylint is complaining that __str__ does not in fact return a string. I don't want to make a habit of ignoring pylint, so should I str() it or can I shut it up somehow?
Why on Earth am I getting this?
how is References defined? is it an enum?
well if that's so, then GS_VALUE is just a variable
it just holds a string
Yeah, it's legacy company code :/
the function expects References type though, i.e. an instance of that class
can you change references to an enum?
if not, you can either do str or a Literal of all of those options as your type-hint for that func
That would make it so you'd have to call it like References.GS_VALUE.value, right?
no
in fact that would not work
because .value would hold the actualy string
to satisfy the annotation, you'd want to pass in one of the enum variants
but yes, inside of the function, you'd do .value to get the string
Yeah, that's a no go
.
This snippet is so spread across the project that it'd probably break something
you can always change that, that would be the ideal solution
just find all references of the References class being used in the code
It would be, but if anything breaks I'm probably fired lol
It's in the high hundreds
LSPs can do all of the finding for you, you might even be able to use some tools like sed to replace the string, but yeah, I get that you don't want to do that with this kind of project
how do other functions in the codebase type-hint accepting one of these?
just look at those and adhere to the established style then
I'm the only one that type hints lol
Isn't there a way to customize Enum so that I can get the value simply by calling the enum name?
if you really want to be fancy, do: py class References(object): A = "A" B = "B" TYPE: ClassVar[TypeAlias] = Literal["A", "B"] and use References.TYPE in the annotation
ideally, the constants there should also be annotated with ClassVar[str]
Ooh let me try
StrEnum should act like a string in some ways
if you'd do that, the type hint obviously couldn't be satisfied, since you'd be passing in that enum value, not the instance
Where do I import TypeAlias from?
typing module
:/
What version you using
3.8
you can still do py class Foo(str, Enum): ... though StrEnum is like 3.10+ or something like that
I see, it was added in 3.10
Although funny cause typealias only really lasted 2 versions
The thing is, I need Foo.X to be "A" and not <Foo.X: 'A'>
We use it as function calls mostly
you can do Foo.X.startswith
for example
or any other str methods
or pass it to a func that expects str
however repr will be <Foo.X: 'A'>
you can probably override the repr if you have to, but know that it could make it annoying to distinguish from a regular string in logs / when debugging
Parameters for ORM lookup
Not sure if Django will strify it
It is already a string
the thing is, the type is literally an instance of the string class
it's just not literally a str, it's an instance of a subclass that inherits from str
I mean whether it would use __repr__ or __str__
try and see 
from collections.abc import Iterator
...
def __iter__(self) -> "Iterator[dict[str, t.Any]]":
return iter(self.__dict__.items())
``` is this proper way to annotate `__iter__` dunder method?
yeah
would that be Iterator[tuple[str, t.Any]]?
actually thats right, thanks
Except why include quotes...
cause i imported it in TYPE_CHECKING if
I c
from __future__ import annotations

yes spam it everywhere, spread the holy verb
I cant cause some of my code depends on annotations
Making them strings would break it
mh? this would not require them as strings, means that you are able to do the following
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from asyncio import AbstractEventLoop
class Example:
loop: AbstractEventLoop
Yes, cause it makes all annotations as strings
Which Like i said would ruin my code
!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__`. In addition, forward references encoded as string literals are handled by evaluating them in `globals` and `locals` namespaces. For a class `C`, return a dictionary constructed by merging all the `__annotations__` along `C.__mro__` in reverse order.
The function recursively replaces all `Annotated[T, ...]` with `T`, unless `include_extras` is set to `True` (see [`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated) for more information). For example:
You should probably be using this instead of annotations anyway
Well i'm not the one who coded the lib im using 
Hello, why am I getting this type checking error? (both underlines have the same error)
Here's my (relevant) code: ```py
from typing import Any, Callable, Concatenate, Coroutine, ParamSpec, TypeVar, overload
from discord import Interaction
from discord.app_commands import Command, Group
from discord.ext.commands import Cog
CommandGroupT = TypeVar("CommandGroupT", bound=Group | Cog)
P = ParamSpec("P")
T = TypeVar("T")
Coro = Coroutine[Any, Any, T]
CommandCallback = (
Callable[Concatenate[CommandGroupT, Interaction, P], Coro[T]]
| Callable[Concatenate[Interaction, P], Coro[T]]
)
def log_slash_command(command: Command[CommandGroupT, P, T]) -> Command[CommandGroupT, P, T]:
callback = command._callback # typed as a CommandCallback[CommandGroupT, P, T]
@overload
def wrapped(self: CommandGroupT, interaction: Interaction, *args: P.args, **kwargs: P.kwargs) -> Coro[T]:
...
@overload
def wrapped(interaction: Interaction, *args: P.args, **kwargs: P.kwargs) -> Coro[T]:
...
@ TODO: @wraps(callback)
def wrapped(*args, **kwargs):
if len(args) == 0:
raise NotASlashCommand
elif len(args) == 1 or isinstance(args[0], Interaction):
interaction, *command_args = args
self = None
assert isinstance(interaction, Interaction)
else:
self, interaction, *command_args = args
assert isinstance(self, Group | Cog)
assert isinstance(interaction, Interaction)
print(f"{interaction.user.mention} used `/{command.name}` with arguments {command_args!r}")
if self is not None:
return callback(self, interaction, *command_args, **kwargs)
else:
return callback(interaction, *command_args, **kwargs)
command._callback = wrapped
return command
This code produces the same issue, removing the discord related stuff ```py
P = ParamSpec("P")
T = TypeVar("T")
Callback = Callable[Concatenate[int, int, P], T] | Callable[Concatenate[float, P], T]
def wrapper(f: Callback[P, T]) -> Callback[P, T]:
@overload
def wrapped(a: int, b: int, *args: P.args, **kwargs: P.kwargs) -> T:
...
@overload
def wrapped(a: float, *args: P.args, **kwargs: P.kwargs) -> T:
...
def wrapped(*args, **kwargs):
if isinstance(args[0], float):
a, *rest = args
assert isinstance(a, float)
return f(a, *rest, **kwargs)
else:
a, b, *rest = args
assert isinstance(a, int)
assert isinstance(b, int)
return f(a, b, *rest, **kwargs)
return wrapped```
I don't really understand what Arguments for ParamSpec "P@wrapper" means
clearly f(a, b, *rest, **kwargs) includes all of the arguments
Why are you unpacking the args if you're just going to add them as positional afterwards?
in the actual code I need to use the unpacked args for logging
removing the if-statement is equally unsuccessful
(that was my original code, I switched it to this: py if self is not None: return callback(self, interaction, *command_args, **kwargs) else: return callback(interaction, *command_args, **kwargs) in an attempt to fix that error)
I don't think the way you're trying to accomplish this is implemented yet
bummer
Perhaps you could try subclassing Command instead? The thing is (besides ParamSpec argument unpacking not being supported) that P from the wrapped signature isn't the same P as the one in the generic command argument in the log_slash_command signature. The outer one does not include Interaction, but the inner one does.
it seems like it doesn't realize that (a, b, *rest) is P.args, so it's telling you that some args are missing from the call site callback(...)? at least that's my guess
higher-order functions and callbacks are still really hard with python typing
Perhaps you could try subclassing Command instead
You mean such that I would return a new instance of a different class, rather than the original (modified) instance? This is not an option for several reasons.
the outer one does not include
Interaction, but the inner one does
Pdoes not include theInteraction. Specifically, theCommandclass definescallbackasCallable[Concatenate[Interaction, P], ...](likeCommandCallback, above)
it's not P.args
*rest is P.args
a, b would be a completely separate int, int
see how Callback = Callable[Concatenate[int, int, P], T]?
and that doesn't explain why return f(*args, **kwargs) doesn't work
Here's my current code (using discord.py) if anyone would like to take another look https://paste.pythondiscord.com/MAVA.
constraints:
- the
commandargument tolog_commandmust be typed asapp_commands.Command[Cog | Group, x, y] - the return value of
log_commandmust be the same instance that was passed as an arguments
otherwise everything else is fair game
discord.py has a way to register an "After" command handler
could look into what's wrong, but sounds like more effort than using that here
is type[T] a new way of doing typing.Type[T]? or its something else
!pep 585
What libs let you overload functions with genericalias typehints and unions like tuple[int|float]|float
frozenset # typing.FrozenSet
there is a thing called frozenset in python builtin xd?
yep

!d functools.singledispatch
@functools.singledispatch```
Transform a function into a [single-dispatch](https://docs.python.org/3/glossary.html#term-single-dispatch) [generic function](https://docs.python.org/3/glossary.html#term-generic-function).
To define a generic function, decorate it with the `@singledispatch` decorator. When defining a function using `@singledispatch`, note that the dispatch happens on the type of the first argument:
```py
>>> from functools import singledispatch
>>> @singledispatch
... def fun(arg, verbose=False):
... if verbose:
... print("Let me just say,", end=" ")
... print(arg)
```...
I've delayed fully learning TypeVars and generics for a very long time now, though I'd like to get my definitions straight first.
Correct me if I'm wrong on any of this, but to my knowledge generics are just variables in place of actual references to existing types like int, float or another user-defined class. These generic variables can be a collection of multiple existing types, like str or bytes, a subtype of something or just a reference to itself, where it can be anything.
sounds about right
I have a small tutorial on generics: https://decorator-factory.github.io/typing-tips/tutorials/generics/
help
I define 2 classes
class 1 has a function that returns object of type class 2
but class 2 is defined after class 1
so I cant type hint the object of class 2
since it's not defined yet in class 1 definition
what is the solution
In [1]: from __future__ import annotations
In [2]: class A:
...: def meth(self) -> B:
...: return B()
...:
In [3]: class B:
...: def meth(self) -> A:
...: return A()
...:
In [4]: A().meth()
Out[4]: <__main__.B at 0x12eb712b650>
In [5]: B().meth()
Out[5]: <__main__.A at 0x12eb7008510>
In [6]: A.meth.__annotations__
Out[6]: {'return': 'B'}
In [7]: B.meth.__annotations__
Out[7]: {'return': 'A'}
In [1]: class A:
...: def meth(self) -> "B":
...: return B()
...:
In [2]: class B:
...: def meth(self) -> "A":
...: return A()
...:
In [3]: A().meth()
Out[3]: <__main__.B at 0x151d1bb8fd0>
In [4]: B().meth()
Out[4]: <__main__.A at 0x151d1da6ed0>
In [5]: A.meth.__annotations__
Out[5]: {'return': 'B'}
In [6]: B.meth.__annotations__
Out[6]: {'return': 'A'}
what future is?
Source code: Lib/__future__.py
__future__ is a real module, and serves three purposes:
• To avoid confusing existing tools that analyze import statements and expect to find the modules they’re importing.
• To ensure that future statements run under releases prior to 2.1 at least yield runtime exceptions (the import of __future__ will fail, because there was no module of that name prior to 2.1).
• To document when incompatible changes were introduced, and when they will be — or were — made mandatory. This is a form of executable documentation, and can be inspected programmatically via importing __future__ and examining its contents...
is it recommanded?
it can be useful in a case like yours
It works with generics too?
No, that's not possible in general
Bruh
It is only possible in some very limited cases, like tuple[str, int]
though in this case it doesn't work with tuples
Why cant generics like list[str] be overloaded
You cannot really know at runtime if something is a list[str]
Y
You can iterate over it and check its contents
Just make sure it has __iter__, and isnt str, bytes or bytearray tho
class Foo:
def __init__(self) -> None:
self._str_lists: list[list[str]] = []
self._int_lists: list[list[int]] = []
@singledispatchmethod
def add(self, arg):
raise NotImplementedError
@singledispatchmethod
def add(self, arg: list[int]):
self._int_lists.append(arg)
@singledispatchmethod
def add(self, arg: list[str]):
self._str_lists.append(arg)
foo = Foo()
list1: list[int] = []
list2: list[str | bytes] = ["something"]
list3: list[dict[str, int]] = []
What should happend on these lines?: ```py
foo.add(list1)
foo.add(list2)
foo.add(list3)
So empty lists will not be allowed at all? That sounds strange
They are technically allowed, because you have a default function without any annotations to fall back to
Thats where they would go unless there are more specific fitting type hints
Second is not a list[str] because lists are invariant. You're not allowed to do x: list[str] = list2 by a type checker, why should this be different?
!paste
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.
Imagine if you had
class Foo:
...
def join(self) -> str:
return "".join(itertools.chain.from_iterable(self._str_lists))
If you later add a bytes object to list2, you are in big trouble
I have more examples here: https://decorator-factory.github.io/typing-tips/faq/runtime-checking/
Python is a dynamically typed language, and type information is pretty much lost at runtime.
Hm ok
Your particular application might be fine with doing some of these assumptions. That's why there are libraries like typeguard, pydantic, beartype etc.
It seems like array.array has room for a generic type, but when I attempt to use it I get a compile error about array not being subscriptable.
what version are you on?
it only appears to have been made subscriptable at runtime on 3.12+
3.11.5. So array[int] won't work until then? Got it.
Okay, I've got mypy hooked up to pyxel but I am finding that I am getting some errors that are straight up wrong
ichor\game.py:18: error: "Image" has no attribute "load" [attr-defined]
ichor\game.py:19: error: "Image" has no attribute "load" [attr-defined]
ichor\game.py:20: error: "Image" has no attribute "load" [attr-defined]
And this is the pyi I'm dealing with... https://paste.ee/p/Pp9cB
It seems like there's multiple definitions for class Image, a foward declaration that simply has ... and a normal one that comes later.
weird, wheres this pyi from?
why is codecs.CodecInfo.decode annotated as def decode(input: bytes, errors: str = 'strict') -> tuple[str, int]:, if at runtime input is not bytes but memoryview ?
also, forcing users to shadow builtin name is bad
im on 3.11, and pyright screams at me
that is confusing, because they are incompatible
memoryview doesnt support .decode, so i have to do mv.tobytes().decode(), but it is still bad because bytes doesnt have .tobytes
well the change was made retroactively
yeah imo it was a dumb feature
now if you want the old behaviour you need to use collections.abc.Buffer
https://grep.app/search?q=decode(input%3D
it says that nobody is calling .decode( with input=, so it is safe to change stupid name of this arg
look at all this pain: https://grep.app/search?q=input.tobytes&filter[lang][0]=Python
open an issue
done
Hey! Dont cross-post in unrelated channels!
in a PEP 649 world it's still possible to have stringised annotations right?
if you manually annotate something as : "SomeString"
You can use whatever you want
If you used string, you will get string
yeah ok thought as much
damn
just like in life
this type hinting thing just got dark
lmao
i want a type alias that takes exactly two parameters i.e. X, Y and evaluates to Annotated[X, list[Y]]. is that doable?
generic type alias* to be clearer
i tried:
from typing import TypeVar, Annotated
X = TypeVar('X')
Y = TypeVar('Y')
XY = Annotated[X, list[Y]]
a: XY[int, str] = 12
which mypy didnt like:
error: Bad number of arguments for type alias, expected: 1, given: 2 [type-arg]
nope, annotated is only generic over its first parameter
well its not possible doing it that way
well ok lmao it does work with 3.12 typealias syntax
ohhh ok cool, like one of these? TIL, thanks
type Combo[X, Y] = Annotated[X, list[Y]]
tried using this in mypy playground and it keeps saying error: invalid syntax; you likely need to run mypy using Python 3.12 or newer even though 3.12 is chosen from the top, both for mypy 1.5.1 and master branch. is there a specific branch ill need to clone or a new version to wait for?
also do you know if there's gonna be any kind of backport for this in typing-extensions maybe?
