#type-hinting
1 messages · Page 27 of 1
-> None:
Hello, flask expose a config property on the Flask object, it is a dict with additional methods, the issue is, when i try to app.config["VALUE"] i get an Unknown value type, anyone know how i can specify the type of the returned value ?
x: Something = app.config[key]
^
yah but no, i did try that, i get Somthing | Unknown which is not better
from what i've seen i should be able to subclass flask and config class but even when i do that pylance is not picking up the new keys i did a ticket for and i'll see what they say
Generally it's going to be very hard to type something very dynamic like that.
One option would be to have something like app.config["MAIN_CONFIG"] and store a typed object (like a dataclass) in it. Then you'd use config: MainConfig = app.config["MAIN_CONFIG"] and it will prevent errors in accessing the wrong config key in a similar way.
i can't get a value from the config object without Pylance complaining
config: Mainconfig will not work
you mean, you have Pylance on strict and it's complaining about a partially unknown type?
do you really want the strict setting? it's a bit of a pain
there's a standard setting nowadays which has most of the options you probably need
aiohttp also has a willy nilly config like this, and it made a pretty clean solution for this. Instead of strings, you can use a typed key: py class AppKey(Generic[T]): def __init__(self, name: str, tp: type[T]) -> None: ... and you use it like this: ```py
config.py
DB_SETTINGS_KEY = AppKey("db_settings", DbSettings) # inferred as AppKey[DbSettings]
main.py
from config import DbSettings, DB_SETTINGS_KEY
app[DB_SETTINGS_KEY] = DbSettings(...) # setitem is type-checked
db_settings = app[DB_SETTINGS_KEY] # inferred as DbSettings
getting the same error on basic level
can you show the full snippet and the error you get?
Environment data Language Server version: 2024.1.104 OS and version: Windows 11 23h2 Python version (& distribution if applicable, e.g. Anaconda): python 3.12.1 Code Snippet class CustomConfig(...
Oh yeah, it's not supposed to pick that up. That's not going to work
why ? it was able to do it once, and i never was able to reproduce it
the value of config_class is not automatically "linked" in any way to Flask.config. At least from the typing standpoint
Is it possible to do it by refactoring flask code ? or it's not possible with current type features
It might be possible with some significant changes
but I don't think flask is going to accept that
i'm more asking about how could you implement it
You would have to make Flask generic, like class Flask(App, Generic[ConfigT]): and then accept a ConfigT value or a Callable[[], ConfigT] factory in the __init__
If you want a very low effort solution, that's gonna be it I think
def main_config(app: Flask) -> MainConfig:
return app.config["MAIN_CONFIG"] # type: ignore
sometimes you just gotta have a # type: ignore
or maybe typing.cast
This is probably going to be a core function in your code, and will be exercised thousands of times in tests. So you don't really gain anything by being "type safe"
it's more about auto-completion than type safe
this will give you auto-completion if you do main_config(app).some_field_of_MainConfig
i went with this then
def get_config(app: Flask) -> DefaultConfig:
return cast(DefaultConfig, app.config)
Should I annotate types for my code as said in this video?
https://www.youtube.com/watch?v=wlbkON0h8Y0
In this tutorial, I explain how to use type hints for basic types like str, int, float, and Bool. In addition, I explain the relation between these types. Annotating types for basic data types in Python pays off in the long run and in large Python projects.
Check out our Python Type Hints for Beginners playlist:
https://www.youtube.com/play...
well, you could also do ```py
def get_config(app: Flask) -> DefaultConfig:
return app.config # type: ignore
ah ye, forgot about that xD
I have a function that takes in an instance of a subclass of a class Message and returns an instance of a different subclass of class Message
Like so:
class Message:
pass
class SimpleMessage(Message):
pass
class ErrorMessage(Message):
pass
def f(message: SimpleMessage | ErrorMessage) -> SimpleMessage | ErrorMessage:
...
return different_message
How can I annotate f to refer to instances of all subclasses of the Message?
just use f(message: Message) -> Message:?
Or is there some reason I'm missing why that's not appropriate?
The method cannot actually get an instance of Message directly
That's not something that the type system can express. However, presumably Message is an abstract class; if you mark it appropriately, type checkers will understand that instances of Message cannot exist
I see, thanks!
Say I have a function like this:
def f(x: str | int):
if isinstance(x, str):
return int(x)
elif isinstance(x, int):
return str(x)
y = f(100)```
Shouldn't a type-checker be able to infer that the type of `y` is `str` in this case? It shows me `int | str`, but I know for a fact that for all inputs of type `int` I should be getting back an output of type `str`
No, type checkers generally do not infer that sort of complex information, only a single return type. You can express this type with overloads
!d typing.overload
@typing.overload```
Decorator for creating overloaded functions and methods.
The `@overload` decorator allows describing functions and methods that support multiple different combinations of argument types. A series of `@overload`-decorated definitions must be followed by exactly one non-`@overload`-decorated definition (for the same function/method).
`@overload`-decorated definitions are for the benefit of the type checker only, since they will be overwritten by the non-`@overload`-decorated definition. The non-`@overload`-decorated definition, meanwhile, will be used at runtime but should be ignored by a type checker. At runtime, calling an `@overload`-decorated function directly will raise [`NotImplementedError`](https://docs.python.org/3/library/exceptions.html#NotImplementedError).
I see! Thank you for the clarification ❤️
hi
Greetings guys,
Hope all are well. When should I use .pyi files? I am making a library, and want to see what's the best practice, and how to use and store these type of files.
Use it if
- you have a C extension module that obviously has no type hints
- the implementation is very magic and does not really play well with type annotations
- you just don't want inline type annotations (e.g. you maintain a library and you hate type hints, but your users really ask for them)
otherwise just use normal type annotations in your library and place a py.typed file in your source
I usually annotate my .py classes, methods, and functions thoroughly.
May I ask what py.typed is?
!pep 561
i would recommend by default adding py.typed and declaring all yout code as type good (just don't run any actions on import)
And for stuff that is exclusively type incompatible, make file copy with .pyi extension and cover it with those stubs.
TLDR: .pyi for exceptional cases when it is impossible to type some file (usually almost nothing is impossible to type)
.pyi will overshadow regular file for its typing
I see. I mean, I always annotate, so I shouldn't add the .pyi files?
So it's not a good practice?
no. u don't need .pyi files then
u need it only if u will have some tricky C code because it is completely untypable. (or some similar case)
Otherwise by default just have py.typed in package root and that's it
I see!
any C extension code would need .pyi
because obviously you can't write a python typehint in C
Ok, so just to clarify, all of my library code is in .py.
So I shouldn't use them (pyi I mean)?
in this case u need .pyi only if u have files with bad code that runs on file import (defend such stuff with if __name__=="__main__")
.pyi can overshadow such files and offer their types-stubs to remove such file executions
as long as u design code for .py only files well, u don't need .pyi
Can you kindly classify between need and should?
I would like to know what's good practice.
by default u should not be using .pyi at all for .py only files 😅
but some situation can force your hand to do that
I see!
for example if your code has import ddtrace datadog importing library.
it is nasty one, as it hacks all code, and dinamicaly patches all code on runtime during its library import
it can be nice to protect typing code executions from it with .pyi file overshadowing where u import such nasty lib
tldr: we need .pyi against bad code only that we can't control directly (in case of having .py only files)
you might also want to create your own .pyi file for someone else's library
if they have not typehinted it
oh yeah. that's a choice too.
OR... u could just inject py.typed file to their library after install 😅
as a less effort solution to get all library types working for you (as long as they don't have bad code running on import, it should be fine)
otherwise .pyi for third party libs can serve as more esoteric defined interfaces/protocols
Ok, that makes better sense for me now.
Thank you so much! I'm gonna remove the pyi files then.
👍
how do I type hint this
def flatten(x):
if isinstance(x, (list, tuple, dict)):
return [a for i in x for a in flatten(i)]
else:
return [x]
the issue is that when I run it on a list[bool], it (pyright) doesn't like it and doesn't know the function returns list[bool]
ok I fixed it I said -> list[Any] and pyright is satisfied
Do you need it to work on more than one level?
also, why do you want it to work on dicts' keys?
In general, you cannot express this function in Python's type system. You can only type a function like list[list[T]] -> list[T] or Iterable[Iterable[T]] -> list[T]
I just needed to remove pyright error message
I have a new one
Argument of type "list[((...) -> Unknown) | None]" cannot be assigned to parameter "loader" of type "list[(...) -> Unknown] | None" in function "_add"
Type "list[((...) -> Unknown) | None]" cannot be assigned to type "list[(...) -> Unknown] | None"
"list[((...) -> Unknown) | None]" is incompatible with "list[(...) -> Unknown]"
Type parameter "_T@list" is invariant, but "((...) -> Unknown) | None" is not the same as "(...) -> Unknown"
Consider switching from "list" to "Sequence" which is covariant
"list[((...) -> Unknown) | None]" is incompatible with "None"PylancereportArgumentType
I think I'll just add a little pyright: ignore
...
I don't think you will be able to explain this function to a type checker in any way
you'll have to add an explicit annotation like def flatten(x: object) -> list[Any]
yes, thats what I've done, I added list[Any]
where did you add it? can you show the code?
sure this is all I did
from typing import Any
def flatten(x) -> list[Any]:
if isinstance(x, (list, tuple, dict)):
return [a for i in x for a in flatten(i)]
else:
return [x]
oh if you mean my new error its a different one and I just chose to ignore it
its with more complex thing
actually I managed to fix it, it was an issue with my annotations
is there an opposite of Sequence
?
Like I have a recursive flatten function which stops when an element is no longer a Sequence
for example [[a, [b, c]]] will call with [a, [b,c]] then stop on a and call with [b, c] and stop on b and c
those a b c could be anything but a sequence
yeah you cannot express that in Python
You can make a bunch of overloads like ```py
@overload
def flatten(x: list[list[list[list[T]]]]) -> list[T]: ...
@overload
def flatten(x: list[list[list[T]]]) -> list[T]: ...
@overload
def flatten(x: list[list[T]]) -> list[T]: ...
@overload
def flatten(x: list[T]) -> list[T]: ...
Imagine if you have a function like this: ```py
X = TypeVar("X")
def do_something(items: list[list[X]]):
flattened = flatten(items)
``` what type does flattened have? You can't really know, because X could be a list of something itself, or it could be not a list.
and there's no way to express that's something isn't a sequence
ok thanks
alright one gripe I have which I spent too much time annotating / adding ignores, is it possible to make it so that when a:list | dict and a.append(1), it will be satisfied because list has append, instead of showing an error because dict doesn't?
You’d have to narrow the type to list somehow, otherwise the error would be valid. Maybe an isinstance check would suffice for your use case.
Is there some type checker or setting to do that globally, basically to relax the checks
To my knowledge, mypy and pyright both allow user-defined levels of strictness. I’d recommend checking the docs for whatever type checker you’re using to determine how to set that.
I don't think current type checkers support that
you can do something like assert isinstance(a, list) and the type checker will be convinced that it's a list afterwards
Alternatively — and this depends on your specific code, obviously — consider if you should be calling append in that place when the validity of that call is uncertain.
you might be able to express that with a recursive type alias in 3.12
how?
stuff like this still makes it kinda impossible
type RList[T] = list[RList[T]] | T is close for the arg type
Well, suppose you have a list[list[int]] and you want to match it with RList[T]. Should T be list[list[int]], list[int] or int?
pyright thinks that the correct answer is list[list[int]] 🙂
What if you remove the | T?
I wonder if you can do something with a union with Never
X | Never is the same as X
not inside a list subscript
The big problem is here ^^
You cannot express "X is not a list" in types
list[Never] means an empty list
yes
perhaps type RSeq[T] = Sequence[RSeq[T] | Never]
That's the same as type RSeq[T] = Sequence[RSeq[T]]

"a sequence where each element is either RSeq[T] or Never"
oh right 🤔 because the list doesn't have to be the same shape throughout, so it already has that behaviour
You can use a type alias like this with a concrete type that you know isn't a list, like type DeepIntList = int | list[DeepIntList]
honestly i don't really understand where you can get a list of unknown depth
uh...
lmao
I had an intuition that something would work 😎
somehow i got this to work
oh wait, I had a different bug
it showed Type of x is list[list[int]] and then (variable) x: int. Probably just a caching bug
from collections.abc import Sequence
type RSeq[T] = Sequence[RSeq[T]] | T
def flatten[T](xs: RSeq[T]) -> list[T]: ...
however
well, it should be RSeq[T] -> list[T]
ys
actually def flatten[T](xs: Sequence[RSeq[T]]) -> list[T]
I wonder why this works with Sequence but not list
mutability
which changes the variance
Sequences are immutable in typing
to the type
lists are sequences, but are not immutable
Sequence doesn't provide any mutating functions
although that doesn't actually express the function properly
because the way they implemented it, it eventually passes a non-sequence
Maybe do *xs: RSeq[T]
Yes, if it actually worked like that, flatten("") would loop forever
flatten(1)
is valid with their implementation
and returns [1]
because of the non-recursive case
while the signature might work for concrete types, it is going to give wrong answers if the item is generic
well, it kinda has to
well, I can call foo(ys) where ys: list[list[list[int]]]
in that case flattened should be int, but it's inferred as list[list[int]]
interesting

well at least it's good for concrete types 🤷♂️ 😄
If you want to be able to express this, there has been an open request for a while to add a Not typeform (or other forms of type negation)
I don't personally think it has much of a chance of being accepted currently, but we have some good knowledge about how it should compose with other type features if added here
||markdown syntax moment||
easy way to remember: on some systems you have to add a space after the link or it won't work in parentheses
Why doesn't this work?
OpenAiKwgs = TypedDict("OpenAiKwgs", AsyncOpenAI.__init__.__annotations__)
Error:
Expected dict or keyword parameter as second parameter Pylance(reportArgumentType)
you must use literal values for the TypedDict keys, otherwise type checkers won't understand it
is there any way I can just copy paste AsyncOpenAI types to my function parameter **openai_kwgs?
eg:
def __init__(self, role: str | None = None, **openai_kwgs: ??):
...
possibly, but I don't know how openai's types exist.
if they use **kwargs: Unpack[SomeTypedDict] you can do the same
not in general, but this is a feature request shared by many. you can't "extract" a typeddict from a function signature, for example.
i feel like it should be possible, but maybe it introduces some kind of soundness problem
python types has no concept of this in typescript. ```ts
// defined in stdlib
type ConstructorParameters<T> = T extends (new (...params: infer P) => any) ? P : never;
type OpanAiKwgs = ConstructorParameters<typeof AsyncOpenAI>
didn't know typescript had that. would be wonderful to have it in python.
It's not even a special type. It's just syntax.
all it needs is extends and infer syntax
The question is if that's a good thing, and I think python not having it is a good thing
Explicitly having your exposed parameters as a publicly typed typeddict for use with unpack signals whether or not a library is intended to actually work a specific way, and allows the same kind of re-use if you want to allow trivial reuse like that. Allowing any signature to just be copied can end up copying things that are only implementation details, and not part of what's intended to be supported
I usually use a custom decorator to copy function arguments
yeah but what's the alternative? forcing someone to dig around in a 5000-line .pyi file in order to write type-checkable code?
i don't think it's a good idea to design around the 1% of interfaces that expose implementation details as function parameters that aren't meant to be touched, and in doing so obstruct a fairly common pattern in python from being type-checkable
sadly no way to extract a paramspec from a concrete function
yup, i've been wanting that since before paramspec existed
i think we first need a Callable that supports kwargs
example: i am writing a function that wraps matplotlib.axes.Axes.scatter and passes along kwargs. and matplotlib itself passes along those kwargs through 3 or 4 layers of other stuff. all of that is user-accesible! you really want me to copy-paste that huge signature? and you really want to force the matplotlib devs to maintain that?
not including Protocol
@rare scarab why would that be a blocker here?
I'm now remembering type checkers already know how to handle functions properly.
No. I think you should look at how Unpack works with TypedDicts. Typing your internal kwargs to be passed around with these reduces how complex your own type signatures are to maintain (the same typed dict gets reused) and can be exported for users
this, in conjunction with the ongoing intersection work if accepted would be much more composable
no, i get that. what i'm saying is that "internal" kwargs are very rare in my experience
"internal" kwargs would be prefixed with _
yes, and they still get copied by anything that would copy types for reuse, so they become part of the API that is now potentially breaking if that kind of construct is added
i've literally never seen that in a library
but the point of **kwargs: Unpack[...] is to be resilient to runtime changes.
besides, you should be ashamed if you use an internal argument and it breaks
i guess, sure. i just feel like this is forcing an even greater divide between "typed python" and "untyped python", instead of making it easy to do the thing that people want to do, using existing code
so okay, it works well for matplotlib, which already uses **kwargs extensively. what about wrapping pd.DataFrame? do i need to copy all those overloads? etc.
I'm all for making it easy to do the right thing, I don't think blindly copying signatures is the right thing except in cases where the use is blind too, and have been actively involved in helping improve on ways to write the right thing. If the use is blind, you can do it with paramspec
I haven't followed this entire discussion closely, but for what it's worth I'd be open to extending the system with some of what you want (e.g., an operator to convert between a TypedDict and a function's kwargs)
It wouldn't be much of an issue IMO if @override had the side effect of implicitly applying the parent's type hints.
i think that might be all i'd want. although the overloads would still be a problem
Time to write a PEP 😛
If your function wraps (blindly) another library, you can already do it with paramspec
i'd probably want to study how typescript does it before i can claim to have any good ideas
Though there's the practical problem that you need some way to deal with positional arguments
Python signatures are complicated
via a decorator, yes.
you dont need to use a decorator
you can't use a paramspec for a concrete function though right? e.g. i can't write where2(s: pd.Series, **kwargs: pd.Series.where.kwargs) -> pd.Series: return s.where(**kwargs)
you can do it via generics
can you share an example?
ParamSpec is generics
and for generics to have any meaning in a function, it has to appear twice
ehhh... not exactly in python terms, but 1 moment
I don't normally wrap libraries in a way where I'd do it quite this way, and I think we can find better ways to spell this kind of behavior, but I'm concerned about the library side of the equation of blind copying with non-blind use where providing types now means that private things are part of an API contract, and adding private things could potentially cause conflicts.
from collections.abc import Callable
from typing import Generic, ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
class _WrappedBehavior(Generic[P, R]):
def __init__(self, wrapped_function: Callable[P, R]):
self._wrapped = wrapped_function
def some_deferred_behavior(self, some_other_per_method_arg: int, *args: P.args, **kwargs: P.kwargs) -> R:
# do something with our other args
return self._wrapped(*args, **kwargs)
def example_func(x: int, y: int) -> int:
...
WrappedExampleFunc = _WrappedBehavior(example_func)
This clearly isn't easy to compose (could get easier than this with HKT support)
the problem comes in if you allow copying and non-blind usage for changing what could be considered breaking
(you can do similar with protocols and partials, and more as well, you don't have to use the generic pattern directly)
@undone saffron i assume you'd also have to implement __call__ on _WrappedBehavior?
or is that what some_deferred_behavior represents?
up to you or not, it depends on what the purpose of deferring to another library is
i see
I agree that there arent easy and ergonomic ways to spell the behavior you want, and I do hope we can find a way to improve that
well that's at least a serviceable workaround. how does that work if example_func has overloads?
That's a decorator without the syntax sugar
yes and no, the result is more flexible because you can attach other generic variables to this for more ocmplex use
well you can't easily "decorate" a function defined in another module
(like something from a 3rd party library, which is the case i had in mind)
it works fine with overloads with pyright, im not sure on mypy, but this is within specified behavior, so if it doesn't, that would be a mypy issue
makes sense. well thanks for the example at least
I guess with copying the signature, we could just say "okay, allow copying funtion signatures as is, but it's the copier's responsibility to know they will never ever be broken by this" idk how I feel about that.
otherwise things that most people would not consider breaking suddenly become breaking, like even just adding an optional kwarg that's meant to be public, but coinceidentally conflicts with a kwarg of a wrapping library the main library had no knowledge of
that is true
personally i still think it would be very useful for smaller and/or ad-hoc programs
you'd also have the same problem with @oblique urchin 's proposal of kwargs <-> typeddict, which i think is really useful for doing something like building up a dict of kwargs incrementally
Am I stupid or this isn't correct??
what the func
its the prompt() method from prompt toolkit
I'm very confused if they made a mistake or??
looks pretty intentional, but not an API design I would have made.
Since I'm trying to typehint list[tuple[str,str]] but mypy screams at me since its not list[tuple[str,str] but that's not valid is it??
no I meant "what the f*ck", but with "function" as a joke
ohh 😂 😂
let's see the whole typehint on that
is that the actual typehint, or is that a generated inlay?
is it because of union that this is valid
cause that looks fine as a generated inlay, but not a valid typehint
not really sure what an inlay is
look at the ] at the end
yeah that's what I just saw
can you paste that typehint as text?
yup
message: str | MagicFormattedText | list[tuple[str, str] | tuple[str, str, (MouseEvent) -> None]] | () -> Any | None = None,
*,
sry terrible formatting
yeah, so (MouseEvent) -> None is rejected syntax
for Callable[[MouseEvent], None] which your other screenshots actually have
this one in particular
yeah
ie. this isn't the actual typehint
list[
Union[
tuple[str, str],
tuple[str, str, Callable[[MouseEvent], None]],
]
]
list is invariant, so list[A] is incompatible with list[A | B]
but in this case it should be, it can be fixed by replacing list with Sequence in their typehint
but in this case giving it list[tuple[str, str]] is fine, and it can be allowed by replacing list with Sequence in their typehint
shouldn't?
i doubt prompt_toolkit will mutate your list
so giving it list[tuple[str, str]] is perfectly fine
actually it's ambiguous on what "it" refers to
so both "should" and "shouldn't" could be right...
changing the variance may not be the correct fix
lifting the union probably is may or may not be either
I can't parse that last sentence
It may not be correct to change it from list to Sequence, and allowing the equivalent of list[A] | list[B] | list[A | B] rather than list[A | B] might be more correct
idk how prompt toolkit is using it
well, actually they are doing isinstance checks, so replacing list with Sequence is not correct without changing the code: ```py
if value is None:
result = []
elif isinstance(value, str):
result = [("", value)]
elif isinstance(value, list):
result = value # StyleAndTextTuples
elif hasattr(value, "__pt_formatted_text__"):
result = cast("MagicFormattedText", value).__pt_formatted_text__()
elif callable(value):
return to_formatted_text(value(), style=style)
elif auto_convert:
result = [("", f"{value}")]
else:
raise ValueError(
"No formatted text. Expecting a unicode object, "
f"HTML, ANSI or a FormattedText instance. Got {value!r}"
)
smh
Just a small question, since im really unsure how I should really be using typehints
Lets say you have a var that is obviously a string as suhc :
my_var = "hello"
People don't do
my_var: str = "hello"
right?
generally not, no
Or something like this
parts = command.split(" ")
parts2: list[str] = command.split(" ")
Because I feel like it becomes "bloated" if I do that
in Python you usually only annotate variables if the type is otherwise ambiguous, e.g. if it's an empty list and you need to tell the type checker what types go into the list
nope, no need to do that either. Your type checker will know that .split() returns a list
hmmm gotcha
What about functions? I feel like that's useful (even if obvious)
(func args and return type)
Always annotate function arguments. For return types, one popular type checker (pyright) always infers return types, but other type checkers generally expect you to annotate return types too
yeahh I'm using mypy
I feel it's useful to annotate return types because it makes your code more self-documenting, but not everyone agrees
I think it's also useful to annotate them as it also ensures no accidental API changes
the return type right?
yeah. If the rest of your code happens to work with a more lax inferred return type, that isn't always a good thing. Being explicit about it enforces consistency with the return type annotation, which means code you can't see (like users of library code) have something consistent.
I 100% agree
I leave some small things to inference, but never anything publically exported
great example is, maybe doesn't matter that much, but I had return statements returning 0 and None within same method
and mypy screamed at me lolol
i find it very useful that pyright is smart enough to infer a lot of stuff
it is very convenient when writing dirty PoC prototyping, mypy usually either screams at you or just infers unannotated args as Any
for sure. when prototyping, the inference is great and can prevent you from being more restrictive than you needed to be. you can often later just take what it infered and then add it as an annotation later.
very much a case of different levels of explicitness being more appropriate at different stages of development
how do I type hint that an argument is a class? not an instance of the class
use Type[A] instead of A.
I tried that, but whence do I import Type then?
class typing.Type(Generic[CT_co])```
Deprecated alias to [`type`](https://docs.python.org/3/library/functions.html#type).
See [The type of class objects](https://docs.python.org/3/library/typing.html#type-of-class-objects) for details on using [`type`](https://docs.python.org/3/library/functions.html#type) or `typing.Type` in type annotations.
New in version 3.5.2.
Deprecated since version 3.9: [`builtins.type`](https://docs.python.org/3/library/functions.html#type) now supports subscripting (`[]`). See [**PEP 585**](https://peps.python.org/pep-0585/) and [Generic Alias Type](https://docs.python.org/3/library/stdtypes.html#types-genericalias).
huh, I didn't realise type replaced it, actually. weird.
why do you want that though?
I'm trying to get rid of some code duplication in django. I have two very similar methods that I'm compressing into one
I'm not convinced this is a good idea but I have nothing else on my plate so might as well explore it
the classes are models and forms
the model classes are used as parameters, and the forms are not but they are instantiated differently depending on get/post
If I dont have access to pydantic/mypy/x validation lib, what are my options?
isinstance and other checks during runtime?
options for what?
Did I just find a pyright bug? ```py
def foo(x: type, y: Any):
if dataclasses.is_dataclass(x):
# Second argument to "isinstance" must be a class or tuple of classes
# Protocol class must be @runtime_checkable to be used with instance and class checks
if isinstance(y, x):
pass
I may need to update my python extension...
It's because is_dataclass() returns true for both instances and classes (as @soft matrix said)
hm though it seems like you should hit this overload: https://github.com/python/typeshed/blob/41245698e07a9959a4643122ddc814dff1ad3482/stdlib/dataclasses.pyi#L219
stdlib/dataclasses.pyi line 219
def is_dataclass(obj: type) -> TypeGuard[type[DataclassInstance]]: ...```
Runtime validation i guess
If you want to validate some JSON at runtime, then yes, you'll need to do a bunch of isinstance checks
This is mostly about function arguments really
Still only have isinstance checks, right?
Why do you want to check the argument types at runtime?
If this is your own code, you should rather use a static type checker like mypy or pyright. It doesn't need anything at runtime, it just looks at your code and points out errors/diagnostics
If this is library code and you want to check inputs from other people, then yes, you need to do a bunch of isinstance checks
you may also be able to use match
@viral glacier doesn't seem to be related to type annotations. Maybe try #data-science-and-ml
my bad. this was a mistake
I cannot
Its py2 💀
use an old version of mypy 😛
Also we dont have ownership of the images we use or deployment
using mypy wouldn't require any runtime changes
but it's been several years since mypy dropped py2 support. It's probably still possible to install the old version but you're very much on your own
mypy i could use but what i need is something like pydantic really
Mypy would be fine if everyone used it but i cant force others to sadly
Check it in CI
import os
assert not os.system('mypy --strict ' + __file__)
paste this in every file
this makes me want to name a file "|| rm -rf .#.py"
I dont have access relating to anything beyond what happens when I push
Maybe getting out of this hellhole is the play
I'll stick to isinstance spam for now
yeah it sounds like whatever you are working on needs better CI/CD tooling
as well as not being on Python 2
I don't think this is yet possible but I will ask to make sure anyway. I'm looking to dynamically create a list of filenames from a path that can be used in a Literal type. Is there a way of doing that?
That'd require your type checker to execute nontrivial code at typechecking time. Perhaps you can make a mypy plugin like that, but I don't think there's any way in the type system itself.
pyanalyze (https://github.com/quora/pyanalyze, I wrote it) supports this because it imports the modules it typechecks
I just upgraded my mypy version
running mypy --ignore-missing-imports as before
but now I'm getting tons of import-untyped errors
when I google how to suppress it, main thing I see people suggest is --ignore-missing-imports
Like, I guess this is because it knows there are stubs. Which I appreciate it, but I want to first get this passing, and then be able to add stubs later
we now recommend --disable-error-code=import-untyped
instead of --ignore-missing-imports
thanks, i'll give that a shot
fwiw I was looking here
didn't see that mentioned
man python changing its mind on annotating optional = None stuff 🤕
What do you mean?
It used to be okay to do foo: int = None
For a function argument
foo would be treated as Optional[int]
This was like an officiK thing
Then they made it officially not a thing 🙂
Ah, right. I feel like I have a vague memory of that behaviour undergoing a deprecation period.
Might be a bit annoying to add Optional to everything, but you could probably get away with using a regex find and replace that's eyeballed by people to determine if it's valid, right?
A regex isn't a bad idea though I'm pretty terrible at them
Maybe I can figure it out though
Thanks for the suggestion
(?!.*Optional)[^:]+:.*\s*=\s*None
You run this through sed -i?
nope, that's PCRE (close to what python uses)
grep -P should use that
I normally use the built-in search in VSCode
How would you apply this to fix the whole codebase at once thrn? Write a python script?
I see
Thanks I'll check out Ruff
I'd do it with python since why not, re.sub(r"(?m)^([ ]*\w+: )(\w+)( = None)$", r"\g<1>Optional[\g<2>]\g<3>", file)
another really weird error, I'm being asked to type annotate a variable that's just
client = pymongo.MongoClient(...)
db = client["..."] # the only usage of client
It's literally just calling a class init here so I'm confused why it would want an annotation
probably it's annotated as generic and it can't infer the generic parameter
ah interesting
Is it normal that mypy isn't screaming at me when I don't use typehinting within func declarations? I read somewhere something about this, but not really sure honestly (I just want to make sure I'm using mypy correctly) (using it via pycharm plugin)
yes, by default it doesn't complain about unannotated functions
👍 would using something like --strict flag be recommended ?
yes
oki thanks
another question, I find this quite ugly, I'm unsure if theres a better way to do this? Or is it normal
self.results: list[dict[str, list[dict[str, str]] | str]] = []
you could create type aliases for parts of that type to make it easier to understand
hmm didn't know that's a thing, great I'll research that 🙂 Thanks!
that's PyCharm behaviour. It's not spec
Pretty sure it used to be okay
A past version of this PEP allowed type checkers to assume an optional type when the default value is None, as in this code:
Python Enhancement Proposals (PEPs)
It was the spec until it wasn't 😛
…ly (#689)
Following discussion in python/typing#275, there is a consensus that it is better to require optional types to be made explicit. This PR changes the wording of PEP 484 to encourage
typ...
You proposed the change it so that it was no longer allowed to treat it as None?
Sorry, as Optional
https://github.com/python/typing/issues/275 Guido proposed it, I just made the change to PEP 484
Gotcha
I mean I'm not against the core idea here
But the benefits seem small and backwards compatibility break is pretty annoying
If mypy didn't have a flag I'd be more irritated but as it is I guess it's nbd
this was six years ago, for what it's worth. There was a lot less typed Python code at the time
We probably got the memo late
I wrote an autofix when I changed the default mypy behaviour: https://github.com/hauntsaninja/no_implicit_optional
Hi folks, I'm currently struggling with some type hints, Unions, and get_args. My example basically comes down to this: https://mypy-play.net/?mypy=latest&python=3.12&gist=d565dd4bb160a457c5eaf2097256f06d . In this small test case mypy tells me everything is fine (just as expected), however, in my project it gets stuck at the assert_never stating, that the types in the Union are not covered. The only difference that I could spot so far is, that my Union definition is in a separate module. Any pointers on what's wrong or what I can do to debug this issue?
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
you are checking the type information of value in your while loop's condition, but you never change value
so I think maybe you didn't reduce the problem correctly
and suddenly it stops working
The mypy Playground is a web service that receives a Python program with type hints, runs mypy inside a sandbox, then returns the output.
thanks so far. 🙂 so any ideas how I can solve this issue? isn't this the correct use uf get_args?
pylance infers it correctly it seems, or rather differently maybe...
hrmm
pylance says Argument of type "A" cannot be assigned to parameter "__arg" of type "Never" in function "assert_never"
and mypy says Argument 1 to "assert_never" has incompatible type "Any | D"; expected "NoReturn"
I do not know why they give different answers for the error
what the heck 🤔
ok very interesting, if I remove any of the code that was below that in the loop, then the type becomes B | C
(btw the issue is not tuple vs. union in the isinstace, I checked that it did not make a difference)
I think pylance is smoking something
oh I see
pylance being greedy with inferring a narrower type. However this doesn't say much about MyPy's behaviour
Here is the MRE
from typing import reveal_type
reveal_type(get_args(int | str))
the revealed type is builtins.tuple[Any, ...]
and I think I know why
I heard something about this being removed from MyPy (because it's not spec)
Union[*Ts]
so get_args has no mechanism
from typing import reveal_type, get_args
reveal_type(get_args)
Revealed type is "def (tp: Any) -> builtins.tuple[Any, ...]"
sorry to ramble 🤭
you can do isinstance(inner, D | Subset)
Wrong reply?
Visiably its much more pleasing, but this still causes the same issue :/
oh nvm I understood now
I can't test any more because I've left my computer for the night
then I ignore the type checks for this for now, thanks for your input folks 😉
I'll look again tomorrow unless someone else solves it
pyright handles this fine
pyright, without using get_args
typing.get_args likely needs typing.TypeForm to be typed correctly.
I believe mypy previously had it as Callable[[Union[*Ts]], tuple[*Ts]]
Which isn't spec
should be (TypeForm[*Ts]) -> type[tuple[*Ts]],
Union[*Ts] (even though it would be useful for other things) is incorrect for get_args, for the same reasons that were went over in this thread for Annotated https://discuss.python.org/t/is-annotated-compatible-with-type-t/43898
Why was Union[*Ts] not approved btw?
So I'm not 100% sure whether I follow the discussion on a side note though: the doc explicitly specifies get_args for Union, doesn't it? https://docs.python.org/3/library/typing.html#typing.get_args
(or, more precisely, the examples)
Yes, but you need a way to annotate that the value is Union[*Ts]. Union as an annotation has a meaning, and that doesn't map to itself as a value.
That's not enough. ```>>> get_args(Literal[1, 2, 3])
(1, 2, 3)
I don't think there's much hope for typing `get_args()` correctly without special-casing it in a typechecker
Thoughts on mypy vs pyright: what do folks view as the pros and cons of each at the moment?
Could be enough if the first type argument to TypeForm was the specific TypeForm... and we get Map too:
@overload
def get_args(typ: TypeForm[Literal, *Ts]) -> tuple[*Ts]:
...
@overload
def get_args(typ: TypeForm[Any, *Ts]) -> tuple[Map[type, *Ts]]:
...
type[tuple[...]] isn't quite right either 
Yes, but there's a question of whether or not you should. pyright (the type checker vscode's language server leverages) has configuration settings that will prevent implicit any, meaning you can stil use things like json.load, but into a known structure
I like pyright, but realistically you should be able to just use any type checker that is working on being conformant to the specification. mypy, pyright, pyre, and pytype all are, but have varying levels of what is and isn't supported currently.
https://careers.wolt.com/en/blog/tech/professional-grade-mypy-configuration
strict mypy can do
warn_return_any = True in its config
it should isolate the scope of any escapings no further than single function
disallow_any_expr or disallow_any_explicit and etc. some anti any to try in addition
https://mypy.readthedocs.io/en/stable/config_file.html
may be useful too
personal opinion on mypy vs pyright:
mypy is much more configurable, type checks collections better, plugins if you use those
pyright is much quicker to implement new type system features, easier vscode integration, better literal type handling
makes sense
I'm not a big fan of literals generally speaking; I do recall now some people posting examples of stuff with pyright and literals that I would just avoid completely
i also remember pyright getting strict on optional earlier (IIRC) and supporting recursive unions earlier; though it seems like mypy has all the stuff I can think of that I care about fo rnow.
the vscode integration is nice
mypy has had strict_optional=true as default for longer than pyright has been around. maybe you're thinking of no_implicit_optional, which yeah, i agree that default was bad legacy which is why i changed it 🙂
definitely recommend starting your mypy config off with strict=true and then adding options that reduce strictness, rather than other way round...
How can I make the typehints correct?
if you use generic type
from abc import ABC, abstractmethod
from pydantic import BaseModel
class AbstractUseCase(ABC):
@abstractmethod
def execute(self, params: BaseModel) -> BaseModel:
pass
class Param(BaseModel):
user_id: str
class Result(BaseModel):
user_id: str
class UseCase(AbstractUseCase):
def execute(self, params: Param) -> Result:
...
sorry, had to step away, but there's reportMissingTypeArgument which prevents many implicit Any while still allowing it to properly exist for things that the type system can't express. (https://microsoft.github.io/pyright/#/configuration?id=main-configuration-options reference)
Right. What I was getting at is that forbidding every single Any probably isn't productive, and it's better to be sure you don't have Accidental uses of Any. json.load is typed as returning Any because it can't know the type. This isn't a mistake, and if you know the actual type with certainty, you're allowed to use this:
e.g.
some_mapping: dict[str, int] = json.load(some_fp)
But if you don't actually know the type, you'd have no way to statically analyze it in the first place.
Any is a useful part of the type system, and it's usually better to look at it as defining the boundary between what you know about incoming data and not than to try and remove it entirely.
If you have an Expected type that you don't trust, you can instead use a library like msgspec to efficiently parse into a typed struct, but this falls under not removing Any entirely, and it being part of the boundary. This just moves that boundary into a library that handles that for you.
Not neccasily. Do you actually know the keys? Some json payloads might be dynanic, and return a mapping between things like strings representing job ids, and some value. Not really important to the point I was getting at there though.
Avoiding Any for the sake of avoiding Any may make your code worse to interact with. If you instead use the places you have Any to handle and signify the boundary between things that come in untyped, and then handle validation as needed, you end up with good results.
Another more obvious example of this comes with sql
row: tuple[str, int] = cursor.execute(
"SELECT memo, amount FROM deposits ORDER BY post_date DESC LIMIT 1"
).fetchone()
If this returns a tuple[Any, ...], the user's annotations just work and this can be assumed to be fine, because presumably your database has strict typing,
Well, you still want to provide a type where you have it, but you don't need to avoid Any to do that. Both of the examples above show using something that gives you something typed As Any, and providing a more detailed type where you know it.
In a case where you have potentially hostile input (Such as user facing web apps) you'd want to actually validate it
so there's no one size fits all option, but you can view Any as a way to see what hasn't been validated yet, and pick the best option for your use to go from that at the boundary of your application, to typed for use in the rest of it
(There are also cases where the type system may not have a way to express the type of what you have, but that doesn't apply to the example you have here)
For JSON specifically you can at least write a "read-only" JSON data type:
JSON_ro: TypeAlias = Mapping[str, "JSON_ro"] | Sequence["JSON_ro"] | str | int | float | bool | None
json.loads of course is annotated to return Any but you can always assign an Any to a variable of any type.
from here, you can take the "parse, don't validate" approach:
from collections.abc import Mapping, Sequence
from enum import StrEnum
from numbers import Real
from typing import TypeAlias
import attrs
JSON_ro: TypeAlias = Mapping[str, "JSON_ro"] | Sequence["JSON_ro"] | str | int | float | bool | None
class Color(StrEnum):
red = "red"
blue = "blue"
@attrs.define
class Widget:
size: float
color: Color
def parse_widget(txt: str) -> Widget:
data: JSON_ro = json.loads(txt)
if not isisntance(data, Mapping):
raise TypeError("Widget must be a JSON object.")
try:
w_size = data["size"]
except KeyError:
raise KeyError("'size' is missing from input data.")
if not isinstance(w_size, Real):
raise ValueError("'size' is not a real number.")
try:
w_color_str = data["color"]
except KeyError:
raise KeyError("'color' is missing from input data.")
w_color = Color(w_color_str) # Raises ValueError if invalid
return Widget(
size=w_size,
color=w_color,
)
of course that's a lot of boilerplate. this is what pydantic, marshmallow, mashumaro, etc. are good for
this is often seen in the OO world with patterns such as alternate constructors. in python you could write this if you like the OO style:
from typing import Self
@attrs.define
class Widget:
size: float
color: Color
@classmethod
def parse_widget(cls: type[Self], txt: str) -> Self:
... # all the parsing stuff
return cls(
size=w_size,
color=w_color,
)
usually better to use a dataclass than either of those, tbh
unless you have some already existent code that you know works, that uses dict, that you just want to add type annotations to
And yes, you should avoid Any, even if you have "untyped" json, you can still type it better than Any, as salt rock's example shows
but most json isn't really untyped; in most cases you ought to be able to simply define a dataclass/attrs class, parse directly into that, and use it
and definitely look into a framework as salt rock suggests
i think worrying too much about avoiding "Any" in this case is quibbling over details. if you read from a pickle file for example, the return type really is Any. your job is not to avoid it entirely, but to contain it. the Any should not "escape" from the immediate location where the data is loaded and parsed.
Sure, for reading a pickle file
we were talking about json though
in your example above, I agree that JSON_ro adds little, but then that whole parse_widget function is basically, as you said, boilerplate that you should just handle via a deserialization framework
well the point is that JSON_ro is barely any better than Any
and you'd write basically the same code in the rest of the function if you wrote Any instead of JSON_ro
the key is to contain the wide-open unknown type, and prevent it from leaking out into the rest of your application
honestly i'd suggest writing out the manual parsing code by hand, or at least sketching an outline, before starting up with pydantic. it's very easy to get caught up in all the pydantic details (especially in v2, there's a lot of complexity there) and lose sight of what you're actually trying to accomplish
it really depends what you're doing. If the scope is small, then obvious it doesn' tmake a big difference, but that's true about basically everything
and you'd write basically the same code in the rest of the function if you wrote Any instead of JSON_ro
and this, frankly, is missing the point of static typing
the correct code looks basically the same, the question is how it helps you catch incorrect code
with the "proper" json type, if you don't validate that the value you extract is of the correct type to go into Widget, mypy will error out, which is kind of the whole point of using mypy to begin with
if you use Any or dict[str, Any] then it will not complain, and you could return a Widget that has all its fields populated, but with some of them being of incorrect type.
If you're fine with a parse_widget than can return an incorrectly typed Widget, then yes, the value is much lower
maybe have json.loads return object and not Any
in my particular case if you wrote data: Any = json.loads(txt) instead of data: JSON_ro = json.loads(txt) you'd leave the rest of the function unchanged because it's immediately followed by the isinstance(data, Mapping) check
I mean, again, this is missing the point of static typing 🤷♂️
unless i'm missing some detail here, i'm pretty confident mypy will perform the same narrowing to Mapping[Any, Any]
so in this particular example yes, JSON_ro is better, but functionally Any is not substantially more problematic than JSON_ro, because JSON_ro is wide open and provides almost no practical type safety
If you forgot this
if not isinstance(w_size, Real):
raise ValueError("'size' is not a real number.")
then mypy will not be able to help you, if you type the json as Any. If you type it with a recursive union, mypy will raise this as an error.
mypy catching errors for you is good
Typing this correctly isn't really more work than typing it as Any... so in the absence of any downside, and with a minor upside, you should type it correctly 🤷♂️
I'm not saying you need to pathologically avoid Any
oh i see what you're saying. sure, yeah, JSON_ro helps a little because you know the keys and values can only be strings or numbers, not functions or compiled regexes etc.
yes, i agree with that
All I really mean is that usually there's a better type than Any, even if it's only a little better. not always though, I happily concede that
yeah, agreed there as well
its' just cool that we can actually do this now :-). I'm upgrading from an old mypy, and generally an old python ecosystem so it's nice to gain access to stuff like this.
also btw, happened to reread this - it's a much bigger difference than that
what you'r edescribing is the differnece between JSON_ro and say, dict[str, object]
Any is not object though, Any basically disables type checking. So it's not really about narrowing from anything to strings/numbers etc
It's about "don't disable type checking"
that's fair. i was thinking of Any as Union[... literally every type ...] in this situation
Yeah, that's not what Any is, for better or worse. I get why it's named ANy and object already existed, but it is pretty confusing
especially when multiple other languages have Any that is the same or closer to python's object
Maybe a better name for Any would have been Untyped, because it emphasizes that you're disabling static type checking, rather than making people think "it can be any type" (which is really object)
Treat object as a union of all types, and Any as an intersection of all types
what does "intersection of types" mean though
i guess the closest thing to that is a bottom type
Any isn't really that either, if it was an intersection of all types, you could assign to any type from an Any, but you won't be able to assign to an Any from anything else
(so I guess, what you're describing is pretty much exactly a bottom type)
true, object is really the "union of everything" type
you could write data: object = json.loads(...) instead of : Any
Any is basically simultaneously a top type and a bottom type, which obviously doesn't really make a lot of sense in terms of type systems
or equivalent when loading from pickle, xml, etc.
hence why it amounts to "disable type checking"
yeah, returning object would be "better" but you could argue that the type system in that case just isn't informative, so it would just make you add boilerplate like typing.cast
at least, I guess some python folk would argue that way
yeah, it would be a disruptive change
but you could/should specifically annotate with : object most of the time you mean : Any as long as you're willing to cast, or assert/if with isinstance()
For sure, from a backwards compatibility perspective, from a perspective of, even now, people often write untyped python and migrate it, it makes less sense
Yes, certainly
i am very sympathetic to the goal of supporting untyped python and avoiding making it harder to write
i personally have basically no interest in untyped python, outside of the REPL and interactive data analysis.
For me I kind of see that as the only reason I would ever want dynamic typing in general; I can't think any other work I've done with programming where being dynamically typed is something I prefer.
but I do recognize there are real world considerations here
(to be clear, it wasn't always this way, in C++03 with verbose typing, I felt differently... but with more modern type systems I've never really felt like the type system is enough of a headache to justify this)
Same for me, I use typing for pretty much any code I don't expect to immediately throw away
Really my main wish is that typing in python felt less tacked on, and that python was more expression oriented, which would gie more opportunities for inference
but again, that's nobody's fault, I recognize engineering realities
@terse sky have you tried the Nim language?
I've done a fair amount of hobby rust lately and I'm really start to get used to its style of type inference where it can combine restrictions from anywhere it's used
it's a bit hard to go back
when you use that though it's really rare you need any local type annotations
If you want hours of reading, there is this issue Handling of Any within an Intersection followed by several more issues of discussion, and it is a rabbit hole I keep getting sucked into.
(beyond ones that actually specify intent)
fwiw mypy is particularly bad at type inference
just about any modern language does it better, and some not-modern ones as well
idk, Go's type inference is probably more basic than mypy 🙂
Kotlin is also relatively simple, but still better than mypy
I haven't. People do bring it up periodically, but it's still really below the threshold for momentum where I would be interested in a language.
it has a bunch of weird (IMHO) choices you hear about immediately, which aren't like intrinsically a big deal per se but kind of send the message of it not being a serious project
sounds harsh but that's my 0.02
obviously time is very limited to randomly learn new programming languages
agreed on all of the above. if you do want to try it for writing a small script or toy program, you might find it interesting. i agree that there are some "weird" elements which might permanently prevent its adoption in industry beyond a certain point, but it's also a tremendously practical language and even after just writing a few programs i find myself wishing i could use it all the time (for non-data-science work at least)
It's dynamically or statically typed?
(or gradually typed/optionally typed)
statically
but its type inference is excellent and you often don't need to write types at all
what's the selling point over using something like kotlin or rust or what not
i think in terms of language design it's similar to kotlin: statically-typed but high level
however it compiles to C, C++, or JS which is kind of cool, and it's meant to have particularly good C interop
also the C/C++ compiler target provides several GC options, which i believe JVM and D also have
"no GC" i think is even an option
I'm having a bit of a weird situation. I want to represent a async function like this Callable[[int, str], float] but is Callable really the right type for that, is there not a built in type for asynchronus functions?
you'd have a Callable that returns Awaitable[float]]
How would you write typings for a opaque object?
For example selector.register takes in a opaque object and that can be whatever I want. I'd like it to be some concrete type, I was thinking doing something like
class SocketReady:
def __call__(self, *args, **kwargs):
self.cb(*args, **kwargs)
def __init__(self, callback: Callable[[FileIO, int, Client], None]):
self.cb = callback
and register this, but its kinda verbose :p (and feels ugly), I was wondering if there was a shortcut.
or is this reasonable?
Generic?
class SocketReady[**P]:
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> None:
self.cb(*args, **kwargs)
def __init__(self, callback: Callable[P, None]) -> None:
self.cb = callback
Object with init and call only makes me think it should be a function / a functools.partial function.
for example if I have the following where self.selector is a selector
for key, events in self.selector:
key.data(ctx, events)
Now, how might I go about statically enforcing at every self.selector.register site to adhere to this signature if they decide to pass a data, anything else should be a type error
making it generic makes it that any signature is allowed, I want to allow only a particular signature.
is there any way to have a any type but disclude a type. Say for example i want all types except for string
im using python 3.12
and this has changes which i don't really know how they look
something like
any | (not 'type') (ofc ik this doesn't work)
is there any wya to have a any type but disclude a type
nope
oh
why do you want this btw?
i think there's no plans to actually add it; it's a big change for the type system.
for the type hinting
@dataclass
class SinglyNode():
data: any
point: 'SinglyNode' = None
i don't want data to be a SinglyNode
?
why does it work then?
Because typehints only need a valid expression
and a defined variable is a valid expression
ok
mostly bc the class is not intended for this case
So from typing import Any
although that class should probably be generic
mhm
This likely isn’t what you’re looking for, but another way of writing that code snippet would be with a function closure, as well as restricting the args and kwargs to a more specific signature. That being said, you could also restrict the call signature on your class there.
from typing import TypeAlias
MySig: TypeAlias = Callable[[FileIO, int, Client], None]
def socket_ready(callback: MySig) -> MySig:
def call_callback(fileobj: FileIO, events: int, data: Client): -> None:
return callback(fileobj, events, data)
return call_callback
I'm experimenting with dynamically generating .pyi files for things like URL paths, .etc. Are there any existing project that do this?
Specifically .pyi? What'd that contain, for url paths?
I'm making a templating library similar to Jinja, I wanted to provide auto complete for template name files since they're easy to mess up.
I tried dynamically generating the python files before, but that was error prone. So I'm experimenting with generating type stubs instead
Bunch of typing overloads to specify the template parameters based on the template name.
as well as restricting the args and kwargs to a more specific signature
Looks like doing this raises a type error if I pass a kwarg?
from typing import Callable
from socket import socket
class Client:
pass
MySig = Callable[[socket, int, Client], None]
class SocketReady:
def __call__(self, *args, **kwargs) -> None:
self.cb(*args, **kwargs)
def __init__(self, cb: Callable[[socket, int, Client], None]) -> None:
self.cb = cb
def socket_ready(callback: MySig) -> MySig:
def call_callback(fileobj: socket, events: int, data: Client) -> None:
return callback(fileobj, events, data)
return call_callback
def ready_func(fileobj: socket, events: int, data: Client) -> None:
print(fileobj, events, data)
with socket() as sck:
# This is fine
SocketReady(ready_func)(sck, 0, data=Client())
# but this raises an error on pyright and mypy
socket_ready(ready_func)(sck, 0, data=Client())
Huh. I guess a callable protocol would need to replace that Callable annotation, then.
from typing import Protocol
def MySigProto(Protocol):
def __call__(fileobj: socket, events: int, data: Client) -> None:
...
def socket_ready(callback: MySigProto) -> MySigProto:
...
Interesting, that does work
actually that does make sense, the callable only types the positional arguments and doesn't give names to those arguments
hi
im trying to write a validator for the following yaml:
shared_documents:
Foobar:
fields:
iuiyi:
type: date
fgfdgf:
type: keyword
gfgdg:
type: keyword
foobar:
type: keyword
fields:
partial:
type: text
analyzer: standard
FoobarDocumentType:
fields:
blah:
type: date
nothing:
type: keyword
testfield:
type: keyword
fields:
partial:
type: text
analyzer: standard
With the following model I am unable to capture the multifields (the ones that have a nested fields keyword):
class ElasticFieldDetail(BaseModel):
type: str
analyzer: Optional[str] = None
class ElasticProperty(BaseModel):
type: str
fields: Optional[Dict[str, ElasticFieldDetail]] = None
class SharedDocumentDefinition(BaseModel):
fields: Dict[str, ElasticProperty]
class Config(BaseModel):
shared_documents: Dict[str, SharedDocumentDefinition]
using latest pydantic stable
originally I tried with:
shared_documents:
Foobar:
iuiyi:
type: date
fgfdgf:
type: keyword
gfgdg:
type: keyword
foobar:
type: keyword
fields:
partial:
type: text
analyzer: standard
(Note the removal of 'fields' at root) With pydantic.RootModel: Dict[str, ElasticProperty] but no luck.
what's the acutal error you see here?
or the otherwise undesirable output
Does anyone use Sphinx for docs on a project with Generic classes? It seems that Sphinx wants you to document your TypeVars, otherwise there is no record of what the bounds and variance are, but that feels really ugly and like I'm saying that the TypeVar's are public.
Hello Folk - anyone here consider themselves an expert in pydantic or dataclasses? i have a question on typehinting and the init function
basically - how tf do you dynamically create a kwargs init from just attributes and typehints
You mean from the typing standpoint? Or in runtime?
kinda both i suppose, its a blury field for me
the question is "how does one metaprogram an __init__ method with kwargs mapped to attributes from only the class attributes and their typehints"
so both typing and runtime metaprogramming
!d inspect.get_annotations
inspect.get_annotations(obj, *, globals=None, locals=None, eval_str=False)```
Compute the annotations dict for an object.
`obj` may be a callable, class, or module. Passing in an object of any other type raises [`TypeError`](https://docs.python.org/3/library/exceptions.html#TypeError).
Returns a dict. `get_annotations()` returns a new dict every time it’s called; calling it twice on the same object will return two different but equivalent dicts.
This function handles several details for you:
You can get annotations through this^
yeah getting the annotations isnt the hard bit, i have tried pulling from instance._attributes and instance.__annotations__
its the defining the init based on them im puzzzled by
Then you can create a __init__ that just accepts **kwargs and assigns the provided attributes
but the typehinting on that __init__ is just a dict
id like to have the **kwargs explicitly named and typed, like dataclasses and pydantic do
Type checkers cannot look what @dataclass is doing inside. Dataclasses used to be special-cased in type checkers, but now you can use:
!d typing.dataclass_transform
@typing.dataclass_transform(*, eq_default=True, order_default=False, kw_only_default=False, frozen_default=False, field_specifiers=(), **kwargs)```
Decorator to mark an object as providing [`dataclass`](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass)-like behavior.
`dataclass_transform` may be used to decorate a class, metaclass, or a function that is itself a decorator. The presence of `@dataclass_transform()` tells a static type checker that the decorated object performs runtime “magic” that transforms a class in a similar way to [`@dataclasses.dataclass`](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass).
Example usage with a decorator function...
dataclasses does something different at runtime, it just generates the source code for the appropriate __init__ and execs it. You can see the source code here: https://github.com/python/cpython/blob/cbe809dfa94385bacaa24beee3976ba77a395589/Lib/dataclasses.py#L1064-L1081
hmm, looks rather complicated for what i was aiming for, thanks for the pointers tho
ill need to go read through this properly to get whats going on
final question: do you think something like this could be done using metaclasses?
Pydantic does it using metaclasses
in any case, type checkers have to hard-code this behaviour somehow (in the form of the dataclass_transform decorator)
@zealous skiff what are you trying to do?
something like generating data for property testing?
id like to enrich my __init__(key_1: str, record_init:bool=True, **arguments) method, but rather than the **kwargs equivalent i want to type it more strongly
there are a few other parameters in the init which control how the class behaves wrt persistance and such but the data parameters all come from the **arguments
(the __init__ is in an abc at this point too, the models that inherit just use it and the abc calls setattr naively )
and you want to catch this at typecheck time? pycharm will warn you if you try to use Foo(not_a_field=7) not sure if mypy or pylance will also warn on this but i should hope so!
yeah its really for the vs code annotations - as its rather generic type checkers have a tough time picking it up
for n, v in attributes.items():
if n not in self.get_attributes():
raise ValueError(f"Attribute {n} specified does not exist"
setattr(self, n, v)
so type hinting dosent have much hints
tbh i moved away from pycharm as its not as concrete with its types ad pylance
for example not highlighting the fact the get() may return None and the split() would then fail
pylance picks this up and tells me im an idiot
maybe im missing an option in the pycharm settings
you just need to install the mypy plugin i guess
i do have it installed - maybe ill give it another go 🤷♂️ mypy is pretty decent - even if configuring it to be "just pedantic enough but not too much" is a pain
Back to type level questions: What's the best way to parse a type and determine if it's a Union? Currently I'm checking the type name and seeing it's UnionType or _UnionGenericAlias. But I would prefer a more codey solution like issubclass when checking if list[str] is a list
on a typedef or on an instance?
@fierce ridge empty values for the shared_documents components
This should work:
def is_union(typ):
return typing.get_origin(typ) in {types.UnionType, typing.Union}
oh right. phind.com was telling me to use .get(__origin__) but __origin__ isn't a a member of the type. get_origin just uses
if isinstance(tp, types.UnionType):
return types.UnionType
so i think i'll try that. 😁
Since python 3.12 we can use TypedDict for more precise **kwargs typing
https://peps.python.org/pep-0692/
https://docs.python.org/3/library/typing.html#typing.TypedDict
Together with features like NotRequired, it should be good enough to declare anything
😅 all is left updating to python 3.12 (my company is all in 3.10, and at most still in 3.11)
It should still work on earlier versions via __future__
You don't need 3.12 for this (or even __future__), you can use from typing_extensions import Unpack
interesting... except typing_extensions need to be installed i think. to be aware about.
True, but it's a very simple dependency and it's so widely used as a dependency that it's fairly likely you already have it installed as a dependency of something else
it is. but... that can be still a problem if you are writing stuff like AWS Lambdas.
U have only boto3 installed there, and for installing extra libs in python.... it is rather perverted experience with vendoring libs to local folder
or creating/using AWS lambda layers.
tricky lib environment for python 😅
Oh, that makes sense. boto and friends are the only libraries that are consistently downloaded more than typing-extensions 😄 https://pypistats.org/top
PyPI Download Stats
FWIW, you're also better off reimplementing the minimum interface you need than using boto...
AWS doesn't use an internal package index for lambdas? that's astoundingly greedy if so (and charitable of pypi)
I think they also sponsor PyPI heavily, for what it's worth
also, If you set up a managed environment, it only fetches from pypi (or other configured index) once (or when updating the environment) no matter how many instances of the environment you make.
fair enough
what's a managed environment in this case? you just talking about the client-side pip cache?
AWS has some things to pre-populate runner environments. It started with just the stuff for apache airflow environments, but one of the dev ops team at my work mentioned you can set it up in lambdas now too, though I havent personally done so or looked into it.
For Pydantic v2, I would like make the two fields in the model mutually exclusive:
class ProcessorDefinition(BaseModel):
script: Optional[str] = None
pipeline: Optional[str] = None
literally one of the worst coverage areas
Make 2 models with a union
ah neat idea, can you show me an example?
nothing about python type hints helps there, pydantic may or may not have a way to mark fields as mutually exclusive, but you would need to do the union for type checkers to handle that.
class ProcessorScript(BaseModel):
script: str
class ProcessorPipeline(BaseModel):
pipeline: str
ProcessorDefinition = ProcessorScript | ProcessorPipeline
lemme check
same way in typescript
smart/neat
@rare scarab thanks a mil 🙂
should have figured it out on my own, but hey, we live we learn
and now you know to think of that
indeed
how would be the ideal way to add a validator for a field, so that if it is missing, we take, for example, a parameter from an environment variable, or optionally, an argument passed to the model
@validator with pre? i
pydantic supports reading env vars by default via python-dotenv if you're interested
that would work
another potentially dumb question
suppose i have:
processors:
- pipeline: generic_add_indexed_timestamp
- pipeline: validate_entity_subdocuments
I would like to have a special Pipeline definition that recognizes that all processors' items are pipelines.
this would simplify my handling of the validated data later, as I can directly access the chained pipeline objects
@rare scarab if you are still around
hm it's not an enum
so a definition to be used later in the model?
so, some pipelines are chains of pipelines.
so all the items in processors will be Pipeline defs
i would like to automatically group those
you may want to play around with generics
ex.
maybe add a property that filters the processors?
` def validate_pipelines(self):
known_pipelines = []
for pipeline in self.validated_config.pipelines:
known_pipelines.append(pipeline.name)
if isinstance(pipeline, PipelineProccesorBasedDefinition):
for processor in pipeline.processors:
if isinstance(processor, ProcessorPipelineDefinition):
if processor.pipeline not in known_pipelines:
self.warn(f"ATTENTION: {pipeline.name} contains a reference to unknown pipeline {processor.pipeline}")`
using match/case may be useful here
could do that yeah, but that adds another field, i just wondered if an elegant runtime solution could be done directly with pydantic
match pipeline:
case PipelineProcessorBasedDefinition(processors):
for processor in processors:
match processor:
case ProcessorPipelineDefinition(pipeline) if pipeline not in known_pipelines:
...
sadly match doesn't fair well with for loops or list items
if the "known pipelines" come dynamically from another validated pydantic attribute i think manually looping and validating each is the best you can do
love seeing install trends dominated by ci
thanks bot
lol
How much traffic to pypi would drop if github actions used a pypi mirror?
probably a better question for the pypa server or #tools-and-devops , but it depends on how much people are using the cache action https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows. Mirroring pypi itself causes downloads that may never get used in any CI actions to be needed. (as the details in the full link point out, setup-python caches by default)
why are matplotlib's types garbage?
feel free to contribute
Fair enough. In reality I'm just venting because I do not like how shaky python typing is.
I just want solid strong typing end to end.
but in the case of matplotlib, they just don't have typing at all for a lot of their functions, i.e. Axes3D.scatter
Need some help with this: https://discord.com/channels/267624335836053506/1209348977334554695
how much traffic does pypi see, seeing as it's append only and fastly has a CDN cache in front of it?
nooo I want to see big number for my download stats
i have a problem with typing overrides.
Lets i have a class
class Typelog
def with_fields(self, *args: LogType) -> "Typelog":
logger = deepcopy(self)
logger._with_fields = args # type: ignore[assignment]
return logger
that can return its Self copy... Self.
I have it created as global var
logger = get_logger(__name__)
but when i try using it it to override its own value it starts to malfunction 🙈 and saying it is Unbound
in
def main(event: ProvisionInput, context: Any) -> ProvisionOutput:
logger = logger.with_fields(typelog.Any("input", event))
it works correctly only if i assigned it to a different named value
is there a way to allow with typing override of the same type to same type?
i override logger: Typelog = with another instance of Typelog. it should be valid override from same type to same type and supposed to work correctly
What is logger and logger.with_fields?
import typelog
from typelog import LogConfig, Loggers, get_logger
logger = get_logger(__name__)
examples/test_examples.py line 10
logger = get_logger(__name__)```
i have this lib published 😊
oh wait
logger = logger.with_fields(...) will fail at runtime
not sure why pylance is not complaining
!e
foo = ...
def bar():
foo = foo.something()
bar()
@trim tangle :x: Your 3.12 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 6, in <module>
003 | bar()
004 | File "/home/main.py", line 4, in bar
005 | foo = foo.something()
006 | ^^^
007 | UnboundLocalError: cannot access local variable 'foo' where it is not associated with a value
why would it?
https://github.com/darklab8/py-typelog/blob/bc422c438fa946fbd2a6f524760e56c07df7ddd5/typelog/logcore.py#L92
i have method that allows getting modified instance of logger from another instance
typelog/logcore.py line 92
def with_fields(self, *args: LogType) -> "Typelog":```
umm... you know.
^^^^^^
🤔
scratches head in confusion
That's how local variables work in Python 🤷
Python sees that global was not present and you assign foo, so foo is considered to be a local variable in that function
i wished it would have did
(global)foo = smth
def bar():
(local).foo = (global).foo.smth()
no?
but it sees it as this then
(global)foo = smth
def bar():
(local).foo = (local).foo.smth()
Consider this:
def bar():
for item in some_items:
if item.is_nice():
logger = make_logger(item)
break
logger.do_something()
``` if `some_items` happens to be empty and `logger` never gets assigned, should python raise an exception or use the global `logger`?
aha.
then this is allowed
def bar():
log = logger.with_fields(logtypes.Task(types.Task(smth="aaa", b=1)))
log = log.with_fields(typelog.Int("asd",5))
log.info("print smth")
pure global to func loc transition problem
yes
Yeah it might be quirky. Python just decides to be consistent in that a variable is either always local, always nonlocal or always global in the same scope
Why not name a module-level logger _logger though? It's not "public"/for export, is it?
i guess it is somewhat nice that it does that. otherwise we could have faced smth like... hello another javascript. (which tries to very overly smart in handling every situation somehow 😅 )
🤔 not got used to private prefix usage beyond its usage in classes 😅
in any case... 🙈 there is no clear benefit to its usage unless u start to use Sphinx Autodoc that has different observed behaviour for stuff u declare public/private
non-underscored symbols will appear higher in autocomplete, and it's clear that it's not for export
and you can use logger as a local variable name 🙂
i could. i thought just using log as shorter nickname.
than shorter then better. ^_^
Well, log is a log, not a logger
logger serving as regular default everyone got used seeing as global var
log as short local used instance with extra fields
A log is what a logger produces
log.info("started provision main")
in golang log is name of logging library 😅
So logging records by default look like this log.Info("Smth")
log is shorter than _logger and still gives meaning correctly enough 😛
If I see a variable named log I'll first think that it's a log, not a logger
okay, we can name it as logus then 😅
(what are some other suffixes for noun forming from log are present 🤔 )
use cyrillic letters if there's a clash with a global
lоgger = logger.with_fields(...)
damn.
it somehow sounds like
my whole team of other devs are French/English speaking people
I am not sure how it will work with them
will they be even able to see correct letter 🙈
also... what if I (or they) wrote name like logger manually, then it will not work as intended
yeah о should look exactly like o in modern fonts
no no no. I am not ready for this 🙈
sounds like a crime to me.
i'll name my local variable as logus 🎶
You get 4 distinct variables
logger
lоgger
loggеr
lоggеr
Hey, good morning. I'm using Pylance/Pyright in VS Code; Pyright updated this morning (to 1.1.348), and I believe I've got a regression - I've got a typing error on some code that's previously come up as clean, and seems like it should be okay.
I've read the open and recently-closed Issues for Pyright, and I don't think I see anyone else raising the same problems I'm seeing, but before filing an Issue and getting my hand slapped for "as designed", I wanted to ask here first, to make sure that I'm not just being an idiot.
I've got:
from __future__ import annotations
import typing
StringOrNone = typing.TypeVar("StringOrNone", str, None)
char_replacements: dict[str, str] # Things like "MS Word quotes" -> regular quotes
def replace_bad_chars(input_string: StringOrNone) -> StringOrNone:
if input_string is None:
return input_string
for bad_char in set(input_string) & set(char_replacements.keys()):
# The following line now shows a typing error, and did not do so before the update
input_string = input_string.replace(bad_char, char_replacements[bad_char])
return input_string
Previously, the line beginning with input_string = did not show a typing error. However, I now get the following two errors on this line:
Expression of type "str* | Unknown" cannot be assigned to declared type "StringOrNone@_replace_bad_chars""replace" is not a known member of "None".
This... doesn't seem to make sense to me. By the time that we're doing character replacements, we know that input_string is str, not None; if it was None, we'd have returned immediately. On top of this, we know that we're not turning input_string into something other than a str, because char_replacements is a dict[str, str], and str.replace returns str. I don't understand where Unknown is able to enter the picture here.
Am I wrong to think that this error is incorrect? Or is this something I should file an Issue about?
Why not replace_bad_chars(input_string: str) -> str?
if someone happens to have a None, they can handle it on their own
This does sounds like a bug though, I would report it to pyright
Anything in pyright can be marked as "as designed" 🙂 so don't let it discourage you
I couldn't post the actual code, so this was a stub-up - on the face of it, you're not wrong, but I have to accept None here to comply with what this function is going to get fed by its parent.
ah
I did report it, though - https://github.com/microsoft/pyright/issues/7302
I'm running Pyright v.1.1.348 as part of Pylance v.2024.2.2 in Visual Studio Code. I updated VS Code this morning, and a portion of code that previously did not display any typing errors is now...
about to grab the extra information requested
I think this is not the right way to use the "enumerated" typevar though. I'd expect it to work like
replace_bad_chars(None) : None
replace_bad_chars(str) : str
replace_bad_chars(str | None) : str | None
but because you're using the comma-separated thing and not the bound kwarg, the third one is not allowed
What exact function shape does the parent expect?
if it was using bound the function would be incorrect because it does not return a subclass of str when passed a subclass
Yep that is true
The goal is to process an element of an iterable of str | None. I've used a TypeVar here in an attempt to ensure that the output type matches the input type.
though maybe str.replace should return Self 😛
you'd be able to have so much fun with StrEnum in that case 😛
Maybe replace_bad_chars(input_string: str | None) -> str | None then?
Or StringOrNone = typing.TypeVar("StringOrNone", str, None, str | None)
Neither of those are what I want, though. If input_string is a str, I know the return type will be a str. If input_string is None, I know the return type will be None. For both of those examples, I no longer know what my return type will be; I lose information about the value.
This having been said, with a use-case this narrow, it occurs to me that I can just write it as an overload. I'll do it that way if this doesn't bear fruit or gets complicated.
If you want to call this function on a str|None, you'll need to add str | None as an explicit case to the TypeVar though
It is a bit silly. But that's how constrained typevars work
How can I get rid of pyright errors (from lsp) when at my variable is None at start and initialize that variable with appropariate value before I call the other methods.
For example:
# FILE - view.py
class View:
def __init__(self, control: Controller):
self.ctrl = control
self.ctrl.view = self
def update(self, *args, **kwargs):
pass
# FILE - controller.py
class Control:
def __init__(self, model: Model):
self.model = model
self.view: Optional[View] = None
def update_view(self, *args, **kwargs):
self.view.update(*args, **kwargs) # "update" is not a memeber of "None"
# FILE - main.py
def main():
model = Model()
control = Controller(model)
view = View(control)
view.mainloop()
Is there any way to get rid of this error thingy?
Right now it's totally possible ("type safe") to do controller = Controller(model) and call controller.update_view(foo=1, var=4). That's why a type checker will not let you do this.
You have 3 options:
- lazily initialize
view
class Controller:
def __init__(self, model: Model, make_view: Callable[[], View]) -> None:
self._model = model
self._cached_view: View | None = None
self._make_view = make_view
@property
def _view(self) -> View:
if self._cached_view is None:
self._cached_view = self._make_view()
return self._cached_view
You create the controller as controller = Controller(model, lambda: view) use self._view inside the controller
2. Keep the multi-step initialization, but put all the checks in one place:
class Controller:
def __init__(self, model: Model) -> None:
self.model = model
self.view = None
@property
def _view(self) -> View:
if self.view is None:
raise RuntimeError("You should initialize the view with a controller before using the controller")
return self.view
- Put
# type: ignorewith the specific error on every use
Note that options 1 and 2 are not just for the sake of typing -- I think they will actually make it easier to write correct code and/or understand the error if you use it incorrectly
Apparently this isn't correct at least on Pyright
from abc import ABC
class A(ABC):
pass
class B(A):
pass
class C(A):
pass
def f(x: A):
g(x)
# ^^^
# Argument of type "A" cannot be assigned to parameter "x" of type "B | C" in function "g"
# Type "A" cannot be assigned to type "B | C"
# "A" is incompatible with "B"
# "A" is incompatible with "C"
def g(x: B | C):
pass```
Correct me if I'm wrong, but a value of type A can either be a value of type A, a value of type B, or a value of type C.
It cannot be a value of type A - on account that A is an abstract class. Therefore the value can only be either of type B or of type C. Meaning type "A" should be able to be assigned to type "B | C".
Because it could be D(A)
But there is no D(A)... I haven't defined it, so there's no reason for the type checker to assume it could be
How can I seal it?
That's not a feature of python
You could use a structure like
class B:
pass
class C:
pass
A = B|C
instead. That sometimes makes more sense than inheritance (if you want to be able to use these variants differently).
A protocol could also be an option
Well, at that point I would also lose the benefits of inheritance and need to duplicate code
How does a protocol apply here?
If you mean B | C and actually need something from B or C, just use that as the annotation.
no need to complicate this further
you can still have a base class to deduplicate the code, just not use it in typing
Not necessarily, you can do ^, like
class AbstractA: ...
class B(AbstractA): ...
class C(AbstractA): ...
A = B | C
I see. It does make sense...
Is there any difference between A = B | C and type A = B | C?
type alias syntax is deferred in evaluation with the annotation future import, but isn't available pre 3.12
At a type level there's no different but if you were to use something like Pydantic or dacite it might mean it tries to make B before C or C before B when reading a dict
Hi what is the best (at least most common) practice of return type for a function that returns a django queryset ? ty (ping me)
Hi, i have this code here:
orders_total: dict[str, list[tuple[str, int]]] = {...}
total = await self.calculate_total_income(orders_total.values())
and pyright is giving me this error:
Argument of type "dict_values[str, list[tuple[str, int]]]" cannot be assigned to parameter "stocks" of type "list[list[tuple[str, int]]]" in function "calculate_total_income"
"dict_values[str, list[tuple[str, int]]]" is incompatible with "list[list[tuple[str, int]]]"
now i know that list != dict_values but why the dict_values needs the type of the keys??? same question for .keys() but switched, why dict_keys needs the type of the values?
the funtion signature is
async def calculate_total_income(self, stocks: list[list[tuple[str, int]]]) -> int:
This function expects a list, you're giving it something that's not a list
a) total = await self.calculate_total_income( list(orders_total.values()) )
b) If the function doesn't need to mutate the list, change the signature to ```py
from collections.abc import Iterable
async def calculate_total_income(self, stocks: Iterable[Iterable[tuple[str, int]]]) -> int:
``` (or Sequence instead of Iterable if you need indexing)
now i know that list != dict_values but why the dict_values needs the type of the keys???
Because of the mapping property on those objects added in 3.10
stdlib/_collections_abc.pyi lines 70 to 75
@final
class dict_keys(KeysView[_KT_co], Generic[_KT_co, _VT_co]): # undocumented
def __eq__(self, __value: object) -> bool: ...
if sys.version_info >= (3, 10):
@property
def mapping(self) -> MappingProxyType[_KT_co, _VT_co]: ...```
Not a pep, but https://github.com/python/cpython/issues/85067
alright ty
chat i just discovered something p game changing and figure other ppl need to know too
https://peps.python.org/pep-0655/#motivation
tldr:
""" before python 3.11 """
class _MovieBase(TypedDict):
title: str
class Movie(_MovieBase, total=False):
year: int
""" after python 3.11 """
class Movie(TypedDict):
title: str
year: NotRequired[int]
and before Python 3.11 too, just use typing_extensions.NotRequired
damn lol i had no idea NotRequired existed at all
usually when deep diving other libraries i see people making their own implementation of it
Can't you also do ```py
class Movie(TypedDict, total=False):
title: Required[str]
year: int
Just saw this line in our codebase. What does it mean? T = TypeVar("T", bound=Mapping[str, object])
What in particular, the TypeVar or the bound?
I've never seen TypeVar before
A TypeVar lets you create generic functions and classes, I have a small tutorial series on it here: https://decorator-factory.github.io/typing-tips/tutorials/generics/
thanks!
bound means that the type variable can only refer to values of that type. For example:
class Widget:
def is_hidden(self) -> bool: ...
W = TypeVar("W", bound=Widget)
def only_visible(widgets: Iterable[W]) -> list[W]:
return [w for w in widgets if not w.is_hidden()]
buttons: list[Button] = ... # Button is a subclass of Widget
label: list[Label] = ... # Label is a subclass of Widget
visible_buttons: list[Button] = only_visible(buttons)
visible_labels: list[Label] = only_visible(labels)
only_visible([1, 2, 3]) # error
I somehow didn't write anything about bound in the tutorial, maybe I'll fix it later
so this: T = TypeVar("T", bound=Mapping[str, object]) means that T should be an object where keys are strings and values are also objects?
Yes. And every time you see two Ts used in a function, they are "linked" together
T should be a Mapping, not just any object. "Also objects" isn't a very useful thing to say, as everything is an object...
How do i notat this type (using typescript notation for the example):
ImgType
ColorType
DictSpecialKeys = {
[img: number]: ImgType
color: ColorType
}```
Not really possible
that's a very strange structure in Python tbh
Just have a dict[int, Image] and a Color as separate fields
you can technically do this i think if subclass dict[int | Literal["color"], ImgType | ColorType] and then adding a bunch of overloads
fascinating moment.
TypedDicts with same keys are type equal!
😅 that means all is enough to make such assignment in a test file, in order to validate type equality of some dicts
yes, it's a structural not a nominal type
Has anyone thought of a tuple-like iterator? ```py
def iter(self) -> TupleLike[int, int, str]: ... # yields a int, int, then str
Note that a TypedDict returns a dict if instantiated. And they are meant to model dicts.
this would mean that type of iterator changes over time (Iterator[int] -> Iterator[str] -> Iterator[Never]), which is impossible to represent in current type system
but i like the idea
Still wish we had dict unpacking...
spreading?
spread dict to variable
like {x, y, z} = d
i found flaky pyright false-positive: https://pyright-play.net/?code=GYJw9gtgBA%2BjwFcAuCQFM5QJYQA5hCSgEMA7UsJYpLMUgZwFgAoFgYwBtj76oAVdGgDafALoAuFlGlQ2ACywcAJuP6CRoqAB8oAOTpopMgG7EOCNKr7a9BlkA
try removing value: T | None line and typing it manually again
sometimes i get the Type variable "T" has no meaning in this context error that references previous line
i also can reproduce it in my local setup that uses pyright LSP
if i touch code somewhere to force re-checking, error usually disappears
not sure what is causing it
i guess some incorrect cache not-invalidation or something like that
noted. thanks
def check(b: B) -> A:
return b
also, having pressence of such code we could make tests for equality without instantiating it ^_^
How can I define a Protocol for a class with a method that is decorated with @asynccontextmanager?
I tried this
class Processor(Protocol):
@asynccontextmanager
async def meow(self, x: int) -> AsyncIterator[str]:
...
But I got this:
Argument of type "(self: Self@Processoar, x: int) -> Coroutine[Any, Any, Unknown]" cannot be assigned to parameter "func" of type "(**_P@asynccontextmanager) -> AsyncIterator[_T_co@asynccontextmanager]" in function "asynccontextmanager"
Type "(self: Self@Processoar, x: int) -> Coroutine[Any, Any, Unknown]" cannot be assigned to type "(**_P@asynccontextmanager) -> AsyncIterator[_T_co@asynccontextmanager]"
Function return type "Coroutine[Any, Any, Unknown]" is incompatible with type "AsyncIterator[_T_co@asynccontextmanager]"
"Coroutine[Any, Any, Unknown]" is incompatible with protocol "AsyncIterator[_T_co@asynccontextmanager]"
"__anext__" is not present
"__aiter__" is not present
What am I supposed to put instead of AsyncIterator? That's what I've seen on stackoverflow....
use def, not async def
But then I wouldn't be able to make await calls inside the function
it's a protocol, there is no "inside the function"
you can still use async def in an implementation
unfortunately the interactions between type annotations are a bit weird here, since the presence of yield inside the function affects the type
I see
So for the concrete implementation I'd use async and it would work then?
yes
what you have says that if you call it, you get an awaitable that when awaited produces an async iterator
instead what you want is that if you call it, you get an async iterator
I see
Thank you for the clarification!
Why not annotate it with AbstractAsyncContextManager?
The function itself doesn't return a context manager, but it's wrapped by an object that does
AbstractAsyncContextManager is the return type of the decorator, not the function it decorates
at the protocol level, it's correct to say it returns AbstractAsyncContextManager though
implementations of the protocol may use @asynccontextmanager, but it should be possible to implement the protocol some other way
You mean like...?
class Processor(Protocol):
def meow(self, x: int) -> AbstractAsyncContextManager[str]:
...
yes but without the async
or you'd have to write async with (await proc.meow(1)):
What did you do?
btw you're probably right, but in the end the ergonomgics were nice and i got full type saftey
one sec
well, you could make an overloaded __getitem__
class AssocaitedPoint(Protocol):
@overload
def __getitem__(self, i: int) -> ImagePoint: ...
@overload
def __getitem__(self, i: Literal["color"]) -> Color: ...
@overload
def __getitem__(self, i: Literal["has_wp"]) -> bool: ...
@overload
def __setitem__(self, i: int, v: ImagePoint): ...
@overload
def __setitem__(self, i: Literal["color"], v: Color): ...
@overload
def __setitem__(self, i: Literal["has_wp"], v: bool): ...
def __contains__(self, i: int | Literal["color"] | Literal["has_wp"]) -> bool: ...
class PointsDict(dict, AssocaitedPoint):
def __getitem__(self, i):
return dict.__getitem__(self, i)
def __setitem__(self, i, v):
dict.__setitem__(self, i, v)
yuuup
That is... mildly cursed 😛
yeah, works excatly as expected
defintely doesn't really make much sense do to unnecessary hashing of the literal, etc
but it's what i did
no get or delitem though :(
also not very type safe, because typing.overload doesn't check that your implementation follows the overloads
or pop or setdefault
you would have to overload all of those manuallt
yes it does
it think
It can't
oh yeah, your right
but it gives you all the type hinting
but yes, you could viloate the Protocol in the impl
it's terrible
overall, in all capacities
but i needed a retroactive type for how i coded it in the first place
i had the residuals of JS every object being a dict in my system
hence the awefulness you see above
won't do it again fs
I think this structure is just not easy to work with, even in JS, compared to two separate fields
but i learned a lot about typing in python in the mean time
its actually pretty ergonomic in TS
it just neans you avoid needing to do object.dict[1], object.color
Enumerating all the (img, ImgType) pairs is pretty awkward
you just do object[1], object.color
yeah, true
idk, this is not the right use case for that pattern in TS
like at all
but there are valid and solid use cases
Yeah, Python's type system doesn't have good tools to compose and remix existing types, sadly
I wish zod from TypeScript was possible in Python
yeah, its not really what it's meant for. TBF i don't know what is is meant for.
anyways i gotta get back to work, nice chatting
mild typo in "assocaited"
from typing import Protocol, TypeAlias, NamedTuple
class HasFileno(Protocol):
def fileno(self) -> int:
...
FileDescriptorLike: TypeAlias = int | HasFileno
class SelectorKey(NamedTuple):
fileobj: FileDescriptorLike
class Client:
def fileno(self) -> int:
return 0
def handle_request(self) -> None:
...
def get_key() -> SelectorKey:
return SelectorKey(Client())
# Error, but shouldn't this be safe?
client: Client = get_key().fileobj
Client is a FileDescriptorLike, so I should be able to assign get_key().fileobj to a variable of type Client?
No it's either an int or an object with fileno() it doesn't know it's a Client anymore
but Client is an object with fileno()
Right but an object with fileno() might not be a Client
oh, its like an Animal and Dog thing isn't it?
And you have the | int to deal with
do I have to put a assert isinstance(key, Client) then
but I literally put a client in the selector!
But you could have put an int in there, the type system doesn't know
I see, would the Selector benefit from being typed as a generic thing then in typeshed?
It's not Generic at runtime so it's problematic to be Generic in the stub
Also I don't think it helps you as Selector would need to be generic
ye, I'll just put a # type: ignore then :(
Feels like the opaque data should be generic
but there I can do the_data: SomeType = key.data