#type-hinting
1 messages · Page 36 of 1
As for this:
@classmethod
def config_class(cls) -> type[ConfigT]:
return BaseConfig
``` mypy complains because this method promises to return some specific subclass of `BaseConfig` depending on the typevar, but it actually returns a plain `BaseConfig`. So if you forgot to override the method in `Foo`, the type signature would be a lie.
This method should probably not have an implementation in Interface, i.e. it should be an abstract classmethod
@trim tangle thanks for explaining! is there a way for the config property to be the type defined by whatever a class defines as the return type of config_class? I also want to allow subclasses the ability to not override that and default to the base class config class
is there a way for the config property to be the type defined by whatever a class defines as the return type of config_class?
No, there's no way to do that automatically. Unless you want to use atyping.Protocoland not require explicit inheritance
I'm not sure what you're making, but it looks like a complicated wrapper around global variables 🙂
(I'm assuming self.config_class()() is going to read some sort of environment variables or files)
I also tried a class level _config_class: type[ConfigT] = ... instead of the method but it gave me an error about being unbound
Anyone has some stats / surveys about typing usage in python ? Not sure if this is the right place to ask..
uh, the last thing was https://discuss.python.org/t/2024-python-typing-survey-analysis/61456 iirc
2024 Python Typing Survey Analysis Overview With help from the Pylance team at Microsoft and PyCharm at JetBrains, we’ve run a survey on typed Python since late July. I wanted to give an update with two weeks of data. Overall, I am thrilled at the number of responses so far. Here are some key stats: 730 responses as of August 17th, the date o...
oooh nice, thx so much
91% of respondents saying they use types “Always” or “Often”
wow
actually I guess the survey is likely to be quite biased
so less wow
There is also a Python community survey that JetBrains and the PSF run every year, right? That might also have info
yes this survey was targeted towards users of typing, so no wonder most respondents used typing
maybe I'm blind but I see zero questions about type annotations here, or type checkers
pretty surprising
you'd think especially an IDE survey would care a lot about that
Not sure what I've done wrong...
def process_bind_param(self, value: type[BaseModel] | None, dialect):
if value is not None:
value = value.model_dump_json()
pylance says value.model_dump_json() has an error: Argument missing for parameter "self"
type[BaseModel] means any child of BaseModel, right? So then in the last line there, value should have model_dump_json as a method? I can't figure out the self note.
BaseModel means value is an instance of BaseModel or any of it's subclasses, by saying BaseModel you're expecting the type BaseModel or any subclass of BaseModel, so instead of
class ExampleModel(BaseModel):
...
process_bind_param(ExampleModel(...), ...) # instead of this, it's expecting
process_bind_param(ExampleModel, ...) # not an instance, but the type itself
DUH! Thank you. Such a stupid mistake. I used type in the init, because I wanted to pass in the class itself, now I am expecting the instance. Stupid stupid mistake.
when I have overloads, and optional arguments, it feels like I basically need to repeat the default, or at least repeat some kind of value over and over?
Yes, it can get rather repetitive
correct. you could just write ... as the default in the overloads too
and type checkers will respect ... specifically, but not e.g. None?
I guess that's reasonable
yes
Cool, doubt I would have figured that out without your help. Much appreciated!
is there a more concise way of defining a contravariant Mapping than this? or is there something else that works the same way? ```py
from typing import TypeVar
DictA = dict[str | int, bool]
Var = TypeVar("Var", bound=str|int, contravariant=True)
DictB = dict[Var, bool]
def works(data: DictB) -> None:
...
def errors(data: DictA) -> None:
...
data = {"x": True}
works(data)
errors(data)
-# real Issue for context: <https://github.com/pola-rs/polars/issues/14468>, <https://github.com/pola-rs/polars/pull/19499/files>
I don't think that makes a contravariant mapping. Instead, because you didn't specialize the generic alias, it gets specialized to Any. dict is always invariant, you can't get around that with aliases.
To make a contravariant mapping you'd likely have to create your own protocol with the methods you care about.
...oh, yeah it does not complains about things like works({(1,2,3): False}) at all 
I have a library (sqlitedict) that implements a MutableMapping[any, any] and doesn't allow generics. How do I provide type annotations to an instance of it?
I want to override relevant methods with MutableMapping[KeyType, ValueType] while not dropping the relevant methods like commit()
https://github.com/python/typing/issues/213 didn't know implementing an Intersection[] could be this hard
this does not seemed to work
Can you explain what you mean by "doesn't allow generics"?
I cannot type annotate it by saying SqliteDict[Key, Value]
https://mypy.readthedocs.io/en/stable/runtime_troubles.html#using-classes-that-are-generic-in-stubs-but-not-at-runtime may be relevant. You may also be able to double-inherit from MutableMapping and SqliteDict (but not from Protocol; Protocols cannot also inherit from concrete classes)
ah, i think my version of pylint does not support bracket style generics
that was the major source of my confusion
sigh
I am not sure if i want the mix-in side effects from subclassing collections.abc.MutableMapping
guess I will just do this?
Why does this type-hinting not achieve the desired result? (Tuples should be all the same type but that type can be one of several):
from typing import Tuple, TypeVar
from typeguard import typechecked
InfoVal = TypeVar("InfoVal", int, float, str, bool)
@typechecked
def foo(data: Tuple[InfoVal, ...]):
print("OK:", data)
for data in (
(1, 2, 3), # ok
(1.0, 2.0, 3.0), # ok
("1", "2", "3"), # ok
(True, False, True), # ok
("1", 2, False), # TypeError
):
try:
foo(data)
except TypeError:
print("TypeError:", data)
OK: (1, 2, 3)
OK: (1.0, 2.0, 3.0)
OK: ('1', '2', '3')
OK: (True, False, True)
OK: ('1', 2, False)
A TypeVar with constraints must be solved to exactly one of the constraints. It doesn't allow mixing types.
Not sure I understand why that means it allows mixed types (the last test case) to not error?
(I copied the multiple types in TypeVar from the docs -> A = TypeVar('A', str, bytes) # Must be exactly str or bytes)
I guess typeguard may not implement TypeVars with constraints correctly.
FWIW MyPy also doesn't seem perturbed. Is there a better way to type a tuple like this?
Pyright seems to get this right:
"tuple[str, int, bool]" is not assignable to "Tuple[str, str, str]"
Tuple entry 2 is incorrect type
"int" is not assignable to "str" (reportArgumentType)
mypy seems to catch it if you remove the @typechecked decorator https://mypy-play.net/?mypy=latest&python=3.12&gist=1d30746df28754f8683db3a654745df2
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
Generally I would recommend against using constrained TypeVars though, they behave oddly and unintuitively. In this case you're probably better off with a union, like tuple[str, ...] | tuple[int, ...]
The mypy error seems unintuitive to me in the linked case
It's related to how mypy infers the type for heterogeneous collections (using a "join" operator)
Using a union of the different tuple-types seems to produce better results, but I find it a shame the TypeVar solution does not work given how unwieldy the union solution becomes
they behave oddly and unintuitively
Do you have any reading on this?
Hey so I was wondering sometimes in built-in methods a parameter is for example from the type SupportsIndex instead of int or LiteralString instead of just str. What does that mean?
Supports{something} usually means a protocol with a __{something}__ method
__index__ is the dunder invoked when something is used as an index in like, a list
LiteralString is well, a string literal. e.g. input() would not be returning a LiteralString, but "hi" is a LiteralString (and strings you make from other literal strings are literal strings too). you could see that being used for e.g. an sql query function: you dont really want to pass arbitrary strings to it
what does __index__ do?
it returns an int representing what the object should be as an index
like, for a list xs, xs[idx] would use type(idx).__index__(idx) as the actual index. for ints, __index__ is just a return self
its basically implicit type casting to be able to use multiple things as an index
in this case, what would type(idx) return?
SupportsIndex or int?
it would be whatever the type of idx is, could be int, could be Zero
class Zero:
def __index__(self) -> int:
return 0
SupportsIndex is a protocol, there are no actual instances of it
i did not write idx.__index__() because operations are implemented on types, not instances. if it makes more sense to you, its idx.__index__(), but not really (if you do idx.__index__ = lambda: 0 for some idx, it wont actually become usable as an index)
so you mean SupportsIndex is only for type hinting
well, it is also runtime checkable with isinstance, but its basically hasattr(type(idx), "__index__")
but yes, its used to express "some type that has __index__(self) -> int defined"
Is there a way to tell type checkers to tell which attributes are valid and not valid when implementing __getattr__() ?
I'd like to explicitly tell which attributes exists and their type hints and give typing errors on the other. It works using @overload to specify a function prototype for an attribute which is handled by __getattr__() and likewise for a regular attributes, e.g. key: int. But even when __getattr__()raises AttributeError for its unknown attributes, the type checker seems to assume a type for every attribute it finds and that default behavior I'm looking for.
You can probably do
def __getattr__(self, name: Literal["x", "y"]) -> types.MethodType:
...
Although I do question __getattr__ if you only have a limited set of attributes you can access
My use of __getattr__is a proxy for methods in another class. Declaring it with @typing.overload works fine, so it works for the positive matches, but it fails for the negative. The Literal apporach works, but as you say, its requires a mainained list of literals, which is not best.
I'd wish one could define that undeclared attributes should err, not attempt to interpret __getattr__ (because it doesn't do that too well).
with sufficient amounts of if TYPE_CHECKING to selectively show/hide signatures you can make it work.
I think you can use @overload
Or a Literal["attr", "other_attr"]
@overload helps on defining the methods so the type checker will find the signature, but it turns out it doesn't work since the method will be registered as an attribute and __getattr__ will never be called. The TYPE_CHECKING approach seems to work thou
I ended up with something like this: https://bpa.st/2ZXJS
It turns out to require a lot of sugar for typing either way if the intent is to implement two classes that contains the same methods, but with "reduced" set arguments in one of the classes.
I thought it was a good idea to use Protocol to declare the functions, but then __getattr__() isn't called for the methods declared in the protocol when used withclass A(MyProtocol):...
how to represent variadic argument in callable type hint?
you most likely want to use ParamSpec
we'd need more details regarding your problem though
i want the type that accepts any number or arguments as long as they are all of a some type (like int for example) and the function returns that type
sof(x: int) -> int f(x: int, y: int) -> int f(x: int, y: int, *args: int) -> intare accepted, but not```
f(x: int, y: str) -> bool
out of curiosity, why not accept a list?
(or Sequence, probably better)
_T = TypeVar("_T")
def f(x: Sequence[_T]) -> _T:
...
you could have a protocol involving a member function that accepts a generic sequence
I'm not sure I understand
Hey, i'm trying to create overloads for a number of functions that have a parameter which defines wether the function should be sync or async. I want the overloads to show that the function can either return T or Coroutine[Any, Any, T] based on the boolean value of the use_async parameter
Some of these functions have very long parameter lists, so i would rather not write all of the overloads manually since the other parameters have no effect on the return type
Here is what it looks like manually
def example_func(... (many kwargs), use_async: Literal[False]) -> str: ...
@overload
def example_func(... (many kwargs), use_async: Literal[True]) -> Coroutine[Any, Any, str]: ...
def example_func(... (many kwargs), use_async: bool = False) -> str | Coroutine[Any, Any, str]:
...```
Since I have so many functions like this, it would be very frustrating creating all of the overloads manually (not to mention a maintenance nightmare)
I have been trying to come up with a way to programmatically mark a function and generate the overload data for it.
I've tried something like this but mypy/vscode intellisense does not recognise it
```python
def asyncify_type(func):
@wraps(func) # Preserve metadata
def _sync(): ...
_sync.__annotations__["use_async"] = Literal[False]
overload(_sync)
@wraps(func)
def _async(): ...
_async.__annotations__["use_async"] = Literal[True]
_async.__annotations__["return"] = Coroutine[Any, Any, func.__annotations__["return"]]
overload(_async)
return func
@asyncify_type
def example_func(input: int, use_async: bool) -> str:
pass
Anybody know if something like this is possible?
Probably you want two modules, one generated by the other using unasync
One of my goals is to provide access to both the sync/async versions of the function through a single interface. I already have the implementation done, i'm just trying to add some nice type hints to make it very clear what the return type will be
Can't you just do def f(*args: int) -> int
the idea I had was to use protocols with an associated type, but I'm not sure if python supports that
You can use __call__ with Protocol
Callable[[*tuple[int, ...]], object] would also work.
i dont think thats what they want, and i dont think there's even a way to express what they want
this only works for a function which is defined as taking varargs, they want it to handle a function that has any amount of int args, e.g.
def f(x: int) -> int:
...
def g(x: int, y: int) -> int:
...
not just
def h(*xs: int) -> int:
...
though im not sure what would one even do with such a type, how would you know how many ints to give to it to call?
So:
class OneOrMoreIntCallback(Protocol):
def __call__(self, x: int, /, *xs: int) -> int: ...
Ah the "as long as they are all the same type" doesn't work. Everything will get promoted to object and you'll get an object back
I ended up just using Callable[..., int]
something like def f(x: int, y: int) is still not assignable
i agree with the above statement that this is just weird design and it should just accept a single sequence of ints, not N params
Oh I see
How are you working out how many args to pass? Introspecting the signature?
I don't have access to source code rn
actually nvm it
https://survey.alchemer.com/s3/8009809/python-developers-survey-2024 this one has a question about it
though it's not its own question
and you can't see the results yet..
Thx 🙏
Hello, how usable is the "returns" library? Do people generally recommend using it or to stay away from it?
So here's something I'm trying, would appreciate tips on if it's feasible
import dataclasses
import inspect
import sys
from typing import Generic, TypeVar
T = TypeVar('T')
@dataclasses.dataclass
class Options(Generic[T]):
@property
def class_type(self) -> type[T]:
return getattr(sys.modules[__name__], self.__class__.__qualname__.replace('.Options', ''))
def make_instance(self, *args, **kwargs) -> T:
return self.class_type(self, *args, **kwargs) # Expected 0 positional arguments
class A:
@dataclasses.dataclass
class Options(Options):
a: int
def __init__(self, opts):
self.opts = opts
class AA(A):
@dataclasses.dataclass
class Options(A.Options):
b: int
x: int
def __init__(self, opts, x: int):
super().__init__(opts)
self.x = x
a = A.Options(a=42).make_instance()
print(inspect.signature(AA.Options(a=42, b=43).make_instance))
aa = AA.Options(a=42, b=43).make_instance(x=44)
want:
- autocompletion for
make_instancebased on the constructor of the class (something withConcatenateandParamSpec?) - the return value for
make_instanceshould be correctly deduced statically so autocompletion continues to work
is something like this possible? in this example class_type is some dynamic attr just for illustration purposes but maybe there's a way to automatically fill in class_type such that it propagates through static type checking idk maybe by having a metaclass do something about it?
That looks weird. What's the declared type?
this```py
def mul[T: (CalcNumber, CalcVector)](self, other: T) -> T:
if isinstance(other, CalcVector):
return other * self
# assert_type(other, CalcNumber)
a = self.number
b = other.number
if not (_is_rational(a) and _is_rational(b)):
a = _convert_fraction_to_decimal(a)
b = _convert_fraction_to_decimal(b)
res = a * b
if _is_integer(res):
res = int(res)
return CalcNumber(res)
Oh, I think pyright's technique for evaluating TypeVars with constraints leaks out here (known as conditional types, https://github.com/microsoft/pyright/blob/main/docs/type-concepts-advanced.md#conditional-types-and-type-variables)
Is there a backport of PEP 702 for python 3.9 or 3.10?
But mypy doesn't seem to support it?
https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=f4c89b1030cd2a54eefaf28817fce207
works on master
PEP 702 support is still being worked on in mypy
python/mypy#16111
not check in python/mypy#17264 likely because it isn't released yet
Actually, this label is more accurate. https://github.com/python/mypy/labels/topic-pep-702
Feeling mad but very satisfied 😇 Managed to write yet another framework for work.
This one autogenerates openapi documentation by importing and parsing our Django code 😋
Polishing last bits and at last submitting PRs for it
The resulting documentation is far more precise than human efforts can achieve.
Everyone was very lazy to fill all fields for nested serializers and django filters manually
All the nested madness of all fields autogenerated
Autogenerated code shows autoparsed DRF Serializers for input, django filters, other fields, and necessary security permissions to execute specific action
Kind of high end typing stuff for web python
in code overrides are possible to define for desired serializers, queryfilters, actions to enhance built docs ^_^ Overrides are declarable by Pydantic data structs, so you know in advance what can be overriden
ah thanks :)
Question about mypy and bytes and memoryview, cough Jelle
I see mypy decided to consider bytearray a virtual subclass (or something like that), of bytes, that seems reasonable to me, at least things like this work
def whut(v: str | bytes | bytearray) -> str:
result = v.decode("utf-8") if not isinstance(v, str) else v
te.reveal_type(result)
return result
This is deprecated and will likely be removed in mypy 2.0
!pep 688
hah, okay...
so you would basically recommend writing v: str | bytes above
and not depending on this
If you want to accept bytearray, include bytearray in the union
ah right, actually, that's fine
but why is memoryview a subclass of bytes if it doesn't support decode, that's the concerning part I guess
like, in mypy this code would type check with a bytearray argument, even if bytearray is not in the union
that's relatively okay since at least decode works either way
but it will also accept memoryview, even though memoryview doesn't have decode
Yeah that's why in PEP 688 I said we shouldn't be doing that
mypy currently accepts both bytearray and memoryview for bytes. PEP 688 says bytes should mean just bytes
I agree that among the two memoryview is more sketchy than bytearray
you can use --disable-memoryview-promotion and --disable-bytearray-promotion today to get the PEP 688 behaviour
ok how do i assert the CalcNumber* type
not sure there is a way unfortunately
Do you have a question about type hinting?
thanks
I have been working as a React developer for 10 years until now.
my friend gave me a task about python framework
were you using TS in React or JS?
i c
well on the surface level, python typehinting is much like typescript
tho it is still valid Python code
we use third-party static type analyzers to verify the types, instead of a compiler
why doesn't this work?
class Thing:
pass
ThingCls = TypeVar("ThingCls", bound=type[Thing])
def thing_class_modifier(base: ThingCls = Thing) -> ThingCls:
return base
error I get is: Incompatible default for argument "base" (default has type "type[Thing]", argument has type "ThingCls")
yea, mypy
I think you need a default=Thing in ThingCls?.. not sure
the pylance behaviour is somewhat surprising here, why is it okay to use Thing as a value for an unknown type ThingCls?
that would be a default type, as written it's a default value
doesn't make a difference
When you call thing_class_modifier(), the ThingCls variable is unresolved, it's not clear what ThingCls means in that context. That sounds like a use case for the recently introduced typevar defaults
So it seems like the default value changes the type of the function, and you cannot express that type yourself kind of?..
I guess it's a way of encoding this overload: ```py
def thing_class_modifier() -> type[Thing]
def thing_class_modifier[T: type[Thing]](base: T) -> T
I think mypy is not wrong here. It's a new inference rule invented by pylance that's not standardized in any PEP
You could ask in python/mypy or python/typing on github
bound wouldn't tell mypy to inference that Thing is acceptable as a default?
FYI, seems like my issue has been around since 2017: https://github.com/python/mypy/issues/3737
I can't believe I'm asking this here, (mostly because I either don't typehint stuf, or make the absolutely worst conceivable typehints ever)
but, how do you typehint properties?, for example, if I have a type_property class that makes absolutely sure that the property's value is of a type in allowed_types, ¿how do you typehint that? and more so, when to __get__ and __set__ the objects are passed as an argument, so there's two typevars there, this are my type-hinting right now:
class typed_property(property, _T.Generic[A, T]):
def __init__(self, *allowed_types: _T.Type[T],
container_subtype = None,
name: str = '',
fget: _T.Callable[[A], T] | None = None,
fset: _T.Callable[[A, T], T] | None = None
): ...
def at_set(self, func: _T.Callable[[A, T], T]): ...
def at_get(self, func: _T.Callable[[A],T]): ...
def __set__(self, obj: A, value: _T.Any): ...
def __get__(self, obj: A, objtype: _T.Type[A] | None = None) -> T: ...
and here, A is the TypeVar for the obj this property belongs to, and T is the typevar for the type of this property
but Generic[A, T] doesnt seem right, (for no particular reason)
(type is forced both before and after the set, that's why the fset function has to return T, and ignore name and container_subtype, the former is for repr if you're fancy, and the latter is internal about the container class this property uses internally)
typed_property[..., A | B]?
do I just swap A, T to T, A in the generic?
no i mean to not have allowed_types, just a single union type
wdym
the point of this property is not just type-hinting, but also runtime-checking, and ensure the type at runtime, so I need the allowed_types
I have to type-hint it like this
class Example:
name: typed_property[str, "Example"] = typed_property(str)
for mypy not to complain, is there any other way to avoid this?
by creating it from a method, that will infer the class type
is it not some type of classVar though?
not sure what you mean by that
and no, you cant use typing.Self here if thats what you're thinking of
since you're defining the property in the class scope (as you should), all other set/get in the class refer to this, so:
class Example:
name: typed_property[str, "Example"] = typed_property(str)
def __init__(self, name: str):
self.name = name # will automatically raise an error when name is not a string
this won't work if you try self.name = typed_property(self)
if you dont provide any getter/setter that would depend on the type of the instance actually being Example - you dont need to have that typevar in the property at all
so do I set it to _T.Any instead of A?
no, you dont use this type at all
just name: typed_property[str]
error: "typed_property" expects 2 type arguments, but 1 given [type-arg]
yes, i mean to not have it, remove it, it is not used here so you cant do anything with it
and if I remove the A typeVar from the generic then there's a bunch other errors
what are you even trying to do? why are you validating types at runtime?
why is that the problem
should I do a help thread or smth? this seems like a big conversation for type-hinting
perhaps
done
ive just found this scary bit of code py class ID(Generic[TypeT], metaclass=abc.ABCMeta): __slots__ = ("id64", "__weakref__") __class_getitem__ = classmethod( GenericAlias ) # want the different behaviour between typing._GenericAlias to do with attribute forwarding
and wanted to check if this was intentional
i think past me was on about the behaviour difference between forwarding of dunders
Part of we wants to eventually merge types.GenericAlias with all the typing classes but I don't know when if ever that will be achievable
yeah i mean same
but that will be breaking at least some stuff
i think ive found the cause of this comment
IndividualID = ID[Literal[Type.Individual]]
class WrapsUser(User if TYPE_CHECKING or DOCS_BUILDING else BaseUser, Messageable["UserMessage"]):
"""Internal class used for creating a User subclass optimised for memory. Composes the original user and forwards
all of its attributes.
Similar concept to discord.py's Member except more generalised for the larger number situations Steam throws at us.
Slightly different however in that isinstance(SubclassOfWrapsUser(), User) should pass.
Note
----
This class does not forward ClientUsers attribute's so things like Clan().me.clear_nicks() will fail.
If DOCS_BUILDING is True then this class behaves like a normal User because we need to be able to access the
doc-strings reliably and memory usage isn't a concern.
"""
__slots__ = ("_user", "_cs_channel")
def __init__(self, state: ConnectionState, user: User | ClientUser):
IndividualID.__init__(self, user.id64, type=Type.Individual)
```damn i write cursed code
but that wouldnt work with the old typing class
the __init__ call on an apparently unrelated class is interesting
maybe youd need to have a chat with whoever implemented 585 and ask them if its intentional and the original was only because it was pure python and you have to forward dunders
BaseUser is a subclass of ID[Literal[Type.Individual]]
I have https://github.com/python/cpython/pull/105511 to fix the analogous issue with PEP 604, need to get @lunar dune to review it though 😛
Hi, how should I type the following decorator correctly? Both pyright and mypy complain that self is missing from the call to a_method on the last line whereas the code exectues fine.
import functools
from typing import Protocol
class Method[S, R, **P](Protocol):
def __call__(_, self: S, *args: P.args, **kwargs: P.kwargs) -> R: ...
def decorator[S, R, **P](f: Method[S, R, P]) -> Method[S, R, P]:
@functools.wraps(f)
def wrapper(self: S, *args: P.args, **kwargs: P.kwargs) -> R:
return f(self, *args, **kwargs)
return wrapper
class SomeClass:
@decorator
def a_method(self) -> int: ...
instance = SomeClass()
instance.a_method()
The issue is mainly with defining the correct Protocol for methods where I want to type self
Don't worry about self. It's part of *args
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
^
The protocol is unneeded. ```py
def decorator[R, **P](f: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(f)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
return f(*args, **kwargs)
return wrapper
My bad, the example is too contrived. I'm actually interested in self because I want to restrict the class to which the method belongs. Let's say:
class Method[S: SomeClass, R, **P](Protocol):
def __call__(_, self: S, *args: P.args, **kwargs: P.kwargs) -> R: ...
You might need to add a __future__ import to make the original example work now.
How can I type the Protocol in such a way that I can make sure the method belongs to a certain class or its subclasses
That doesn't seem to work in combination with Protocol and __call__. I also checked both the typing spec and the pyright docs. Guidance on typing methods where you want to control what self can be is absent.
Would it be a PyRight bug that it adds Literal[True] to the type or it's just trying to be clever by deducing that a.y is probably have similar type to the fallback value?
if x := getattr(a, "y", False):
reveal_type(x) # Type of "x" is "Any | Literal[True]"
that seems a bit weird. Open a discussion in the repo and ask if you can't find anything after a brief search
Sure, started a discussion - https://github.com/microsoft/pyright/discussions/9401
.
How can I type hint a function that takes a Callable and then *args, **kwargs, and the Callable takes those args/kwargs as its parameters? Like this example:
def test(callable: Callable[..., None], *args, **kwargs):
callable(*args, **kwargs)
def test[**P](func: Callable[P, None], *args: P.args, **kwargs: P.kwargs) -> None:
func(*args, **kwargs)
Ah ok, I knew it was with ParamSpec somehow but was getting confused
two other things to consider:
On python < 3.12, you'll have to do
from typing import ParamSpec
P = ParamSpec("P")
Also consider if you need the function to return None. If any output will suffice, consider using Callable[P, object] instead
Thanks!
Is there a way to non-exhaustively use Unpack and TypedDict for **kwargs, such that arbitrary keyword arguments are still allowed?
Thanks!
(So there's not a way yet)
Might be in typing_extensions first
Yeah I need to implement the latest version in typing-extensions
I'm using the redis-py library, which annotates certain functions like hset with Union[Awaitable[int], int]. Since I'm using the async API, every time I call await client.hset(...), Pyright throws an error saying "int is not awaitable." This happens frequently in my code (10 or more times), making # type: ignore impractical. Is there another way to silence this specific warning?
That sounds like a problem with the library. Union[Awaitable[int], int] is a really bad return type for users of a library
(specifically because they'll need to use # type: ignore on every line)
Yeah... this is going to be a pain to use https://github.com/redis/redis-py/blob/00f5be420b397adfa1b9aa9c2761f7d8a27c0a9a/redis/commands/core.py#L5016-L5023
redis/commands/core.py lines 5016 to 5023
def hset(
self,
name: str,
key: Optional[str] = None,
value: Optional[str] = None,
mapping: Optional[dict] = None,
items: Optional[list] = None,
) -> Union[Awaitable[int], int]:```
There isn't a good way to silence this warning, since it's very general. If you turned it off, it would not catch legitimate errors when you're awaiting something that's not awaitable.
Actually, pyright just shoves this under reportGeneralTypeIssues
You should at least use # pyright: ignore[reportGeneralTypeIssues] instead of a plain # type: ignore, so that other errors like a typoed variable name are still caught
Have you tried the types-redis package? (from here https://github.com/redis/redis-py/issues/2399)
hot take: unions as return types without overloads should be prosecuted ex officio
generics without parameters too...
unless default as in pep 696!
well... sometimes it is really unknown what type be returned until runtime
e.g. User | None
of course! that's why i called it a hot take.
optionals are fine.
but things like
-> list[str] | str
where it could easily be overloaded e.g. with regard to parameter value (aka argument) stream being either True or False
those are especially frustrating to work with when they don't have overloads
I like to avoid this kind of behaviour in the first place
(i.e. maybe it's better to split it into two functions)
another thing I don't like is flags to apply some post-processing to the output. Like add(3, 5) == 8 and add(3, 5, stringify=True) == "8"
my favorite pattern in that area learnt from Python's stdlib/more-itertools is functions X and iX
where iX is something typically giving us an iterator as a result
and X an all-in collection
for those types of distinctions
that's a terrible pattern
in that case i'd at most make sth like add and add_stringified that wraps add
given the existence of the second routine would really be necessary
one of the things that typing did to me was beginning to rely less on unpredictable things like getattr() or bizarre unions in returned values.
i consider that a vast improvement
let's say I am implementing something like itertools.product, like so
class Product[T]:
def __init__(self, a: Iterable[T], b: Iterable[T]) -> None:
self.a, self.b = a, b
def __iter__(self) -> Self:
cache = []
for a in self.a:
if cache:
yield from cache
for b in self.b:
cache.append(b)
yield b
Now, I would like Product('AB', 'CD')[0] to be 'AC', but Product(iter('AB'), iter('CD'))[0] to be a type error of sorts. Is that possible within the type system? My only idea is to try to somehow runtime type check and produce a different subclass based on whether both arguments are a sequence, but I would prefer to not do excessive runtime type checks.
so you want it to fail when b is an iterator, because it will be iterated multiple times?
ah yea, I did forget to cache in the impl, my bad.
the key point is that when given two Sequences, Product should support len and __getitem__, but when given a non-sequence, it should support neither
ah ic
from collections.abc import Iterable, Sequence
from typing import no_type_check, overload
class Product[C: Iterable]:
@overload
def __new__[A](cls, a: Sequence[A], b: Sequence[A]) -> Product[Sequence[A]]:
...
@overload
def __new__[A](cls, a: Iterable[A], b: Iterable[A]) -> Product[Iterable[A]]:
...
@no_type_check
def __new__(cls, a, b):
...
def __len__[A](self: Product[Sequence[A]]) -> int:
...
def __getitem__[A](self: Product[Sequence[A]]) -> tuple[A, A]:
...
something like that maybe
not sure if typing new even works lmao
seems to work actually
maybe having Product[A] = IterProduct[A] | SeqProduct[A] would be less voodoo
and a function product with the overloads to return the correct one, by checking if len exists, perhaps
that's sort of what I want to avoid
I don't really want to have a bunch of runtime type checks just to satisfy the static type checks
Oh god I have to make this heterogenous as well
Is there a type like Sequence but without .count and .index? Just getitem len?
Why? Sequence only requires those two and gives the others for free
They aren't exactly meaningful in my case.
I mean you could always do
class IndexableSized[T](Protocol):
def __len__(self) -> int: ...
def __getitem__(self, idx: int) -> T: ...
I don't think there's anything builtin
This table shows the progression of builtin types: https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes @cinder bone 's suggestion of a Protocol should be good (once you fix the misspelling of __getitem__)
May you link the documentation for the new class/def name []? (): ...
!pep 695
Thanks a lot!
IS THERE ANYONE THAT CAN CODE GODOT?
im really trying to get into programming but it never wanna do as i want
Good morning, How can django orm do this?
class User(BaseEntity):
username = models.CharField(max_length=100)
avatar_url = models.URLField()
balance = models.FloatField(default=100)
booster = models.BooleanField(default=False)
banned = models.BooleanField(default=False)
cards = models.ManyToManyField("user.UserCard", blank=True)
def __str__(self) -> str:
return self.username
User.username # CharField[str]
User().username # str
type hint changes whether the class is instantiated
Either they just abuse setattr or CharField is a descriptor
I'm assuming that models.CharField is a descriptor, see https://docs.python.org/3/howto/descriptor.html
how would setattr abuse do anything here?
pseudocode
field = getattr(self, fieldname)
if isinstance(field, CharField):
setattr(self, fieldname, some_computed_str)
I'm writing a package, and have a type hint along the following:
ErrorOptions = Literal["raise", "warn", "ignore"]
def my_func(option: ErrorOptions):
...
How can I surface to my users that the type is of Literal["raise", "warn", "ignore"]? When hovering over it in the IDE it just shows the function signature which isn't really clear to the user what the available options are
!cban @river carbon scam
:incoming_envelope: :ok_hand: applied ban to @river carbon permanently.
you could try adding Google Docstrings to function to enhance your type
ErrorOptions = Literal["raise", "warn", "ignore"]
def my_func(option: ErrorOptions):
"""
Any desired text
Args:
option:
Some description
That will be visible in IDE and buildable to sphinx autodoc
"""
i noticed my vscode shows description of Google Docstrings at least for extra hints right on hover
As another solution u chould check what enum shows for you
from enum import EnumStr
class ErrorOptions(EnumStr)
raise = "raise"
warn = "warn"
ignore = "ignore"
def my_func(option: ErrorOptions):
...
it will accept regular literal strings too 🙂 and comparing them as ErrorOptions.warn == "warn" should equal to true
str enums are somewhat dangerous in typing termss though, not recommending them
i would more recommend just using regular enum
from enum import Enum, auto
class ErrorOptEnum(Enum)
raise = auto()
warn = auto()
ignore = auto()
def my_func(option: ErrorOptEnum):
...
that will be self documentedly obvious
and having better stable interface
!e
from enum import Enum, auto
class ErrorOptEnum(Enum):
raise = auto()
:x: Your 3.12 eval job has completed with return code 1.
001 | File "/home/main.py", line 4
002 | raise = auto()
003 | ^
004 | SyntaxError: invalid syntax
there is the issue with the keyword raise
raise is a keyword but is not a soft keyword
so you cant use it as variable
You don't have to ping me for that, I already knew 😉
I am having a problem with mypy type checking with awaitable.
# self.connection.accept method
async def accept(
self,
loop: asyncio.AbstractEventLoop,
) -> Awaitable[Tuple[socket.socket, Tuple[str, int]]]:
return await loop.sock_accept(self.sock)
# method using above method
async def accept(self, loop: asyncio.AbstractEventLoop) -> ConnHandle:
sock, address = await self.connection.accept(loop) # MYPY error: "Awaitable[tuple[socket, tuple[str, int]]]" object is not iterable [misc]
logger.info(f"New client: {address}") # MYPY error: Cannot determine type of "address" [has-type]
return ConnHandle(sock, address)
I am not able to understand why its not considering the type inside Awaitable why??
that looks very strange
can you create a standalone minimal repro? What version of mypy?
An async def function's return type is automatically wrapped in Coroutine
e.g. if you have async def foo(x: int) -> str, then foo(42) is an Awaitable[str]
You should try doing typing.reveal_type(self.accept)
Honestly I don't like this feature
in particular because an async def function with a yield keyword does not have that
For the convenience it's nice
I hate doing it in ts and having to put Promise after everything
It only saves a bit of typing at the cost of added confusion (like this question), inconsistency (with async+yield and normal yield functions) and inflexibility
inflexibility as in: you can't write an async def function and have its contract be that it returns an Awaitable, not necessarily a Coroutine
and it also prevents you from specifying the send and return types on the coroutine
I would guess that the main reason this was done was the lack of defaults. Because writing Coroutine[ReturnType, Any, Any] every time would be pretty bad
It's the same deal as with *args: Cat, **kwargs: int instead of *args: tuple[Cat, ...], **kwargs: dict[int, str] (TypeScript uses the latter version). It adds a few extra characters, but buys a lot more flexibility: there's no need to add extra constructs to support stuff like *args: tuple[int, str] | tuple[int], **kwargs: MyTypedDict (which required changed to the language/standard library with Unpack)
idk isnt a lot of the point that python does make some of these decisions for you
i agree it can be annoying but in terms of actual practicality im still firmly on the side of make it easier to do the common one
I'd love some help with static methods and typing.Self. I have this:
@staticmethod
def get(user_id: Optional[int] = None) -> Self:
But pylance says "Self" is not valid in this context.". Isn't this or classmethods exactly where I'm supposed to use typing.Self?
Self makes no sense in a staticmethod because theres no way to refer to the enclosing class in subclasses
It would make more sense to have that a classmethod ig?
yes
How can I define a function that takes two arithmetic types, regardless if they are fundamental types or classes with add etc ?
To simplify, how can I define
def add(a: T, b:T) -> T:
return a + b
that works for floats, ints and custom classes with __add__ ?
you can define a Protocol with an add method
Thats not enough. Fundamental types will not be considered to implement that protocol
why not?
Hmm you're right, it does work for floats, but not for my Float2 class. I have
class Arithmetic(Protocol):
def __add__(self: Self, other: 'Arithmetic') -> 'Arithmetic': ...
def __sub__(self: Self, other: 'Arithmetic') -> 'Arithmetic': ...
def __mul__(self: Self, other: 'Arithmetic') -> 'Arithmetic': ...
and I get
"__add__" is an incompatible type
No overloaded function matches type "(other: Arithmetic) -> Arithmetic"
for my Float2
def __add__(self, arg0: Union[Float2, Int2, Tuple[float, float]]) -> Float2:
I guess its the union that messes things up?
but it doesn't seem like it
The problem is that your protocol is saying that it's valid to call arith.__add__(another_arith), but Float2 requires a Float2 specifically.
Yeah, the protocol specifies that __add__ should work with any other Arithmetic, not just some of them
arithmetic operations are kinda complicated to type IIRC
because there's also __radd__ and the rules about which method is called in which order
I think you'll want to just annotate self, other and the return value with a typevar.
Doesn't work for radd though, I think you might need a union of 8 protocols to handle all the combinations?
Regardless of how I defined the protocol I cant get it to match my custom Float2 ...
class Arithmetic(Protocol):
def __add__(self: Self, other: object) -> Self: ...
class Float2:
def __add__(self, other: 'Float2') -> 'Float2':
return self
def dup(a: Arithmetic) -> Arithmetic:
return a + a
dup(Float2())
you say that for a type to be assignable as an "Arithmetic", its add has to work with any object as the rhs, but Float2's add only works with another Float2
class SupportsAddWithSelf(Protocol):
def __add__(self: Self, other: Self, /) -> Self:
...
def dup[A: SupportsAddWithSelf](a: A) -> A:
return a + a
maybe you want something like this
What do you mean by an arithmetic type? What operations do you need to perform with the objects? (and are they all the same type?)
if you just need addition, multiplication etc. to work within one type, then what Lambda suggested should work
Well the type needs to also accept floats on the right hand side. But always returns Self
But other: Self | float may be good enough
yeah
from typing import Protocol, Self
class Addable(Protocol):
def __add__(self, other: Self | float, /) -> Self:
...
def double[A: Addable](x: A) -> A:
return x + x
n = double(3.45)
Except now again we need a type that can take itself and float
wdym
Just a note of caution, using Self as a method parameter is almost always incorrect and will lead to false negatives: https://github.com/python/typing/discussions/1761#discussion-6773211 at least with how type checkers work currently
But here I don't think there's a better way
actually, it might be fine in a protocol specifically
🤔
Hmm my Float2 type uses '@typing.overload' to define operators for both Float2 and float which doesnt seem to work...
can you show the definition?
Ah, I need to use overload also in the Protocol
class Arithmetic(Protocol):
@overload
def __add__(self, arg0: Self) -> Self: ...
@overload
def __add__(self, arg0: float) -> Self: ...
vs
class Float2:
...
@typing.overload
def __mul__(self, arg0: Union[Float2, Int2, Tuple[float, float]]) -> Float2:
...
@typing.overload
def __mul__(self, arg0: Union[Int2, Tuple[int, int]]) -> Float2:
...
@typing.overload
def __mul__(self, arg0: float) -> Float2:
...
well except the mul add mismatch here
You need to add an / in the protocol definition
What does that do?
positional-only argument
otherwise the name of the argument in the implementing class must match
This overload should be equivalent to def __add__(self, arg0: Self | float) -> Self:
if it's not, it's a bug in the type checker
Then it's a bug in the type checker
class Arithmetic(Protocol):
def __sub__(self, arg0: Self, /) -> Self: ...
def __add__(self, arg0: Self, /) -> Self: ...
@overload
def __mul__(self, arg0: Self, /) -> Self: ...
@overload
def __mul__(self, arg0: float, /) -> Self: ...
works
class Arithmetic(Protocol):
def __sub__(self, arg0: Self, /) -> Self: ...
def __add__(self, arg0: Self, /) -> Self: ...
def __mul__(self, arg0: float | Self, /) -> Self: ...
doesn't work
But can you assume the type checker can combine overloads and see that they correspond to or:ed type expressions?
FYI: Final result:
class Tweenable(Protocol):
def __sub__(self, arg0: Self, /) -> Self: ...
def __add__(self, arg0: Self, /) -> Self: ...
@overload
def __mul__(self, arg0: Self, /) -> Self: ...
@overload
def __mul__(self, arg0: float, /) -> Self: ...
def tween[T: Tweenable](start: T, stop: T, steps: int, ease: Callable[[float], float]) -> Generator[T, None, None] :
x = start
for i in range(steps):
x = (stop - start) * ease(i / steps) + start
yield x
while True:
yield x
and thanks for the help!
what's the diference between Iterable[T] and Generator[T, None, None]?
Iterable[T] is something you can call iter() on, i.e. something with an __iter__(self) -> Iterator[T] method
(for example a list object is iterable)
so Iterator[T] then?
Iterator[T] is an Iterable[T] that also has a __next__(self) -> T # throws StopIteration. It's the thing that actually goes over a collection and keeps some state about that going-over
Generator has extra methods (send and throw)
so Iterator[T] is exactly equal to Generator[T, None, None] ¿right? since None, None makes it so that the object doesnt expect any values sent or errors thrown
See https://docs.python.org/3/library/typing.html#annotating-generators-and-coroutines
The second parameter specifies the send type, i.e. what you can put in the send method. For a generator, next(gen) should be equivalent to gen.send(None).
The third parameter specifies the return type of the generator, not what kind of exceptions is expects
The third parameter specifies the return type of the generator, not what kind of exceptions is expects
this generator methods are so rare I always forgot, yeah, it'sGenerator[iter_type, send_type, return_type]
!e
def foo():
yield 420
yield 69
return "done"
gen = foo()
print("first", next(gen))
print("second", next(gen))
try:
next(gen)
except StopIteration as exc:
print("StopIteration with value:", repr(exc.value))
:white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | first 420
002 | second 69
003 | StopIteration with value: 'done'
here the correct annotation would be -> Generator[int, None, str] (or -> Generator[int, object, str])
but the functions
def func():
for i in range(10):
yield i**2
the docs you send say that it can be anotated either by Iterator and Iterable, but Iterable is something to call __iter__ upon and Iterator is something that was returned by __iter__ (as I understand it)
or calling the function calls __iter__ automatically and returns the generator?
Every iterator is also an iterable, and its __iter__ returns self
Calling the function just gives you the generator object, no intermediate iterable is created
some people argue that you should use Iterable intead of Iterator for generator functions, in case you want to change the implemention to return an iterable which is not an iterator, such as a list
Yeah, don't publish a contract unless you really need to
On the other hand, using Iterator better communicates that you shouldn't reuse the result of the function
If you made some changes and now get a list somehow, you can just return iter(that_list) to keep the interface
ig
If you want a literal number to be treated as an int, do you normally just do int(0) ?
What do you mean?
I have an array of lambdas that returns tuples of numbers. If I don't put int() around any literal it can't figure out the type of the array
Do you have a code example?
This works with mypy:
from typing import reveal_type
first = [lambda: (0, 0), lambda: (0, 0), lambda: (0, 0)]
second = [lambda: (1, 2), lambda: (2, 3), lambda: (3, 4)]
reveal_type(first) # Revealed type is "builtins.list[def () -> tuple[builtins.int, builtins.int]]"
reveal_type(second) # Revealed type is "builtins.list[def () -> tuple[builtins.int, builtins.int]]"
what type hint should i give for objects having the send method```py
async def send_chunks(sendable: Any, s: str) -> None:
"""Send messages in 2000-character chunks, to bypass Discord's 2000-character
limitations"""
for i in range(0, len(s), 2000):
await sendable.send(s[i:i+2000])
you can make a Protocol class
oh i didn't think of that
@trim tangle
z = 3
fx = [ lambda: (2, 4), lambda: (z, 1) ]
But its PyLance in vscode that tells me
Type of "fx" is partially unknown
Type of "fx" is "list[Unknown]"
Actually this is enough: fx = [ lambda: 2, lambda: 3]
But yes, mypy on the command line with reveal_type() gets it as lambda -> int
if you turn on the strictListInference setting in the configuration, it does infer the type as fx: list[(() -> tuple[Literal[2], Literal[4]]) | (() -> tuple[int, Literal[1]])] (yuck)
This does look like a bug in pyright/pylance, perhaps file an issue
Alternatively, you can add an explicit annotation:
from collections.abc import Callable
z = 3
fx: list[Callable[[], tuple[int, int]]] = [ lambda: (2, 4), lambda: (z, 1) ]
I think python/typeshed#12181 broke my code.
mode = f"w:{ext}".strip(":")
with tarfile.open(dest, mode) as tar: # No overloads for "open" match the provided arguments
Can I somehow get a @staticmethod to indicate it's going to return an instance of the class defining it? I asked about Self previously, and the response was it doesn't make sense (which I disagree with, but that's nether here nor there), and if I do -> MyClass, it says MyClass is not defined, which I guess makes sense? Or do I have to make it a class method? As the logic makes more sense as a static method, I'd prefer to leave it as such, but if I have to make it a class method to move forward, I will.
Why do you disagree that it doesn't make sense?
class Animal:
@staticmethod
def default() -> Self:
...
The semantics of Self are such that if you make a class Dog(Animal), Dog.default() should return a Dog
As it's a matter of a type checker, and we're not referencing self as in a pointer to the instance of the class, I don't see why a type checker wouldn't know what class is defining a static method.
But I fully acknowledge as I don't know the inner workings and have only briefly read over the PEP that added Self, my thinking could be contrary to the implementation.
Which is why I said my opinion on it is neither here nor there.
If I can't use Self to indciate the return, I'm just wondering if there's another mechanism, or if I have to do a class method.
In other programming languages, it's entirely common to have a factory method as a static method, thus it was my first instinct here.
Self is not just a shortcut for the current class name, it it equivalent to a using a typevar like this:
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def double(self) -> Self:
return type(self)(self.x * 2, self.y * 2)
# same as:
def double[S: "Point"](self: S) -> S:
return type(self)(self.x * 2, self.y * 2)
The purpose of Self is to make the method of a subclass return an instance of that subclass. So if you have a class FancyPoint(Point), doing fancy_point.double() returns a FancyPoint, not just a Point.
If you just want to name the class that's not defined yet (like the current class), you do not need Self. You can put the class name in quotes:
class Point:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
def double(self) -> "Point":
return Point(self.x * 2, self.y * 2)
Thanks so much, that helps me understand the logic behind Self much better! I'll try to make time to read the PEP more closely as well. And I wasn't aware I could use double quotes like that, thank you.
I earnestly tried googling for how to do this, and the most common response was "It can't be done". I was really confused, because this seems like something that would come up often.
I was thinking the PEP to understand the why, as your explaination comparing it to a typevar seemed pretty clear.
there is one disadvantage to Self - typevars aren't parametrizable, so you can't really specify typevars of Self if "Self" (your type) is generic
Hm, interesting
which can kinda make sense though
given that a generic subclass of your generic class may have a different "generic signature"
class Foo[X, Y]:
pass
class Bar[Y](Foo[int, Y]):
pass
but yeah, it is what it is
Self is very useful regardless
Oh, I realize a flaw in my logic... as a staticmethod doesn't yet have it's wrapper class defined, it can't return an instance of itself.
I guess I have to make it a class method so it at least has a reference to it's class.
could you elaborate?
wdym by "wrapper class defined"
class MyClass:
@staticmethod
def my_method():
# MyClass isn't defined here yet, it couldn't return in instance of MyClass
I'm recognizing that was meant earlier when i asked the preceeding quesiton.
No, that part is fine if the staticmethod is only called after the class has been defined
Just like you can do this:
def f():
return g()
def g():
return 42
Type hinting saying it doesn't knw what MyClass is
I'm gonna restart my IDE, maybe there's something funky going on with vs code
Yup, it was vs code
Sorry
Maybe try basedpyright instead of the recommended Pylance 🙂
I'll take a look at that, thanks!
though that might not fix anything, pyright and its descendants sometimes seem to just get stuck and not brain at all
which is relatable, to be fair
Yah, and as the addage goes "turn it off and on again"
restarting vs code now shows it without error
So that was me thinking I misunderstood the logic, due to VSCode saying it was wrong
Though I admit, now I'm confused, the method knows what the wrapper class is, but I can't use it for the return type?
(without double quotes I mean)
!e
See this: https://decorator-factory.github.io/typing-tips/main-tutorial/1-working-with-classes/#adding-kittens
Basically, when you add an annotation, it's stored on the function for introspection:
class Foo:
def bar(self, x: list[str]) -> int:
print(self)
print(Foo.bar.__annotations__)
But when the method is being defined, the Foo class hasn't been created yet. So if you do -> Foo, it's not clear what should be placed in the __annotations__
:white_check_mark: Your 3.12 eval job has completed with return code 0.
{'x': list[str], 'return': <class 'int'>}
Ok, that does make sense
There have been some attempts to fix this (see PEP 563)
The second attempt (PEP 649) is supposed to land in Python 3.14
I look forward to PIthon 😛 (I'm sure that joke has been made a million times)
generally speaking, remember that methods come from plain functions, so you can always expect them to behave like ones, with no magic involved like in other languages that can see things from a class inside methods without referencing self/this
when you make a class, what you do is put regular functions in a class scope which is then passed to your metaclass that creates your class, and things you put in the class scope become that class's __dict__
later on, when you access a name via a . from that class/instance (here: not set it like in foo.bar = ..., but access the value to only read it, like in foo.bar), it triggers attribute lookup that returns your attribute immediately or calls __get__ when it's available -- for plain functions, that creates you method objects that can bind self as the first argument of your function wrapped in the method
try this:
def foo(bar: int) -> None:
print(f"{bar=}")
method = foo.__get__(10)
print(method) # what is it? equivalent to partial(foo, 10) ;)
method() # bar is just the conventional self
every function is just a descriptor, which makes methods with self auto-inserted for you possible
staticmethod and classmethod implement custom __get__ that changes the default function.__get__ behavior, because they are custom objects in your class scope that implement new __get__ and they accept your function as their first arg
learn more here:
Author, Raymond Hettinger,, Contact,,. Contents: Descriptor Guide- Primer- Simple example: A descriptor that returns a constant, Dynamic lookups, Managed attributes, Customiz...
The notion of CalVar makes sense but bothers me so much, heh.
Thank you! I'll def read more into that tomorrow!
Thanks so much for helping direct me. For now, bed, so I can figure out wtf this sqlalchemy documentation is trying to say in the morning, because holy binary, I really hate this documentation.
oh, i remember reading it
it actually boosted my vocabulary so much i started getting a lot of A grades for essay assessments in my english classes
it can be a good training :)
For me, I'm just getting a brain fog so bad I'm almost tempted to make my own ORM (insert bender's classic joke here)
The function descriptors are really good, if overly dense
The tutorials are just horrible
check out sqlmodel -- a facade over sqlalchemy and pydantic -- maybe it proves easier for you if learning sqlalchemy isn't the goal, and you just need an ORM to deliver a project quickly
I did, but decided sqlmodel wasn't for me, there are still features I'd like that aren't built yet, and it's documentation is still light, given it's relatively recent project
But to use SQLModel, I'd still need to learn SQLA's core
i learnt a lot from reading it
it's a good read, i really recommend
i even have an artifact of doing that!
https://github.com/sqlalchemy/sqlalchemy/issues/6983
good old times. :')
I'm just bothered by things like this: I'm reading how to do inserts. So it talks about inserts and returning, and the tutorial is unclear. So I'm reading the method, method sqlalchemy.sql.expression.Insert.returning. And the example for that method, a method on the Insert class, it shows an update query.
update != insert
(let's switch to #databases channel mby)
Hm.. pyright bug?
import tarfile
from typing import Literal
def open_tar(file: str, fmt: Literal["gz", "bz2", "xz", ""]) -> tarfile.TarFile:
if fmt == "":
return tarfile.open(file, "w")
return tarfile.open(file, f"w:{fmt}") # error
return tarfile.open(file, "w:" + fmt) # no error
fmt = "gz"
reveal_type(fmt) # Type of "fmt" is "Literal['gz']"
reveal_type("w:" + fmt) # Type of ""w:" + fmt" is "Literal['w:gz']"
reveal_type(f"w:{fmt}") # Type of "f"w:{fmt}"" is "LiteralString"
well... not sure if it's a "bug"
how type inference works is not specified in any specification
seems like it just hasn't implemented this inference rule for f-strings
It only became an issue after python/typeshed#12181
ah
I think I understand what went wrong. mode is a kwonly arg.
https://github.com/python/typeshed/blob/1cbfc7532544e3220c719c31922bab759d29acfe/stdlib/tarfile.pyi#L233-L237
stdlib/tarfile.pyi lines 233 to 237
@overload
def open(
name: StrOrBytesPath | None = None,
*,
mode: str,```
seems kind of inconsistent within the other overloads
How do I type this?
from enum import EnumType
from typing import Self
class _CaseInsensitiveGetItem(EnumType):
def __getitem__(self, name: str) -> Self:
if not isinstance(name, str):
raise KeyError(name)
for key, value in super().__dict__["_member_map_"].items():
if key.casefold() == name.casefold():
return value
raise KeyError(name)
Fails mypy strict: https://mypy-play.net/?mypy=1.13.0&python=3.12&flags=strict&gist=10fcacf27ad0aa4ac80bf46af39fe826
EnumType is a metaclass, so e.g. when you do py class ColorChannel(Enum): # same as: class ColorChannel(metaclass=EnumType): red = 1 green = 2 blue = 3 ColorChannel is an instance of EnumType, not a subclass of it. Self means the type of an instance, so this method signature implies that if you had: py class ColorChannel(metaclass=_CaseInsensitiveGetItem): ... then ColorChannel["red"] would return an instance of _CaseInsensitiveGetItem, not an instance of ColorChannel.
I'm not sure if there's a way to type this method currently, but you can use __class_getitem__ on a subclass of Enum (not EnumType) instead
So something like this ```py
from enum import Enum
from typing import Self
class _CaseInsensitiveGetItem(Enum):
@classmethod
def class_getitem(cls, name: str) -> Self:
if not isinstance(name, str):
raise KeyError(name)
for key, value in cls._member_map_.items():
if key.casefold() == name.casefold():
return value # type: ignore[return-value]
raise KeyError(name)
The proper way to implement a case insensitive enum is to use _missing_
class Label(StrEnum):
RedApple = auto()
GreenApple = auto()
@classmethod
def _missing_(cls, name):
for member in cls:
if member.name.lower() == name.lower():
return member
>>> Label('redapple')
<Label.RedApple: 1>
this isn't part of stdlib enum
stdlib only has _missing_ which works on call
missing only works on the call syntax
i.e, Label("redapple") # works by calling _missing_
Label["redapple"] # fails, doesn't call _missing_
maybe the call will work
I don't know OP's usecase
!pip aenum if you can use packages
this fails
i mean fails at runtime
How are you using it?
from enum import Enum
from typing_extensions import Self
class _CaseInsensitiveGetItem(Enum):
@classmethod
def __class_getitem__(cls, name: str) -> Self:
if not isinstance(name, str):
raise KeyError(name)
for key, value in cls._member_map_.items():
if key.casefold() == name.casefold():
return value # type: ignore[return-value]
raise KeyError(name)
class Test(_CaseInsensitiveGetItem):
APPLE = "Fruit"
print(Test["apple"])
Traceback (most recent call last):
File "\test.py", line 22, in <module>
print(Test["apple"])
File "\cpython-3.9.20-windows-x86_64-none\lib\enum.py", line 432, in __getitem__
return cls._member_map_[name]
KeyError: 'apple'
print(Test["APPLE"]) works as expected
Hmm, seems like a __getitem__ on a metaclass overrides __class_getitem__
metaclasses and typing don't really get along.
I don't think that necessarily has to be the case. But I agree that most currently existing major type checkers have limited support for metaclasses
What if we used a generic on __call__?
class MyMeta[T](type):
if TYPE_CHECKING:
def __call__(self, *args, **kwargs) -> T: ...
def __getitem__(self, key: str) -> T: ...
```?
so is my solution just adding # type: ignore comments?
@stiff acorn The main problem with the original code is this -- Self is not the right return type. You should use the same method signature that's used in EnumType._getitem__:
from enum import EnumType
from typing import Self
class _CaseInsensitiveGetItem(EnumType):
def __getitem__[T](self: type[T], name: str) -> T:
if not isinstance(name, str):
raise KeyError(name)
for key, value in self._member_map_.items(): # type: ignore
if key.casefold() == name.casefold():
return value
raise KeyError(name)
Does someone know a python library or project, that makes extensive use of current (3.12+) type-hinting features. I'd like to explore a bit and learn what can be done when taking the current type system to its limits.
although I still need 2 type ignore comments
but that's better than 3
Why is the super().__dict__["_member_map_"] needed?
it should be the same as self._member_map_
This has the same problem, Self is not the right return type for this method
oh I forgot to update the gist https://mypy-play.net/?mypy=1.13.0&python=3.12&flags=strict&gist=121569a329f0ad400aae48854da216a3 here's the new one
it raises attribute error (on 3.9 atleast)
from enum StrEnum, EnumType
from typing_extensions import TypeVar
T = TypeVar("T")
class _CaseInsensitiveGetItem(EnumType):
def __getitem__(cls: type[T], name: str) -> T:
if not isinstance(name, str):
raise KeyError(name)
for key, value in super()._member_map_.items():
if key.casefold() == name.casefold():
return value
raise KeyError(name)
class Test(StrEnum, metaclass=_CaseInsensitiveGetItem):
APPLE = "Fruit"
print(Test["apple"])
AttributeError: 'super' object has no attribute '_member_map_'
super().__dict__["_member_map_"] on the other hand works
you're right, that works
although now i get a new error from mypy
"type[T]" has no attribute "_member_map_"
Yeah, you'll need to # type: ignore here probably
Or maybe this:
from enum import EnumType
from typing import Self, TypeVar
T = TypeVar("T", bound=EnumType)
class _CaseInsensitiveGetItem(EnumType):
def __getitem__(self: type[T], name: str) -> T: # type: ignore[misc]
if not isinstance(name, str):
raise KeyError(name)
for key, value in self._member_map_.items():
if key.casefold() == name.casefold():
return value # type: ignore[return-value]
raise KeyError(name)
(the added bound)
Some things are just not possible to do without a bunch of # type: ignores, Python is just much more dynamic than the type system supports 🙂
yea that's true, just want to keep them to a minimum and I learned alot of new things from this so it was a productive chat for me
thanks a lot
Success: no issues found in 1 source file 🎉
# Determine the base directory based on user input.
if choice == 1:
# Get the custom directory path from the user.
base_directory: str = input("Enter custom output directory path: ").strip()
# Check if the custom directory exists and is writable.
if not os.path.exists(base_directory):
print(f"Error: The directory '{base_directory}' does not exist.")
return
if not os.access(base_directory, os.W_OK):
print(f"Error: The directory '{base_directory}' is not writable.")
return
else:
# Default to the current working directory.
print("Using current working directory for output.")
base_directory = os.getcwd()
if i specified the type-hint once then do i have do it in all the place where im using the same variable
like base_directory: str i specified it once, so do i have to do it for all the other lines where i use this variable ?
No, it applies to the whole scope (function, class, global). And you shouldn't need to annotate that anyway since input() returns str, and so does str.strip()
Hello,
I'm trying to have a generic class with several methods that return different elements of a tuple which type is defined with a generic arg. And I'm trying to extract type at position n of the generic arg to annotate return type of my method.
class MyClass[T: tuple[Any, ...]]:
def __init__(self, t: T) -> None:
self.t = t
@property
def first(self): # Missing return type annotation here
return self.t[0]
# Define second, third
Is there someway to do this or some other pattern to avoid this ?
Thanks
have you considered making the element type itself generic, rather than the whole tuple? as in: ```py
class MyClass[T]:
def init(self, t: tuple[T, ...]) -> None:
self.t = t
@property
def first(self) -> T:
return self.t[0]```
I feel like I can't achieve what I want this way because then all methods (first, second,etc) will return the same type T
oh, i'm not sure about that then, there's TypeVarTuple but afaik you can't annotate a particular element with that...
i believe sqlalchemy solves this by programmatically generating a bunch of overloads:
https://github.com/sqlalchemy/sqlalchemy/blob/rel_2_0_36/tools/generate_tuple_map_overloads.py
class MyClass[First, Second, *Rest]:
def __init__(self, t: tuple[First, Second, *Rest]) -> None:
self.t = t
@property
def first(self) -> First:
return self.t[0]
@property
def second(self) -> Second:
return self.t[1]
derivative question, is it possible to overload an entire class?
what would that mean?
Alternatively, you can do this if you don't want to add extra parameters to the class
class MyClass[*Items]:
def __init__(self, t: tuple[*Items]) -> None:
self.t = t
@property
def first[F, *Rest](self: "MyClass[F, *Rest]") -> F:
return self.t[0]
@property
def second[_F, S, *Rest](self: "MyClass[_F, S, *Rest]") -> S:
return self.t[1]
This also has the advantage of allowing MyClass[*tuple[()]] and MyClass[int] to exist (even though you can't use all of MyClass's methods on them)

having looked at sqlalchemy's auto-generated overloads, i imagined something like this: ```py
@overload
class MyClass[T0]:
def init(self, t: tuple[T0]): ...
@property
def first(self) -> T0: ...
@property
def second(self) -> NoReturn: ...
@overload
class MyClass[T0, T1, *Tx]:
def init(self, t: tuple[T0, T1, *Tx]): ...
@property
def first(self) -> T0: ...
@property
def second(self) -> T1: ...```
No, that's not possible
You can annotate self to restrict certain methods https://typing.readthedocs.io/en/latest/reference/generics.html#generic-methods-and-generic-self
thought the reference only presents the case that can be replaced with Self, so admittedly it's not helpful here
ye i was wondering how it would have differed from Self in their example, but otherwise TIL
neat that it recognizes when you pass insufficient elements for second/first
# Default values for parameters
fps: str = '1'
start_time = None
end_time = None
crop_width = None
crop_height = None
crop_x = None
crop_y = None
# Prompt for custom settings
if input("Want to set custom timing? (Yes/No): ").strip().lower() == "yes":
try:
start_time = int(input("Enter start time in seconds: "))
end_time = int(input("Enter end time in seconds: "))
except ValueError:
print("Invalid timing input. Using default timing (full video).")
if input("Want to set custom FPS? (default fps = 1) (Yes/No): ").strip().lower() == "yes":
fps = input("Enter FPS (e.g., '2/3' or '30'): ").strip()
if input("Want to crop the video? (Yes/No): ").strip().lower() == "yes":
try:
crop_width = int(input("Enter crop width: "))
crop_height = int(input("Enter crop height: "))
crop_x = int(input("Enter crop X offset: "))
crop_y = int(input("Enter crop Y offset: "))
except ValueError:
print("Invalid cropping input. Skipping cropping.")
what will be the type hinting for start_time, end_time, crop_width, crop_height, crop_x, crop_y
int | None
ohk
Though you probably don't need to add explicit annotations here
i was thinking of that but wasnt sure, tysm
fps: str = '1' is unnecessary, just do fps = '1'
You only need an explicit annotation on a variable if its type cannot be determined from context. Mostly with empty collections:
numbers = [] # type checker can't know what kind of list this is
numbers: list[int] = [] # now it can
I think Optional[int]
i dont know about optional can you please explain it to me a bit further
ffmpeg_input = ffmpeg.input(original_video_location, **ffmpeg_input_args)
in line like these we already know that ffmpeg.input() -> Any, so i dont need to explicitly specify the type hint right ?
Optional[T] mean it could be T or None. This case start_time could be int or None
ohh so it will always consider NONE and other mentioned datatype
Optional is cringe, use a union
yea like: user could input fps or not, so in case fps has no default value, it should be Optional
why? I saw it in almost github repo. So I don't think it's bad
def createDir() -> Optional[str]: or def createDir() -> str | None:
both are same right ?
just cause it may be common on GitHub doesn't mean it's not cringe
Yes, but... the function always returns. It's not an option. It doesn't make sense
try:
os.makedirs(folder)
print(f"Successfully Created: {folder}")
return folder
except OSError as e:
print(f"Error: Could not create directory. {e}")
return
folder is a str type
we talked about this before right?
use return None
it's a usable return value, so you should indicate it as such
yep we did i just wanna cross check
If I don't mind what?
.
^
def createDir() -> Union[str, None]: is this correct
i know im asking a lot of silly questions, but its my first time dealing with type hinting
ohh ohk
u mean -> Union[str | None] ?
ic
the 2nd one ?
Union[str, None] => str | None
I understand what you mean. How about in parameter? If caller don't have to specify param value, it should be Optional rather than Union, right?
I still don't like it then
that was the very first thing that i did😅 , then i changed it to union or optional
because you can have an optional arg that doesn't have a value of None
With | is also a union
ohhkkkk
start_time: Optional[int] = None
should i replace these as well with int | None ?
I would
ok
Why give a special name to unions which are with None?
😄 a bit confused. I would avoid | if I want my module compatility with older py version
No, you should use from __future__ import annotations
i originally asked in #1035199133436354600 but it got closed for inactivity, could anyone here help me ? https://discord.com/channels/267624335836053506/1309181190368006154
That's currently not possible. type[X] only accepts an actual class, not a union, generic alias, Any or other special objects. There's a draft PEP 747 to address this
However, you can use a hack like this:
from typing import TYPE_CHECKING
def _parse_obj(source: object, specification: object) -> object:
...
if TYPE_CHECKING:
class Parser[T]:
def parse(self, value: object, /) -> T:
...
else:
class _ParserFactory:
def __getitem__(self, typeform):
return _Parser(typeform)
class _Parser:
def __init__(self, typeform):
self.typeform = typeform
def parse(self, source):
return _parse_obj(source=source, specification=self.typeform)
Parser = _ParserFactory()
Parser[Literal[2, 4]].parse(some_int) # ok
thanks, i'll take a look tommorow
I have ```python
def findBodies(doc: FreeCAD.Document):
return cast(Sequence["PartDesign.Body"], doc.findObjects("PartDesign::Body"))
is it possible to make the "PartDesign.Body" a parameter as there are several different ones and the two strings are always the same except for the :: vs .
What do you mean by making it a parameter?
cast doesn't actually do anything at runtime, it's purely a type checking directive
something like
def f[T](t: type[T], doc: FreeCad.Document) -> Sequence[T]:
return doc.findObjects(t.__qualname__.replace(".", "::")) # type: ignore
maybe
then f(PartDesign.Body, doc) should work
but thats kinda hacky
@restive rapids it says T is not defined so maybe there's some syntax to quantify it somehow
yeah mb im eepy so didnt add it, edited
@trim tangle it looks like they call it a type parameter
Maybe Freecad provides a better way to do something like this? I'm trying to find the documentation for the FreeCAD.Document class but I haven't succeeded yet
||the freecad experience 💀||
I don't know either, but you can pip install freecad-stubs or look at the edit actually: https://github.com/ostr00000/freecad-stubs/blob/00baedec06c9ed5bf5547a8b1e00a77c14408baa/freecad_stubs/FreeCAD-stubs/__init__.pyi#L1733
freecad_stubs/FreeCAD-stubs/__init__.pyi line 586
class DocumentObject(FreeCAD.ExtensionContainer):```
apparently the Python classes are generated with PyCXX, so I got confused a bit
Is there some documentation on how these ::'d names are constructed? If it's just <module_name>.<class_name>, then what Lambda suggested is good
though maybe add a runtime check that t.__module__ is in a whitelist
T = TypeVar("T")
def findObjects(t: type[T], doc: FreeCAD.Document) -> Sequence[T]:
return doc.findObjects(t.__qualname__.replace(".", "::")) # type: ignore
findObjects(PartDesign.Body, doc)
this is what I have so far. The problem is that module 'PartDesign' has no attribute 'Body'.
it is a class though
PartDesign.Body() fixes that problem
if PartDesign.Body fails to execute, then PartDesign.Body() should definitely not work 
yes I'm still confused
the typechecker is satisfied with findObjects(PartDesign.Body, doc) but at runtime module 'PartDesign' has no attribute 'Body'. I haven't finished with PEP484, but it looks like there's no equivalent of https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/required_type_arguments.html
This is a runtime issue, so it shouldn't have anything to do with type checking
Is there some documentation for the PartDesign Python module? I'm getting this:
>>> import PartDesign
>>> dir(PartDesign)
['InvoluteGearFeature', 'SprocketFeature', 'WizardShaft', '_PartDesign', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'makeFilletArc']
>>>
my expectation is that the t: type[T] doesn't have to get evaluated since it's only for the type checker
type annotations are just for the type checker, so if you remove them, the code should have the same behaviour. I.e.: ```py
def findObjects(t, doc):
return doc.findObjects(t.qualname.replace(".", "::"))
findObjects(PartDesign.Body, doc)
This is why I asked this. It seems like the ::'d names refer to C++ classes or something like that and do not directly map to the Python module/class hierarchy
it seems like the Body class advertises itself as being part of the PartDesign module, but it's not in there:
>>> [body] = doc.findObjects("PartDesign::Body")
>>> body
<body object>
>>> type(body)
<class 'PartDesign.Body'>
>>>
Maybe you'll find someone who knows about this in the freecad server: https://discord.gg/PhAK2qXn
Any idea when the next typing-extension version is released? 👀 I want to try using TypeForm
@oblique urchin I have a question about the design of typing-extensions. It seems like the package serves two distinct purposes:
- Backporting brand new accepted typing features
- Giving access to experimental not yet accepted typing features
I don't understand why items in both categories have the same "published status". For example, ifDocorTypeFormdon't get accepted, they'll linger intyping_extensionsforever. Worse, if a future PEP proposes something else calledDocorTypeForm, you either have to remove the old items (breaking the compatibility) or waiting on the items until they're accepted (and still breaking compatibility).
Wouldn't it have been better to have a separate module (liketyping_extensions.experimental) for stuff that's clearly experimental?
the documentation says
No typing PEP that affected
typing_extensionshas been rejected so far, so we haven’t yet figured out how to deal with that possibility.
this is a bit concerning 😄
Yes, there is a bit of potential for conflict there. In practice PEP 727/Doc is providing the first sample of what to do about a PEP that doesn't make it. We'll keep the name typing_extensions.Doc around essentially forever, so it will continue to work at runtime.
You're right that we'd be in trouble if a future PEP proposes to use the same name for an incompatible object. That doesn't seem too likely though.
Even if the semantics are different, the runtime behavior of the name is still likely to be compatible.
well, you can make things raise deprecationwarnings and the like - but ofc that'd introduce a hefty delay before it can get removed and then reused
So if I want to add a typing item to a language, I can just make a draft PEP and don't have to get it accepted
that was essentially my worry, that e.g. PEP 727 may be rejected from the language (for example because it's deemed too verbose/occupying too much space/other reasons), but it is now de facto added to the language
though, of course, type checkers and IDEs don't have any such compatibility guarantees and may decide to not use typing_extensions.Doc
yes, though PEPs still need a core dev sponsor. And even if it's in typing-extensions, type checkers will not support it if the PEP is rejected
that's fair
also don't look for typing_extensions.IntVar
Is there anything like this in Pydantic where models are "selected" from enums with a tag?
Example in Rust with serde and serde_json:
Figured it out, Tagged Unions are used for this
I thought Python 3.14 and its deferred annotations would let us avoid forward refs when inheriting from a generic type, but it looks like it's not the case, and I can't find any relevant information in PEP 649:
# generic_base.py
class Foo[T]:
...
# Works fine for a type annotation.
foobar: Foo[Bar] = Foo()
# But not when inheriting from the class.
class FooBar(Foo[Bar]):
...
class Bar:
...
% python3.14 generic_base.py
Traceback (most recent call last):
File "/home/pawamoy/generic_base.py", line 8, in <module>
class FooBar(Foo[Bar]):
^^^
NameError: name 'Bar' is not defined
% python3.14 -V
Python 3.14.0a0
Looking at the code, it seems obvious, because in the base class case, Foo[Bar] is runtime. But the [Bar] part really is typing information, and doesn't affect runtime, no?
Unions are used for this. ```py
class LinkFull(BaseModel):
page: str
class LinkTitleOnlyCustom(BaseModel):
title: str
theme: str
link: str
class LinkTitleText(BaseModel):
text: str
LinkType = LinkFull | LinkTitleOnlyCustom | LinkTitleText
note that unions are resolved left to right and uses the first successful parse.
so str | int would be considered invalid as everything could be interpreted as a str
I resolved it with Union[<type>, <type>], thanks
that's the |
yes
PEP 649 only affects annotations and a base class is not an annotation
yeah, makes sense 🤔 too bad though!
Would it help if typing symbol candidates went in a submodule of typing-extensions, e.g. typing_extensions.candidates, that had far fewer and/or a different set of backward compatibility guarantees, preventing a name from being promoted to the library’s global namespace without the corresponding PEP being accepted?
Oh . . .
I didn’t read the precursor message. Whoops. Just ended up repeating things. My bad.
An issue with using a submodule is that then if it was accepted, early adopters would need to migrate 3 times - first use typing_extensions.candidates or whatever, then typing_extensions once accepted, then finally typing once the min Python version covers usage. Not really desirable.
technically you could do ```py
generic_base.py
class Foo[T]:
...
class FooBarT=Bar:
...
class Bar:
...
not exactly
(smart mode is the default)
An API I'm working with exposes a "fetcher" function that I need to call over and over to return either new valid values, or a sentinel value that indicates that there's no more output. I'm not a huge fan of that behavior, so I'm wrapping it to be able to lazily iterate over the valid values.
In my wrapper, I just yield each valid value in turn; when I get to the sentinel value, I discard it, don't yield anything, and let the generator end. I'm annotating the fetcher function as Callable[ParamSpec, ReturnType], and the wrapper is returning Generator[ReturnType, None, None].
That all works, without any issues, but when a caller tries to use it, because the output of the generator is still ReturnType, unchanged, that means that the sentinel value is still being seen as one of the possible values produced by the generator, which causes some type-checking errors in the caller if I try to act as though the sentinels have been filtered out.
Here's my question: is there any way to annotate the return type of my wrapper as something like Generator[Difference[ReturnType, SentinelType], None, None], instead - or any other way to indicate that the sentinel value isn't a possible result from the generator?
I have a strong suspicion that this is not something Python's typing system currently allows for, given the long debates I've seen over things like Intersection types - but I wanted to double-check to make sure I wasn't missing something.
(In my exact use-case, the sentinel value happens to be None, and all valid values are non-None, so there might be some None-specific way to address this - but ideally, it'd be nice to have a way to annotate this that works for any arbitrary sentinel type.)
You can do something like this ```py
from collections.abc import Iterator, Callable
from typing import reveal_type
def stinky_generator(foo: int, /, bar: str, *, baz: bool) -> int | None:
...
def unstinky[T, **P](fn: Callable[P, T | None]) -> Callable[P, Iterator[T]]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Iterator[T]:
while True:
value = fn(*args, **kwargs)
if value is None:
break
else:
yield value
return wrapper
nice_generator = unstinky(stinky_generator)
reveal_type(nice_generator)
Type of "nice_generator" is "(foo: int, /, bar: str, *, baz: bool) -> Iterator[int]"
Putting the T | None in the parameter position essentially acts like a "difference" when figuring out the type variable
(**P is the 3.12 way of spelling ParamSpec, so if you need to support 3.11 or lower use typing/typing_extensions.ParamSpec)
Beautiful! Thank you, I'll run with that, and file that one away for later, too.
Maybe you just want the built-in iter function?
!e
class Stinky:
def __init__(self, start):
self.x = start
def generate(self):
if self.x > 0:
self.x -= 1
return self.x + 1
else:
return None
stinky = Stinky(5)
it = iter(stinky.generate, None)
print(list(it))
:white_check_mark: Your 3.12 eval job has completed with return code 0.
[5, 4, 3, 2, 1]
stdlib/builtins.pyi lines 1463 to 1470
@overload
def iter(object: SupportsIter[_SupportsNextT], /) -> _SupportsNextT: ...
@overload
def iter(object: _GetItemIterable[_T], /) -> Iterator[_T]: ...
@overload
def iter(object: Callable[[], _T | None], sentinel: None, /) -> Iterator[_T]: ...
@overload
def iter(object: Callable[[], _T], sentinel: object, /) -> Iterator[_T]: ...```
For some reason it happens to have an overload specifically for your case 😛
...well, I may feel like an idiot, but I guess at least now I don't have to write those unit tests
Thanks, lol
could mypy properly get a function decorator that executes itself and returns the value?
from typing import *
T = TypeVar('T')
def as_value(func: Callable[[], T]) -> T:
return func()
@as_value
def n():
return 42
assert n == 42
couldn't get something similar to work with singleton class decorator that takes type[T] and returns T, so I resorted to a metaclass with a registry
from typing import *
T = TypeVar('T')
def as_value(func: Callable[[], T]) -> T:
return func()
@as_value
def n() -> int:
return 42
assert n == 42
this type checks fine https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=d16bb9d0dee2dbaf817ce22b2b77f7cf
how do I tell mypy a class implements another without inheriting?
I'm making my own typing.NamedTuple with additional methods
but I naturally can't inherit NamedTuple more than once, so I'm using typing.NamedTupleMeta which is the actual implementation, whereas NamedTuple is an empty class
imagine the following hierarchy:
NamedTupleMeta -> CustomNamedTupleMeta -> CustomNamedTuple -> DataStructure
where CustomNamedTupleMeta has regular metaclass business going on
where CustomNamedTuple is just a regular class as NamedTuple is
where DataStructure is a dev defined object
up until CustomNamedTuple, mypy gives proper lsp hints
at DataStructure the regular NamedTuple's lsp hints are gone
forget it, found my answer
is there a reason you're using NamedTuple instead of a dataclass?
the thing must be an immutable-ordered-sequence in nature, the string indexing are just aliases for their respective values
because the system that will consume the objects really cares about ordering and and iterating
but the input that must be transformed and organized are unordered and indexed by field name
the dev must know the values by name/context but the system only cares about ordering and strictness
imagine merging a dict and a namedtuple in behavior but it actually is just a tuple
I feel like it'd perhaps be more sensible to just define your own base class or protocol, and define all the dunders needed to make it work like you want
but idk, sounds messy/complicated
does dataclasses preserve the order of the attributes defined when you get the field names?
I think so. Dicts also maintain order
is this too complicated to you?
from typing import Any, NamedTupleMeta, SupportsIndex # type: ignore
from pydantic import ConfigDict, TypeAdapter
# SEE typing.NamedTupleMeta implementation
class ModelMeta(NamedTupleMeta):
__adapters: dict[type, TypeAdapter] = {}
def __new__(cls, name, bases, attrs):
nm_tpl = super().__new__(name, bases, attrs)
if nm_tpl not in cls.__adapter:
cls.__adapters[nm_tpl] = TypeAdapter(nm_tpl, config=ConfigDict(strict=False))
return nm_tpl
def _type_adapter(cls)->TypeAdapter:
return cls.__adapters[cls]
class Model(metaclass=ModelMeta):
def __getitem__(self, name: str|slice|SupportsIndex,/) -> Any:
if isinstance(name, str):
try:
return getattr(self, name)
except AttributeError as err:
raise IndexError(err)
return self.__getitem__(name)
you definitely need some comments and/or docstrings
I see
the implementation of NamedTupleMeta fits on the screen and I understood it quickly, so I thought most people also could
doing this current project alone, so struggling with guessing what other people would find difficult
I think as soon as you start messing with metaclasses and dunders you will quickly lose a lot of people, and ModelMeta/Model aren't super descriptive names that you immediately understand what they're for. But if it's mostly just copying the behaviour of namedtuple/meta then you can expand your SEE comment to say that; and then describe what the classes do/should be used for in their docstrings. If you expect people to be reading and/or modifying the implementation of those dunder methods I'd probs pop some comments in there as well - but when given the context from your discord messages I can mostly figure them out without too much problem.
but does the class definition translates literally to a dict?
wouldn't there be an influence of the parsing on it?
Agreed
kind of? they do ultimately end up in __dict__
not sure what you mean with this
is there a chance python would parse a class so that the attributes wouldn't be registered exactly in the order they were defined?
(also we're pretty fram from the topic of #type-hinting by now, heh)
(yeah)
no, "The order of the fields in all of the generated methods is the order in which they appear in the class definition." https://docs.python.org/3/library/dataclasses.html#module-contents
happy to help, good luck!
https://gist.github.com/aavogt/d4b78be6bb4c64c2b59a2d29795e6dd2 it works fine now that define my own PartDesign.Body
from datetime import timedelta
def tt(ss):
return timedelta(seconds=ss)
if name == "main":
seconds = 684500
kq = tt(seconds)
print(kq)
!e
from datetime import timedelta
def tt(ss):
return timedelta(seconds=ss)
if name == "main":
seconds = 684500
kq = tt(seconds)
print(kq)
:white_check_mark: Your 3.12 eval job has completed with return code 0.
7 days, 22:08:20
What is the proper way to annotate class methods that may be inherited. E.g. this makes the type checker confiused, as factory returns a specific type that's not inherited:
from __future__ import annotations
class A:
@classmethod
def factory(cls) -> A:
return cls()
class B(A):
pass
b = B.factory() # reveals as A but is a B instance
!d typing.Self
or a typevar T that you'd bind from cls: type[T], @classmethod def f[T](cls: type[T]) -> T: ...
typing.Self```
Special type to represent the current enclosed class.
For example...
type[T] sounds cleaner. Thanks
probably not, the Self version looks like this py class A: @classmethod def factory(cls) -> Self: return cls() and the typevar version looks like this ```py
after 3.12
class A:
@classmethod
def factory[T: "A"](cls: type[T]) -> T:
return cls()
before 3.12:
T = TypeVar("T", bound="A")
class A:
@classmethod
def factory(cls: type[T]) -> T:
return cls()
Do you need the quotes for the 3.12+ version?
yes
PEP 749 is scheduled for 3.14 for now
it seems to work fine
i thought i managed to convince them that that would be useful to use the mechanism for 749
huh, it seems like this information is not stored at runtime at all?
!e
def f[T: "yes" + 42]():
pass
:warning: Your 3.12 eval job has completed with return code 0.
[No output]
yep
!e
def f[T: "yes" + 42]():
pass
f.__type_params__[0].__bound__
:x: Your 3.12 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 4, in <module>
003 | f.__type_params__[0].__bound__
004 | File "/home/main.py", line 1, in T
005 | def f[T: "yes" + 42]():
006 | ~~~~~~^~~~
007 | TypeError: can only concatenate str (not "int") to str
oh
I honestly don't understand the proposed mechanism at all, never even tried
does it just change __annotations__ to a descriptor that executes the annotations when they are first accessed?
yes
What is the type hint for something like str | bool | int | ...? Basically the first param for typing.cast? I see the docs show it as type[_T@cast], but I can't seem to add that directly to my code, and I'm not sure if there's something else I should use.
I'm trying ot use typing.cast and want to make sure I type hint properly
If you're using typing.cast, you don't need to worry about it
Sorry, to clarify
The value is coming from a variable
Like this:
cast_type = UserMeta.MetaKeys[self.key].value[2]
return cast(cast_type, self._value)
I was thinking of doing a union, which would work
I wasn't sure if there's a more generic value to use though
Any is probably your best bet here
Pylance complains about any
In which case there's no reason to call cast anyways
what does it say?
pyright shouldn't complain unless you have some config or you're using basedpyright
Hm, the error changed, now it's saying Variable not allowed in type expression
So maybe it's my code
yeah, you shouldn't be using typing.cast here
Bah, not sure how to do this then
Just don't use typing.cast, return self._value directly
What does it mean to cast the value?
to convert types?
typing.cast doesn't do anything at runtime, if you're trying to change types at runtime
Oh... the way the docs read, I thought it literally cast
Oh, it's just doing the type checking cast
That's... useless? Well, maybe there's a use I don't see
it's literally just
def cast[T](ty: type[T], obj: object) -> T:
return obj # type: ignore
Yah, interesting
It's only useful for static type checking
yeah
There's no such thing as "casting to a type" in Python. typing.cast is just a shortcut for this:
# assume that `foo` is a str | None, but the programmer knows it's a `str` here
y: str = foo # type: ignore
y = typing.cast(str, foo)
it also avoids some pitfalls that a blanked # type: ignore has, like not catching a typo (food instead of foo)
Ok, so then since it's basically str | int | bool, I can just do cast_type(self._value)
I guess I don't really know the typing for that either... it's a callable, but of specific types?.
What values do cast_type and self._value take?
Can you give some examples of what it should do maybe?
Cast type is literally str or int or bool. Not as in it's of type str, but the actual type str, as in str()
It's a situation where if x == 1, y is an int, if x == 2, y is a bool, etc
Maybe it's better to think of cast_type as a function that attempts to convert a value?
Right, a callable, but I was hoping to limit the types so I could set up the returns correctly. I tried Literal, but Literal[int] gets an error. I guess it'd be Literal["int"]?
the value int has the type of type[int]
But whenever you want to use type[Something] as a parameter, in 90% of cases it's better to use something else. Like Callable[[object], int]
Ok, I'll look into the callable syntax more
I appreciate it!
I may end up restructuring my db anyway, but this is useful knowledge
Ok, another type hinting question. Is there a way to tell the static checker that in a certain if block, I know the type of a value will be a certain something? Something like
if x == 1:
# here, y will always be a str
elif x == 2:
# here, y will always be a bool
IMO this is a really sketchy thing to do in general. Yes python will LET you mix and match types in a single var, but it is a bad idea most of the time.
Generally, I agree, but I'm in one of those few situations where I have a variable that's mixed type, due to how values are stored in my database
While I have some solutions I'll be looking at to try to break it apart, for now, it is what it is
Could you move the ```if x == 1:
# here, y will always be a str
elif x == 2:
# here, y will always be a bool
to a function, then have it return the value of y? The function sig would be `def a(x: int) -> str | bool:`
if x == 1:
return "something"
if x == 2:
return False
if __name__ == '__main__':
b = a(1)
b.strip()
b = a(2)
b.strip()
Mypy understands this, and will give the correct error
mypy_example.py:1: error: Missing return statement [return]
mypy_example.py:10: error: Item "bool" of "str | bool" has no attribute "strip" [union-attr]
mypy_example.py:12: error: Item "bool" of "str | bool" has no attribute "strip" [union-attr]
Found 3 errors in 1 file (checked 1 source file)
Yah, that makes sense
I'll handle that tomorrow... I've been trying to create my own enum type for the last hour and my brain hurts now, time to sleep 😛
Speaking of enums, why does this happen?
Code
from typing import reveal_type
from enum import Enum
class Foo(Enum):
BAR = "bar"
reveal_type(Foo._value2member_map_)
print(Foo._value2member_map_) # {'bar': <Foo.BAR: 'bar'>}
# Shouldn't the reveal_type show Dict[str, Foo]?
Type checkers probably don't special case that attribute, so they use a type that's valid for all enums
yep
I suppose you could have something like this in the stubs ```py
class EnumType:
...
@property
def value2member_map[S: Enum](self: type[S]) -> Mapping[Any, S]:
...
yeah probably. I think str would be wrong because enums can have other kinds of values, so you'd still need Any as the key type
here is the relevant line in typeshed: https://github.com/python/typeshed/blob/f7c6acde6e1718b5f8748815200e95ef05d96d32/stdlib/enum.pyi#L175
stdlib/enum.pyi line 175
_value2member_map_: dict[Any, Enum] # undocumented```
oh right
I came up with this horror lol
from enum import Enum, EnumMeta
from typing import Any, TypeVar, KeysView, ValuesView, reveal_type
from types import MappingProxyType
_ET = TypeVar("_ET", bound="EnumMeta")
class EnumExtras(EnumMeta):
def values_to_keys(self: type[_ET]) -> dict[_ET, str]:
assert isinstance(self.__members__, MappingProxyType)
# return {v: k for k, v in zip(self.keys(), self.values())} # <- does work but gives a type error
# return {v: k for k, v in self.__members__.items()} # <- also works, but gives a type error
reveal_type(self.__members__) # Type of self.__members__ is Never (HUH????)
return {v: k for k, v in zip(self.__members__.keys(), self.__members__.values())} # This works and gives no type error (somehow)
def keys(self) -> KeysView[str]:
return (self.__members__.keys())
def values(self: type[_ET]) -> ValuesView[_ET]:
assert isinstance(self.__members__, MappingProxyType)
return self.__members__.values()
class SupportedConfigurationFormats(Enum, metaclass=EnumExtras):
AOC_CONFIGURATION_FILE = ".advent"
PYPROJECT_TOML = "pyproject.toml"
# ...
print(SupportedConfigurationFormats.values()) # type error
print(SupportedConfigurationFormats.keys())
print(SupportedConfigurationFormats.values_to_keys()) # another type error
Nvm! Fixed it!
from enum import Enum, EnumMeta
from types import MappingProxyType
from typing import Any, KeysView, TypeVar, ValuesView, reveal_type
_ET = TypeVar("_ET")
class EnumExtras(Enum):
@classmethod
def values_to_keys(cls: type[_ET]) -> dict[_ET, str]:
assert isinstance(cls, EnumMeta)
return {v: k for k, v in cls.__members__.items()}
@classmethod
def keys(cls: type[_ET]) -> KeysView[str]:
assert isinstance(cls, EnumMeta)
return cls.__members__.keys()
@classmethod
def values(cls: type[_ET]) -> ValuesView[_ET]:
assert isinstance(cls, EnumMeta)
return cls.__members__.values()
class SupportedConfigurationFormats(EnumExtras):
AOC_CONFIGURATION_FILE = ".advent"
PYPROJECT_TOML = "pyproject.toml"
# ...
print(SupportedConfigurationFormats.values())
print(SupportedConfigurationFormats.keys())
print(SupportedConfigurationFormats.values_to_keys())

Yes. Type hints are just typing.GenericAlias or type objects, meaning type hints are not you signaling information to the interpreter, because it ignores hints unless they contain illegal python syntax. Type hints are just python expressions that return an object containing metadata consumable by static type checkers, which can be accessed at runtime through the inspect module. Mypy for instance, doesn't even evaluate your code, it retrieves the type hints from the ast and check them against their rules. So, yes, Literal[int] and Literal["int"] are different.
And the error you get happens because "int" is a constant value, meaning that knowing your variable is a string isn't enough for the type checker. You either need to tell it that string variable is constant with typing.Final[str], ignore the error, cast it to typing.Literal["int"], or just return typing.Literal["int"].
from typing import Final, Literal, TypeAlias, Any, Union, Never
regular_string: str = 'int'
apple_string: Final[str] = 'apple'
int_string: Final[str] = 'int'
Options: TypeAlias = Literal['int', 'str', 'float']
def unsafe_is_option(ty: Options) -> Union[Options, Never]:
if ty in Options.__args__:
return ty
raise ValueError # raising error == returning Never
unsafe_is_option(regular_string) # typing error, runtime ok
unsafe_is_option(apple_string) # typing ok, runtime error
unsafe_is_option(int_string) # typing ok, runtime ok
There are syntactic sugars related to defining hints, but for the point of them being objects, this is the most literal syntax.
y: str | bool
if x == 1:
y = 'something'
elif x == 2:
y = True
elif x == 3:
y = 3 # typing error, type 'int' cannot be assigned to type 'str' nor 'bool'
if isinstance(y, str):
# do thing
elif isinstance(y, bool):
# do thing
elif isinstance(y, int):
# typing error, block is unreachable
On a note: int is a subclass of float, so int can be assigned to float, but never the contrary; even if somehow the type checker knows your float has no decimal points, like how dividing with / gives you a float, but diving with // gives you an int, but 10/2 is a float and 10//2 is an int even though numerically it shouldn't matter.
Yet, since int is a subclass of float, 5.0 == 5 is True at runtime, because python does care about numerical representation at number comparisons, so the type checker won't complain about int == float expressions.
Well, int is not really a subclass of float (isinstance(42, float) is False). But type checkers consider ints to also be floats
which is stinky by itself, but to make things worse, mypy doesn't fully understand this fact
def f(x: float | str) -> None:
if isinstance(x, float):
print(x + 3.14)
else:
print(x + "!!!")
f(42) # allowed by mypy, will explode at runtime
Just double checked and you're right. Found the source of my misunderstanding: https://docs.python.org/3.11/reference/datamodel.html#numbers-number
Python docs says float are real numbers and int/bool are integral numbers, which fair enough.
>>> import numbers
>>> isinstance(42, numbers.Integral)
True
>>> isinstance(42, numbers.Real)
True
>>> isinstance(42.5, numbers.Integral)
False
>>> isinstance(42.5, numbers.Real)
True
>>> isinstance(42, float)
False
I do remember reading somewhere that checking for instances of numbers.Number and such is not recommended. Can't remember why, but either way, I'm doubly sorry.
No need, it's a part of the type system that I think is not good. Making one type a virtual subtype of another as a lie for supposed convenience
Fun fact: int has an is_integer method for compatibility with floats
That's why I believe you either duck type or statically type. Having a implements alternative for isinstance would be great. I still don't know why can't we generate runtime-checkable Protocol objects. Even if they hard coded a rule where the class needed to be type hinted from top to bottom, like they hard coded types.SimpleNamespace despite the implementation being technically trivial. Obviously, it is way more complicated than that, it always is, but one can wonder.
Runtime checking protocols in general doesn't really work, because you can only check the names at runtime. You can see that I have an __add__ method, but you can't run a check to see if it always returns e.g. an int
!d typing.runtime_checkable
@typing.runtime_checkable```
Mark a protocol class as a runtime protocol.
Such a protocol can be used with [`isinstance()`](https://docs.python.org/3/library/functions.html#isinstance) and [`issubclass()`](https://docs.python.org/3/library/functions.html#issubclass). This raises [`TypeError`](https://docs.python.org/3/library/exceptions.html#TypeError) when applied to a non\-protocol class. This allows a simple\-minded structural check, very similar to “one trick ponies” in [`collections.abc`](https://docs.python.org/3/library/collections.abc.html#module-collections.abc) such as [`Iterable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable). For example...
^but this does exist
And there's also the classes from numbers that will also support int and float and custom classes registered with these ABCs (numbers from numpy probably are)
👀 when was this added
I remember being very annoyed it didn't exist a while ago
Wheter it does return an int, is not the point. You can run inspect.signature to get the returning type hint of a function, that's my point. Like how a function block is just ... when you decorate it with overload or abstractmethod. If it def __add__(int) -> int like an int, it must be an int. Am I too lost on the plot?
Or maybe
implements(int.__add__, 42.5) # True
implements(list.__add__, 42.5) # False
Because classes are hashable since type is always hashable. And Callable[[int], int] could be like ((int,),int) which is hashable too. So theoretically, could hash(signature(int.__add__)) == hash(signature((42.5).__add__))?
question on type hinting an iterator
class PostEmbedImageIterator(Iterator):
def __init__(self, embed: dict):
super().__init__()
self._embed_images: list = embed.get("images")
def __next__(self) -> EmbedTypeImage:
try:
item: dict = self._embed_images.pop(0)
return EmbedTypeImage(item)
except IndexError:
raise StopIteration
...
match embed:
case PostEmbedImageIterator():
for image in embed:
print(image)
PostEmbedImageIterator() produces EmbedTypeImage. i expect the LSP to catch this but it doesn't. is there a way to typehint the product of a custom iterator like this?
You probably need to inherit from Iterator[EmbedTypeImage]
Or just implement an __iter__ method, then there's no need to inherit from Iterator explicitly
You probably need to inherit from Iterator[EmbedTypeImage]
this works 👏
is implementing __iter__ 'better' in this case?
It might be easy for when you expect an int and the method signature specifies a return of int. But:
- Some types are more complicated. What if you expect an
int | strand the implementor hasint? What aboutlist[int | str]andlist[int]? So now you'll need to implement an entire type checker at runtime, that would understand stuff like recursive types and variance. And it would do that in a single check that would seem to be cheap computationally. - Signature information can be missing or misleading. For example,
contextlib.contextmanagerpreserves the annotations of a method. So here:
@contextmanager
def open_s3_file(s3: S3, path: str) -> Iterator[S3File]:
...
``` the `open_s3_file` function actually returns a `ContextManager[S3File]`, but inspecting its signature will say that it returns an `Iterator[S3File]`. and other decorators may not call `@functools.wraps` so the annotations will just be missing
- Would the check return `False` if the class doesn't have type annotations? For example, `int` and `float` don't have any type information at runtime, it's all in the stubs. (and there will be users of your library that don't want to use type annotations)
Maybe there's more to PostEmbedImageIterator, but for what you showed, why not a generator?
def get_images(embed: dict) -> Generator[EmbedTypeImage, None, NoReturn]:
images: list[Any] = embed.get('images', [])
while len(images):
yield images.pop(0)
I think it's mostly preference. Iterator only really gives you an __iter__ method that returns self, which is maybe not interesting enough to have an extra import item and a base class
what you saw on PostEmbedImageIterator is all there is
and that does look clean
I hope you don't have list.pop(0) in a loop in production code, because that's quadratic 👀
what i need is a FIFO queue
i have no idea if stdlib python has it
a quick google search doesn't turn it up
but i may be looking at the wrong place
Do you need to add items from the front?
I mean, he can... but all the list.copy() would make it exponential instead xD
no, i just need to pop from the front
def get_images(embed: dict) -> Iterator[EmbedTypeImage]:
return map(EmbedTypeImage, embed.get('images', ()))
wait map returns an iterator
yes, map is a class whose instances are iterators 🙂
map used to return a list in Python 2
as does filter
i haven't been programming at all for a couple of months look at me
of course, Python does not respect its own convention and uses smashedtogethercase for some classes
😆
at this point why bother 😆 , just go
get_images: Callable[[dict], Iterator[EmbedTypeImage]] = lambda embed: map(EmbedTypeImage, embed.get('images', ()))
the golfing starts kek
pythonistas will say you should never assign a lambda function, but they didn't see anything
If you want a loop, then you can do ```py
def get_images(embed: dict) -> Iterator[EmbedTypeImage]:
images = embed.get('images', [])
for raw_image in images:
# imagine something else
yield EmbedTypeImage(raw_image)
Isn't embed['images'] iterable already? iter raises TypeError which is nice in case it isn't, but then you have to do embed.get('images', None)
no prob
I got confused because initially wanted to show that you don't need a linked list or other style of queue for this, you can just make an iterator and next it
class PostEmbedImageIterator(Iterator):
def __init__(self, embed: dict):
self._embed_images = iter(embed.get("images", ()))
def __next__(self) -> EmbedTypeImage:
item = next(self._embed_images) # may raise StopIteration
# imagine something else
return EmbedTypeImage(item)
which is similar in spirit to storing the full list and keeping a counter
yet another alternative might be reversing the list and popping from the front ```py
class PostEmbedImageIterator(Iterator):
def init(self, embed: dict):
self._embed_images = embed.get("images", ())[::-1]
def __next__(self) -> EmbedTypeImage:
if not self._embed_images:
raise StopIteration
item = self._embed_images.pop()
# imagine something else
return EmbedTypeImage(item)
i like this one
took a bit to get it but it's simple
honestly, @fluid jay , if you want typing, try a TypedDict or a dataclass with a method
class DatabaseObject(TypedDict):
images: Sequence[bytes]
def serialized_images(self) -> Iterator[EmbedTypeImage]:
for img in self.get('images', ()):
yield EmbedTypeImage(img)
can't have methods on a typeddict
a typeddict just describes the shape of a dict, e.g. ```py
class Point(TypedDict):
x: int
y: int
p: Point = {"x": 4, "y": 5}
if you can get away with a generator function, always go for it 🙂
Fair enough. I'm always a fan of p = Point(x=4, y=5) so it gets confusing
or go full overengineering and subclass list[EmbedTypeImage] just to override __init__
so much for
There should be one-- and preferably only one --obvious way to do it.
you're welcome! 🤗
What's the correct approach to running mypy over a library that supports python 3.9 to 3.13?
For example, I have pytest running for each python version
Should I do the same for mypy?
That is, run mypy for each python version? Or run it for the lowest only (3.9)? Or run it for the highest only (3.13)?
I run it for each
Using tox
It's a hassle, so it's common to run it for the latest version only
Or latest version for each platform
Have you ever encountered a case where mypy succeeds on one version but fails on another?
that is possible, most likely if you use some stdlib feature that changed
How do you deal with them?
oh, that's why it's still referred to as a built-in function often, including the docs
good to know! :)
you could have different pieces of code for different Python versions (with if sys.version_info checks), or you could write code in such a way that it works on all Python versions, by avoiding features that were added later than your earliest supported version
That makes sense, thank you. So do you think I should run mypy per version?
Ideally yes. If that's too expensive (e.g. it slows and it annoys developers more than it helps), I'd recommend running mypy on the oldest supported version
I don't mind it being slightly slower so guess I'll do that. Thank you!
This is metacommentary but I'm really not in favor of the "TypedDict" name. It does make sense but something is just a bit off about it.
P.S. I meant that it's not necessarily making a dictionary typed, since it's already typed, but rather adding more information (its scheme) to the type.
Weird question: I have a file that currently uses the older type hinting style for optionals Optional[T], but I'm wanting to convert it to the modern style T | None. The file I have is quite large, and doing those changes by hand would be incredibly tedious. Is there a simple way to do it? I've futzed around with regext but I keep running into weird walls on it. Was wondering if there was a tool or script I could use to do it for me. For context on the current regex I have:
old = r"\bOptional\[(.+?)\]"
new = r"\1 | None"
That runs into problems with nested Optional conditions:
# this
Optional[list[T]]
# turns into this
list[T] | None
Thoughts? Suggestions?
(apologies for jumping in the middle while you were typing, yield)
I hit enter before I looked
It's ok, no worries! What I was saying was not very important anyway.
you can use pyupgrade, or one of Ruff's rules with an autofix
I tried the autofixes, however from what I found, it won't fix it since the only rule that comes close just doesn't like type hints that default to None but don't have any optional noted
So like it'll correct:
ham: int = None
But since that stuff is already hinted, it won't convert
I don't think that's right
The only rule I could find for it is RUF013
Check for type annotations that can be rewritten based on [PEP 604] syntax.
Unless I'm missing something
Derp
I'm blind then, apologies
I appreciate it. Sorry for the dumb question!
Argument of type "BufferedReader" cannot be assigned to parameter "data" of type "ReadableBuffer"
this is not good python design
they sound like the same thing
i am trying to calculate crc for a binary opened file, and I didn't want to do fp.read() as that puts the whole file in a bytes object which can be wasteful
Not the right channel for this question, but if I understand what a CRC is, one way around this is to call file.seek(…, os.SEEK_END) (relative to the end of the file) on the opened file and only then call file.read() to get the last however many bytes that are relevant to the CRC. That should avoid the memory overhead.
Bit similar, yeah. By chance, got any ideas for better names for one or the other? I’m drawing a blank.
Ah yeah thanks. That is clear, however the crc I want would be computed by the native zlib module
Using the entire file contents
Seems like it needs the whole file contents in memory, then. Idk enough to say otherwise.
ReadableBuffer sounds exactly like an object that can be called .read
Ah, that is confusing. I see how you got from a to b now.
It refers to the buffer protocol (https://docs.python.org/3/c-api/buffer.html), which is mostly a C-level protocol for objects to expose raw bytes
Possibly not the best name, it's more like a "bytearray protocol", but it's an established name by now
I see, google sent me to this page but it wasn't too convincing because the type 'hint' isn't mentioned
ReadableBuffer is specific to typeshed. The buffer protocol was initially C-only; PEP 688 introduced a Python visible way to use it, but it doesn't support distinguishing between readable and writable buffers.
Hmm. I have a bit of a tangential question: it seems that in my installations of python 3.12 and 3.13, the common builtin buffer types like bytes, bytearray, and memoryview aren't subclasses, virtual or otherwise, of collections.abc.Buffer, or don't pass the getattr check to match that "protocol". However, typing-extensions registers those three as virtual subclasses of its Buffer placeholder.
Am I missing a reason for that seeming lack of equivalent behavior? Or maybe is my installation borked?
Belay that, I might just be dumb. Never mind.
did you check isinstance instead of issubclass
A bit late, but yes, eventually 
that's fine, cool that there is a base abstract class though
Aye. I like that it doesn't rely on subclass registration to make isinstance/issubclass work with the stdlib concrete implementations.
Fair. Seems flexible in some ways.
Hi all. I tried asking this as general question but didn't get much so I'll ask here (slightly modified). I'm wondering why pyright gives the error:
error: Overload 1 for "myfn" overlaps overload 2 and returns an incompatible type (reportOverlappingOverload)
when I run it against this code:
from typing import Literal, overload
import numpy as np
import numpy.typing as npt
from typing import reveal_type
@overload
def myfn[T: np.generic](
values: npt.NDArray[T],
other_arg: int = 0,
copy: Literal[False] = False,
) -> None: ...
@overload
def myfn[T: np.generic](
values: npt.NDArray[T],
other_arg: int = 0,
copy: Literal[True] = ...,
) -> npt.NDArray[T]: ...
def myfn[T: np.generic](
values: npt.NDArray[T],
other_arg: int = 0,
copy: bool = False,
) -> npt.NDArray[T] | None:
output = values.copy() if copy else values
# ... mutate output, maybe using other_arg ...
# Nothing to return if not copying
return output if copy else None
reveal_type(myfn(np.arange(10), copy=False)) # Correctly reveals as None
reveal_type(myfn(np.arange(10), copy=True)) # Correctly reveals as an ndarray
this is because both overloads have a default for the copy argument
only one of them (the one that gets picked if the parameter is omitted) should have it