#type-hinting
1 messages · Page 30 of 1
!d object.set_name
object.__set_name__(self, owner, name)```
Automatically called at the time the owning class *owner* is created. The object has been assigned to *name* in that class:
```py
class A:
x = C() # Automatically calls: x.__set_name__(A, 'x')
``` If the class variable is assigned after the class is created, [`__set_name__()`](https://docs.python.org/3/reference/datamodel.html#object.__set_name__) will not be called automatically. If needed, [`__set_name__()`](https://docs.python.org/3/reference/datamodel.html#object.__set_name__) can be called directly...
I dont think I understand how I would use this inside of the foo example
also why would ruff complain here, does it just not know __class_getitem__, or is it because its inside of a function param
does the stat name have to be encoded into the type like this? or rather, would it not be acceptable to have the function take PlayerStat without a particular name? or do you intend to pass the appropriate stat at runtime based on the annotation?
or do you intend to pass the appropriate stat at runtime based on the annotation?
exactly this
the name can be whatever the user wants it to be, though it being a PlayerStat, and not a GlobalStat is importatnt
class Foo:
def __class_getitem__(cls, name: str) -> type[int]:
return int
x = Foo['asdf']
reveal_type(x)``` Pylance thinks its of type `Foo` here, why
To be fair, it's trying to resolve asdf to a type
because Pylance only knows about "proper" generics
class Foo[T]: ...
x = Foo[str]
reveal_type(x)
Type of "x" is "type[Foo[str]]"
if you defined this via class getitem, it wouldn't understand it
is __class_getitem__ not really supported
it's an implementation detail
For types
not strings
Can you do something with this? ```py
from typing import NewType
Foo = NewType("Foo", int)
def use_foo(foo: Foo):
pass
use_foo(Foo(0))
Foo is int at runtime.
ah well that kinda sucks
dont think so, I need to actually be able to create Foo instances, and also annotate with it whilst giving it a name
What's the point in annotating it as int?
actually it is fine if it thinks it is the same class
just picked int as just an arbitrary class
brings me back to this though ```py
from abc import ABC
class Stat(ABC):
def class_getitem(cls, name: str) -> ...:
return something_based_on_the_name
class PlayerStat(Stat):
...
def foo(p: PlayerStat['whatever']) -> None:
pass
print(foo.annotations)
{'p': <class 'int'>, 'return': None}```
pylance is okay with it, ruff is not
does ruff use reveal_type also, just Pylance giving me anything with that
im in vscode havent really looked into much of installation for ruff just clicked some buttons like a month ago
ruff is dumb when it comes to type hints. That being said, this is an invalid type hint.
Only Literal is allowed to have strings
well having it be PlayerStat[Literal['whatever']] kinda defeats the purpose of not making it an attribute
this is kind of annoying, perhaps an attribute might be best
Consider using Annotated instead.
well it would still be a very large annotation
You can reuse annotated types
and this is what users would have to enter, some who wont know anything about annotated nor literal
Are you the one calling the function, or the developer?
the developer is creating the foo function
yes, but are you the one consuming it?
so only this should be the least struggles as possible
Use Annotated.
the library is the only one calling it yes
Example: ```py
def foo(p: Annotated[int, PlayerStat(name="whatever")]) -> None:
pass
or ```py
Whatever = Annotated[int, PlayerStat(name="whatever")]
def foo(p: Whatever) -> None:
pass
have you considered using the parameter name to define the stat instead of putting it in the type?
^ this is what fastapi does for query arguments
yes but the output can have names that are not just a-zA-Z0-9_
eh this all seems a lot of trouble for a couple of characters less to type
do they often have names that wouldn't be valid python identifiers? if not, you could still use that approach and offer a more advanced syntax for the occasion, e.g. py @stat(xyz="weird stat name") def foo(abc: Stat, xyz: Stat): ...
**kwargs with Unpack[TypedDict] could be another option.
would work
but eh I feel like it would make it so complicated
I think I will just make it an attribute and be done with it, thanks so much for the help
like, complicated for the end user or complicated for you to implement?
in the latter case, i think you can handle it with something fairly simple like this
Dose you know how to annotate the decorator so that input types are shown except *args and **kwargs.
def hi(op, *nope, **kwargs):
... # decorator implementation
@hi
def function(hansne, iejeb, okeenn):
... # function implementation
It shows the function input as args and kwargs.
class EmptyDict(TypedDict):
pass
def foo(arg: str, arg2: int, *args: Unpack[tuple[()]], **kwargs: Unpack[EmptyDict]):
...
I want it to be very simple for the users, Ill put myself through anything B)
I do like this approach
That can also be used to make aliases
mmm anyway I gotta go for a bit ill play around with it some more later and see where it gets me, thanks again
I can't do that cause , before parsing data , it's anonymous
What are the differences between using these two type aliases for numpy?
Vector = np.ndarray[Tuple[int], np.dtype[np.float64]]
Vector = NDArray[np.float64]
Like, I am aware that the first one is also giving you more information about its shape. But I did a few experiments with Mypy and I did not manage to trigger an error by accessing a dimension that does not exist. Therefore, the only difference I see is about documentation
Is there a way to trigger Mypy errors in these cases? (without any other library than numpy itself).
Edit: I just saw that NDArray is defined as a type alias of the first case for any size. Then what's the purpose of type hinting the size?
Can somebody explain me why in this code decorator deco allows non-int values, while deco2 doesn't?
https://pyright-play.net/?code=GYJw9gtgBALgngBwJYDsDmUkQWEMoCCKcANFAMICGANtZQEbUCmZ5YKAxpTEyty1ADivJiCQcyABUohKEAMoImEqABUArgmZlViJgDUZAWABQp1VAC8avYZAAKAOSrHASlOSrUabIVKOTpJupqYAJkzAUOEcYPYyaABcaq4AtAB8FDR0jEwA2rlUtAzMuZI6ALrlrFnFeWVqlQmmUC1REW0xAPqoKKL2wOqcCYXZJfWq5a5Q6ZlFOaUVTSatK1ADnAB0qgSCXvHNqy0gTDDqIChrgxwhy63Hp%2BcdYN0ovSA34ZHRYABMcSCJZIzEa1fIg%2BZsTj8Pg8XJEUhqKRVBrI8ElSFcHgwvLwnRIirlJYrT5PF5vfpXYY1CHsTG8fhw4h47zIiZTYHU9G06EM3GIlmLA6HdYcLY7PYAoWre5nC4iw4K1pSqUyx7fMmiG4cOgAZx1Ql4ACFKDqmPZhG9xLk2UTWiTOi8kDAHfZTdRgGRKElVOyMgA5dhMW2HN3ADZetR7LW6-UW1RMHUwc1Gk15VAwSbBtqRB2oJ0u0OepLpsj0YsoGC%2BqAB3pZlY6zR9VwbXMofOdOLuW6HKX1pjujZlzAVrz0XtK7tQAAC33sAEYu8T2iLOpRXf2PVAh%2Bmq%2Bm63cTrKoKHB1AANRb5WTmfKWIxbDMAAe87IP1ci7ty6unXo6-dpfLStpgyPdxyOQ9HhPehz0vScpRvGI-gXKUSRXAJCy3QDdwrfdwIeC4oJgsc4Ovb4-nvLQmGfOdX3fFCv04TpQj-TdtwrbCYFwqBVQIjdTwvYiVivDBrDjBMkwAVjIAA2VwgA
(And how should I share code here?)
Formatted code block or a pastebin-esque link would work
Block example:
```py
code
```
Ok, noted
from typing import Any, Callable, Concatenate, Generic, ParamSpec, Tuple, TypeVar
T = TypeVar('T')
P = ParamSpec('P')
def deco(arg: T)-> Callable[[Callable[P, T]], Callable[P, T]]:
def deco_inner(func:Callable[P, T]) -> Callable[P, T]:
func.TAG = arg
return func
return deco_inner
def deco2(arg: T)-> Callable[[Callable[Concatenate[Any, T, P], T]], Callable[Concatenate[Any, T, P], T]]:
def deco_inner(func:Callable[Concatenate[Any, T, P], T]) -> Callable[Concatenate[Any, T, P], T]:
func.TAG = arg
return func
return deco_inner
class GenBase(Generic[T]):
def __init__(self, a: T) -> None:
self.a: T = a
class GenTest(GenBase[int]):
def __init__(self, a: int, b: int) -> None:
super().__init__(a)
self.b: int = b
@deco(1)
def func_a(self, b: int) -> int:
return self.b + b
@deco(complex(1, 2))
def func_b(self, b: int) -> int:
return self.b + b
@deco2(1)
def func_c(self, b: int) -> int:
return self.b + b
@deco2(complex(1, 2))
def func_d(self, b: int) -> int:
return self.b + b
g = GenTest(5, 6)
func_b passes type check, while func_d doen't (as expected)
The decorator doesn't seem to be doing anything here other than attachinga dynamic value to a method (Which isn't supported by type checking to begin with, and you may want to look into implementing a descriptor instead), what's this intended to be doing/ what problems is this supposed to be solving?
That's just an example - it doesn't matter what's inside.
My question is why deco accepts any Number insted of only int, while deco2 works as intended - only int.
It's due to variance, the numeric tower, and the fact that only one of your decorators is binding both the input and return type to the same type of the object given to the decorator
as for it being "just an example", people come in with all kinds of things that people have to try and interpret the purpose of to actually determine if something should work or if there's a better way.
having an example that's accurate to what you actually intend to do and explains the purpose can help people actually be able to answer with better or more specific details.
Sorry, should have removed that part, yea.
But even if it's binding only the return type, which is specified in all cases as "int" - why does it accept other types? Or it must bind the input only?
Is there a PEP or a place where I can see the progress of covariant/contravariant TypeVarTuple? I saw in the TypeVarTuple PEP that this feature is still not supported until the community experiments a bit more with TypeVarTuple 🙂
im attempting to type the following API response; the full json response is much longer (and contains an dynamic number of inner dicts).
{
"BTCLN_BTC": {
"network_fee": {
"f": "0.00054191",
"m": "0.00046733",
"s": "0.00000000"
},
"rate": "0.99502488",
"rate_mode": "dynamic",
"reserve": 131.82293557,
"svc_fee": "0.500000000000000000"
},
"BTCLN_DAI": {
"network_fee": {
"f": 5.58609124842418,
"m": 2.44248670112417
},
"rate": "64267.239050000003771856",
"rate_mode": "dynamic",
"reserve": "81781.384251849303836934",
"svc_fee": "0.500000000000000000"
},
}
this is my current attempt to type it, but i dont think this is correct (network_fee is not present in every "pair")
from typing import Literal, Mapping, Optional, TypedDict, Union
# alias: eXch types numbers as both strings and floats
ExchNumbers = Union[str, float]
class ExchNetworkFeeAmounts(TypedDict):
f: ExchNumbers
m: ExchNumbers
s: Optional[ExchNumbers]
class ExchApiPair(TypedDict):
network_fee: Optional[ExchNetworkFeeAmounts]
rate: str
rate_mode: Literal["dynamic", "flat"]
reserve: ExchNumbers
svc_fee: str
class ExchApiResponse(TypedDict):
Mapping[str, ExchApiPair]
what im most hung up on is ExchApiResponse, where i dont believe Mapping[str, ExchApiPair] can be the proper way to type such a dict with an dynamic/unknown number of inner dict definitions
ExchApiResponse = Mapping[str, ExchApiPair]?
yup. guess i was just overcomplicating it in my head. thanks
“this cant be the solution”
“that was the solution
”
i forgot dicts were maps for a minute and stackoverflow subsequently reminded me so i just attempted to shove it into a class bc surely everything is a typeddict
those are some funny words magic man
i’ll pretend i understand 
maybe lil late but I think your s in ExchNetworkFeeAmounts should be annotated as typing.NotRequired[ExchNumbers] instead of Optional
Optional[T] meaning T or None, NotRequired[T] meaning T or absent
perhaps this also is the case for your network_fee but I cant tell from the example you gave
good catch, i didnt know there was a difference. both should be changed to NotRequired as neither support a None value (and are only present/absent)
Ah yes, having both NotRequired and Optional with completely unrelated meanings in a library
looks like NotRequired wasnt introduced until 3.11 and im deving in 3.10
!pypi typing_extensions probably has it
py3.10 isnt a hard must for my project, if anything it finally gave me the push to use pyenv (and have to fix pipx and poetry to play nice w it)
pyenv is based
Also, it's probably worth the build time to enable all the optimizations: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
for the sake of context and completion, this is the proper type hint (after upgrading from 3.10->3.11)
from typing import Literal, Mapping, NotRequired, TypedDict, Union
# alias: eXch types numbers as both strings and floats
ExchNumbers = Union[str, float]
class ExchNetworkFeeAmounts(TypedDict):
f: ExchNumbers
m: ExchNumbers
s: NotRequired[ExchNumbers]
class ExchApiPair(TypedDict):
network_fee: NotRequired[ExchNetworkFeeAmounts]
rate: str
rate_mode: Literal["dynamic", "flat"]
reserve: ExchNumbers
svc_fee: str
ExchApiResponse = Mapping[str, ExchApiPair]
Hello ladies and gentlemen
I still cant understand why the next code allows a different type for a decorator.
Am I misunderstanding how it should work?
T = TypeVar('T')
P = ParamSpec('P')
def deco(arg: T)-> Callable[[Callable[P, T]], Callable[P, T]]:
def deco_inner(func:Callable[P, T]) -> Callable[P, T]:
return func
return deco_inner
def deco2(arg: T)-> Callable[[Callable[Concatenate[Any, T, P], T]], Callable[Concatenate[Any, T, P], T]]:
def deco_inner(func:Callable[Concatenate[Any, T, P], T]) -> Callable[Concatenate[Any, T, P], T]:
return func
return deco_inner
class DecoTest:
@deco(1)
def func_a(self, a: int) -> int:
return a
@deco(complex(1, 2)) # <<<<<<<<<<<<< This does not produce a type error (int, float, complex work while other types like str do not)
def func_b(self, a: int) -> int:
return a
@deco2(1)
def func_c(self, a: int) -> int:
return a
@deco2(complex(1, 2)) # This correctly says a wrong type
def func_d(self, a: int) -> int:
return a
See the last point in this list.
deco2 is binding T in args to T in the result.
deco1 doesn't keep track of the correspondence at all between that argument and the result.
It does not, but it should (?) follow the return type, which is explicitly specified as int, right?
As if I change the argument to another type - second decorator will fail (as it has the same type for argument and return), but hte first one still has the same behavior.
Or is there a requirement for the return type to be in arguments as well?
The return types for all your methods are explicitly int; therefore the arg type for the outer layer of deco1 and the return type of the final callback will match up.
Actually . . .
I take that back.
I'm confused 🙂
Right, this is where the aforementioned numeric tower comes into play, I think.
To the typechecker, int is a . . . subtype of complex, from the looks of it? Because of that, at the second layer of your decorator, the function you've passed with a type of (Any, int) -> int is incompatible with the expected function of (FooTest, complex, **P) -> complex). Your function isn't a subtype of the expected function, because your argument type is too narrow. And that's where the variance bit comes into play, because the "too narrow" condition is a matter of function arguments being contravariant.
I could be wrong, but I think that's the throughline for why the type checker complains about deco2.
You can only get narrower on the result type and wider on the argument types when subtyping a callable.
Something like that.
This probably explains the variance bit better than I can: https://blog.daftcode.pl/covariance-contravariance-and-invariance-the-ultimate-python-guide-8fabc0c24278
Correct me if I'm wrong, but this should stand for one specific argument and "the rest"? Not the (**P, complex)?
Concatenate[Any, T, P]
And, yes, it is incompatible - as expected.
Should've been (Any, int) -> int and (FooTest, complex, **P) -> complex).
And yeah, that explains why your first deco didn't catch anything: complex isn't in the arguments of the signature of the function you're passing in. That's covered up by P.
And return types are allowed to be wider, so complex there is acceptable.
def test() -> int:
return complex(1, 2) # <<<<< Error here
def test2() -> complex:
return int(1)
Oh wait
int is a subtype of complex; thus the contract for what you've labelled as the return type for test can't be met by returning a supertype.
int might have methods that complex doesn't.
Ye, I think I got it now
It's a bit of a mindbender, but once you get it, you get it.
As the first decorator goes
def deco(arg: complex)-> Callable[[Callable[P, complex]], Callable[P, complex]]:
def deco_inner(func:Callable[P, complex]) -> Callable[P, complex]:
return func
return deco_inner
And as we provide it with a int, which is always a valid complex - it is accepted
This breaks me a bit, but (at least I do) I apply the same logic to the second one:
def deco2(arg: T)-> Callable[[Callable[Concatenate[Any, T, P], complex]], Callable[Concatenate[Any, T, P], complex]]:
def deco_inner(func:Callable[Concatenate[Any, T, P], complex]) -> Callable[Concatenate[Any, T, P], complex]:
return func
return deco_inner
Return types are accepted, yea.
But why cant a less generic type go into the argument?
As this is valid:
def func_complex(a: complex) -> complex:
return a
func_complex(int(1))
And I assumed the same happens in deco2
This works because you're not comparing functions, just regular types; i.e. is int a subtype of complex, to which the answer is yes. complex as an argument specifies a minimum interface that must be met; int meets that.
Callables are a bit different.
...
I got it the other way around...
The last code that I shared - this is not how it goes
The decorator says "i am waiting for a function with a complex argument" and I give it a funciton with an "int" arguement, which is indeed incorrect - funcion wants only ints, but will be given a broader type.
Thanks for the help!~
man pydantic is great
Hi everyone. I'm trying to get a better handle on how class composition using Mixin classes is used in Python.
I once wrote in a language that would allow for an interface requirement to be specified onto the composing class from within the mixin class.
E.g. say I would have a mixin class Printable which had def print(str) -> ..., it would expect the composing class to have a public method of, say, get_content() -> str.
Is such functionality available using Python's typing system?
Sure. Python has an ABC class that you can inherit from and an abstractmethod label for methods that you expect subclasses to implement.
e.g.
from abc import ABC, abstractmethod
class Printable(ABC):
def print(obj: str) -> None:
... # some logic.
@abstractmethod
def get_content(self) -> str:
raise NotImplementedError
class Base:
...
class Concrete(Base, Printable):
...
pr = Concrete() # Type checker should complain that get_content isn't implemented.
Pyright will show this, for example
@tranquil ledge and hence this would be ameliorated by defining get_content on Concrete, do I have that right?
Yep.
Splendid! Thank you very much.
No problem.
As a followup: is there a particular reason you use the empty Base class in your example?
You mentioned Printable being a mixin in your example; figured I could demonstrate this working even with multiple inheritance, that’s all.
huh, i just found out this works
class MyBaseType(BaseModel):
pass
class MySubType(MyBaseType):
pass
T = TypeVar("T", bound=MyBaseType)
class MyGenericClass(Generic[T]):
foo: T
class MySubClass(MyGenericClass[MySubType]):
pass
MyGenericClass().foo # type inferred as Unknown
MySubClass().foo # type inferred as MySubType
the odd thing is why isnt MyGenericClass().foo inferred as of type MyBaseType?
!pep 696
this is why
bounds are not automatically used as defaults
what is the difference between these two statements?
connections: ConnectionCache = ConnectionCache["Location", "Location"]()
connections: ConnectionCache["Location", "Location"] = ConnectionCache()
the second one yields the type checking error Class definition for "ConnectionCache" depends on itself
wait no, they both do
pyright just seems to bug and fail to detect it 70% of the time
is there a way to forward reference a typevar in a class definition?
to be honest my code has become so convoluted i'm considering moving to a different language....
# OriginT and DestinationT are not defined here
class ConnectionCache(dict, Generic[OriginT, DestinationT]):
def to(self, destination: Location) -> Connection[OriginT, DestinationT]:
return self[destination.id]
class Location(BaseModel, ABC):
id: LocationId
name: str
connections: ConnectionCache[Location, Location] = ConnectionCache()
land_group: str | None = None
@property
@abstractmethod
def map_parameter(self) -> str: ...
OriginT = TypeVar("OriginT", bound=Location)
DestinationT = TypeVar("DestinationT", bound=Location)
put the TypeVar before the classes and put the bound in quotes
ah that worked, i assumed it didnt at first because vscode/pyright failed to recognize and highlight it
how do i get letters and numbers in 12 digits with every possible combination and have it write it on a website
this is what i have
import itertools
import string
import pyautogui
def generate_combinations(length, characters=string.ascii_lowercase + string.digits):
"""Generate combinations of printable characters of a specified length."""
if length <= 0:
raise ValueError("Length must be a positive integer.")
printable_characters = ''.join(c for c in characters if c in string.printable)
combinations = itertools.product(printable_characters, repeat=length)
for combo in combinations:
yield ''.join(combo)
if name == "main":
combination_length = 12 # No leading zeros
try:
for combo in generate_combinations(combination_length):
pyautogui.typewrite(combo)
pyautogui.press('enter')
except ValueError as e:
print(e)
doesn't seem related to type hints
see #❓|how-to-get-help if you have a general Python question
@balmy oyster does pydantic support tagged-unions?
then type the type field with Literal["link"]
I'm honestly learning the pydantic framework. I've also seen you in the FastAPI server and I'm impressed by how you look into the specifics.
i know nothing about pydantic and fastapi honestly
but it should let you do what you want
see how tagged-unions work in typescript
I'll try to look into that. Thanks for pointing it out.
https://docs.pydantic.dev/2.0/usage/types/unions/ I found this where it talks about tagged unions.
Support for a model attribute to accept different types.
@rustic gull thanks for pointing out tagged unions to me. I was able to implement them in my application.
TL;DR: Wonderig if ParamSpec is my best option here or if there's a better choice for specifying added args in a Protocol implementation:
class DimensionMapper(Protocol[_TWorld]):
"""Camera projector for mapping between world and screen space.
Key goals:
1. Compatible with Py 3.8+ w/ typing_extensions available
2. ``project`` takes a _THigher and returns `Tuple[float, float]`
3. ``unproject``:
* takes a `Tuple[float, float]` and returns a _THigher
* Allows providing extra arguments as part of the protocol
"""
def project(self, higher: _THigher) -> _TLower:
"""Map from higher dimensional space to a lower one."""
...
# I'm unsure about the line below. Is ParamSpec the best option?
def unproject(self, lower: _TLowerm, *etc) -> _THigher:
"""Map from a lower dimensional space to a higher one."""
...
If users of the protocol do not care about the extra arguments, you can assign object type to etc to indicate that the unproject method can be implemented with any number of extra arguments.
Does the function accept keyword arguments in addition to positional arguments? ParamSpec-annotated functions must have both ParamSpec.args and ParamSpec.kwargs defined in the signature
I have sort of tried ParamSpec but have had trouble getting it working
you can assign object type to etc to indicate that the unproject method can be implemented with any number of extra arguments.
I'm interested in this
the unproject method could take positional + arbitrary keyword args
I've tried to bind a typing_extensions.TypedDict to a TypeVar, then use that in Unpack
however, that does not seem to go so well
The 3.8+ requirement seems like a hard one and I'm not having a good time with it, lol.
You can also circumvent this problem altogether by having implementors store the "extra arguments" as state before being invoked. For example:
class DimensionMapper(Protocol[_THigher, _TLower]):
def project(self, higher: _THigher) -> _TLower: ...
def unproject(self, lower: _TLower) -> _THigher: ...
class DimensionMapperWithRange(DimensionMapper[_THigher, _TLower]):
def __init__(self, min_z: int, max_z: int) -> None:
self.min_z = min_z
self.max_z = max_z
def project(self, higher: _THigher) -> _TLower: ...
# No need to pass min_z and max_z as arguments to unproject
def unproject(self, lower: _TLower) -> _THigher: ...
The _TLower can probably be simplified away as just Tuple[float, float]
Perhaps you could provide some examples of how the protocol is being used
Yes, hold on
Here's the perspective camera https://github.com/DragonMoffon/arcade/blob/camera-touchups/arcade/camera/perspective.py
is there a function that accepts DimensionMapper object without knowing about its specific implementation? how does it process the extra arguments?
this should inform you what should be defined in the interface
I'm not sure how to solve that yet. To my understanding, we sort of have to annotate in Window
Searching, gimme a sec
So in theory, Window shouldn't have to care https://github.com/pythonarcade/arcade/blob/5bb27eccddf76006fc822282e6ca9f1f434ea39d/arcade/application.py#L219-L225
arcade/application.py lines 219 to 225
self._current_view: Optional[View] = None
self._default_camera = DefaultProjector(window=self)
self.current_camera: Projector = self._default_camera
self.textbox_time = 0.0
self.key: Optional[int] = None
self.flip_count: int = 0
self.static_display: bool = False```
thank you bot
does it actually call any of the methods defined in the interface?
Yes.
Not in Window, but self.window.current_camera shows up repeatedly throughout the codebase
do they care about the extra arguments?
for example, the UIManager uses the Window's current camera to perform world coordinate mapping and then offset into GUI for event handling
The GUI stack doesn't, but the idea of the protocol was to allow people to define their own custom cameras
I'm very tempted to say the 3.8+ requirement makes the nice generics too hard
and that saying sorry, vec2 or vec3 (pyglet.math type subclassing typing.NamedTuple) + tuple is all you get
I think this is the way to go then
since your framework will call unproject without any extra arguments
Hmm, so tuple-only + extra args are instance state?
therefore, any concrete implementation must be compatible with this way of calling unproject
custom cameras can still have extra arguments but they must all have default values in order to remain compatible with the interface
if the default value is not available then they'll have to initialize such arguments in the constructor as mentioned here
Yeah, this seems like the best way then. Ty for confirming it.
mypy is very unhappy with binding the typing_extensions versions of TypedDict to a type variable + putting it in typing_extensions.Unpack, so I'll put it down as a todo for the future.
We might leave depth since it's a 2D-first framework
it's a little ugly, but my discussion with the new Camera system's initial implementer suggests this covers 90% of usecases.
Another contributor added depth buffer features for controlling sprite draw order a while back, and I think the depth keyword might integrate with that.
just keep in mind that if you add depth as an argument to the interface, all implementors will have to include it
Yeah, that's a sacrifice I'm about to discuss with the GL guy.
It's a contentious one that I'm not in favor of due to purity issues.
However, the nature of the framework and the depth buffer features might mean it's worth it.
TL;DR: Ty for the help! Do you have a GitHub/ GitLab / want credit in release notes?
I've linked the convo over in the arcade server. You helped resolve not just camera issues, but prevent a whole bunch elsewhere. Are you up for being credited in the release notes? If so, GitHub / GitLab link? I can try to add you to the release notes section when I update it with Camera info.
You're welcome! You don't need to credit me, this is just a small thing xD If you insist though, I think my GitHub should be linked to my Discord profile
click on my PFP in the menu, more info should show up
click on my pfp in this menu scroll down
...this is the worst UX choice I've seen from Discord yet, lol
ty
I think it might be technically correct, because classmethods are inherited
like, if there was a subclass B of this enum A, then A.smth won't be an instance of B, and so for B.from_status_error this typehint would be invalid.
but it's using cls.smth, that would at that point be B.smth
and the variant just gets inherited
no?
cls should just be type[Self] by default, or am I missing something?
hmm, fair enough, the variant should be shared..
it also seems to be a regression
I'm just updating from a very old version of pyright, where this worked
(I haven't updated for almost a year though)
Because there should be a fixed amount of instances
I think an explanaiton on why Self doesn't work here, that might make sense is that, while enum subclassing isn't possible, Self doesn't handle for that, so it assumes that subclasses are in fact possible. Now, since the enum variants are held as class variables, and are dynamically created using an EnumMeta metaclass, which basically converts the class variables into instances of the enum class, and type checkers handle for this, by making these class variables have Foo type (instances of Foo class specifically), so even though I'm accessing a cls.VARIANT here, even if a potential subclass would inherit the VARIANT class variable, this class variable would be of type Foo, rather than of type Self, making it incompatible
I don't actually know for sure if this is the reason for this, but it's what makes sense to me
from dataclasses import dataclass
from typing import Protocol, Sequence
class LabelProtocol(Protocol):
name: str
class CollectionProtocol(Protocol):
contents: Sequence[LabelProtocol]
@dataclass
class Label:
name: str
@dataclass
class Collection:
contents: Sequence[Label]
def f() -> CollectionProtocol:
return Collection((Label('hello'), Label('World')))
In the code above, Pylance complains about the last line:
Expression of type "Collection" cannot be assigned to return type "CollectionProtocol"
"Collection" is incompatible with protocol "CollectionProtocol"
"contents" is invariant because it is mutable
"contents" is an incompatible type
"Sequence[Label]" is incompatible with "Sequence[LabelProtocol]"
Understandably, it complains that a sequence of Labels is not the same as a sequence of LabelProtocol, but to my mind if Label adheres to LabelProtocol, this should work out just fine.
How do I handle this without coupling the Protocol to the class?
I'm able to avoid the type error by explicitly indicating that Collection implements CollectionProtocol, but I guess that would couple the protocol to your class
seems that this has something to do with the protocol not having dataclass
They're defined in separate modules, I bundled them here for the sake of the question, but assume the concrete implementations are imported from a separate module
I don't see why this should impact the protocol though
dataclass does some extra stuff to the class which might cause the type checker to think that it isn't compatible with the original class
seems that setting Collection.contents to Sequence[LabelProtocol] also solves the type error
I don't think this should interfere with downstream uses of the class?
Does using the standard __init__ instead of dataclass fix it?
It would, but I don't want to couple the concrete implementations to the protocols because they're defined in a separate module and supposedly the whole idea of these Protocols is that I don't need to couple them
that alone doesn't appear to be enough. Collection.contents is a mutable property so the T in Sequence[T] becomes invariant
Can I make it an immutable property to fix it? I'm not sure how it being mutable or not makes any difference
ok I think I solved the issue
just make the protocol immutable
yep
from dataclasses import dataclass
from typing import Protocol, Sequence
class LabelProtocol(Protocol):
name: str
class CollectionProtocol(Protocol):
@property
def contents(self) -> Sequence[LabelProtocol]: ...
@dataclass
class Label:
name: str
@dataclass
class Collection:
contents: Sequence[Label]
def f() -> CollectionProtocol:
return Collection((Label('hello'), Label('World')))
I'm honestly not sure why this works 🤔
class A:
pass
class B(A):
def hello(self): ...
class FooProto(Protocol)
values: Sequence[A]
class Foo:
values: Sequence[B]
def add(foo: FooProto, value: A):
foo.values = [*foo.values, value]
foo = Foo()
add(foo, A())
foo.values[0].hello() # Fails
If Foo is assignable to FooProto, the above code would type check successfully even though the last line causes a runtime error (since hello doesn't exist on object A)
So B being a subclass of A is similar to B implementing a protocol A? There are some differences though, like I can't have concrete instances of a protocol... But I see your point
yeah, more specifically, the implementor of the protocol can define additional properties that are not originally in the protocol
And this only becomes a problem because the values property is mutable, right?
yeah, since it basically lets you overwrite the original "type-checked" value
Does setting @property makes it immutable though? Wouldn't the concrete implementation need to have @property as well?
@property values cannot be assigned to directly (unless you further define a @property.setter), so it is basically immutable
Wouldn't the concrete implementation need to have
@propertyas well?
This is a bit more tricky. I'm not familiar with the internals of Pyright but I believe that it only analyses@property-decorated attributes to the extent of the descriptor protocol (https://docs.python.org/3/howto/descriptor.html), rather than checking that the attribute is an instance ofproperty. As long as the interface of attribute lookup remains consistent, it should pass the type checker
I see! Thank you very much for the clarifications and solutions!
Your help is much appreciated ❤️
Glad to help!
Is there much guidance on how to gradually roll out type-annotations to a larger library and do at least partial checks on the library to ensure correctness?
context: I want to add type annotations to pyqtgraph, ...no way I'm going to be able to do that in 1 PR, so will have to roll it out gradually, i'd like to check if the annotations I've added are correct as I'm rolling them out...
https://mypy.readthedocs.io/en/stable/existing_code.html has a good guide
looks like this is the argument I need (and just have a list of files that are already annotated): https://mypy.readthedocs.io/en/stable/config_file.html#confval-files
reading the differences w/ pyright I'm thinking I may lean towards using that instead of mypy; need to see if there is an equivalent option there
How would I typehint this? I tried my best but it doesn't seem to like it because of the func(obj) call
_Self = TypeVar("_Self", bound=Person)
def apply_on_family(func: Callable[[_Self], object]) -> Callable[[_Self], _Self]:
@wraps(func)
def wrapper(self: _Self):
for obj in self.get_family():
func(obj)
return self
return wrapper
Note that I've changed the example for privacy, so if it seems a little contrived the naming of stuff has been changed.
What’s the exact error you’re getting?
code
it's usually better to do the reverse and add a per-module ignore errors, like so: https://mypy.readthedocs.io/en/stable/existing_code.html#ignoring-errors-from-certain-modules
(files can interact in interesting way with mypy's default import following behaviour, also usually nicer to check more things on new modules)
Person cannot be assigned to type _Self@apply_on_family
What is the type of Person.get_family()?
A method returning Iterable[Person]
Right, that's the problem. Suppose you have this:
class Person
class Programmer(Person)
class Builder(Person)
class Cook(Person)
programmer = Programmer(children=[Builder(), Cook()])
def program(p: Programmer) -> None:
p.program(language="python")
According to the type of apply_on_family, it should be legal to call apply_on_family(program)(programmer). But the programmer's family can include Persons that are not programmers
the correct fix depends on what restrictions you actually need
So basically it's because it's invariant?
How would I go about allowing it?
Do you understand why this is invalid?
P = TypeVar("P", bound=Person)
def apply(f: Callable[[P], None], parent: P, children: Iterable[Person]) -> None:
f(parent)
for person in children:
f(person)
Oh it's covariant?
variance is a property of generic calsses (like list/tuple/Callable), doesn't really apply here
oh ok
The problem is, P is bound to a particular type (like Programmer), but the children are arbitrary Persons, not necessarily related to P
Maybe you wanted this?
def apply_on_family(func: Callable[[Person], object]) -> Callable[[_Self], _Self]:
@wraps(func)
def wrapper(self: _Self):
for obj in self.get_family():
func(obj)
return self
return wrapper
Looks like what I want, thanks!
Good day guys!!
Please did someone ever use fonttools pypi package
I want to merge two .ttf files into one
Doesn't seem related to type hints. Perhaps see #❓|how-to-get-help and open a help post, or ask in #media-processing
What does a * after a type definition mean?
This is from Pyright hinting of a variable after typechecking it with a TypeGuard
in mypy it's an inferred type, maybe pyright does something similar
Ah I see, it does make sense (in the context of my project)
As far as I understand, there's no way to really distinguish an unbound method from a function. Am I right on this?
I'm trying to write a decorator class that can be applied to both methods and functions. I can make it work, but it's almost impossible for me to type it.
an overload, maybe?
((T, **P) -> R) -> ...
((**P) -> R) -> ...
https://discord.com/channels/267624335836053506/1235562520622792735 @real hazel for arbitrary type narrowing functions you could use TypeGuard, or TypeIs if you have typing-extensions>=4.10.0, although for that particular example, writing if val is None: directly would also narrow the type for you
Is TypeGuard just a boolean at runtime?
I know that I can inline the function and the type checker would be happy, but that's only a good idea in the example I gave because I simplified so much
oh right, only TypeIs is allowed to narrow the falsy branch
but ya both would be boolean functions
Okay
I think in practice I will stick with my workaround which was just to inline the function, because TypeGuard looks so weird (why would a type with a generic actually be a boolean) and I'm slowly introducing typing into a codebase with no previous typing lol
(Edit: See below)
Are you decorating the unbound method directly, like so:
class MyClass:
@my_decorator
def my_method(self): ...
or are you decorating the unbound method after it is assigned to the class (but not yet bound to the instance)?
class MyClass:
def my_method(self): ...
@my_decorator(MyClass.my_method)
def my_func(): ...
You can still distinguish between these two cases like so:
Actually nvm, I was wrong. I tested it further and it seems that the unbound method is immediately made a descriptor in both cases. Still, you can distinguish an unbound method from a regular function if you make the assumption that the first argument is self:
from typing import Callable, ParamSpec, Protocol, TypeVar
TOwner_contra = TypeVar('TOwner_contra', contravariant=True)
P, R_co = ParamSpec('P'), TypeVar('R_co', covariant=True)
class UnboundMethod(Protocol[TOwner_contra, P, R_co]):
def __call__(__self, self: TOwner_contra, *args: P.args, **kwargs: P.kwargs) -> R_co: ...
def any_func(f: Callable[P, R_co]): ...
def unbound_method(f: UnboundMethod[TOwner_contra, P, R_co]): ...
class MyClass:
@any_func
def foo(self): ... # OK
@unbound_method
def bar(self): ... # OK
@any_func
def foo(): ... # OK
@unbound_method
def bar(): ... # NOT OK
Is there a way to type a function that accepts N callables and returns the output of each in a tuple? I'd like to preserve the length of the tuple and the position of each callable's return type. Here's my not-yet-working code:
import typing_extensions
TTuple = typing_extensions.TypeVarTuple("TTuple")
def call_fns(
*callables: typing_extensions.Unpack[TTuple],
) -> tuple[typing_extensions.Unpack[TTuple]]:
return (callable() for callable in callables)
# This should be tuple[Literal[1], Literal[2], Literal[3]]
results = call_fns(lambda: 1, lambda: 2, lambda: 3)
Not with TypeVarTuple. What you can do is manually define an overload for different numbers of functions though, up to say 5. Also your implementation isn't correct, you're returning a generator expression not a tuple.
Darn, oh well. And thanks for the return value catch
You'd need something like the hypothetical Map operation
@overload
def call_fns[T](f1: Callable[[], T], /) -> tuple[T]:...
@overload
def call_fns[T1, T2](f1: Callable[[], T1], f2: Callable[[], T2], /) -> tuple[T1, T2]: ...
If you're willing to bend the code significantly just for the sake of typing, there's this
class _Gather[*Xs]:
def __init__(self):
# plz don't call directly
self._items: tuple[Awaitable[object], ...] = ()
def __call__[T](self, item: Awaitable[T]) -> _Gather[*Xs, T]:
g = _Gather()
g._items = self._items + (item,)
return g
async def invoke(self) -> tuple[*Xs]:
return tuple(await asyncio.gather(*self._items)) # type: ignore
gather: _Gather[*tuple[()]] = _Gather()
Hopefully gather just gets deprecated then later removed in favour of task groups
import trio as asyncio
How the hell do I typehint a class that must inherit from this "private" class? (_Loss)
type[_Loss]?
If you get warnings because it's private, well, maybe it shouldn't be private
(or maybe you shouldn't be using it, if it's a third-party module)
It's from the pytorch lib :/
I'm not using it directly, the function just needs to receive a class that inherits from _Loss
e.g. this one, which inherits from another private class that itself inherits from _Loss
Should _Loss be a string? I can't even import it
you should be able to import it, judging from the screenshot you shared
tools might complain about using the private name
It really sounds like _Loss shouldn't be private
If you don't mind being a bit less strict, I guess you can type alias _Loss as Callable[[torch.Tensor, torch.Tensor], torch.Tensor]
It really shouldnt be. These have different semantics. While task groups are great for 98%, there are still use cases for gather
Not sure whether I should put this question, but it's about changing a function signature so I'll put it here. Suppose I have two versions of this function (v1, v2) and am currently in the process of deprecating v1 (in v1.5):
# v1
def foo(a_old: int = ..., b: float = ...) -> None: ...
# v2
def foo(a_new: int = ..., b: float = ...) -> None: ...
# v1.5
@overload
def foo(a_old: int = ..., b: float = ...) -> None: ...
@overload
def foo(a_new: int = ..., b: float = ...) -> None: ...
def foo(*args, **kwargs) -> None:
# How to implement this?
How should I distinguish between the cases where a_old is passed via positional arguments vs keyword arguments during runtime so that I can emit a deprecation warning that the name of the parameter will change from a_old to a_new?
actually wait I think I just answered my own question (just pop from kwargs lol)
hmm is there a way to keep the overloads of a function after applying a decorator to it?
I can use a TypeVar to annotate the function in the decorator, but then Pyright complains about the wrapped function not being assignable to the return value of the decorator
F = TypeVar('F', bound=Callable[..., Any])
def deprecate_kwargs(*kws: str):
def wrapper(fn: F) -> F:
@wraps(fn)
def inner(*args, **kwargs):
deprecated_kws = {k for k in kwargs if k in kws}
if deprecated_kws:
warnings.warn(DeprecationWarning(f"Deprecated arguments: {deprecated_kws}"))
return fn(*args, **kwargs)
return inner # Pyright complains about this line
return wrapper
you need to annotate the return type of deprecate_kwargs as Callable[[F], F]
though maybe pyright can infer that?
the line where pyright complains can be fixed with ParamSpec
pyright doesn't complain about the outer wrapper even without Callable[[F], F]
if I use ParamSpec then the overload information is lost
using the above example,
@overload
def foo(a_old: int = ..., b: float = ...) -> None: ...
@overload
def foo(a_new: int = ..., b: float = ...) -> None: ...
@deprecate_kwargs('a_old')
def foo(a_new: int = ..., b: float = ...) -> None:
raise NotImplementedError
if I replace F with Callable[P, R] then I won't get the error that the implementation of the overloaded function has incompatible signature
Yeah I'm thinking about opening a PR on the repo to turn Loss and WeightedLoss into public classes
Can you show the function where you want to accept a _Loss?
Sure!
def train_model(model: nn.Module, train_loader: DataLoader, criterion: nn.modules.loss._Loss, optimizer: optim.Optimizer, num_epochs: int = 10) -> None:
"""
Train a PyTorch model.
Args:
model (nn.Module): The model to train.
train_loader (DataLoader): The DataLoader for the training data.
criterion (nn.modules.loss._Loss): The loss function.
optimizer (optim.Optimizer): The optimizer.
num_epochs (int, optional): The number of epochs to train for. Defaults to 10.
"""
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
for images, labels in train_loader:
labels = labels.float()
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs.squeeze(), labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
print(f'Epoch {epoch+1}, Loss: {running_loss/len(train_loader)}')
@jade viper in that specific case I think this is good enough
Yeah, if you just need a callable, then accept a callable
Also, remove the types and defaults from the docstring, they are kinda redundant.
And I guarantee eventually they'll get out of sync 🙂
Looks like there's a PR on the PyTorch repo to implement a Loss typealias
LossFn = Callable[[torch.Tensor, torch.Tensor], torch.Tensor]
def train_model(
model: nn.Module,
train_loader: DataLoader,
loss_function: LossFn,
optimizer: optim.Optimizer,
num_epochs: int = 10,
) -> None:
"""Train a PyTorch model"""
I don't know anything about torch, but maybe you want a Module instead of _Loss?
(as the PR seems to imply)
though I don't understand why such an alias is needed
_Loss inherits from Module
well, why do you need it to be a _Loss and not just any callable?
What do you mean?
I thought that was Google's standard? And that makes sense!
just because Google does it doesn't mean it's good 🙂
if you're using type annotations, there's no need to duplicate them in the docstring
In your function, you're only calling criterion: criterion(outputs.squeeze(), labels). So why does criterion have to be a _Loss an not e.g. an ordinary Python function?
like 2-space indents ||or literally everything that happened to YouTube in the last 8-10 years||
Bc otherwise the pytorch model doesn't get trained correctly
lol, can't blame YT on the actual devs tho
🤔 why
I don't think that's the case. backprop should work even outside nn.Module
unless you have some more code in the function, this should work as a criterion the same way as some_loss does
some_loss: _Loss = ...
def my_awesome_loss(param1, param2):
rv = some_loss(param1, param2)
print(f"Is this loss? {rv!r}")
return rv
Oh I see what you mean
Still, doesn't it make more sense to typehint it as expected?
I don't know, that's ultimately for you to decide
It's a very unfamiliar domain for me
If e.g. you're developing a library, you might want to be conservative and require e.g. a Module instead of a callable, because in the next library version you might want to access some module-specific properties of the parameter
imo Callable[[torch.Tensor, torch.Tensor], torch.Tensor] is better because loss functions are not necessarily implemented as nn.Modules
No, it's part of my undergrad thesis, I just like to keep my code neat
That's fair, but I only plan on using loss functions from the pytorch lib, which all inherit from _Loss at some point AFAIK
What should I do in that case?
there is another reason: torch.nn._Loss doesn't actually seem to define how it should be called, so the function signature is essentially unrestricted
Callable[[torch.Tensor, torch.Tensor], torch.Tensor] can provide better type safety for your code
e.g. so you won't accidentally call the loss function with three arguments
That's very fair. So I can just do like LossFunction: TypeAlias = Callable[[torch.Tensor, torch.Tensor], torch.Tensor]?
yep
tysm guys!
class DmWebsocketMessage(BaseModel):
chat: Literal['dm']
type: str
dm: int
link: Optional[str] = None
serverinviteid: Optional[int] = None
text: Optional[str] = None
file: Optional[str] = None # will convert into bytes
filetype: Optional[str] = None
otheruser: str
username: str
profile: str
date: str
@model_validator(mode="after")
def check_sent_right_data(self):
if self.type == "link":
if self.serverinviteid is None:
raise Exception("No serverinviteid was sent to match with the link")
self.link = str(uuid4())
elif self.type == "text":
if self.text is None:
raise Exception("No text was sent")
elif self.type == "file":
if self.file is None or self.filetype is None:
raise Exception("Either a file or/and filetype was not sent")
else:
self.file = base64.b64decode(self.file.split(",")[1])
elif self.type == "textandfile":
if self.text is None or self.file is None or self.filetype is None:
raise Exception("Either a file or/and filetype or/and text was not sent")
else:
self.file = base64.b64decode(self.file.split(",")[1])
else:
raise Exception("invalid message type")
return self```
Any critiques about the way I'm validationg this data?
Seems that some of your attributes are based on the type field. You should look into using tagged unions
Data validation using Python type hints
This enables type annotations that are more precise
Is there a way to "map" the parameters of a function to another type? I am trying to annotate a decorator that converts each argument into a different type regardless of how many args/kwargs the input function has.
Similar to
class StrFunc(Protocol):
def __call__(__self, *args: str, **kwargs: str) -> str: ...
class IntFunc(Protocol):
def __call__(__self, *args: int, **kwargs: int) -> int: ...
def str_to_int_func(fn: StrFunc) -> IntFunc: ...
but I want the output function to have the same number/name of args/kwargs as those of the input function.
So if I decorate a function as follows:
@str_to_int_func
def foo(a: str, b: str) -> str: ...
the resulting function should have a signature like
def foo(a: int, b: int) -> int: ...
My intuition tells me this is not possible because it involves higher-kinded types, but I would like to hear some thoughts from others. Is it possible to at least annotate this decorator for a decent subset of cases?
Not possible
can someone please test this in pycharm? Does new have the doc-string and correct signature?
import functools
def test(a: int, b: str):
"description"
pass
new = functools.partial(test, b="25")
new()
in vs code it does show doc string but signature is incorrect
sounds like a bug in pyright/pylance, the signature should look like (a: int, *, b: str = ...)
shouldn't it omit b argument?
since you can call new as new(42) or new(42, b="huh"), but not new(42, "huh")
oh, you're right
I didn't know that partial still allows override keyword arguments
any ideas how it can be possible to
-
set
newsignature in IDE to be same astestbut withoutbargument? -
make sure
newwill have the same doc-string in IDE astest?
partial keeps the doc-string in pylance but it's just pylance being clever and it doesn't work in PyRight, not sure about outher language servers.
All type checkers that supoort partial special case.it, partial's.behavior isnt possible to describe within the current specified type system
any hacky ideas to make it possible? 👀
None which apply generically, but there are a few things you can do depending on use case, 1 moment
I have a dataclass A with type hints. A new class B is dynamically made which shall contain the same attributes a A, but with different object types. Is there a way to create the type hints of B from A? I.e. is it possible to add type hints dynamically?
from typing import Protocol
import functools
def test(a: int, b: str):
"description"
pass
class ModifiedTest(Protocol):
def __call__(self, a: int):
...
class TestLike(Protocol):
def __call__(self, a: int, b: str):
...
def wrap(f: TestLike) -> ModifiedTest:
return functools.partial(f, b="25")
new = wrap(test)
Ugly, and requires special knowledge about the signatures. Can't be inlined as:
new: ModifiedTest = functools.partial(test, b="25")
in pyright, that should also be a bug though.
the problem with this method that I'll still need to do a lot of code generation for each function like test (I have many of them and just need to exclude the b argument from them), generating ModifiedTest and TestLike with correct signatures
it's basically replacing signature with TestLike._call__ signature instead of excluding one argument
Yeah, there isn't an easy widely applicable alternative though, at least not yet.
Function transforms of this kind in general are . . . I don't want to say underspecified, but unexplored within the Python type system, maybe? Beyond Concatenate and Paramspec, at least.
Literally just not something the type system supports right now. I'd just leave it be with partial personally (as you have it now), pyright (and others) can still detect misuse, and you can document the set of functions however you want
no.
Is it possible to define a default type for T unless it has been explicitly specified? class A(Generic[T]) where reveal_type(A()) shows A[Unknown]
!pep 696
thanks
summary: yes, with TypeVar("T", default=SomeThing)
Yeah, I found it. I was actually reading PEP965 so very close 😄
Then the solution to my above question seems to be something akin to:
T = TypeVar('T', default=int)
@dataclass
class A(Generic[T]):
a: T
b: T
class B(A[str]):
pass
or in Python 3.13, class A[T=int]: ...
I wouldn't call that dynamic use, that's still static, but yeah that could work if the types are uniform.... I just can't see writing that over writing the dataclasses directly.
(could also use multiple typevars if not, but the point just... I don't see a benefit to writing it this way)
There are other functional reasons for having B, this was just the typing parts. If the only difference were the type, then A[int] and A[str] would be a better way to use them...
@oblique urchin re https://github.com/python/cpython/pull/118648 isn't typing.Generator and typing.AsyncGenerator deprecated? Why are they getting new features?
Issue: gh-118647
📚 Documentation preview 📚: https://cpython-previews--118648.org.readthedocs.build/
to mirror a change in typeshed
And typeshed needs the change because collections.abc.Generator and AsyncGenerator are aliases of the deprecated types?
typeshed wants this change to make Generator more convenient to use
right but only collections.abc.Generator and AsyncGenerator should get the defaults
The deprecated names should not get new features
TypeVar(..., default=...) worked brilliantly in my IDE. Not so much in actual py code. 3.13. Alas.
use typing_extensions.TypeVar
thanks
(commented on the issue)
It's right. it made b an optional argument.
partial, as well as property, are one of those features that are special cased into the type checker.
oh, actually you're right, I missed that
then I think it will work for me, as it will move the overridden argument to the end of the signature
can someone please try this in pycharm?
want to confirm that it shows the relevant signature and doc-string there also
I don't really like how pyright is showing ... rather than the new default there (threw off me noticing something about that) but yeah. pyright's special casing handles it okay.
while that's true, this isn't a type stub, and it's incongruent with how pylance otherwise handles defaults:
Plus, we do actually include defaults in typeshed now because they're useful for IDEs
And it isn't even a special case of untransformed function, this is just partial's special casing looking surprising to people used to how pylance normally handles defaults
Is this one of those quirks of having partial be special cased?
probably
partial would be great to not have special casing, or to at least have specified behavior for a documented special case
yes
pyright understands descriptors (including user defined ones)
it can be written in a type-safe way and not be special cased
but it's not specified in the type spec iirc
the .setter stuff definitely needs special casing
maybe
(or a far more powerful type system)
it's been a while since I tried playing with it
why? is it because of function redefinition?
mypy has flag that allows redefinition, it is weird that it doesnt allow defining two function with the same name
I've been collecting my thoughts on improving some areas of the type system while minimizing disruption. There are 4 areas I'm aware of where python isn't theoretically sound, but is close enough that if done carefully, disruption could be very minimal
it's a lot of effort to work through all the theory vs current underspecification though
because the presence of the second function changes the type of the first one
is property.setter atomic?
properties that have a setter need HKTs to be expressed generically
by that, I mean does it return a new instance or modify self?
returns new
i believe so
yes
yes
brilliant! Is there any reference for this stuff btw
(Sorry to keep annoying with questions)
type is a special form and I'm not sure you'd see it explicitly, but in most cases you can see this in typeshed: https://github.com/python/typeshed/blob/727f3c4320d2af3af2f16695e24dd78e79b7c070/stdlib/typing.pyi#L389
stdlib/typing.pyi line 389
class Iterable(Protocol[_T_co]):```
the spec does say type[] is covariant, https://typing.readthedocs.io/en/latest/spec/special-types.html#type (at the very end)
Another question is there a difference between list[T | U] vs list[T, U]?
I don't think the latter is valid
list only accepts one type argument
Oh makes sense
is there way to use some default type if generic type wasn't specified? See example below
from typing import Optional, Union, Type, TypeVar, Generic
T = TypeVar("T", bound="Animal")
class Animal:
pass
class Dog(Animal):
pass
class AnimalContainer(Generic[T]):
def get_class(self) -> Type[T]: ...
t = AnimalContainer[Dog]().get_class()
reveal_type(t) # Dog
t = AnimalContainer().get_class()
# TODO how to make it Animal if generic wasn't specified?
reveal_type(t)
You can do this now, with default="Animal". You’ll need to import TypeVar from typing_extensions, since this is only added to Python 3.13.
thank you!
is it possible to make Type[T] to also accept aliases? e.g.
from typing import Optional, Union, Type, TypeVar
class Dog:
pass
class Cat:
pass
Animal = Union[Dog, Cat]
T = TypeVar("T", Dog, Cat)
def test(t: Type[T]) -> Type[T]:
return t
# currently causes a type error
a = test(Animal)
# NOTE: should return Animal
reveal_type(a)
a = test(Dog)
reveal_type(a) # Dog
a = test(Cat)
reveal_type(a) # Cat
Unfortunately no, because type only accepts actual classes.
A PEP is currently being worked on for TypeForm, which would allow this. What you could do is make Animal a superclass, or make T bound to Union[Type[Dog], Type[Cat]].
if you only use the type for constructing new instances, you can replace that with a callable protocol
unfortunately I will need to do stuff like this - I'll need type checker to warn me that some attribute is not applicable to all animals
def test(a: Animal):
a.bark_count # type error: Cat doesn't have `bark_count`
can you please show an example?
from typing import Protocol
class AnimalFactory(Protocol):
# args and kwargs corresponds to those in __init__
def __call__(__self, *args: ..., **kwargs: ...) -> Animal: ...
def create_animal(factory_or_cls: AnimalFactory) -> Animal:
return factory_or_cls(...)
okay, it's going to be kind of verbose but I think it might work for my case. Basically I've added some new type Animal_Type that will be possible to use to overload to make sure my function will return a union Animal in that case.
from typing import Optional, Union, Type, TypeVar, overload, assert_type
# fmt: off
class _base: ...
class Dog(_base): ...
class Cat(_base): ...
# NOTE: in my case there will be many unions like `Animal`
Animal = Union[Dog, Cat]
class _base_type: ...
class Animal_Type(_base_type): ...
# fmt: on
T = TypeVar("T", bound="_base")
@overload
def test(t: type[Animal_Type]) -> Animal: ...
@overload
def test(t: type[T]) -> T: ...
def test(t: Union[type[_base_type], type[_base]]) -> _base: ...
a = test(Animal_Type)
assert_type(a, Animal)
a = test(Dog)
assert_type(a, Dog)
a = test(Cat)
assert_type(a, Cat)
Is it possible to set a default type for a generic type?
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
class BaseMulti(Generic[T1, T2, T3]): pass
class DerivedMulti(BaseMulti[int, str, float]): pass
class DerivedMulti2(BaseMulti[int, str]): pass # <<<<<<<< So that this line would not produce errors as only 2 out of 3 generics are set
You can set a default value to T3 - https://peps.python.org/pep-0696/
I assume it's available only in 3.13 :<
Nope, you can use typing_extensions.TypeVar
What is the difference doing T = str and T = TypeVar("T", bound=str) ? Similarly T2 = TypeVar("T2", str, int, None) vs T2 = Union[str, int, None].
What is the difference doing
T = strandT = TypeVar("T", bound=str)?
ATypeVarcan resolve to any subclass of the bound type (for example aStrEnum)
T2 = TypeVar("T2", str, int, None)vsT2 = Union[str, int, None]
In the first case,T2can only be one ofstr,intorNone. However, in the second case,T2can be any ofstr,intorNone.
Ah, got it. Thanks
def f1(x: str | int, y: str | int) -> str | int:
...
A = str | int
def f2(x: A, y: A) -> A:
...
B = TypeVar("B", bound=str | int)
def f3(x: B, y: B) -> B:
...
C = TypeVar("C" str, int)
def f4(x: C, y: C) -> C:
...
f2 is the same as f1, and can be called as:
f2(42, "foo") # returning str | int
f2("a", "b") # returning str | int
f2("a", f2(1, 2)) # returning str | int
``` `f3` links the `x` and `y` arguments together, and requires them to be of the same type that's a subtype of `str | int`: ```py
f3(1, 2) # valid, B is resolved as int, call returns int
f3("a", "b") # valid, B is resolved as str, call returns int
k: Literal["k"] = "k"
f3(k, k) # valid, B is resolved as Literal["k"], call returns Literal["k"]
f3(f2(1, 2), f2(3, 4)) # valid, B is resolved as `str | int`, call returns `str | int`
f3(69, "yes") # valid, B is resolved as `int | str`, call returns `int | str`
f3(69, k) # valid, B is resolved as `int | Literal["k"]`, call returns `int | Literal[k]`
``` in `f4`, `C` is only allowed to resolve as exactly `str` or exactly `int`. ```py
f4(1, 2) # valid, C is resolved as int, call returns int
f4("a", "b") # valid, C is resolved as str, call returns str
f4(1, "b") # invalid, C cannot be resolved
f4(f2(1, 2), f2(1, 2)) # invalid, C cannot be resolved
class SpecialString(str): pass
f4(k, SpecialString()) # valid, C is resolved as str, call returns str
f4(True, False) # valid, C is resolved as int, call returns int
maybe I should put these examples somewhere
I have a function that accepts async and sync callbacks. If it's given an async function then it awaits it, otherwise it just calls it. How do I properly type this?
import asyncio
import typing
T = typing.TypeVar("T")
async def call(cb: typing.Callable[[], T]) -> T:
if not asyncio.iscoroutinefunction(cb):
return cb()
return await cb()
async def async_fn() -> int:
return 1
def sync_fn() -> int:
return 1
async def main() -> None:
# Return type should be int but it's Coroutine[Any, Any, int]
async_res = await call(async_fn)
sync_res = await call(sync_fn)
asyncio.run(main())
It's not possible to type in a non-janky way
why do you want this though?
(I've seen this behaviour in some frameworks and it leads to some unfortunate edge cases)
I have a method that my users can pass callbacks to. I wanted to let them pass either async or sync callbacks
I'd just accept async callbacks only
Wouldn't the Flask folks be mad about that? Our library essentially adds durability to your functions. Like in this example, passing your function to step.run will add retries, concurrency, etc.:
https://www.inngest.com/docs/reference/python/steps/run#examples
(The example is async but we have sync variations for Flask folks)
yeah but you're using await anyway
examples/functions/two_steps_and_sleep.py lines 33 to 43
def fn(
ctx: inngest.Context,
step: inngest.StepSync,
) -> str:
user_id = step.run("get_user_id", lambda: 1)
step.run("print_user_id", lambda: f"user ID is {user_id}")
step.sleep_until(
"zzzzz",
(datetime.datetime.now() + datetime.timedelta(seconds=3)),
)```
Damn, that preview codeblock is an awesome Discord bot feature
If you really want this behaviour, you can use overloads: ```py
from collections.abc import Callable, Coroutine
from typing import overload, TypeVar, Any
T = TypeVar("T")
@overload
async def call(cb: Callable[[], Coroutine[T, None, None]]) -> T: ...
@overload
async def call(cb: Callable[[], T]) -> T: ...
async def call(cb: Callable[[], Any]) -> Any:
if asyncio.iscoroutinefunction(cb):
return await cb()
else:
return cb()
If people only use this directly on an async def'd async functions or def'd non-async functions, it shouldn't cause too many problems
hmh, is overload needed here? cant union? e.g.
async def call[T](cb: Callable[[], T | Coroutine[T, None, None]]) -> T:
...
or maybe a union of 2 callables
If I pass in a Callable[[], Coroutine[int, None, None]], should the function return an int or a Coroutine[int, None, None]?
Overloads are evaluated in declaration order, while unions should be order-independent
ah ic
Nice, the overload works. Thank you!
Of course, this will have some edge cases. For example, if you pass in a custom callable object:
class Thing:
def __init__(self, ...): ...
async def __call__(self, ...): ...
It will not work at all (because a Thing() is not a coroutine function)
The thing will be called, but the result won't be awaited. If you're luckly you'll get a warning at runtime
also if you have something like: ```py
async def foo(x, y, z):
...
def enhanced_foo(x, y, z):
if z is None:
raise TypeError
return foo(x, y, z)
``` enhanced_foo is not an async def function, so it won't work either. This might look silly, but you could have a decorator that only does some transformations on the arguments (like runtime type validation with beartype or typeguard)
def prohibit_none_arguments(fn):
def decorated(*args, **kwargs):
if None in args or None in kwargs.values():
raise TypeError(f"None in arguments to {fn}", args, kwargs)
return fn(*args, **kwargs)
return decorator
There's inspect.markcoroutinefunction, but it's a very recent addition and a bit of a hack
Python is the best language because it's so dynamic but it's also the worst language because it's so dynamic 😆
a more reliable way to check would be to call the function and see if the result is awaitable (in which case, await it)
Great point
Is there a way to eliminate the dynamic class in the following, without explicitly defining child classes?
import typing as ty
T = ty.TypeVar("T")
class Foo:
pass
class Ref(ty.Generic[T]):
@classmethod
def get_ref_type(cls, target_type):
class RefT(Ref[target_type]):
pass
return RefT
@classmethod
def get_target_type(cls):
return ty.get_args(cls.__orig_bases__[0])[0]
ref: Ref[Foo] = Ref.get_ref_type(Foo)()
print(ref.get_target_type()) # <class '__main__.Foo'>
what are you trying to accomplish?
You could do something like ```py
class Ref(Generic[T]):
def init(self, target_type: type[T]) -> None:
self._target_type = target_type
def target_type(self) -> type[T]:
return self._target_type
ref = Ref(Foo) # inferred as: Ref[Foo]
Thanks! I guess I was trying to make it work somehow without storing any direct reference to the target class, but I'm not sure if that is possible
Well, you are storing it 🙂
it's just that in your example it's stored in the class attributes instead of inside the object
Yeah, I'm just curious whether e.g. in your example that the type information would exist during execution if not explicitly kept in self._target_type
Or if the runtime simply does not have that information
What do you mean by "that type information"?
If you're making an instance of a generic class, the resolved generic parameters are not stored anywhere if that's what you mean. It only exists for static analyzers like mypy andpyright (and for your editor)
Yeah, that was what was helpful to confirm
In that case it's clear that I must keep it, and I'd then prefer to just keep a target_type attribute in the generic class
creating classes at runtime is definitely a very esoteric thing, and you probably don't need it
Thank you for helping! 🙂 Gives good clarity to complete some changes in my type setup
ty 😨
you're welcome 🙂
😮💨
For example when you make a list such as [1, 2, 3], it doesn't remember that it's a list of integers. That would be correct to remember anyway, it could be a list[int | str] or a list[object]. And type annotations on variables can't impact the creation of an object on the right side of the assignment
foo: list[int | str] = [1, 2, 3] # direct annotation
bar = [1, 1, 1, "a"] # should this be inferred as list[int] or list[object] or list[Literal[1, 'a']]?
# Python doesn't specify that, that's up to the type checker
``` or a more complex example, where the type is inferred through less direct measures: ```py
foo: list[int] = [1, -2, 3]
bar = list(map(abs, foo))
If you're interested, aiohttp has a somewhat similar thing https://github.com/aio-libs/aiohttp/blob/v3.9.5/aiohttp/helpers.py#L849-L869
I guess those rules are the same for e.g. C++, however in that case there would be actual generic instances with ability to get that type
C++ is a statically typed language, so that's different
For example you can do ```cpp
template<class T>
void push_empty(std::vector<T>& vec) {
vec.push_back(T());
}
Sure, but with e.g. type variables the boundary feels a little more fuzzy between static and dynamic if you don't know the rules - to an extent it seems intuitive that a type variable would hold the type during runtime
I get why - it's just not necessarily super easy to get 😅
Well, the type might now always be passed in at runtime. As I showed above for example:
foo: list[int] = [1, -2, 3]
bar = list(map(abs, foo))
``` the type variable for `list` in the `bar` variable is only known to a type checker, it's not passed anywhere at runtime
In C++ the type parameter is also not stored on the instance AFAIK, that would be a huge waste. Instead it's just known statically
Yeah, in those examples it is very clear, but with e.g. generics it feels a little less, as RefMyType could also be seen as dynamically passing an argument with the MyType type information
With C++ historically separate code is generated per type, so it's not a direct runtime cost, but does add bloat
well, it's a tradeoff
It does generate more code and does make it kinda impossible to use template types as part of a stable ABI, but it removes potential levels of indirection
IIRC Swift chose a different route
For example, if you wanted to use the same code to handle any kind of vector, you'd need to store an array of pointers (kinda like in Python) or pass around the information about size and alignment at runtime
Hello, I am having a bit of trouble with type hinting when relating to private types such as those in the argparse module.
An example of the problem:
import argparse
class GroupDict(dict[str, argparse._ArgumentGroup]):
pass
groups = GroupDict() # Type gets reported as GroupDict[str, Any]
group = groups["Group 1"] # Type gets reported as Any
it works as expected with mypy 1.10: https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=749d9c4efabf3b05aa86b102ef42887c
import argparse
class GroupDict(dict[str, argparse._ArgumentGroup]):
pass
groups = GroupDict()
reveal_type(groups)
group = groups["Group 1"]
reveal_type(group)
main.py:7: note: Revealed type is "__main__.GroupDict"
main.py:10: note: Revealed type is "argparse._ArgumentGroup"
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
PyCharm thinks it's any. Though I did figure out a work around:
from argparse import *
parser = ArgumentParser()
ArgumentGroup = type(parser.add_argument_group())
SubParsersAction = type(parser.add_subparsers())
del parser
``` then replacce `import argparse` with `import argparse_ as argparse`
don't use pycharm for typechecking, it is bad at it
Yeah, its analyzer is kinda stinky tbh
there is now an lsp plugin for using pyright in it though
hoping for astral type checker in rust
wait what, PyCharm professional has LSP support but not the community one?
dont be poor ig? 😎
I am literally unable to buy it 😔
though not sure why I would, I'm happy with vscode
I have no clue what PyRight is.
Well, it's not glued to vscode
You can run pyright with vim/emacs/sublimetext/other editor with LSP support, or run it as a CLI program
unlike pylance which is both closed source and restricted to the microsoft build of vscode
(Not sure if this is the correct topic...)
How to define an abstract typed class attribute?
class A(ABC):
some: int # <<< Abstract attribute
class B(A):
some = 5 # <<< To force this
b = B()
class typing.Protocol(Generic)```
Base class for protocol classes.
Protocol classes are defined like this:
```py
class Proto(Protocol):
def meth(self) -> int:
...
``` Such classes are primarily used with static type checkers that recognize structural subtyping (static duck-typing), for example...
So it's only through the Protocol? Or is there a way to use @ abstractmethod or alike?
class Some(Protocol):
some: ClassVar[int]
class A(Some, ABC):
pass
class B(A):
some = 5
b = B()
@abstractmethod can be layered on @property to get the behavior you want, I think.
why is
@url.setter
def url(self, value: Optional[str]) -> None:
if not isinstance(value, str | None):
raise TypeError("err: URL must be a string")
c_log.debug(f"url set: {type(value)}")
self._url = value #<- error on this line
giving
Incompatible types, expression has Optional[str] Variable has None
isnt optional string just Str | None ?
which type checker are you running? it might have a bug
I suspect it doesn't suggest isinstance() checks with the | syntax
let me see what happens with optional
Optional[str] doesn't work with isinstance either
if value is None or not isinstance(value, str) should work
i see, thank you
Damn, mypy has 2.6k issues 💀
one fewer because I just closed that one as a duplicate 😄
How many would they have if they behaved like tiangolo and converted every non-confirmed issue to a discussion?
🤔
I know I'm not in a position to complain, mypy is run by volunteers and I'm not willing to volunteer myself
just a bit surprised at the volume of issues
there's a lot of duplicates
sometimes I think of going in and closing some duplicates
but there are 2.6k issues
yeah
also there are things that probably won't be implemented in any reasonable timeframe, like python/mypy#17218
yeah I feel there's tons of similar #topic-type-narrowing issues asking for support for any() or all() or whatever but I don't feel like going through them all
I think I closed around 300 in two weeks when I first became a triager. But it's hard to sustain that level of energy 🙂
My favourite genre of mypy "bug report" is the "hey! Mypy doesn't understand arbitrary Python code!" genre. I feel like we get an issue every few weeks that boils down to that
I think the issue above kinda fits that 😛
I mean, it's understandable. False positives are literally mypy saying your code has issues when there aren't any
Maybe I'm doing something wrong, but this doesn't work
class A(ABC):
@property
@abstractmethod
def some(self) -> str: pass
class B(A):
some = "asd" # <<<<< Type error
b = B()
It gives:
Expression of type "Literal['asd']" is incompatible with declared type "property" "Literal['asd']" is incompatible with "property"
And working with Protocols seems to be more intuitive and easy.
You have to implement it as a property in the subclass. That’s the contract specified by making a property abstract. And fair enough, a protocol can work fine, but an abstract property throws an error at runtime if not implemented in a subclass, so it’s technically “stronger”.
Changes the interface a bit from regular assignment, though.
Yeah, my bad - property works
class A(ABC):
@property
@abstractmethod
def some(self) -> str: pass
class B(A):
@property
def some(self) -> str:
return "asd"
b = B()
It might be stronger (which I didnt know, thanks), but... just compared to Protocol...
class Some(Protocol):
some: ClassVar[int]
class A(Some, ABC):
pass
class B(A):
some = 5
b = B()
Hello!
I remember that there was a site with tasks on type hints. The tasks were divided by difficulty, but that's all I can remember. It would be great if someone could tell me the name of this site. 🙂
(i never actually used this, just a quick google search)
maybe this: https://python-type-challenges.zeabur.app/?
Yes, it is!! Thank you
How can I connect all these types for function what_i_need to work but avoid creating lots of overloads?
The problem with overloads - they seem very boilerplate (in real life case I'll have hundreds of classes, not just Dog and Cat) and it seems there should be a better way to organize this.
from typing import Union, TypeVar, assert_type, overload
# fmt: off
class Base: ...
# NOTE: I wish I could use generics like Dog[Good]
# but I can't since Dog[Good] supposed to have some different
# attributes than Dog and Dog[Bad]
class GoodDog(Base):
def smile(self): pass
class BadDog(Base):
def bark_angrily(self) :pass
Dog = Union[GoodDog, BadDog]
class GoodCat(Base):
def purr(self): pass
class BadCat(Base):
def destoy_things(self) :pass
Cat = Union[GoodCat, BadDog]
# fmt: on
def why_i_use_unions(cat: Cat):
# I need warnings like this, so I could work with `Cat` classes in general
# but be notified about the differences:
# Cannot access attribute "purr" for class "BadDog"
cat.purr()
# fmt: off
# T classes used to address types in general, without Unions
# as Unions doesn't allow to store any information in them
class T:
class Base: pass
class Cat(Base): pass
class Dog(Base): pass
TV = TypeVar("TV", bound=T.Base)
class Cage:
def add_animal(self, c) -> Base: ...
class GoodAnimalCage(Cage):
# @overload
# def add_animal(self, c: type[T.Cat]) -> GoodCat: ...
# @overload
# def add_animal(self, c: type[T.Dog]) -> GoodDog: ...
def add_animal(self, c: type[T.Base]) -> Base: ...
class BadAnimalCage(Cage):
# @overload
# def add_animal(self, c: type[T.Cat]) -> BadCat: ...
# @overload
# def add_animal(self, c: type[T.Dog]) -> BadDog: ...
def add_animal(self, c: type[T.Base]) -> Base: ...
# fmt: on
def what_i_need():
cage = GoodAnimalCage()
cat = cage.add_animal(T.Cat)
assert_type(cat, GoodCat)
dog = cage.add_animal(T.Dog)
assert_type(dog, GoodDog)
or any other suggestions to reorganize this are also welcome
Is it possible to make a generic protocol with class attribute?
Something like that:
T = TypeVar('T')
class Some(Generic[T], Protocol):
some: ClassVar[T] # <<<<<<<<<< "ClassVar" type cannot include type variables
class A(Generic[T], Some[T], ABC):
pass
class B(A[int]):
some = 5
b = B()
The idea is to have a required attribute in a generic abstract to force the child to implement it.
perhaps you can make a generic metaclass
You mean with a runtime check? Is there a way to do it with a type check?
No, I mean that B would be an instance of a generic metaclass, but not typechecking at runtime. The generic metaclass would have instances with a some attribute, bound by a typevar on the metaclass
it's a difficult problem for sure
I'm just not sure how to add said attribute
I might be missing something, but what is the need for metaclass then?
I can define "some" in my base class "A", but it means that "B" will have no obligation to define it.
I was meaning like this
from typing import reveal_type
class MyMeta[T](type):
def __new__(cls, name, bases, attrs, **kwargs):
return super().__new__(cls, name, bases, attrs)
def __init__(self, name, bases, attrs, *, some: T):
super().__init__(name, bases, attrs)
self.some = some
class B(metaclass=MyMeta[int], some=5):
pass
reveal_type(B.some)
although it does give that warning idk why
"B" class definition does not force or warn you of not specifying the "some" argument.
Not to mention the ambiguity error :<
it's not really ambiguous IMO
I did write how I'm not sure if typecheckers are advanced enough to understand it, but I edited because I couldn't figure out how to word it 😄
As far as I understand, it is ambiguous (not sure if defining class variables in any other way won't be as well...) - B[int] is the same very class as B[str]... However, in this case B is not supposed to be Generic on it's own, but rather a specific one, thus (just like you mentioned), in my opinion as well, it should not be ambiguous...
I didn't make B generic here in my example
the metaclass is generic but is being specified
sorry didn't read you whole message, it kinda looks like you changed your opinion half way through?
Quick question, I have a python code written I just need help with a few lines of it. Who can I contact to get help from?
Yeah, that is what I meant in the seconde part
this is a specific topic channel
is there a way to type lists like how you'd do for tuples, specifying each element individually? I know tuples are supposed to used for that but the library I'm trying to add types to currently uses lists instead of tuples (kinda frustrating)
The first part was if B was still generic
no
you can just lie about the types if the library doesn't use any-list specific methods
yeah I could just use tuple[a, b, c] instead of list[a | b | c]
maybe that'll get them to change the types :p (yeah right)
It's not possible to do that with lists, no
You'd need to remove a lot of list methods, like clear. and you'd need to tell functions like random.shuffle that this list is no good
so you'd only be left with __setitem__
asyncio.gather 😩
a type safe __setitem__ for what's effectively a mutable tuple? maybe if you use type-level nats as indices 😄
Yes, why don't I use these nats
the typechecker could generate stuff like ```py
@overload
def setitem(self, index: Literal[0], value: A, /) -> None: ...
@overload
def setitem(self, index: Literal[1], value: B, /) -> None: ...
@overload
def setitem(self, index: Literal[2], value: C, /) -> None: ...
🙂
Can I define generic specific attributes? E.g. A[int] to have an attribute value and A[list] to have an attribute values?
Well you could do stuff with __class_getitem__ but typecheckers won't understand
And unless you actually instantiate it like A[int]() it wont work
You can add restrictions to methods
class Foo(Generic[T]):
def square(self: 'Foo[float]') -> float: ...
def count(self: 'Foo[list[int]]') -> int: ...
Oh right interesting, could you combine that with property?
That's really cool! The downside that you're probably still will be able to see square in the drop-down list of attributes when you will do Foo[float](). in IDE but at least you'll get a type error using it.
Haven't tested but why not? It should answer my next question that was "is it possible to make generic specific attribute types?" - seems possible with this method, property and bunch of overload
Is there any way to define a generic abstract class field?
This is the closest I got, but the typing doesn't work here...
T = TypeVar('T')
class A(Generic[T], ABC):
@classmethod
@abstractmethod
def some(cls) -> T: pass
class B(A[str]): # <<<< 'str'
@classmethod
def some(cls) -> int: # <<<< 'int'
return 5
b = B()
import abc
from typing import Any, Callable
class classproperty[T, R]:
def __init__(self, func: Callable[[type[T]], R]) -> None:
self.fget = func
def __get__(self, instance: T | None, owner: type[T]) -> R:
return self.fget(owner)
class A[T](abc.ABC):
@classproperty
@abc.abstractmethod
@classmethod
def some(cls) -> T:
...
class B(A[int]):
@classproperty
@classmethod
def some(cls) -> int:
return 42
maybe
i feel like there should be a simpler way lmao
oh lmao it typechecks but doesnt run
import abc
from typing import Callable, cast
class classproperty[T, R]:
def __init__(self, func: Callable[[type[T]], R]) -> None:
self.func = cast(Callable[[type[T]], R], getattr(func, "__wrapped__"))
def __get__(self, instance: T | None, owner: type[T]) -> R:
return self.func(owner)
class A[T](abc.ABC):
@classproperty
@classmethod
@abc.abstractmethod
def some(cls) -> T:
...
class B(A[int]):
@classproperty
@classmethod
def some(cls) -> int:
return 42
print(B.some)
does though
but thats too cursed
tested, it will work, though there are some issues with it - https://github.com/python/mypy/issues/9937
Assuming a issue.py module: $ mypy issue.py issue.py:86: error: Invalid self argument "MyContainerStr" to attribute function "bounds" with type "Callable[[MyContainerBase[i...
Sorry for the late reply.
In your example specialising B also does not check the type of a classmethod (the same thing as in my case):
class B(A[str]): # <<<<<<<<<<< 'str'
@classproperty
@classmethod
def some(cls) -> int: # <<<< 'int and no error anywhere
return 42
it actually does for me, using pyright in vscode (with python 3.12)
Oh... after vscode reinstall it was set to basic. In strict it shows this error, yes.
Hmm... but why this shows only in strict?
And with this the code I shared earlier works as well with the same error.
Though I just wonder if there is a more clear way to define this?
I mean something like this (it doesn't work thoguh):
class B(A[int]):
some = 5
do you need it to be a class var only?
because
class A[T]:
some: T
class B(A[int]):
some = 42
works (and gives an error if its not an int)
the problem with this goes like this:
ah right
Generic class fields do not like it :<
That is why I started trying the method approach, which seems ugly
seems like its a limitation of pep 526, which has been noted back in 2018 but still no progress
https://github.com/python/mypy/issues/5144#issuecomment-439524567
Yea, saw that when searching on how to do it, as it seemed strange for me that a specialised child still considers it as invalid
Has anyone made a PEP or something for Proxy[T]? If so, would it be worth a PEP or a simple PR?
I think it would be more worth it to implement composite types
What would Proxy even do?
If it's anything like javascript's Proxy, that's not type safe. It's basically return new Proxy(obj, {}) as typeof obj)
Why isn't this typesafe?
This is the Proxy constructor in typescript. Note that it returns T.
https://github.com/microsoft/TypeScript/blob/0b37062eb07d1666245a02471b7d0e80c5d5aa5d/src/lib/es2015.proxy.d.ts#L108
src/lib/es2015.proxy.d.ts line 108
new <T extends object>(target: T, handler: ProxyHandler<T>): T;```
You can implement this with __new__
type Proxy[T] = T
def retry(wait: float, stop_after_delay: float):
def wrapper(func):
@functools.wrap(func)
def wrapped(*args, **kwargs)
start_time = time.monotonic()
while True:
try:
return func(*args, **kwargs)
except Exception:
if time.monotonic() - start_time > stop_after_delay:
raise
time.sleep(wait)
return wrapped
return wrapper
How do I type annotate this decorator?
from collections.abc import Callable
from typing import TypeVar, ParamSpec
T = TypeVar("T")
P = ParamSpec("P")
def retry(wait: float, stop_after_delay: float) -> Callable[[Callable[P, T]], Callable[P, T]]:
def wrapper(func: Callable[P, T]) -> Callable[P, T]:
@functools.wrap(func)
def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
Id probably use F = TypeVar("F", bound=Callable) so it can be applied to all callables and preserve the type
no, it will always return a function
oh yep fairs
That might actually be a consideration before ParamSpec though
Does anyone know if type hinting via comments such as # type: int will carry on being supported?
I vaguely remember it getting deprecated, but I cannot find a source for it, nor what is deprecating support for it.
Would be nice if I misremembered.
Just found a mypy PR for about fixing something related to it. So at least I am not the only one relying on it. Nevermind me.
they arent supported by pyright so if this a client library you shouldnt be using them
Mm... Thanks for the headsup. Can't use Pyright then.
To my knowledge (as of a few months ago, at least), pyright still can understand them, but the maintainer mentioned in an issue that they won't guarantee that functionality will stick around.
There's no active efforts to deprecate them but I'd like to deprecate support at some point; there's no need to use type comments in modern Python, and their existence makes the language more complicated and makes type checkers harder to implement.
Ah okay. Good to know. Safe for now then. Hopefully that will be after I can stop supporting Python 3.5.
3.5? I'm sorry
Does anyone know if stuff like dict[str, int] is valid in python 3.8 or if I still have to use typing.Dict[str, int]
dict[str, int] only works since 3.9
!pep 585
Is this overkill if trying to be compatible with 3.8 without breaking runtime introspection?
import sys
from typing import TYPE_CHECKING
if sys.version_info >= (3, 10):
from typing import TypeAlias, TypeGuard
elif TYPE_CHECKING:
from typing_extensions import TypeAlias, TypeGuard
else:
if sys.version_info >= (3, 9, 2):
from types import GenericAlias as _GenericAlias
else:
from typing import _GenericAlias
class _TypeGuardGenericAlias(_GenericAlias):
def __repr__(self):
return f"<placeholder for {super().__repr__()}>"
class _TypeGuardMeta(type):
def __getitem__(self, item: object) -> _TypeGuardGenericAlias:
return _TypeGuardGenericAlias(self, item)
def __repr__(self):
return f"<placeholder for {super().__repr__()}>"
class TypeGuard(metaclass=_TypeGuardMeta):
pass
class TypeAlias:
def __repr__(self):
return f"<placeholder for {super().__repr__()}"
Cant be before 3.9 is EOL, there were still stdlib collections not generic at runtime where a type comment was the only option
Actually...3.11
Array.array isnt generic in stdlib till 3.12
How can I get TypedDict from Person dataclass without recreating it explicitly?
from dataclasses import dataclass
from typing import TypedDict
class PersonDict(TypedDict):
name: str
age: int
@dataclass
class Person:
name: str
age: int
def test(p: Person | PersonDict): ...
test({"name": "Jack", "age": 5})
you can't
you can just quote annotations for that, no need to use type comments
causes runtime introspection libraries issues to tell people to put invalid annotations in quotes
When did python get support for using the types directly? E.g. var: list[str] = x as opposed to from typing: var: List[str] = x ?
3.9
!pep 585
Given a class Callback(Protocol): def __call__( is there a way to declare that def my_callback() is a Callback type without needing to repeat the definition?
You could do _: Callback = fun and if that typechecks, then fun is a valid Callback.
You should include the typehints on your function
I really wish we had a decorator for inferring the correct type signature
something like ```py
class Callback(Protocol):
def call(self, x: str, *, foo: bool) -> str: ...
@implements(Callback)
def foobar(*args, **kwargs):
...
I feel like this may have been a rejected idea when Protocols were being discussed.
That would very valuable when making proxies and decorators and ensuring the outer function can be generic, but type checked proper.
If you're intentionally just using it to copy a signature, you can already do that
def implements(sig: Callable[P, R]) -> Callable[[Any], Callable[P, R]:
return lambda f: f
you can also do this, and keep the type consistency check:
def _foobar(*args: Any, **kwargs: Any) -> str:
...
foobar: Callback = _foobar
Note that the signature of your foobar isn't actually consistent with that callback, and I had to modify it adding the correct return type
.nox/test-3-13/lib/python3.13/site-packages/pip/_vendor/typing_extensions.py:1375: in _set_default
type_param.__default__ = None
E AttributeError: attribute '__default__' of 'typing.ParamSpec' objects is not writable
does typing-extensions not support 3.13 yet?
I guess not.
we'll cut a release soon
i think it's not irredeemably broken though
looks like you hit this in pip?
Thanks! I've discovered that this doesn't preserve the type when wrapping a callable class, but I'm only doing this in a test file so I've just slapped a # mypy: disable-error-code="attr-defined" at the top of the file.
doesn't preserve the type when wrapping a callable class
well... because it returns a function, not an instance of your custom class
ah right
e.g. if you pass in a partial object you won't get a partial back
It turns out that @functools.wraps copies over the original attributes. TIL.
https://github.com/ichard26/pip/blob/1ceaed9f7351509c72cc1ac0722849949c108d57/tests/unit/test_utils_retry.py this is probably a bit cursed then
Yup, I'm trying to type annotate a decorator (so I can replace a vendored dependency). We can wait for the next typing-extensions release, no rush.
So this questions isnt about type hinting per say, but it has to do with Types in python. I had some dialogue with some random redditor and I'm looking for honest feedback on if I handled this with grace or if I was just being a douche. And any false information I may have given https://www.reddit.com/r/dfpandas/comments/1ciskog/dtype_differs_between_pandas_series_and_element/
Sadly there is no way to preserve overloads
<#type-hinting message>
pylance huh?
looks like a bug 🙂
Am I missing something or why this does not give an error?
from abc import ABC, abstractmethod
class A(ABC):
@classmethod
@abstractmethod
def some(cls) -> int:
pass
class B(A):
pass
print(B.some()) # <<<<<<< "some" is not defined in B, but it is an abstract method...
What are you talking about?
You should give me permission
I am a software developer
I want to interact all of you
If you're talking about voice permissions, make sure to read #voice-verification. Especially the sentence between the ⚠️ signs
Ok, I'm dumb.
"@abstractmethod" restricts only the instantiation even if it is applied to a classmethod, which does not prohibit it to be called directly.
Is there any way to force a child to actually implement it?
Well, if you inherit from an abstract class and don't implement all the abstract method, you get another (valid) class which is also abstract
I mean the abstract class field
ah, you mean there's no typechecking error?
that does seem like a bug to me...
In the code I provided earlier. The error is only given upon instantiation, not actually using the class field without the instance.
And it seems to be the intended behavior
Can't find a way to declare a generic abstract class attribute :<
Maybe __init_subclass__ is a better tool for your usecase
Yea, was going to use it, but it means there will be no type check fir it, right?
Type checkers should be able to understand that method. If not, probably suggest an enhancement.
Then I guess I have no idea how to do that correctly... (if it's possible)
T = TypeVar('T')
class metabase(Generic[T], ABC):
def __init_subclass__(cls) -> None:
cls.some = 0 # <<<<<<<<<<<<<<<<<< Error here: Cannot assign to attribute "some" for class.....
super().__init_subclass__()
class A(metabase[int]):
pass
a = A()
a.some # <<<<<<< No error here
A.some # <<<<<<< But error here: Type of "some" is unknown...
Or is there any way to type check either a decorated method or a class attribute against a base class generic type?
My case is that I would like to have a generic base class for a (the closest one would be) dispatcher. The child class has to define a set of methods (with arbitrary names) that will be used for that dispatch. And I would like to be able to type check it instead of giving a runtime error if the types are wrong.
The best case would be to have a decorator that could type check a method against the generic parent class...
Imo it would be better to define an interface (i.e. typing.Protocol) instead of an ABC (assuming you're using strict mode that is. I don't think type checkers would complain about failing to implement an interface otherwise)
that way you won't even have to define abstractmethods
Sorry for the late reply.
Tried that, but you still cannot define a class generic variable using a Protocol.
class Foo(Protocol[T]):
x: T
Protocol and TypedDict accept type arguments like Generic
if you swap pass for raise NotImplementedError you'll get the behavior you should. pass and ... both count as trivial bodies to the type checker (unchecked, assuming it's a stub) and it counts as an implementation to the abstract method, which prevents raising on a creating an instance...
pyright doesn't think that pass is an empty body, it will complain that function doesn't return anything
T = TypeVar('T')
class Pr(Generic[T], Protocol):
some: T
class A(Generic[T], Pr[T], ABC):
pass
class B(A[int]):
pass
b = B() # Expected Error: <<<<<<<<<<<<<<<<< Cannot instantiate as "some" is not defined
print (B.some) # Error "Access to generic instance variable through class is ambiguous" which does not say that it is not defined
The last line will produce the same error even if B is defined like this:
class B(A[int]):
some = 5
did you mean B.some or b.some?
use a metaclass?
And what to do there? As far as I know metas are not loved by type checks?
metaclasses are fine. It's mixins that type checkers don't like
and even then, inheriting mixins is fine.
But what will it give me in terms of forcing a child to implement it?
It would be much better with composite types. We could type mixin functions as def foo(self: Self & Foobar)
What exactly is your usecase?
T = TypeVar('T')
class metabase(Generic[T], ABC):
def __init_subclass__(cls) -> None:
cls.some = 0 # <<<<<<< Cannot assign to attribute "some" for class
class A(metabase[int]):
pass
a = A()
a.some
A.some # <<<<<< Type of "some" is unknown
just annotate it
class myclass:
some = 0
class A(myclass):
pass
class B(myclass):
some = 2
A.some # 0
B.some # 2
assign it in the parent and it will be the default.
THere is no default - it is a generic class
My use case is to have a generic static variable that will be handled by a meta parent class to initialise a dispatch method
So like this? ```py
class Parent(Generic[T]):
some: T
def init_subclass(cls, *, some: T):
cls.some = some
class Child(Parent[str], some="fooo"):
pass
Wait... I do remember trying this way, but I was not shown an error for missing argument in class definition....
Hmm... this might work in my case it seems
Have no idea what I did differently for it not to show an error.
Almost related to my question - is it possible to type check against a parent class in a decorator for a child method?
class B(A[int]):
@deco # <<<<<<<<<<<<<<<<<< To validate that "arg" is of the same type as generic parent, i.e. A[int]
def func(self, arg: int) -> None:
pass
Why would you need that decorator?
Basically for the same reason as previously - to mark methods as dispatch targets, and I can handle them in parent meta.
The only reason you'd need that decorator is if you were using B.func(b, arg) instead b.func(arg)
!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 know about that one, but I need my own logic for a dispatch
It's your code ¯_(ツ)_/¯
Why is that?
Maybe I misread your comment
The only thing that I would like to have is that the decorator must be applied only to a specifically typed method (according to the parent specialisation)
Try defining the classvar as an instance variable in the protocol, then use MyProtocol instead of type[MyProtocol]?
It gives this error right away: "ClassVar" type cannot include type variables
class TypeA(Protocol):
some: Callable[[], int]
class A:
@classmethod
def some(cls) -> int:
return 1
def foo(cls: TypeA):
print(cls.some())
foo(A)
this passes on my end
This one is not generic
this allows for generics
T = TypeVar('T')
class TypeA(Protocol[T]):
some: Callable[[], T]
class A:
@classmethod
def some(cls) -> int:
return 1
def foo(cls: TypeA[int]):
print(cls.some())
def bar(cls: TypeA[str]):
print(cls.some())
foo(A) # Passes
bar(A) # Fails
Why then type hint always complain about class variable not being allowed to be generic?
I'm not sure myself, but the above example can bypass that limitation
Oh, that's waht I was talking about:
T = TypeVar('T')
class TypeA(Protocol[T]):
some: T
class A:
some = 5
def foo(cls: TypeA[int]):
print(cls.some)
def bar(cls: TypeA[str]):
print(cls.some)
foo(A) # Fails
bar(A) # Fails
you just need to define some as ClassVar
T = TypeVar('T')
class TypeA(Protocol[T]):
some: T
class A:
some: ClassVar[int] = 5
def foo(cls: TypeA[int]):
print(cls.some)
def bar(cls: TypeA[str]):
print(cls.some)
foo(A) # Passes
bar(A) # Fails
... why is that allowed to do that? I mean Protocol does not state that "some" is a ClassVar...?
TypeA is meant to be used where you would otherwise have type[A]
so an "instance variable" on TypeA would be a class variable on A. that is how it bypasses the need for ClassVar on the protocol, enabling generics to be used
Wait... Im dumb
I guess this was the thing I missed
As I trie to declare ClassVar[T] in Protocol
As a bit separate question - what am I missing in this?
T = TypeVar('T')
class A(Generic[T]):
pass
DT = TypeVar('DT')
def deco(func: Callable[[A[DT], DT], None]) -> Callable[[A[DT], DT], None]:
return func
class B(A[str]):
@deco # <<<<<<<<<<<< ..... Type "A[str]" is incompatible with type "Self@B"
def func(self, arg: str) -> None:
pass
Or it's not possible to reference the parent class at this point?
you can annotate self: A[str] to avoid this particular problem
Yea, tried it, but it really looks ugly...
But I guess that doesn't address the root of the issue. The problem is that:
Bis a subtype ofA[str]- Since parameters are contravariant,
Callable[[B, DT], None]is a supertype ofCallable[[A[DT], DT], None] - So,
Callable[[B, DT], None]cannot be assigned toCallable[[A[DT], DT], None]as an argument todeco
Am I missing something?
Callable is intended to get a A[DT], while we decorate a funciton that passes B, which is a valid child of A[DT?
pff how do you inline that?
use single backtick instead of triple
Ty
you should read up on why parameter types are contravariant
And I got it... Yea... Again I just took it vice versa...
That is strange that self: A[str] does not trigger an error then
by annotating self: A[str], you basically disallow the function from using anything that is specific to B, so it is effectively A[str]
True, in this case it just works as an interface
It seems not possible to reference the generic type of a parent in a child class without limiting it to be a parent class?
what do you mean by that?
In my previous code to check that arg is of the same type as the one specified for the parent class A
Yeah that is not possible: we would need to make A[DT] itself a TypeVar which cannot be done in Python due to lack of support for higher-kinded types
generic type parameters, Final[T] essentially means T, but reassigning to that variable is a typechecker error
list[int] would mean a list of ints, stuff like that
Is there an order being required? like:
typing.something[array[type]]
Or can I make: list[Final[str]] or str[Final]
order matters
only some types have type parameters, str doesnt even have one, so str[Final] would be an error, yes
also, final is not valid in this context, its only valid as the "first" type in the chain, i think, when its literally Final[T], not used in some other one
As order matters, but maybe only one of the parameters in the list shouldn't be changed. Shouldn't it work this way?
More over, as far as I understand, the interpretation order will not allow it, as method decorators are interpreted first and only then the class itself with inheritance. Or did I misunderstand?
no, you can have forward references in type annotations
does typing_extensions comes with python 3.11 by default?
No, it's an external package
!pypi typing-extensions
But it comes with mypy so you can use if typing.TYPE_CHECKING: import typing_extensions
yeah, thank you.
mainly was interested whether it actually comes with python - had multiple pythons installed and all of them had typing_extensions but I didn't remember downloading it 😅
Could've been installed as a dependency on a package you installed
yeah, probably
it is a dependency in many projects
at that point, the only thing stopping you from doing import typing is slight api differences
I know this isn't quite type hinting, but does anyone know if there is a rule on any linter that warns about properties with side effects?
that would require that one can typehint that a function does side effects. The best you can get with that is for example MutableSequence, but still doesn't guaranteee that the function is actually mutating.
and then you would need a way to typehint that something makes a network call or system call with side effects
hmm I was hoping that some linter could figure out from AST that it's a non-pure function
Oh well, until the python type system gets better I guess my dreams are not gonna happen xD
from typing import Optional
def get_foo() -> Optional[str]:
return None
foo = get_foo()
# Works:
if foo is not None and True:
print(foo.split("_"))
# Fails:
predicate = foo is not None and True
if predicate:
print(foo.split("_")) # "split" is not a known attribute of "None" Pylance reportOptionalMemberAccess
# PS: Removing `and True` works in both cases
Are there ways to reduce issues in reporting of access of None for cases which should seemingly work, such as this? (Using Python 3.11.8)
huh, i thought pyright couldnt narrow types based on an indirect type guard (assigning to a variable), but i guess it only works for simple type guards like cond = foo is not None... might be worth filing a bug report? i usually duplicate type guards in if-statements to avoid that issue
(there's a section in pyright docs about type guards, but it doesn't seem to describe those edge cases)
Checking open issues, might file if you think it is correct that this behavior should work
Rarely see projects with such excellent attention to issues 👀
Describe the bug Evaluating a non-trivial predicate outside of an if-statement leads to a false positive report of member access on None. Removing the and True in the code below makes the example w...
I wonder how could I create a TypedDict which has keys with "normally illegal" names for example
class BMHeaders(TypedDict):
userId: int
# Access-Token: str
# User-Agent: str

use the call syntax, BMHeaders = TypedDict("BMHeaders", {"Access-Token": str})
Thanks 
it works
Also just to avoid future misunderstandings, when I have that BMHeaders TypedDict and I need another one related to Headers which will have the same keys as BMHeaders with some of the new keys added, would I need to rewrite all of the keys like this
NewHeaders = TypedDict(
"NewHeaders",
{ "userId": int, "Access-Token": str, "User-Agent": str}
)
or is there any faster way like inheritance? Keeping in mind that this NewHeaders class could also contain illegal names for the keys @oblique urchin
The call syntax doesn't support inheritance, you have to duplicate the fields
Got answer now - it is working as designed (compound expressions are not allowed): https://microsoft.github.io/pyright/#/type-concepts-advanced?id=aliased-conditional-expression
Description
Honestly I don't quite understand inheritance with TypedDicts. It seems like it can't decide whether extra fields are allowed or disallowed
Like
class Foo(TypedDict):
x: int
class Bar(Foo):
y: str
foo1: Foo = {"x": 1} # ok
foo2: Foo = {"x": 1, "y": 2} # not ok
bar1: Bar = {"x": 1, "y": 2} # ok
foo3: Foo = bar1 # ok???
oh, literally the section just below the one i linked 🙃 thought it was something unrelated but i didnt read it thoroughly enough
Extra fields are generally allowed, but there's a special case for directly assigning a dict display
Is there special casing? I thought it is just allowed as part of inheritance, and conversion from dict enforces matching keys.
It's called out as a special case in the TypedDict PEP
How do I type hint a class type instead of a class instance?
Is it like type[Class]?
e.g. optimizer_class: type[optim.Optimizer] = optim.Adam for the following:
yes
A slightly faster alternative would be to use object as the dict value, since that has no effect. Doesn't work for multiple base classes though.
Yeah that's the problem, I could make the dict use object and the dynamically made class only for abc, so it's not twice in the mro, that probably would be faster, but honestly I don't really care that much about the speed here, it only gets ran with unit tests
Hey, I'm trying to type-hint my method, which should be able to take any kind of mutable sequence, that consists of comparable objects (and creates a heap).
However, while there are no type issues reported by pyright within the heapify method itself, when calling heapify(x) on my list, I'm getting: ```
Pyright: Argument of type "list[int]" cannot be assigned to parameter "lst" of type "MutableSequence[T@heapify]" in function "heapify"
"list[int]" is incompatible with "MutableSequence[T@heapify]"
Type parameter "_T@MutableSequence" is invariant, but "int" is not the same as "T@heapify" [reportArgumentType]
I do know about invariance with mutable containers, but I was hoping that since I used a typevar here, it should be able to just bind the `int` type and not cause such issues, but apparently, that didn't work. Any ideas how to solve this?
```python
from typing import Protocol
from collections.abc import Callable, MutableSequence
class Comparable(Protocol):
def __eq__(self, other: object, /) -> bool: ...
def __lt__(self, other: object, /) -> bool: ...
def heapify[T: Comparable](lst: MutableSequence[T]) -> None:
"""Convert a list into a **max** heap."""
parent: Callable[[int], int] = lambda index: (index - 1) // 2 # noqa: E731
# Iterate through the list in reverse order, skipping the first (index 0)
# (note: we can't use enumerate here, as it wouldn't work with reversed)
for idx in range(len(lst) - 1, 0, -1):
elem = lst[idx]
parent_idx = parent(idx)
parent_elem = lst[parent_idx]
if elem > parent_elem:
lst[idx], lst[parent_idx] = parent_elem, elem
def main() -> None:
x = [5, 8, 1, 3, 6, 10, 55]
heapify(x)
assert x[0] == 55
please ping on reply
I think the issue is with the definition of Comparable (i.e. int does not actually implement Comparable)
in particular, int doesn't support eq/gt comparison against object
^ comparable generally only compares against itself
If you want to compare against other types, you should implement a Comparable protocol.
also, in your code, you're actually using __gt__ comparison, not __lt__
class Comparable(Protocol[A, B]):
def __call__(self, a: A, b: B, /) -> int:
pass
oh, this looks super annoying though, it'd require people to use custom type wrappers just to get that compare method
is there a way to make a better protocol that uses the python methods?
if you will only be comparing int to int, you may be able to use def __lt__(self, other: Self, /) -> bool: ...
I tried this: ```py
class ComparableT:
def eq(self: T, other: T, /) -> bool: ...
but this fails with: ```
Pyright: Method "__eq__" overrides class "object" in an incompatible manner
Parameter 2 type mismatch: base parameter is type "object", override parameter is type "T@Comparable"
Type "object" is incompatible with type "T@Comparable" [reportIncompatibleMethodOverride]
!d typing.Self
typing.Self```
Special type to represent the current enclosed class.
For example...
self does the same
Doing it your way, the T should be a param of Protocol
in 3.12 it shouldn't
it doesn't work with Self either: py class Comparable(Protocol): def __eq__(self, other: Self, /) -> bool: ... gives basically the same thing: ```
Pyright: Method "eq" overrides class "object" in an incompatible manner
Parameter 2 type mismatch: base parameter is type "object", override parameter is type "Comparable"
"object" is incompatible with protocol "Comparable"
"lt" is not present [reportIncompatibleMethodOverride]
I didn't see that [T]
or well, it could, but I'd have to declare T elsewhere
yeah
the point here is that object uses : object to annotate other param
What happened to lt?
since it just returns NotImplemented there
I only showed it on eq here for simplicity
interestingly enough, __lt__ doesn't have that error
maybe cause object doesn't even define __lt__
eq should still use Any or object
so this does pass pyright: ```py
class ComparableT:
def eq(self: T, other: object, /) -> bool: ...
def __lt__(self: T, other: T, /) -> bool: ...
but how do I even annotate heapify now?
I can't do T: Comparible[T] can I?
Don't use T. Just use Self
this is odd. the typeshed stub doesn't work as expected
oh!
from collections.abc import Callable, MutableSequence
from typing import TYPE_CHECKING, TypeVar
if TYPE_CHECKING:
from _typeshed import SupportsRichComparison
# Sorry, I'm not using Python 3.12
T = TypeVar("T", bound=SupportsRichComparison)
def heapify(lst: MutableSequence[T]) -> None:
"""Convert a list into a **max** heap."""
parent: Callable[[int], int] = lambda index: (index - 1) // 2 # noqa: E731
# Iterate through the list in reverse order, skipping the first (index 0)
# (note: we can't use enumerate here, as it wouldn't work with reversed)
for idx in range(len(lst) - 1, 0, -1):
elem = lst[idx]
parent_idx = parent(idx)
parent_elem = lst[parent_idx]
if elem > parent_elem: # this fails!
lst[idx], lst[parent_idx] = parent_elem, elem
def main() -> None:
x = [5, 8, 1, 3, 6, 10, 55]
heapify(x)
assert x[0] == 55
yeah, I tried that one too
but this actually worked!
from typing import Protocol, Self
from collections.abc import Callable, MutableSequence
class Comparable(Protocol):
def __eq__(self, other: object, /) -> bool: ...
def __lt__(self, other: Self, /) -> bool: ...
def heapify[T: Comparable](lst: MutableSequence[T]) -> None:
"""Convert a list into a **max** heap."""
parent: Callable[[int], int] = lambda index: (index - 1) // 2 # noqa: E731
# Iterate through the list in reverse order, skipping the first (index 0)
# (note: we can't use enumerate here, as it wouldn't work with reversed)
for idx in range(len(lst) - 1, 0, -1):
elem = lst[idx]
parent_idx = parent(idx)
parent_elem = lst[parent_idx]
if elem > parent_elem:
lst[idx], lst[parent_idx] = parent_elem, elem
def main() -> None:
x = [5, 8, 1, 3, 6, 10, 55]
heapify(x)
assert x[0] == 55
What's the difference there?
the typeshed stubs use Any to annotate the other argument so it shouldn't matter
from collections.abc import Callable, MutableSequence
from typing import TYPE_CHECKING, TypeVar
if TYPE_CHECKING:
from _typeshed import SupportsAllComparisons
T = TypeVar("T", bound=SupportsAllComparisons)
def heapify(lst: MutableSequence[T]) -> None:
"""Convert a list into a **max** heap."""
parent: Callable[[int], int] = lambda index: (index - 1) // 2 # noqa: E731
# Iterate through the list in reverse order, skipping the first (index 0)
# (note: we can't use enumerate here, as it wouldn't work with reversed)
for idx in range(len(lst) - 1, 0, -1):
elem = lst[idx]
parent_idx = parent(idx)
parent_elem = lst[parent_idx]
if elem > parent_elem:
lst[idx], lst[parent_idx] = parent_elem, elem
def main() -> None:
x = [5, 8, 1, 3, 6, 10, 55]
heapify(x)
assert x[0] == 55
This works for me
hmm
well it's not in my way of doing it
yeah
the SupportsAllComparisons is literally a type that has all of the dunder methods for comparison
the SupportsRichComparison is just one that has lt or gt (union)
I'm not sure why it's failing with it though, as that should actually be enough
yeah
that is odd
it'd either use __gt__ from elem or __lt__ from param_elem in that comparison