#type-hinting
1 messages Β· Page 25 of 1
Why not? This is exactly what it's for.
Works, but IMO it's nice when reading to code to be alerted that the list contains uninitialized elements (before it's fully initialized)
it is for testing/debugging purposes
No?
asserts are ignored if the code is ran with a certain flag (optimization flag)
Yes, which is what's wanted here. Asserts describe invariants that you expect to always succeed, and indicate a bug in the code logic if they ever fail. The reason for the flag is that after you're sufficiently tested the program in development, it should be fine to omit them for performance.
I think this is exactly what assert is for, too. It doesn't matter that this might be disabled at runtime. When the assert is run, I believe that there will never be a None in the list. I might be wrong, so it's nice that the assert is run at debug time, or in tests
guess so
And in a type-hinting context, checkers will use asserts to allow you to specify various narrowings.
but also I did say testing/debugging which Jakob seems to agree on π€·ββοΈ therefore I'm not completely wrong. I think you just gotta be careful with them π€·ββοΈ
assert isinstance() does work for example, Mypy 1.7 added len(some_tuple) == X. The is a open issue for Mypy, if someone wants to implement it.
that len part seems nice
def f(t: tuple[int] | tuple[str, str]) -> None:
if len(t) == 1:
reveal_type(t)
else:
reveal_type(t)
doesn't work if you do match:case: tho
def f(t: tuple[int] | tuple[str, str]) -> None:
match len(t):
case 1:
reveal_type(t)
case _:
reveal_type(t)
For match, using sequence patterns would probably be better.
so i was in here a bit ago with a typing problem for a class that can be instantiated with keywords, a dict or list of tuples, or alternating keys/values. It's meant to be a matcher for a dictionary, to see if that dictionary contains the key/value pairs it's initialized with.
The trimmed-down class:
K = TypeVar("K", bound=Hashable)
V = TypeVar("V")
class ContainsTheEntry:
...
# Keyword argument form
@overload
def __init__(self, **kv_args: V) -> None:
...
# Key to value dict or list of tuples form
@overload
def __init__(self, kv_args: Mapping[K, V] | list[tuple[K, V]]) -> None:
...
# Alternating key/value form
@overload
def __init__(self, *kv_args: K | V) -> None:
...
def __init__(self, *kv_args: Any, **kv_kwargs: Any) -> None:
# init goes here
The test:
class TestContainsTheEntry:
def test_can_be_instantiated(self) -> None:
cte_single = ContainsTheEntry(key="value")
cte_multiple = ContainsTheEntry(key1="value1", key2="value2")
cte_dict = ContainsTheEntry({"key2": 12345})
cte_alternating = ContainsTheEntry("key3", False) # <-- line 149, the error
and this mypy error:
mypy.....................................................................Failed
- hook id: mypy
- exit code: 1
tests/test_resolutions.py:149: error: Argument 1 to "ContainsTheEntry" has incompatible type "str"; expected <nothing> [arg-type]
tests/test_resolutions.py:149: error: Argument 2 to "ContainsTheEntry" has incompatible type "bool"; expected <nothing> [arg-type]
If i remove the | V from the third overload, mypy is happy, but that's not a correct type. Only the keys need to be hashable for the dictionary.
What's wrong with this? How do i properly type this class?
i tried making the class Generic[K, V] but that caused even more mypy errors requesting type defs for the other three lines in the test
... what if i do V = TypeVar("V", bound=object)? π€
honestly sounds like a problem with mypy
Oh wait, your class is not generic
You need to do ```py
class ContainsTheEntry(Generic[K, V]):
i did try that, but then i get these:
tests/test_resolutions.py:146: error: Need type annotation for "cte_single" [var-annotated]
tests/test_resolutions.py:147: error: Need type annotation for "cte_multiple" [var-annotated]
tests/test_resolutions.py:149: error: Need type annotation for "cte_alternating" [var-annotated]
tests/test_resolutions.py:149: error: Argument 1 to "ContainsTheEntry" has incompatible type "str"; expected <nothing> [arg-type]
tests/test_resolutions.py:149: error: Argument 2 to "ContainsTheEntry" has incompatible type "bool"; expected <nothing> [arg-type]
note that it does not fix the original errors
The alternating key/value bit isn't expressible with python typing for an arbitrary length the way you have up there. that's allowing the key type or the value type for any entry.
the iterable of tuples works fine, but not just iterable[K | V]
I think the overloads are ambiguous, namely overloads 2 and 3 when you provide a single argument
oh wait, are they evaluated in order?
anyway, that's not relevant because that's not the overload that causes a problem
it's not a typable concept to begin with as described, and the untypable concept matches where the original error was shown.
well, yes, it's not perfectly typeable
What if you add an explicit : ContainsTheEntry[str, bool] to cte_alternating?
Right now mypy has no way of determining the parameters. It could be
K = str | bool, V = NeverK = str, V = boolK = bool, V = str
...
why do you want to support this format btw? it seems cumbersome to use
my library bends over backwards to make end-to-end tests very easy to read, so i wanted to support a lot of ways to ask "does this dictionary contain this entry"
i guess i can just say object instead of K or V for the last overload...
which brings me to another question, what am i saying when i use object instead of Any?
yeah, i wish there were some way to type this, but i get that it's pretty... strange
x: object = ...
x.a # error, not all objects have .a attr
x + 1 # error, not all objects are addable with 1
y: Foo = ...
y = x # error, not all objects are Foo
x = y # ok, every Foo is an object
i believe there is a better explanation of "object vs Any"
hello, I have a question about a subclass problem of Protocol:
E.g., I have an interface/ protocol A that would be generics, as I would expect there are interfaces that looks like A[str] and A[int], my approach is to inherit A and then create a modified version of protocol A, e.g., StringA being the same interface with generic A[str].
My question is:
- why does
MyClasseven though implemented the interface ofStringA, theissubclasscheck is still returning False? - but if I remove the protocol inheritance of
StringA(A[str]), that check returns as True? - is there some way I can still maintain a check that relies on
Generics[A]?
from typing import Generic, Protocol, TypeVar, runtime_checkable
T = TypeVar("T")
@runtime_checkable
class A(Protocol, Generic[T]):
@staticmethod
def hello() -> T:
...
# I expect there are differen interface
# e.g., A[str] A[int]
class StringA(A[str]):
@staticmethod
def hello() -> str:
...
### Assuming MyClass cannot be modified,
### as I expect this to be implemented by different people otuside of my package
class MyClass:
@staticmethod
def hello() -> str:
return "hello world"
print(issubclass(StringA, A)) # true
print(issubclass(MyClass, StringA)) # false??
If you're intending StringA to be a protocol, it needs to inherit from A[str] and Protocol. In its current state, it's a concrete class implementation of A[str], which explains the issubclass check returning false.
Source: https://peps.python.org/pep-0544/#merging-and-extending-protocols
Python Enhancement Proposals (PEPs)
That's my understanding, at least. Not an expert.
thanks. I guess if need to check protocol, I can only check it without subtyping protocl then π¦
You can make inheriting protocols runtime-checkable as well.
I think.
e.g. This works.
from typing import Generic, Protocol, TypeVar, runtime_checkable
T = TypeVar("T")
@runtime_checkable
class A(Protocol, Generic[T]):
@staticmethod
def hello() -> T:
...
@runtime_checkable
class StringA(A[str], Protocol):
...
class MyClass:
@staticmethod
def hello() -> str:
return "hello world"
print(issubclass(StringA, A)) # True
print(issubclass(MyClass, StringA)) # True
Oh. But issubclass won't check the types.
@runtime_checkable
class IntA(A[int], Protocol):
...
class MyClass:
@staticmethod
def hello() -> str:
return "hello world"
print(issubclass(MyClass, IntA)) # True
Like, this is misleading.
It's not behavior you want, I assume.
yep.
Yeah, so I take it back.
It'll do that, but it won't check the return types of protocol methods, I guess.
That's probably somewhere in the PEP as well.
oh wow it works. π
i know why it doesn't work the last time,
if I put Protocol, TypeA
it deson't allow it :D:D
only TypeA, Protocol could allow me to do this
Oh, didn't know order mattered there. Interesting.
I think it need s to check TypeA first, then runtime check the subtype protocol π
Huh. TIL.
That would make some sense.
thanks . this is super helpful. I need that interface inheritance, since I have that interface generic only two different classes.
which works like a bridge between the output from a interface, then convert the output from the same interface with different generics.
Yeah, no problem! It's not a perfect fix, since issubclass and isinstance don't seem to care about what types are in place of the generics at runtime, but better than nothing, I suppose.
need to write this behaviour down in my notebook. can't seem to find any thing related to this in google :D:D
However, it should be possible for protocol types to implement custom instance and class checks when this makes sense, similar to how Iterable and other ABCs in collections.abc and typing already do it, but this is limited to non-generic and unsubscripted generic protocols (Iterable is statically equivalent to Iterable[Any]).
Found it in the PEP. Unfortunate.
Thanks friend, it sounds like typing as object is probably not what i want here. Iβll just do Any.
typing as object is pretty rare thing
object makes sense over Any in some cases where Any would obscure other issues and object is sufficient, but most of those cases are better expressed with a protocol for what you actually need the object to have (Hashability, for example)
With the following code
1 from typing import Self
2
3 class Thing:
4 def __init__(self) -> None:
5 self.things: dict[int, list[Self]]
6
7 def insert(self, id: int) -> None:
8 self.things[id].append(Thing())
9
10 def __getitem__(self, id: int) -> list[Self]:
11 return self.things[id]
Mypy complains with the following strange message :
/private/tmp ΞΆ mypy test.py
test.py:8: error: Argument 1 to "append" of "list" has incompatible type "Thing"; expected "Self" [arg-type]
Found 1 error in 1 file (checked 1 source file)
If I change self.things: dict[int, list[Self]] to self.things: dict[int, list[Thing]] then mypy complains about
__getitem__'s signature.
/private/tmp ΞΆ mypy test.py
test.py:11: error: Incompatible return value type (got "list[Thing]", expected "list[Self]") [return-value]
Found 1 error in 1 file (checked 1 source file)
I wonder if that's a bug?
also reproducible with --new-type-inference
That error is correct. If you created a subclass of Thing, your annotations imply the list would contain instances of the subclass, but in fact you add instances of Thing to it
You should possibly use Thing instead of Self
Oh! Thank you!
Interesting, that would mean the Self type is different from "Thing" type, so I'll have to resort to using "Thing" within the class :(
you can also do from __future__ import annotations to just use the class as-is (all annotations becomes strings to avoid runtime errors)
(for "Thing" -> Thing)
Ah, Nice!
I am really having a hard time to understand in which kind of situations we may need to explicitly check the type of an object at runtime, with ABCs or typing.Protocol. I mean, a use case that cannot be solved with duck typing or that it is not very elegant in that case. Can anyone help me?
Whenever you have an interface that accepts a union of objects? E.g.: a function that accepts either a Distance object or a string such as "euclidean".
Its easy to show for union types like str | int, you'll need to check if something is a str to be able to safely call methods like .upper() on it.
T | None is also a common
but in those case a if x: check usually gets the job done if you know that T is never falsy, or if it is you don't care eitherway.
its usually if x is None: because None is a Sentinel
Use better if x is None: in the second case, unless you want to spend a couple of hours debugging, and several hours more to redo all your experiments.
I agree
So, it is always possible to use duck typing but there are circumstances in which is better to check?
It is not always possible to use duck typing, that is false.
Consider this toy example:
class Cowboy:
def draw():
...
class Painter:
def draw():
...
def function(a: Cowboy | Painter):
...
Here in function is not possible to use anything except isinstance to distinguish between the two.
typecheckers do allow duck typing sometimes
in your example, you can do a.draw() without isinstance-checks because a have .draw in any case
sometimes you genuinely can't trust your values. e.g a common case is at a web api boundary where you may want to check that values match the schema you expect
Hello there,
I've a module containing two classes, which at some point need to self-reference them in the return type.
This cannot be achieved per se, hence me using the Type['classname'] approach to overcome the type hinting issue.
However, at some point I need also to return - in a list comprehension - a set of objects of one of these class, which when compared with the other instances within the code return a type hinting error as "Expression of type "MyClass | None" cannot be assigned to declared type "type[MyClass]"
How can this be solved?
I tried declaring an alias, and casting the return type of my new object within the comprehension list without success.
type[MyClass] means that you return the class itself (not an object of that class)
still not clear how can I achieve the two of them though
I understand that at one point I'm instantiating the object but why comparing the object with its type does fail?
In my mind, Obj() ... has a signature that should be represented by Type['Obj'] and the 2 should be comparable at a type hint level, no?
def f() -> type[int]:
return random.choice([int, bool])
def g() -> int:
return 42
type[...] means you're returning a class
If you don't know a type object and don't indent to use it - it's better to use object than Any π€
Sorry... I understand I need to change my return, but that's where I'm struggling with a initiated class
if you want to return an object of MyClass then you just do -> MyClass
class MyClass:
def foo(self) -> MyClass:
return self
or, depending your usecase you do the following:
from typing import Self
class MyClass:
def foo(self) -> Self:
return self
class BoundText(tk.Text):
"""A Text widget with a bound variable."""
def __init__(self, *args, textvariable: tk.StringVar | None = None, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._variable = textvariable
if self._variable:
# insert any default value
self.insert('1.0', self._variable.get())
self._variable.trace_add('write', self._set_content)
def _set_content(self, *_) -> None:
"""Set the text contents to the variable"""
self.delete('1.0', tk.END)
self.insert('1.0', self._variable.get())
"get" in last line (self.insert('1.0', self._variable.get())) is underscored in vscode:
"get" is not a known member of "None" PylancereportOptionalMemberAccess
even if i check self._variable, is that inevitable?
wrap it in if self._variable: like you did in __init__
or make _variable non-noneable
or pass _variable as an argument
assert self._variable would also work
thanks
I might actually not expressed me very well. Sorry but yesterday I had no chance to write down some code. See you below a bare example:
class Class1:
def getlist(self) -> list["Class2"]:
self.mylist = [Class2(1, 2, self) for x in something]
def foo(self) -> Class2:
retutn self.mylist[0]
class Class2:
def __init__(self, x, y: Class1):
...
Both my classes live in the same file.
The problem is with Class1.foo that can return Class2.
But when I do so my other part of the code, where the class are implemented returns a type hint error message. If I convert into a Type["Class2"], I resolve the issue from the implementation side, but have problems with the class itself.
add at the top of your file this
from __future__ import annotations
or have you already imported it?
Sorry I hit enter by mistake and my code snippet was not ready yet. Check out now.
The problem is a circular problem ... whichever way I change I get a type hint error message
If I change the code as below ... the file containing my two classes laments the following type hinting message: Expression of type "Class2 | None" cannot be assigned to declared type "type[Class2]"
class Class1:
def getlist(self) -> list["Class2"]:
self.mylist = [Class2(1, 2, self) for x in something]
def foo(self) -> Type["Class2"] | None:
x: Type["Class2"] = self.mylist[0]
retutn self.mylist[0]
class Class2:
def __init__(self, x, y: Class1):
...
If I change the implementation of foo in def foo(self) -> myfile.Class2 | None: I get a partially initialized module error message.
So I am stuck in understanding how this can be done
Ok, this is the answer ... sorry ... my line is very slow .. I got with this done in the end.
What's the role of the annotation thing then.
I think I read that in 3.12 that was no longer necessary?
it makes all the annotations strings at runtime to avoid errors
Apparently it does more than that, because I was able even to return the class itself as per my previous example.
Fair enough.
But am I wrong when I say it's not anymore recommended with 3.12?
again, that type annotation is wrong, you're not returning the class itself, you're returning an object (an instance) of that class which is different from returning the class itself
and about that import
I'm referring to this code block #type-hinting message
And yes, I need an instance of my object.
What's wrong with this?
Looking for some help with this:
I have a class based on TypedDict, and I want to write a function that can take an argument of any key from within the TypedDict, specified by a type annotation.
class MyType(TypedDict):
key1: str
key2: str
myDict: MyType = {"key1": "value1", "key2": "value2"}
def getValueFromMyType(key: SOME_TYPE_HINT_HERE) -> SOME_RETURN_TYPE_HERE:
return myDict[key]
I could use Literal["key1", "key2"] for the key type hint, but in my actual implementation, MyType has too many keys for that to be reasonable and this doesn't feel very DRY.
Anyone have a solution for this?
I don't think it's possible
If you want to also link the return type to the input key, that's definitely not possible
You can extract a type alias, like ```py
MyTypeKeys = Literal["key1", "key2"]
unfortunate, thanks for the suggestions, ill see if i can make it work
python's type system is pretty bad at mixing/transforming existing types
yeah something like this would be relatively easy in typescript π¦
yep
you can use Unpack with a typed dict and total=False on the typed dict
actually, lemme double check, that may not compose, there were a lot of annoying limitations added against composition
Hmm, something I expected to work doesnt, and I seem to have also run into a bug in pyright that's only tangentially related in the process.
If you have a case like the above where the values are homogenous, you can do:
type MyKeys = Literal["key1", "key2"]
type MyType = dict[MyKeys, str]
myDict: MyType = {"key1": "value1", "key2": "value2"}
def getValueFromMyType(key: MyKeys) -> str:
# matching the exact case you had
return myDict[key]
I'm guessing that isn't actually the case in the corresponding real world code though
that is the case, otherwise the typeddict is kinda pointless.
π€
you can have typeddicts where the values vary in type:
eg:
class Movie(TypedDcit):
year: int
title: str
that wouldn't work with the way I gave above
(though whether you should have typed dicts like that is another matter, and I'd argue one should not)
thats what i mean, having a typedict with all the same types is practically the same as just using a regular dict type hint dict[str, str]
One thing you should be able to do is the entire process in reverse ```py
from typing import Literal, get_args
MyTypeKeys = Literal["key1", "key2"]
MyTypeValues = Literal["value1", "value2"]
keys: tuple[MyTypeKeys] = get_args(MyTypeKeys)
values: tuple[MyTypeValues] = get_args(MyTypeValues)
myDict: dict[MyTypeKeys, MyTypeValues] = dict(zip(keys, values))
def getValueFromMyType(key: MyTypeKeys) -> MyTypeValues:
return myDict[key] ```
I got this to work in MyPy strict, but as you can see it doesn't like it, and I couldn't get it working with the typed dict. Couldn't get anything working for pyright.
No, the dict would be like TypedDict with all the fields NotRequired
also TypedDict allows extra fields iirc
If the macro pep ever goes anywhere, you could have a macro that generates all the overloads needed in that case. There's also the fact that you should just be able to lie to the type checker and have it work here, but some intentional design decisions of both pyright and mypy preclude that
The precluded fix to just use the type of something that should have that signature being:
class MyType(TypedDict):
key1: str
key2: str
myDict: MyType = {"key1": "value1", "key2": "value2"}
if TYPE_CHECKING:
# this should work, but neither mypy nor pyright generate internal overloads here
getValueFromMyType = myDict.__getitem__
else:
def getValueFromMyType(key)
return myDict[key]
I'm hinting my type as an.. introvert #type-hinting 
<@&831776746206265384> this guy spammed this message in multiple channels
(mirror of #1176091147068780614 message) - is there a way to specify 'returntype of X' a la TypeScript?
E.G.
def foo(data: str): # return type inferred by pylance
... # filled in by user
def process_1(data: ReturnType[foo]) -> Any:
... # filled in by user
def process_2(data: ReturnType[foo]) -> Any:
... # filled in by user
framework.code(foo, process_1, process_2) # signature: (str) -> T, (T) -> Any, (T) -> Any
no
Dang
there's no way to refer to a value from a type
I mean reveal_type(foo) tells me the correct (inferred) type of foo, including its return value
It's a little frustrating I can't manipulate that type, or at least use the framework code from later on to narrow the argument types earlier
See discussion in https://github.com/python/typing/issues/769 π
Thanks for the pointer π
Argh, it's almost possible in two different ways but doesn't work for two different reasons
T = TypeVar('T')
class ReturnType:
def __class_getitem__(cls, item: Callable[[str], T]) -> type[T]:
return item.__annotations__['return']
def return_type(fn: Callable[[str], T]) -> type[T]:
return fn.__annotations__['return']
These both nearly work, but don't because #1 causes type checkers to infer ReturnType (instead of the declared result of __class_getitem__) and #2 causes a type checking error because apparently you're not allowed to use calls in type expressions (despite the call being fully type-hinted so no actual code execution should be necessary for type inference)
(does anyone know the purpose of those limitations, btw? IMO __class_getitem__ would be a more useful feature if you were allowed to completely override the returned type, and allowing calls for arbitrary type transformations would also make type-hinting more powerful)
Why is this a type error (starting from mypy 1.6.0)? The types seem compatible to me.
def foo(x: str) -> str | None:
return None
foo = lambda _: None # Incompatible types in assignment (expression has type "Callable[[str], None]", variable has type "Callable[[str], str | None]") [assignment]
It probably has something to do with assigning to a function. If I instead declare foo : Callable[[str], str | None], there is no type error.
def foo(x: str, /) or lambda x: None should work
Ah, good point, I see what is going on here!
I guess this could be called a bug of the error message being confusing.
I ran into this when trying to type 3rd party code that monkey-patches ctypes.util.find_library.
I guess that means there must be more to foo's type than reveal_type reveals. Is there a way to show it all?
Actually, I guess reveal_type does show it, I just didn't notice the x :P
pyright gives more info about function signature
not sure about mypy
Yes, it says Type of "foo" is "(x: str) -> (str | None)" with reveal_type(foo).
I think this is the bug, but the report is confused with return values and subclasses.
Interestingly, if I change the return value of foo to None (so there's no narrowing of the return value), mypy gives a better error message:
Incompatible types in assignment (expression has type "Callable[[Arg(int, '_')], None]", variable has type "Callable[[Arg(int, 'x')], None]") [assignment]
Would this not work? It passes pyright, and fails as expected if I say make foo return a str and pass an int into process_1 ```py
from typing import Callable, Any
def foo(data: str): # return type inferred by pylance
... # filled in by user
def return_type[T](fn: Callable[[str], T]) -> type[T]:
return fn.annotations['return']
def process_1[T: return_type(foo)](data: T) -> Any:
... # filled in by user
def process_2[T: return_type(foo)](data: T) -> Any:
... # filled in by user```
no thats not having the types be inferred correctly
guys how to type the instance of a class
*annotate
type[T] is the class, T would be the instance
is there a way to get only the instance
from typing import TypeVar
T = TypeVar("T")
no_use = type[T]
def foo(x: T) -> ...: ...
idk
annotate an instance of an instance of the class
maybe you want TypeVar("T", bound=type)?
i'm not really sure what you're asking
the instance of type is a class
well i want to annotate the instance of that class
the class can vary
thats why it cannot be static
class Foo: ...
def foo(x: Foo) -> None: ...```
isn't that what a plain typevar does?
do you want foo to accept any subclass of Foo?
class Foo: ...
class Bar(Foo): ...
def foo(x: Foo) -> None: ...
b: Bar
foo(b)
Success: no issues found in 1 source file
i want foo to accept any object which metaclass is any class
and that class is created from type
for example, here you are expecting x to be an instance of Foo
its exactly what i want but !, consider the class can vary
but isn't that just Any?
mmm
no, sorry. ignore me. too early
but if you can clarify what you're looking for with a practical example, it might help
you are helping for free, no need to say sry
well
yeah honestly typing.Any seems accurate
i just thought of smth like this
# typevar stuff
AnyClass = type[T]
def foo(obj: T) -> None: ...
Hey type hinting peeps. I posted a help thread, which is closed now but hopefully you can still see it: https://discord.com/channels/267624335836053506/1176670615495790672
Would appreciate if anyone has any knowledge on this!
!d typing.Literal
typing.Literal```
Special typing form to define βliteral typesβ.
`Literal` can be used to indicate to type checkers that the annotated object has a value equivalent to one of the provided literals.
For example...
It'll be using this
Thanks for the help! Got it to work now 
class Node():
def __init__(self, data):
self.data = data
self.next = None
def set_next_node(self, node: Node):
self.next = node
def get_next_node(self):
return self.next
Is it possible to set a type hint for a class within that class's definition? Getting an error on node: Node.
node: "Node"
Neat. Thanks.
can also do from __future__ import annotations at the top of the file
You can use typing.Self which now allows referencing the current class. Available since 3.11.
Well, it has different semantics
Actually isn't accepting Self as an argument kinda dubious? π€
class Node:
def set_next_node(self, node: Self) -> None:
self.next = node
class MySuperNode(Node):
...
super_node = MySuperNode(1)
other_super_node = MySuperNode(2)
normal_node = Node(3)
super_node.set_next_node(other_super_node) # 1. ok, as expected
super_node.set_next_node(normal_node) # 2. error, as expected
def f(hmm: Node) -> None:
hmm.set_next_node(normal_node) # obviously ok
f(super_node) # also ok, but this is the same as 2.
Yeah it's hella unsound
@oblique urchin what do you think?
yes that seems dubious. I guess we didn't think hard enough when writing PEP 673: https://peps.python.org/pep-0673/#use-in-parameter-types
Python Enhancement Proposals (PEPs)
oh wait you actually sponsored it
I just pinged you as an authoritative typing person lol
guys
def method(obj, *args, **kwargs):
def wrapper(func):
return func(obj, *args, **kwargs)
return wrapper
class Foo:
def func(self):
print(self.x)
foo = Foo()
@method(foo, 1)
def __init__(self, x) -> None:
self.x = x
foo.func()``` can you all type hint this for me but in a very cursed way
π€ π€
this code does not work
it works
i just wanna add type hints but in a cursed way
i got a friend who is a python coder, and i wanna troll him
do you have the docs
!d typing.ParamSpec
class typing.ParamSpec(name, *, bound=None, covariant=False, contravariant=False)```
Parameter specification variable. A specialized version of [type variables](https://docs.python.org/3/library/typing.html#typevar).
In [type parameter lists](https://docs.python.org/3/reference/compound_stmts.html#type-params), parameter specifications can be declared with two asterisks (`**`):
```py
type IntFunc[**P] = Callable[P, int]
``` For compatibility with Python 3.11 and earlier, `ParamSpec` objects can also be created as follows:
```py
P = ParamSpec('P')
``` Parameter specification variables exist primarily for the benefit of static type checkers. They are used to forward the parameter types of one callable to another callable β a pattern commonly found in higher order functions and decorators. They are only valid when used in `Concatenate`, or as the first argument to `Callable`, or as parameters for user-defined Generics. See [`Generic`](https://docs.python.org/3/library/typing.html#typing.Generic) for more information on generic types.
PEP646 is better if you want truly cursed annotations imo
Python's typing cannot even begin to compete with the curses of some TypeScript
i play classical guitar as well
thanks
lol
ngl im kinda not understanding paramspec
Mapping camelCase to snake_case in records at the type level is what exploded my mind
From a test I wrote as part of a mypy PR today:
from typing import Unpack, Tuple, Any
def good(a: int, b: str, *args: Unpack[Tuple[Unpack[Tuple[Any, ...]], int]]) -> int: ...
oh, is it supported actually?
Yuss, the latest version supports PEP646!
yes but you have to write it with Unpack
Yeah that should be fine, it's specified as part of PEP646
i dont think thats valid tho
well, yes, with the beauty of Unpack of course
!e
print(tuple[*tuple[str, ...]])
@trim tangle :white_check_mark: Your 3.12 eval job has completed with return code 0.
tuple[*tuple[str, ...]]
@trim tangle :white_check_mark: Your 3.12 eval job has completed with return code 0.
[*tuple[str, ...]]
I uhh I suppose yeah, though this^ looks kinda cursed
from typing import Callable
def method(obj, *args, **kwargs):
def wrapper[T, **P](func: Callable[P, T]) -> T:
return func(obj, *args, **kwargs)
return wrapper
class Foo:
def func(self):
print(self.x)
foo = Foo()
@method(foo, 1)
def __init__(self, x) -> None:
self.x = x
foo.func()```
the wrapper func
is it well annotated
.uwu ```py
from typing import Unpack, Tuple, Any
def good(a: int, b: str, *args: Unpack[Tuple[Unpack[Tuple[Any, ...]], int]]) -> int: ...
fwom typing impowt unpack, tupwe, any
def good(a: i-int, b: stw, *awgs: unpack[tupwe[unpack[tupwe[any, ...]], i-int]]) -> int: . ^^ ΚwΚ
.uwu
Might need Concatenate as well to account for obj.
yeah
Hello, using a type hint like
myfunct(x: pd.Series[int]), raises a TypeError.: type Series is not subscriptable. What can I do?
I have read about some solutions:
- adding from future import annotations
or - writing like x:"pd.Series[int]"
Which of the two is more appropriate?
I'm building something that requires python>=3.9, is that fine?
thanks. I'll ask you one more question if possible
ts1: Union[np.ndarray[Union[int, float]], pd.Series[Union[int, float]]]
With this type hint I wanted to express that I need the parameter of my function to me a numpy array or pandas series, containing either integer or float values. Does the type hint I wrote reflect my intention semantically?
however the fact its not subscriptable might mean Series[int] doesn't actually have any meaning to a type checker, but i don't know much about pandas so i might be wrong about that
yeah I was told it doesn't, so I guess it makes no sense to discuss it. I was just trying to write some good documentation for my code through type hints/docstrings. I already handle input validation myself
Assuming pandas objects just arent supported for type hints which I assume they are not, then tools like mypy would simply ignore that hint and not enforce it?
Never mind I misread, sorry for the ping
is it possible to somehow typehint that the variable is a dataclass?
_typeshed.DataclassLike should do it
_typeshed?
is it ok to use import _typeshed inside of if TYPE_CHECKING: in .py files?
yes but prefer to only use things marked stable
There's a version with runtime types on pypi
I forgot what it's called though
useful_types
!pypi useful_types
Thanks!
untapped market
?
!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...
you can put that on a base class, decorator, meta class, etc
i cant win
Could drop the type-hint and throw in assert isinstance(user, interactions.Member) after that line.
Could also use typing.cast
imo it's better to use assert in this case, because you can run python with the -O option
Your comment says "DM Handler", but you want a Member. Member implies that the user is in the context of a guild - so you will always get User for DMs.
Hi all, hopefully a quick one :)
I'd like to type my ABC as requiring a field implementation (i only care about get access). Usually I'd do this with an abstract property:
class Base(ABC):
@property
@abstractmethod
def name(self) str: -> ...
class Impl(Base):
name: str
but with pylance set to strict, I get the error: "name" incorrectly overrides property of same name in class "Base"
How would you do this?
That's now allowed because it breaks this:
def f(base: type[Base]) -> None:
assert isinstance(f.name, property)
That makes sense, thanks. Is there a way to require an abc implementation to have a field? I would use a protocol, but my ABC has shared logic in it (that depends on the field)
**
is this irony
The dirty little secret is that some of the most dynamic code in the Python standard library is actually in the typing module
Don't tell anybody
the logging framework is pretty dynamic, it basically uses each LogRecord instance as an arbitrary mapping
types.py has a couple of hacks
and there is def __iter__(self): if False: yield somewhere in the stdlib, dont remember where exactly
That's actually a pretty common pattern to create an empty generator function, though β I don't know if that one even counts as a hack!
@trim tangle :white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | 3 0 RETURN_GENERATOR
002 | 2 POP_TOP
003 | 4 RESUME 0
004 |
005 | 5 6 LOAD_CONST 1 (())
006 | 8 GET_YIELD_FROM_ITER
007 | 10 LOAD_CONST 0 (None)
008 | >> 12 SEND 3 (to 22)
009 | 16 YIELD_VALUE 2
010 | 18 RESUME 2
011 | 20 JUMP_BACKWARD_NO_INTERRUPT 5 (to 12)
... (truncated - too many lines)
Full output: https://paste.pythondiscord.com/LESRUUU45IF5HICTSI4AGG4K24
oh damn, that's a lot of bytecode
I know the following probably raises warnings, but
!e ```py
import dis
@dis.dis
def f():
return
yield
@brisk hedge :white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | 3 0 RETURN_GENERATOR
002 | 2 POP_TOP
003 | 4 RESUME 0
004 |
005 | 5 6 RETURN_CONST 0 (None)
006 | >> 8 CALL_INTRINSIC_1 3 (INTRINSIC_STOPITERATION_ERROR)
007 | 10 RERAISE 1
008 | ExceptionTable:
009 | 4 to 6 -> 8 [0] lasti
I think it's less performant, though I also quite like it in terms of style π
This is probably why it's less performant π
def f():
# empty iterator/generator
if False:
yield
This one also works with async generators
Hey guys, can I "dynamically lint" a function? What I mean is:
def function_router(*args, **kwargs):
if kwargs.get(some_param) == 'some_value':
return function_a(*args, **kwargs)
return function_b(*args, **kwargs)
Let's say function_a and function_b have the exact same parameters, how can I make it so that when calling function_router your linter will recognize the correct params?
!d typing.ParamSpec this?
class typing.ParamSpec(name, *, bound=None, covariant=False, contravariant=False)```
Parameter specification variable. A specialized version of [type variables](https://docs.python.org/3/library/typing.html#typevar).
In [type parameter lists](https://docs.python.org/3/reference/compound_stmts.html#type-params), parameter specifications can be declared with two asterisks (`**`):
```py
type IntFunc[**P] = Callable[P, int]
``` For compatibility with Python 3.11 and earlier, `ParamSpec` objects can also be created as follows:
```py
P = ParamSpec('P')
``` Parameter specification variables exist primarily for the benefit of static type checkers. They are used to forward the parameter types of one callable to another callable β a pattern commonly found in higher order functions and decorators. They are only valid when used in `Concatenate`, or as the first argument to `Callable`, or as parameters for user-defined Generics. See [`Generic`](https://docs.python.org/3/library/typing.html#typing.Generic) for more information on generic types.
Reading the documentation I'm not quite sure how to use it?
Sounds a bit more like you're looking for overloads that *indicate different return types based on a specific kwarg's value/type.
you might be able to jam enough typing.overloads on top to get the desired outcome, assuming the parameters are the same, but the return value is different and that's what you're interested in (and some_param is a string literal that won't change)
ParamSpecs very quickly hit limits when confronted with named arguments or any modification of them that isn't the very spare addition of positional arguments via Concatenate
I have a system that basically does the reverse by using a decorator to register routable functions, sadly even though Literals are usable because I route by strings I don't think it's possible to dynamically construct a Literal that anything will actually parse.
!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...
Is there a standard practice for documenting type variables when using Sphinx reST?
#: Doc
ADescriptiveTypeVarName = TypeVar("ADescriptiveTypeVarName")
with 3.12 syntax, you'd just describe it in the body, like below. Basically just provide people information where it makes sense to.
class G[T]:
"""
...
Parameters
----------
typ: T
describe how this is used
"""
def __init__(self, typ: T):
...
cool, ty. I forget if there was any config to enable reading #: in conf.py, but it sounds like it's on by default.
Do you know if there's a backport of this in some module?
pretty sure #: is just a default thing for sphinx
Can't be backported as I understand it.
Ah, a shame. Ty though.
for what it's worth, I actually prefer not using the 3.12 syntax if the typevar itself should be documented in any detail
it's much easier to attach the right information to it that way
little rare that that's the case (needing more than minimal detail on it) though, only come up twice personally.
I misunderstood what you meant by 3.12 syntax.
I've been away from Python for 2 weeks and I'm slowly reacquinting myself.
Also, I haven't used 3.12 much aside from building it and testing compatibility for a library. π
ah, right, I meant that the 3.12 generic syntax doesn't really provide an opportunity to attach a doc to the symbol, so you need to do it in the class doc, but since it can't be used outside the class in that case (no re-use) it's fine except when there's something specific worth noting extra.
There's a proposal for Annotated being used to attach docs, but there's a lot of problems with that proposal and it seems unlikely to be accepted.
Interesting, ty.
@frigid jolt this seems like what you were asking about or proposing, at least in a way
ok ty
Sorry for being late to the party, but here you go.
Not cursed enough.
well, itβs fully typed, so technically it canβt get more cursed. But because python type checkers arenβt βbidirectionalβ enough, it actually doesnβt work if you ever try to use this method function.
fully typed python = cursed
I disagree. I think it'd be an interesting but different language.
the list vs List distinction makes no sense to me
they could have made types and classes less different and simplified things for everyone
but the way things are, we're committed to some types having lower case names while others aren't.
we fixed that years ago
!pep 585
does this look right
from interactions import Permissions
from typing import Any
PermDict = dict[str, tuple[Permissions] | tuple[()]]
RoleDict = dict[str, dict[Any, Any] | PermDict]
ROLES: dict[str, RoleDict] = {
"staff": {
"allow": (
Permissions.VIEW_CHANNEL,
Permissions.SEND_MESSAGES,
Permissions.EMBED_LINKS,
Permissions.ATTACH_FILES,
Permissions.ADD_REACTIONS,
Permissions.READ_MESSAGE_HISTORY,
),
"deny": (),
},
"bot": {
"allow": (Permissions.VIEW_CHANNEL,),
"deny": (),
},
"ignore_messages": {},
"everyone": {
"allow": (),
"deny": (Permissions.VIEW_CHANNEL,),
},
}
the type hints
dict[Any, Any] was what i could come up with for an empty dict
i just dont like the auto hinting saying Unknown
from typing import Never
type EmptyDict = dict[Never, Never]
oh dang
you can just remove the dict[Any, Any] I think, an empty dict fulfills dict[str, ...]
this seems to be a python 3.12 feature and im on 3.11
only the type part
you can still use Never
well its not working out
you want tuple[Permissions, ...] instead of tuple[Permissions]
tuple[Permissions] is a tuple of exactly one
you truncated the error message, so hard to say what the issue is
Expression of type "dict[str, dict[str, tuple[Literal[Permissions.VIEW_CHANNEL], Literal[Permissions.SEND_MESSAGES], Literal[Permissions.EMBED_LINKS], Literal[Permissions.ATTACH_FILES], Literal[Permissions.ADD_REACTIONS], Literal[Permissions.READ_MESSAGE_HISTORY]] | tuple[()]] | dict[str, tuple[Literal[Permissions.VIEW_CHANNEL]] | tuple[()]] | dict[str, PermDict]]" cannot be assigned to declared type "dict[str, RoleDict]"
"tuple[Literal[Permissions.VIEW_CHANNEL], Literal[Permissions.SEND_MESSAGES], Literal[Permissions.EMBED_LINKS], Literal[Permissions.ATTACH_FILES], Literal[Permissions.ADD_REACTIONS], Literal[Permissions.READ_MESSAGE_HISTORY]]" is incompatible with "PermDict"
"tuple[()]" is incompatible with "PermDict"
"tuple[Literal[Permissions.VIEW_CHANNEL]]" is incompatible with "PermDict"
"tuple[()]" is incompatible with "PermDict"
"tuple[()]" is incompatible with "PermDict"
"tuple[Literal[Permissions.VIEW_CHANNEL]]" is incompatible with "PermDict"PylancereportGeneralTypeIssues
seems like you have one too many levels of dict[str, ...]
shit youre right
okay
from interactions import Permissions
PermDict = dict[str, tuple[Permissions, ...] | tuple[()]]
RoleDict = dict[str, PermDict]
ROLES: RoleDict = {
"staff": {
"allow": (
Permissions.VIEW_CHANNEL,
Permissions.SEND_MESSAGES,
Permissions.EMBED_LINKS,
Permissions.ATTACH_FILES,
Permissions.ADD_REACTIONS,
Permissions.READ_MESSAGE_HISTORY,
),
"deny": (),
},
"bot": {
"allow": (Permissions.VIEW_CHANNEL,),
"deny": (),
},
"ignore_messages": {},
"everyone": {
"allow": (),
"deny": (Permissions.VIEW_CHANNEL,),
},
}
hm it seems like i can remove the empty tuple part too
h a h. when all that work becomes useless cause i realized i wanna turn it into a json file
i mean i learned stuff
Hi, it seems like you're in the wrong channel. This channel is for the Black autoformatter project.
huh
Discussion of type hints, function annotations, and type checking, including tools such as mypy, pyright and pydantic.
oh lol
how did I think this was #black-formatter ????
I am so sorry! I evidently need a nap π€
!ban @leaden oak smh using the wrong channel
:x: @trim tangle, you may not ban someone with an equal or higher top role.
I'm fairly certain I'm just being dense here, but Google isn't turning up anything useful.
High-level problem: My return type is Iterator[Whatever]. I'm returning a Generator[Whatever], and this is marked as incompatible with Iterator[Whatever].
I've got a method where you can either provide a single value as an input, or a Sequence of values. If you provide a single value, you get a single result. If you provide a Sequence of values, you get an Iterator with one result for each input value. I've added typing.overload definitions to indicate this.
The problem is that I want to return a generator for the second use-case; when I try to return it, I get a typing warning.
FooDict = dict[int, str]
class Bar:
# (Just assume that list(Bar) returns a list of str)
@typing.overload
def foo(self, i: int) -> FooDict: ...
@typing.overload
def foo(self, i: Sequence[int]) -> Iterator[FooDict]: ... # Typing warning on this line
def foo(self, i):
if isinstance(i, int):
return {i: list(self)[i]}
if isinstance(i, Sequence):
return ({x: list(self)[x]} for x in i)
raise TypeError
This gives a typing warning:
Overloaded implementation is not consistent with signature of overload 2
Function return type "Iterator[FooDict] is incompatible with type "dict[int, str] | Generator[dict[int, str], None, None]"
"Iterator[FooDict]" is incompatible with "dict[int, str]"
"Iterator[FooDict]" is incompatible with "Generator[dict[int, str], None, None"
The documentation for typing.Generator even says "Alternatively, annotate your generator as having a return type of [...] Iterator[YieldType]", so I'm not sure what I'm doing wrong here.
The error goes away if I adjust the return type for the second overload to Generator[FooDict, None, None], but it's not clear to me why Iterator[FooDict] doesn't work here.
I'd guess Pyright strict doesn't implicitly widen to Iterator from Generator on overloads unless the actual function signature, with the union of the overloads, has Iterator in it.
yep, confirmed, that zaps the error (in my real code, moreover)
Same, but I didn't want to be that guy that barely understands type theory and loudly claims they've found a bug π
I'll file an Issue, then. Cheers for the alteration - very much appreciated.
No problem. I have no idea whether the inference we expect here would be unsound, honestly; I'm just relaying what I know makes the code compatible with the tool.
Still worth asking about in an official issue, of course.
I think type checkers are like 10% solid type theory and 90% hacks and special cases
Pyright is comparable in code size to a Haskell compiler
I think it's a lot larger than JHC and on the same order of magnitude as GHC. But I might have calculated too many lines in pyright
https://github.com/microsoft/pyright/issues/6616 for anyone interested.
eric is quick
I think that response makes sense in this case. By doing typing overloads, you are saying that you want to provide extra information, so you get held to a stricter standard. I can also imagine a counterexample where you overload with both a generator and iterator return, where the implicit widening would generate false positives.
is there a way to inherit parameters from another function with the type hints?
like
def base_(*, a: int, b: int, c: str):
pass
def derived_(*, extras, **inherited_from_base_):
base_(extras=extras, **inherited_from_base_)
did you mean that extras doesn't work?
nvm i found a way to not inherit lol
oki
A fun new CPython bug just dropped:
>>> from typing import Annotated
>>> Annotated[str, 1]
typing.Annotated[str, 1]
>>> Annotated[str, True]
typing.Annotated[str, 1]
cache problems?
damn...
Friends don't let friends add random caches everywhere
you'll always get some cursed behaviour out of it in the end
is there a type-aware lru_cache in yhe stdlib?
tbh making bool a subclass of int causes a lot of issues
like aio-libs/yarl#746
>>> url = yarl.URL("http://example.com")
>>> url2 = url.with_port(True)
>>> url2
URL("http://example.com:True")
>>> url2.port
*ValueError*
well, you could argue storing a URL as a string and then extracting stuff to get the port is also a mistake
Yeah, functools.lru_cache has a typed optional keyword argument (which defaults to False)
int & ~bool might solve the problem
no, it wont
somebody can pass custom int subclass with custom __str__ that will break str->int conversion later
does something like Exact[int] make any sense?
well, it won't really compose with all the other existing code
It just needs to call int.__str__(port) rather than a subclass
tbh making bool a subclass of int causes a lot of issues
Sounds like an easy problem to fix π
yeah
x: int = MyInt()
takes_exact_int(x) # no error
- overriding
__str__in int subclass is not a great idea (but bool and enum.IntFlag do this, and we cant change it) - relying on
__str__to construct values is also not a great idea
Is this greyed out text considered a type of type hinting?
Thank you! I couldn't even figure out what they are called.
i have it on for returns
It must be an extension that I don't understand running in VS Code
Okay, I see. I will turn that off. I think I get it now but don't quite understand why it's suggesting list[Any]. I guess I need to circle back to list types.
I believe itβs because re.findall can return either a list[str] if there are 0/1 capture groups, or a list[tuple[str, str]] if there are multiple groups.
Toy example:
def a(a: bool, b: bool) -> int:
return {
(False, False): 1,
(True, False): 2,
(False, True): 3,
(True, True): 4,
}[a, b]
print(a(True, True))
Why the error? Seems fine to me.
Here is a github issue where someone had the same problem, and it goes more in depth on why pyright does things that way, but the solution in there works for your code as well. ```py
def a(a: bool, b: bool) -> int:
responses: dict[tuple[bool, bool], int] = {
(False, False): 1,
(True, False): 2,
(False, True): 3,
(True, True): 4,
}
return responses[a, b]
print(a(True, True))```
Thanks, I understand it know and the solution worked.
Same here. Only for returns
Is there an existing discussion on not typing _ (usually to indicate discarded values during tuple destructuring)
data: list[tuple[str, float, int]]
for _, _, val in data:
pass
This currently raises an error:
iirc pyright ignores them, as expected
hey there, i like unpacking values into a union as this is often nescessary as so:
Union[ *set( dataFormatForCommitType.values())]
this parses at the cpython interpreter, however the lsp gives a warning which prevents it from giving any other useful output:
are there any annotations i can use to tell the checker to ignore this?, the checker is pylsp with pyflakes
try # type: ignore comment. Do note that this is not how you should be using typehints tho. The way they are made is that any valid expression can be passed, but that doesn't mean that it has a defined meaning
!e
x: list[print("This typehint doesn't make sense")] = ...
@viscid spire :white_check_mark: Your 3.12 eval job has completed with return code 0.
This typehint doesn't make sense
thanks, will try, i had a feeling it was not a well defined method however that type can take any of the values from that dict
nah still doesnt parse, probably causing an error to be raised in the checker
it will never be defined like that
unpacking in typehints acts on other types
a set instance is not a type
and the result of dict.values is also not a type
are you aware of a method of using a global dictionary~s values in a union for type hinting?
I have a Game class that has some overall attributes which represents stats for the given game, and then I have a Gamemode class that has specific stats for a given gamemode which forms part of the game. In the Gamemode class, I have more specific stats for the different types of difficulties the gamemode can have, as well as overall stats. All of the stats of the gamemode class are also present in the overall Game stats. However, Game itself doesn't have specific stats depending on the difficulty.
class Gamemode:
def __init__(
self,
room: Optional[Literal["one", "two", "three"]],
difficulty: Optional[Literal["normal", "medium", "hard"]]
) -> None:
self.best_time: int = ...
self.completions: int = ...
# etc.
if room is not None and difficulty is None:
self.normal = Gamemode(room=room, difficulty="normal")
self.medium = Gamemode(room=room, difficulty="medium")
self.hard = Gamemode(room=room, difficulty="hard")
class Game(Gamemode):
def __init__(self) -> None:
super().__init__(room=None, difficulty=None)
self.total_games_played: int = ...
# etc.
self.room_one: Gamemode = Gamemode(room="one", difficulty=None)
self.room_two: Gamemode = Gamemode(room="two", difficulty=None)
self.room_three: Gamemode = Gamemode(room="three", difficulty=None)
Is there any way I can show that Game will never have the normal, medium, and hard attributes?
Have you considered reversing the class hierarchy? EDIT: On second glance, maybe not as easy as that.
generally for having different attributes based on state, i would look towards representing each of those states as classes, e.g. ```py
class Gamemode:
...
class NormalGamemode(Gamemode):
... # normal-specific stats
class MediumGamemode(Gamemode):
... # medium-specific stats
class HardGamemode(Gamemode):
... # hard-specific stats
class DifficultyGamemode(Gamemode):
def init(self, *args, **kwargs) -> None:
super().init(*args, **kwargs)
self.normal = NormalGamemode(*args, **kwargs)
self.medium = MediumGamemode(*args, **kwargs)
self.hard = HardGamemode(*args, **kwargs)
class Game(Gamemode):
...``` or by using composition, you might have GameStats, NormalStats containing GameStats, RoomStats containing GameStats+NormalStats+MediumStats+HardStats, etc.
I'm not entirely sure what you mean by composition, but for the first part NormalGamemode, MediumGamemode, and HardGamemode would all be exactly the same classes, so I don't think that would be my best route to take. There is really no difference when it comes to the attributes, the values are the only ones changing.
You can use a car game for example:
Game would be the stats for all cars. So like, total hours driven, total amount of cars, total etc.
Gamemode would be the different stats for the different types of cars. So there would still be total hours driven and total amount of cars, they would just be for, say, Ferrari cars, or just for Nissan cars.
Then the difficulty could be for the model of the car. And so on so forth.
So Game, Gamemode and the difficulty are all exactly the same, with exactly the same attributes. They only thing that changes is how specific the stats are.
by composition, i mean that instead of inheriting the stats directly, you store the stats objects in your classes, i.e. ```py
class GameStats:
def init(self) -> None:
self.best_time = 0
self.completions = 0
# etc.
class DifficultyStats:
def init(self) -> None:
self.total = GameStats()
self.normal = GameStats()
self.medium = GameStats()
self.hard = GameStats()
class Room:
def init(self, name: str) -> None:
self.name = name
self.stats = DifficultyStats()
class Game:
def init(self) -> None:
self.stats = GameStats()
self.room_one = Room("one")
self.room_two = Room("two")
self.room_three = Room("three")
game = Game()
game.stats.best_time
game.room_one.stats.total.best_time
game.stats.normal.best_time # Member "normal" is unknown```
Hmm I guess that might work
Upon some further looking, I found typing.Never, so I thought I could maybe override the difficulties in Game with that?
I think the stats would be better off as a dictionary, maybe something like dict[GameDifficulty, GameStats]
because you might want to e.g. iterate over the results
I'm going to assume this would be the right channel to post this question in but it's not let me know and I'll remove it
my question is simple, i was just curious to knowing the difference between basic and strict when it comes to type checkers. e.g ruff etc.
that depends on particular tool you are using
please read their documentation
their documentation.
Ruff is not a type checker. In general, type checkers can be configured in a gradual way, where the stricter you get, the more issues the type checker catches, but also the more changes you may have to make to satisfy the type checker
gotcha gotcha, thank you.
sorry for the assumption, was just curious.
never got in-depth understanding of them.
Does anyone know if there's a way to type narrow through a constant property, similar to something like this:
from typing import Literal
class A:
is_a = True
class B:
is_a = False
def foo() -> A|B:
return A()
value = foo()
if value.is_a:
assert type(value) is A
I've experimented with TypeGuards and Literals, but I can't get anything to work
You probably want to annotate them as ClassVar[Literal[True]], and make the class @final. I believe Pyright knows to do this, not sure about Mypy though.
pyright doesn't work for this
from typing import Literal, ClassVar, reveal_type
class A:
is_a: ClassVar[Literal[True]] = True
class B:
is_a: ClassVar[Literal[False]] = False
def foo() -> A | B:
return A()
value = foo()
if value.is_a:
reveal_type(value)
Try using an explicit is True:?
that does it
Probably better to use something other than a bool, in case you need a third class at some point.
also == does it
so using truthiness won't narrow it with pyright, but using == or is will
It's not on the massive list of type guards:
https://microsoft.github.io/pyright/#/type-concepts-advanced?id=type-guards
uh, truthiness is on there
x(wherexis any expression that is statically verifiable to be truthy or falsey in all cases)
maybe "in all cases" is the kicker
Truthiness, but not obj.x.
obj.x is an expression
Hello, I've been trying to play around with pytype, I figured that pytype uses a typegraph representation of a program's AST. I was curious as to how one can parse the typegraph generated AST (like you would a normal python AST) and modify the types of every node of the AST. Is there a definitive approach one can take for this purpose?
What's the proper way of type-hinting specialisations of mixins? IIRC you can't re-generic a Self, so there's presumably a better way of doing it
you'd need HKT (Higher Kinded Types) support for Self[T], currently there's no way you need to do MyClass[T]
Dang
yeah, it's quite annoying
Do type checkers still correctly hint it if I do self: MyMixin[T] -> MyMixin[T] or does it erase the concrete type in favour of the mixin?
this tells the type checker the returned type will be the mixin class, ereasing the concrete type
Dang
Wait would Self work on a specialisation method? I've never thought to try it
(also apparently you're supposed to define mixins in terms of Protocol, which I didn't know)
i need Self[Self] for my thing :-(
Lmao what
yeah, we all do
Is that like Box<Box<T>> or something
mind giving an example here, I'm not sure what you mean
not necessarily, it depends on what you're doing
class IteratorMixin(Generic[T]):
def map_each(self: Self[Iterable[T]], fn: Callable[[T], U]) -> Self[Self[T]]:
self.__class__(map(lambda itm: self.__class__(map(fn, itm)), self))
you can't do self: Self[Iterable[T]] you can do self: IteratorMixin[Iterable[T]] though
similarly with the return hint
so, you can do specializations in this way, so that only IteratorMixin instances that have Iterable[T] as their generic attr can be used with this method (well, at least to the type-checker), but yeah, you will loose the information about the actual type self should be, and the returned type can then only be IteratorMixin[IteratorMixin[T]]
HKT issue: https://github.com/python/typing/issues/548
ooh, new pyright update
Added a new typeCheckingMode called "standard". It's a subset of "strict" but a superset of "basic". It is the new default mode, and it should cover all of the required checks for conformance with the Python typing standard.
Pyright is being annoying - what's wrong with this? wtf does _ | Unknown mean??? https://paste.pythondiscord.com/7MRA Error:
Return type of generator function must be compatible with "Generator[_ | Unknown, Any, Any]"
Β Β Type "_ | Unknown" cannot be assigned to type "BaseClient"
Β Β Β Β "_" is incompatible with "BaseClient" (reportGeneralTypeIssues)
Never used boto3, but based on the fact that boto3-stubs exists you might need it to be able to use pyright with it. One thing that might also work is explicitly typing a variable, then returning that py client: BaseClient = boto3.client("s3", region_name="us-east-1") yield client
No idea. Might want to ask on the pyright repo's discussions page or issue tracker.
I've instead opted to adding # pyright: ignore to the end of the line, since it's typed correctly but not recognized as such. thanks for the idea though
Return type of generator function must be compatible with "Generator[_ | Unknown, Any, Any]"
Β Β Type "_ | Unknown" cannot be assigned to type "BaseClient"
Β Β Β Β "_" is incompatible with "BaseClient" (reportGeneralTypeIssues)
what's your question, do u not understand this error.
See their previous message above for context.
ive seen the bottom one done before however is the top one reccomended?
queue = queue.PriorityQueue[tuple[float, str]]()
queue: queue.PriorityQueue[tuple[float, str]] = queue.PriorityQueue()
it seems like a simpler approach
pyright seems to accept both under its "strict" mode
I try to use the top one but barely see it in libraries I look into
using the top one incurs extra runtime costs, and should generally be avoided.
ah, that's a good point.
can you explain this further please, what is the difference between each approach?
queue = queue.PriorityQueue[tuple[float, str]]()
Looks up the name queue, accesses an attribute of it PriorityQueue, attempts to get an item from that class using a key that also involves a runtime getitem on a type... (And gets back the class itself if used as intended), then initializes it. The extra steps in bold happen at every execution.
queue: queue.PriorityQueue[tuple[float, str]] = queue.PriorityQueue()
The steps from the previous version in bold only happen at annotation time (Specifically, the point in time when that annotation is evaluated), which can be never in some cases (from __future__ import annotations)
nice explanation, thank you!
!e To be more specific, here's what it compiles to:
import dis
@dis.dis
def f():
x=queue.PriorityQueue[tuple[float, str]]()
@chrome hinge :white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | 2 0 RESUME 0
002 |
003 | 4 2 LOAD_GLOBAL 1 (NULL + queue)
004 | 12 LOAD_ATTR 2 (PriorityQueue)
005 | 32 LOAD_GLOBAL 4 (tuple)
006 | 42 LOAD_GLOBAL 6 (float)
007 | 52 LOAD_GLOBAL 8 (str)
008 | 62 BUILD_TUPLE 2
009 | 64 BINARY_SUBSCR
010 | 68 BINARY_SUBSCR
011 | 72 CALL 0
... (truncated - too many lines)
Full output: https://paste.pythondiscord.com/XVJR6EPUWIQETGHVM53J7DN4ZA
that's 6 extra instructions (lines 005 to 010) - name lookups, class __getitem__... It doesn't matter in most code, but I wouldn't want to do this in some hot loop.
TIL pyright can do this: ```py
class X[T]:
a: T
class Y:
b: X[t.Self]
class Z(Y):
...
z = Z()
reveal_type(z) # Z
reveal_type(z.b) # X[Z]
reveal_type(z.b.a) # Z
reveal_type(z.b.a.b) # X[Z]
reveal_type(z.b.a.b.a) # Z
class X[T]:
n: T
class Y:
a: X[t.Self]
class Z(Y):
...
b = Z()
reveal_type(b.a.n.a.n.a) # π
:incoming_envelope: :ok_hand: applied timeout to @peak hollow until <t:1702054131:f> (10 minutes) (reason: duplicates spam - sent 4 duplicate messages).
The <@&831776746206265384> have been alerted for review.
I have this class:
class NumberPattern(itertools.cycle): pass
pat = NumberPattern([0,1])
assert next(pat) == 0
assert next(pat) == 1
assert next(pat) == 0
val = next(pat)
Can I annotate this, so the IDE knows NumberPattern can only take Iterable[int] and val is int?
I tried:
class NumberPattern(itertools.cycle[int]): pass
and it works in the IDE, but then running the code gives me:
TypeError: type 'itertools.cycle' is not subscriptable
I think I'm missing something obvious here and am lost.
if typing.TYPE_CHECKING:
base = itertools.cycle[int]
else:
base = itertools.cycle
class NumberPattern(base): pass
tbh, this looks like a bug
i think itertools.cycle should have __class_getitem__
I figured something like that'd work, even though it looks like a hack.
Thank you :)
i opened an issue: https://github.com/python/cpython/issues/112896
Would be reasonable to raise an issue to make list act as a callable in this context rather than a type? Like how int works
def to_ints(nums: list[str]) -> list[int]:
return list(map(int, nums)) # Ok
def to_chars(strings: list[str]) -> list[list[str]]:
# Err: Argument 1 to "map" has incompatible type "type[list[Any]]"; expected "Callable[[str], list[_T]]
return list(map(list, strings))
(This is mypy)
I'd still use a comprehension here: you're doing list(map(...)) which is slower than [list(x) for x in strings]
my natural question is how do you know? Are comprehensions always faster?
They made them much faster in 3.12 saves two function calls
interesting, thanks!
But it's weird mypy doesn't pass here
I'm guessing because ints have these two overloads defined
https://github.com/python/mypy/blob/master/mypy/typeshed/stdlib/builtins.pyi#L224-L228
whereas list is just
https://github.com/python/mypy/blob/master/mypy/typeshed/stdlib/builtins.pyi#L920-L924
mypy/typeshed/stdlib/builtins.pyi lines 224 to 228
class int:
@overload
def __new__(cls, __x: ConvertibleToInt = ...) -> Self: ...
@overload
def __new__(cls, __x: str | bytes | bytearray, base: SupportsIndex) -> Self: ...```
`mypy/typeshed/stdlib/builtins.pyi` lines 920 to 924
```pyi
class list(MutableSequence[_T]):
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, __iterable: Iterable[_T]) -> None: ...```
That sounds a bug anyway... list should match Callable[[str], list[str]]
What does
x: Callable[[str], list[str]] = list do?
that passes
I also stumped by this error:
from typing import Iterable, TYPE_CHECKING
if TYPE_CHECKING:
from _typeshed import SupportsRichComparisonT, SupportsRichComparison
def fake_sorted(
iterable: Iterable[SupportsRichComparisonT],
) -> list[SupportsRichComparisonT]:
return list(iterable)
def fake_sorted_strict(
iterable: Iterable[SupportsRichComparison],
) -> list[SupportsRichComparison]:
return list(iterable)
def get_data() -> Iterable[tuple[int, int]]:
return [(0, 0)]
# Err: mypy-bug.py:23: error: Argument 1 to "map" has incompatible type "Callable[[Iterable[SupportsRichComparisonT]], list[SupportsRichComparisonT]]"; expected "Callable[[Iterable[tuple[int, int]]], list[SupportsRichComparisonT]]" [arg-type]
map(fake_sorted, [get_data(), get_data()])
# Ok
map(fake_sorted_strict, [get_data(), get_data()])
Is this a bug in mypy or is it in typeshed?
Seems fine, and pyright interprets it fine, imo. Might be an mypy bug where itβs not correctly correlating the TypeVar?
β¦ markdown, why.
it's the other way around, not (a)[url], but [a](url)
I tried both and still failed, lol. Might just have missed a symbol due to being on mobile.
you also shouldn't have the arrow brackets there
There we go. Cheers. Wanted to prevent an embed in case one would be autogenerated, but I guess it wasnβt necessary.
def g(): yield
print(g().__qualname__)
Cannot access member "__qualname__" for type "Generator[None, Any, None]"
IDE claims __name__ and __qualname__ don't exist on generator or async generators, but they do.
Works fine for functions and async functions.
!e
def g(): yield
print(g.__qualname__)
@viscid spire :white_check_mark: Your 3.12 eval job has completed with return code 0.
g
!e
def g(): yield
print(g().__qualname__)
print(g.__qualname__)
@trim tangle :white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | g
002 | g
le huh
ah man, I tried to file a report, and the report asked to provide a playground link and it worked in the playground!
Looks like I needed to update mypy :(
Is there a way to make pip scream at me for having outdated packages?
or should I use my system package manager to install mypy?
Not a way built into pip, to my knowledge. You could manually run pip list --outdated though or set up a script to automatically run it.
async def f(): pass
print(f.__qualname__)
print(f().__qualname__)
What's surprising to me is, that the IDE correctly identifies both of these as having __(qual)name__.
this is weird
GeneratorType in typeshed/types.pyi has __(qual)name__
https://github.com/python/typeshed/blob/main/stdlib/types.pyi#L366
stdlib/types.pyi line 366
__name__: str```
that's not what gets used, type checkers infer collections.abc.Generator
@soft matrix opened some discussions about this recently
I suggest not doing that with pip itself, as some package versions may be incompatible. For global packages, I suggest using pipx.
if you use poetry, github's dependabot will open pull requests for most package updates
Oh, I made that suggestion under the assumption of being within a virtual environment. We agree on never doing that globally, lol.
I probably should've made that clear.
for non-global packages, use poetry, hatch, or pdm. Even pip-tools will work
this is unfortunately blocked by countless other issues
i also just dont really have the energy to work on typing things atm so if you want to see if you can get things going https://github.com/python/typing/issues/1480
though honestly might be something for the typing council to decide on
hi guys, tough question for me tho, which option is more incentivized?
Annotated[str, 'test'] | None or Annotated[str | None, 'test'] ?
checking equality resulted in False ofc, but following the documentation it all resolves to str | None in type checking
They mean different things. It depends on what the Annotated refers to
for example, using https://github.com/annotated-types/annotated-types, you might reasonably say Annotated[int, Gt(10)] | None (either None, or an int > 10), but it wouldn't make sense to say Annotated[int | None, Gt(10)]
because None can't be greater or smaller than an int
so I use pydantic and a common case I use Annotated is an optional field which needs validation, serialization or simply description
I will take an example like this
from pydantic import BaseModel, Field
class Foo(BaseModel):
spam: Annotated[str | None, Field(description="just a spam")]
could you go deeper in this example and help me figure out which way using Annotated fits more?
in this case Field applies to the whole field, so it should go on the outermost level
so mine sounds correct right? the same might goes for a validation/serialization in Annotated?
yes, what you wrote looks right to me
Validation could reasonably go at a deeper level. You'd basically say "this is either a None, or a str validated with this validator"
But haven't checked what pydantic supports there
I see, so it boils down to the final intention right?
but am I correct stating that both options are resolved as str | None in type checking?
Static type checkers are generally supposed to ignore Annotated[], yes
thank you so much for the answer, that clarified a lot for me
nicee
least insane type hint
I've never not seen a type checker spout this sort of nonsense
fix error's law: in a flexible type system, as the function is getting more dynamic its type signature starts to mirror its implementation
hopefully intersections can help make it a little less syntax soup. Describing as a composition of duck typing compatible protocols would make a lot of these flexible functions look more reasonable.
I think min is missing the same thing asyncio.gather, zip etc. are missing, wrapping every element of a TypeVarTuple into a generic
zip and map can't be fixed by just applying a generic with TypeVarTuples
gather, min, max, etc all could
they require type-velev Map, right?
-level* π
yeah
which would be neat in a few real world cases beyond some builtins too
Technically, zip operating on a homogenous set of homegnous iterators hsould "just work", but that's the simple case.
gather is defunkt use a TaskGroup and side effects to get results out
the result is an aggregate list of returned values.
A list though not a tuple I'm pretty sure
that error
Christ allmighty
yeah but typeshed types it as returning a tuple 
Ok now I'm not sure what it returns...
There are still a few cases to use gather rather than a task group, but most of them are super niche
Such as?
- (not niche) older python versions
- working with multiple event loops and transfering tasks to background loops/threads
Why would you need gather for 2?
Hi, I'm having an issue with pydantic model.
This is my JsonFeedOptions class.
class JsonFeedOptions(BaseModel):
address: Optional[AddressType] = None
signer: Optional[Union[AccountAPI, str]] = None
Type: Optional[FeedType] = None
And I'm trying to validate the data like this.
opts = {"signer": "abcd"}
options = JsonFeedOptions.model_validate(opts)
But got this error
> options = JsonFeedOptions.model_validate(opts)
E TypeError: validate() takes 2 positional arguments but 3 were given
Can you show the full traceback?
!paste
If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/
After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.
Is that the whole traceback?
Why does it say validate() takes 2 positional arguments but 3 were given? you're calling model_validate
In [1]: opts = {"signer": "abcd"}
In [2]: from bee_py.types.type import JsonFeedOptions
In [3]: JsonFeedOptions.model_validate(opts)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[3], line 1
----> 1 JsonFeedOptions.model_validate(opts)
File ~/.local/pipx/venvs/pdm/lib/python3.9/site-packages/pydantic/main.py:503, in BaseModel.model_validate(cls, obj, strict, from_attributes, context)
501 # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
502 __tracebackhide__ = True
--> 503 return cls.__pydantic_validator__.validate_python(
504 obj, strict=strict, from_attributes=from_attributes, context=context
505 )
TypeError: validate() takes 2 positional arguments but 3 were given
that's why I'm confused
other models are working just fine
the probelm shows in validate() not model_validate
you don't need it, but you can keep certain things somewhat simpler using it in a way that doesn't work as well with task groups. without it, you could implement the minimum needed from it without asyncio.wait, but I'd need to review some design docs and wouldnt be able to sahre the specific code where I ran into it being a cleaner option.
Can you print(pydantic.__version__)?
Also can you show AddressType, AccountAPI, FeedType?
HexStr = NewType("HexStr", str)
HexAddress = NewType("HexAddress", HexStr)
AddressType = NewType("AddressType", HexAddress)
AccountAPI : https://github.com/ApeWorX/ape/blob/main/src/ape/api/accounts.py#L28
class FeedType(Enum):
"""
Enum class for feed types.
Attributes:
SEQUENCE: Sequential feed type.
EPOCH: Epoch feed type.
"""
SEQUENCE = "sequence"
EPOCH = "epoch"
Wait is that the whole traceback?
Can you run it in a plane Python file?
also does it fail with this?
class JsonFeedOptions(BaseModel):
address: Optional[int] = None
signer: Optional[Union[int, str]] = None
Type: Optional[int] = None
Hmm looks like it's taking ape's basemodel instead of pydantic's. when AccountAPI type is passed
can you show the whole module where the model is defined?
JsonFeedOptions ?
yes
src/bee_py/types/type.py line 811
class JsonFeedOptions(BaseModel):```
this one woked
This is not valid because AccountAPI is a type variable
signer: Optional[Union[AccountAPI, str]] = None
did you mean ```
signer: Optional[Union[Signer, str]] = None
for signer the type is AccountAPI. I mean I'm going to use ape module's accounts which is of type AccountAPI
this is the absctract class for accounts
but I'm still confused why can't I do this?
just installed the latest version
can you run this as a plain python file and share the whole code and traceback?
plainer than this ?
I mean like python repro.py
β python3 funky_experiments/pydantic_test.py
Traceback (most recent call last):
File "/home/avik/Desktop/Work/AlienRobotNinja/bee/bee-py/funky_experiments/pydantic_test.py", line 30, in <module>
options = JsonFeedOptions.model_validate(opts)
File "/home/avik/.local/pipx/venvs/pdm/lib/python3.9/site-packages/pydantic/main.py", line 503, in model_validate
return cls.__pydantic_validator__.validate_python(
TypeError: validate() takes 2 positional arguments but 3 were given
from enum import Enum
from typing import Optional, Union
from ape.managers.accounts import AccountAPI
from ape.types import AddressType
from pydantic import BaseModel
class FeedType(Enum):
"""
Enum class for feed types.
Attributes:
SEQUENCE: Sequential feed type.
EPOCH: Epoch feed type.
"""
SEQUENCE = "sequence"
EPOCH = "epoch"
class JsonFeedOptions(BaseModel):
address: Optional[AddressType] = None
signer: Optional[Union[AccountAPI, str]] = None
Type: Optional[FeedType] = None
opts = {"signer": "abcd"}
options = JsonFeedOptions.model_validate(opts)
print(options)
I'll try installing eth-ape and see if I can reproduce it
how can I add validation here so that when int is passed to encrypt is throws validation error ?
you need to change the model definition to pass a config for strict
Data validation using Python type hints
honestly i think it was a mistake this wasnt on by default
you can also make individual fields strict
i think this is the "parsing vs validating" conceptual difference. pydantic by default guarantees that the data has a particular type after init, and doesn't much care about the types you pass in, as long as they can be converted
it's un-pythonic imo (normally python is relatively strongly typed) but it does make it easy to accept or process data from sloppy sources, or stringly-typed sources
Did something like this & it worked
@validator("pin", pre=True, always=True)
def validate_pin(cls, value): # noqa: N805
if not isinstance(value, bool):
msg = "Pin field must be a boolean"
raise ValueError(msg)
return value
that seems exceptionally unnecessary in this case
yeah, just use strict https://docs.pydantic.dev/latest/concepts/strict_mode/
Data validation using Python type hints
Thanks
Hello guys,
I've a problem with the functools.wraps. Basically, upon implementation, the __name__ property returns what is needed, but the __annotations__ is not.
The documentation would suggest this to be captured without no additional efforts.
That said, I tried to be more specific with something like
@wraps(func, assigned="__annotations__")
def something(*args, **kwargs):
return myfunc(*args, **kwargs)
But when I do so the __name__ is lost too.
Not sure what I'm getting wrong. So I though, I will implement directly the functools.update_wrapper but in this case I'm not sure what the first argument of the method should be.
Any ideas on what's wrong (on both cases)?
i want to say your python version is out of date
@soft matrix you are saying to me?
yes
because thesource code looks like it should get carried across
anyway assigned should be a tuple/list of str
@soft matrix I tried even with a tuple ... but in fact .. now that I'm realising it, the issue I have is with an internal method of my wrapper.
The source code of what I've been implementing is here:
https://github.com/andreamoro/Dispatcher
What I'm after is to get the annotations of the method where the register method is used (https://github.com/andreamoro/Dispatcher/blob/4f7a34b5addb48b0b5d845b4b2229e07c571b016/tests/test.py#L12) whereas today I'm getting the annotation of the method where is first added (https://github.com/andreamoro/Dispatcher/blob/4f7a34b5addb48b0b5d845b4b2229e07c571b016/tests/test.py#L9)
I've tried also to used a second wrap around the register method without success.
tests/test.py line 12
@foo.register```
`tests/test.py` line 9
```py
def foo():```
I have a bit of a puzzle. I've been given a dataclass.Field with the promise that it's type is either a type like int or str, or else a union type declaration one item is a type like int or str and the other is None. However, this can be written at least 5 ways (Optional[int], Union[int, None], and int|None, and the order-swapped versions of the last two).
Furthermore, my project has to be portable back to python 3.9, where int|None isn't permitted. In this case, obviously the annotation won't be int|None since the syntax didn't exist then.
Programmatically, how do I write def get_field_type(field: dataclasses.Field[Any]) -> Any: ... so that it works in all these cases?
I have this, it seems to work, but it feels like I must be missing something that would make it easier.
UnionType: type
if sys.version_info >= (3, 10):
from types import UnionType
else:
UnionType = type(Union[int, float])
def get_field_type(field: Field[Any]) -> Any:
field_type = field.type
if isinstance(field_type, str):
raise RuntimeError(
"parameters dataclass may not use 'from __future__ import annotations"
)
origin = get_origin(field_type)
if origin in (Union, UnionType):
for arg in get_args(field_type):
if arg is not None:
return arg
return field_type
(new here; is this better placed in the python help system? if so I can delete and re-post there)
i thought | on types returned a UnionType, is that not the case?
oh, i see. there's a way to get the parsed annotation whether or not the future import was used
That may be a 3.9 vs current difference, I thought I was seeing a different type depending on the notation
import sys
from typing import get_origin, Union, Optional
print(sys.version_info)
try:
print(get_origin(int | None))
except TypeError as e:
print(e)
print(get_origin(Union[int, None]))
print(get_origin(Optional[int]))
3.9:
sys.version_info(major=3, minor=9, micro=2, releaselevel='final', serial=0)
unsupported operand type(s) for |: 'type' and 'NoneType'
typing.Union
typing.Union
3.11:
sys.version_info(major=3, minor=11, micro=2, releaselevel='final', serial=0)
<class 'types.UnionType'>
typing.Union
typing.Union
```so it's the 'origin' of `int | None` type annotation that's different than the 'origin' of `Union[int, None]` or `Optional[int]`. I don't know if this is intentional, but that's how it is in 3.11 (through the latest 3.13 alpha docker image) behaves.
The way to test if something is a union is to do get_origin(typ) in {typing.Union, types.UnionType}
Though I've heard talks to make typing.Union an alias for types.UnionType
Thanks, I appreciate the confirmation that I didn't overlook a simpler alternative.
from a purely theoretical perspective, would it be possible to declare a type like "if any input is a pd.Series, then the result is a pd.Series, otherwise it's a NDArray"?
def f(
x: float | ArrayLike | NDArray[np.floating[Any]] | pd.Series[Any],
y: float | ArrayLike | NDArray[np.floating[Any]] | pd.Series[Any],
z: float | ArrayLike | NDArray[np.floating[Any]] | pd.Series[Any],
) -> NDArray[np.floating[Any]] | pd.Series[Any]:
...
this is what i have currently
i know there's really nothing i can do in the current system to improve this. but it would be really nice if mypy could narrow the return type here
You can do this with type-level functions, for example in TypeScript
cool, i didn't know typescript had that
i know i can do it in e.g. idris, but it doesn't strike me as something that actually needs dependent types
i wonder if there would ever be something like that in python
oh man, i want this too https://www.typescriptlang.org/docs/handbook/2/mapped-types.html
Generating types by re-using an existing type.
i think theres a chance that might happen
i remember seeing a pep floating around for it
which, type-level functions or mapped types?
mapped types
please and thank you
i've been wanting that for years
it's a real pain if you don't have a protocol handy, or if you want to construct a protocol from a concrete type
An unreasonably large number of overloads is the only thing that comes to mind, lol.
"combinatorial overload" in more ways than one
from typing import *
from pydantic import BaseModel
class A(BaseModel):
pass
class AA(A):
pass
T = TypeVar('T', bound=A)
class Component:
pass
class B(Component, Generic[T]):
pass
class C(B[AA]):
pass
c = C()
print(c.__orig_bases__) # (__main__.B[__main__.AA],)
class PydanticComponent(BaseModel):
pass
class B(PydanticComponent, Generic[T]):
pass
class C(B[AA]):
pass
c = C()
print(c.__orig_bases__)
# pydantic 2: (<class '__main__.PydanticComponent'>, typing.Generic[~T])
# pydantic 1: (__main__.B[__main__.AA],)
Trying to figure out what kind of magic pydantic v2 has invoked to alter __orig_bases__ on class C. My goal here is to be able to recover information about AA from C when it ultimately inherits from a pydantic BaseModel.
Please feel free to ping on replies.
using django, wondering how to fix this error?
Orig bases is from mro entries
!d types.get_original_bases
types.get_original_bases(cls, /)```
Return the tuple of objects originally given as the bases of *cls* before the [`__mro_entries__()`](https://docs.python.org/3/reference/datamodel.html#object.__mro_entries__) method has been called on any bases (following the mechanisms laid out in [**PEP 560**](https://peps.python.org/pep-0560/)). This is useful for introspecting [Generics](https://docs.python.org/3/library/typing.html#user-defined-generics).
For classes that have an `__orig_bases__` attribute, this function returns the value of `cls.__orig_bases__`. For classes without the `__orig_bases__` attribute, `cls.__bases__` is returned.
Examples:
I understand that __mro_entries__ can change the bases, but from the text, I expect __orig_bases__ to be the bases before modification, but what I'm seeing is that the bases have been modified in some way when subclassing Pydantic's BaseModel. That pydantic would use some magic to change things makes sense, but it's not clear why __orig_bases__ would end up being any different between the version that subclasses pydantic vs doesn't.
im slightly confused as to what pydantic 2 is referring to here
B.orig_bases?
this all looks correct to me
this isnt pydantic changing them its surely just GenericAlias doing it
There's two Cs in that example, one that inherits from Pydantic's BaseModel (through Component), and one that doesn't.
They give different answers for __orig_bases__ despite having effectively the same structure
With the only difference being the BaseModel class
oh ic now
so its not having orig bases be set on C for C (its not in its dict)
looks like a bug i remember someone complaining about something similiar recently
orig bases is set, but what it's set to has been transformed somehow
In particular, it has typing.Generic[~T] instead of __main__.B[__main__.AA]
In [2]: C.__dict__["__orig_bases__"]
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Input In [2], in <cell line: 1>()
----> 1 C.__dict__["__orig_bases__"]
KeyError: '__orig_bases__'```
its not
whereas with the normal case py In [4]: C.__dict__["__orig_bases__"] Out[4]: (__main__.B[__main__.AA],)
I don't actually see a custom __mro_entries__ on pydantic's BaseModel https://github.com/pydantic/pydantic/blob/main/pydantic/main.py#L61 or ModelMetClass https://github.com/pydantic/pydantic/blob/main/pydantic/_internal/_model_construction.py#L59
pydantic/main.py line 61
class BaseModel(metaclass=_model_construction.ModelMetaclass):```
`pydantic/_internal/_model_construction.py` line 59
```py
class ModelMetaclass(ABCMeta):```
Another interesting thing I found, in trying to work around this, I thought I could maybe put a __init_subclass__ on B
without the pydantic basemodel, it gets called. With the pydantic basemodel, __init_subclass__ on B is not called
Okay, it could be that pydantic is doing odd shenanigans with __dict__
pydantic/main.py line 862
_object_setattr(self, '__dict__', state['__dict__'])```
probably the better instance of potential shenanigans to link: https://github.com/pydantic/pydantic/blob/main/pydantic/main.py#L234
pydantic/main.py line 234
_object_setattr(m, '__dict__', fields_values)```
Just landed on creating an issue with the pydantic folks: https://github.com/pydantic/pydantic/issues/8410
Found a workaround for myself, which is to use inspect.getmro() and then pull out what I'm looking for there.
Could you please link it here? I tried to find it but didn't ;(
INTERNALERROR> File "/home/runner/work/steam.py/steam.py/steam/abc.py", line 89, in <module>
INTERNALERROR> class Commentable(Protocol):
INTERNALERROR> File "/opt/hostedtoolcache/Python/3.12.1/x64/lib/python3.12/typing.py", line 1825, in __init__
INTERNALERROR> cls.__callable_proto_members_only__ = all(
INTERNALERROR> ^^^^
INTERNALERROR> File "/opt/hostedtoolcache/Python/3.12.1/x64/lib/python3.12/typing.py", line 1826, in <genexpr>
INTERNALERROR> callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File "/home/runner/work/steam.py/steam.py/steam/enums.py", line 80, in __get__
INTERNALERROR> return self.__func__(type)
INTERNALERROR> ^^^^^^^^^^^^^^^^^^^
INTERNALERROR> File "/home/runner/work/steam.py/steam.py/steam/abc.py", line 102, in _COMMENTABLE_TYPE
INTERNALERROR> return _CommentThreadType[cls.__name__]
INTERNALERROR> ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
INTERNALERROR> File "/home/runner/work/steam.py/steam.py/steam/enums.py", line 152, in __getitem__
INTERNALERROR> return cls._member_map_[key]
INTERNALERROR> ~~~~~~~~~~~~~~~~^^^^^
INTERNALERROR> KeyError: 'Commentable'```hmm @lunar dune is there a case for using getattr_static here or was that removed?
i can get around this but its a bit annoying
Commentable here is meant to only be used as an abc and hence doesnt support itself as a thread type
lemme have a look
hum, interesting, some cursed interaction between protocols and a custom enum?
Can you give me an MRE easily?
I have Questions about why a getattr call is raising KeyError, though, that seems bad
But whether or not what you're doing is sensible is a separate question to whether typing.py should be more cautious here
So a MRE would still be interesting
!e ```py
from enum import IntEnum
from typing import Protocol
import abc
class classproperty:
def init(self, func):
self.func = func
def __get__(self, instance, type):
return self.__func__(type)
class _CommentThreadType(IntEnum):
WorkshopAccountDeveloper = 2
class Commentable(Protocol):
"""A mixin that implements commenting functionality."""
__slots__ = ()
_state: int
@property
@abc.abstractmethod
def _commentable_kwargs(self):
raise NotImplementedError
@classproperty
def _COMMENTABLE_TYPE(cls):
return _CommentThreadType[cls.__name__]
@soft matrix :x: Your 3.12 eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "/home/main.py", line 15, in <module>
003 | class Commentable(Protocol):
004 | File "/lang/python/default/lib/python3.12/typing.py", line 1818, in __init__
005 | cls.__callable_proto_members_only__ = all(
006 | ^^^^
007 | File "/lang/python/default/lib/python3.12/typing.py", line 1819, in <genexpr>
008 | callable(getattr(cls, attr, None)) for attr in cls.__protocol_attrs__
009 | ^^^^^^^^^^^^^^^^^^^^^^^^
010 | File "/home/main.py", line 10, in __get__
011 | return self.__func__(type)
... (truncated - too many lines)
Full output: https://paste.pythondiscord.com/J5RAI4FC42NI7YWMTY2F4VISRI
thats the gist of it
Thanks!
Yes, so, imo you should probably try to take steps to ensure that properties never raise non-AttributeError exceptions. Whether or not typing.py switches to using getattr_static here, there will be other code in various third-party packages that will make the same (fairly reasonable) assumption (that a getattr call could only ever raise AttributeError). So, if I were you, I'd change your _COMMENTABLE_TYPE property to something like this:
@classproperty
def _COMMENTABLE_TYPE(cls):
try:
return _CommentThreadType[cls.__name__]
except KeyError:
raise AttributeError("...") from None
ah didnt realise getattr caught AttributeErrors if you provided a default
As for whether we should change typing.py to use getattr_static:
- the
__callable_proto_members_only__attribute is only computed at class creation time these days, so we could probably change that to usegetattr_staticfairly easily without too much of a performance cost (I'd want to measure the import time oftyping.pybefore and afterwards to verify -- it's a reasonable benchmark as it's a module that defines a lot of protocol classes). But we also do agetattr()call here -- would we want to change that as well? https://github.com/python/cpython/blob/a545a86ec64fbab325db101bdd8964f524a89790/Lib/typing.py#L1895-L1896 That would slow downisinstance()checks. But maybe we could move more logic into_ProtocolMeta.__init__()to avoid the performance regression - There's also a risk that using
getattr_statichere could have other behaviour changes, though; need to think about that
Lib/typing.py lines 1895 to 1896
if val is None and callable(getattr(cls, attr, None)):
break```
Can you create an issue for this? I think it's reasonable to ask typing.py to be more cautious about getattr() calls on class objects, given that we've already taken the decision to be more cautious about getattr() calls on instances.
!e ```py
import sys
print(sys.version_info)
@lunar dune :white_check_mark: Your 3.12 eval job has completed with return code 0.
sys.version_info(major=3, minor=12, micro=0, releaselevel='final', serial=0)
Strange, I can't reproduce this locally, and I'm using the same Python version
ive tried looking for it and now cant :^) maybe it was all a figment of my imagination
Hahah, okay, it's because sets don't have a deterministic iteration order. Sometimes, it iterates over the __protocol_attrs__ set in exactly the right order so the all() expression short-circuits before we get to the problematic getattr() call
glorious
@lunar dune What do you think about mapped types (https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) and intersection types in Python? Would you support such peps and why?
(I know that intersection types have been in discussion for quite a while with no noticeable progress but I don't think anyone has brought up mapped types yet)
Generating types by re-using an existing type.
mapped types definitely have been it was at the same time as an AttributeOf pep
Here's a more reliable MRE that doesn't depend on the iteration order of sets π
from enum import IntEnum
from typing import Protocol
class classproperty:
def __init__(self, func):
self.__func__ = func
def __get__(self, instance, type):
return self.__func__(type)
class _CommentThreadType(IntEnum):
WorkshopAccountDeveloper = 2
class Commentable(Protocol):
@classproperty
def _COMMENTABLE_TYPE(cls):
return _CommentThreadType[cls.__name__]
My opinion is that intersection types are an important missing feature and possibly the logical "next big feature", but that they're also an extremely complicated feature both to specify and for type checkers to implement. So it'll probably take a while longer for the discussions around them to reach consensus, but that's okay; sometimes these things take time
I remember that a guy has fully implemented them in Mypy actually. He named it Basedmypy if you've heard of it
I'm also afraid I don't have the energy right now to properly engage with those discussions and meaningfully help them along
I have heard of basedmypy
No worries, we can pick it up other day :)
fully implemented is interesting considering we dont have an actual spec for them
What i am saying is that what he has makes sense and works :)
Obviously it can take time to make it fully correct and consistent with other implementations but the biggest work has already been done in terms of raw implementation, imho.
That's great, I'm sure that work will be useful when it comes to implementing them in mypy proper! That's a pretty different thing from the maintainers of the type checkers agreeing that a hypothetical spec could be plausibly implemented, though. Part of the reason why basedmypy has so much freedom in what it does is that it isn't bound to conform to any particular spec; it's deliberately backwards-incompatible with parts of PEP-484 etc.
i dont think the implementation is the hard part here considering how long the discussion has been going on :p
Implementation is always an iffy part because a proof of concept usually helps to significantly speed up the discussion
Mapped types look interesting, but I don't think we should be prioritising them as a feature right now; I think there are more important things for us to be working on adding to the type system. (Also, I think the current mood is that we should maybe slow down feature development and work on thinking a bit more about how the different features all fit together for a bit. I think that's part of the purpose of the spec that the Typing Council is working on putting together.)
Agreed completely. Though for me the main blocker is always intersection types :)
if youre interested idk if youve seen where the discussion is happening https://github.com/carlijoy/intersection_examples
Yup, I know. I follow it
those working on it have found issues with what was implemented in basedmypy, idk if anyone is intending on reaching out to have it be improved in those areas
personally, im more focused on just making sure the spec is fine, implementation can happen after we have a spec.
If you run this code snippet with the CPython main branch, you get to experience the glorious traceback enhancements that will be coming in Python 3.13, BTW:
Multiline source code snippets displayed, as well!
You get the whole source code of that multiline generator expression printed in your py313 traceback
oh that is actually good
what are the differences?
i can see this:
- rgb (but it is doable without interpreter modifications)
- multiline squiggles
class ...: ...<2 lines>...- whatever this is
class GeoRow(NamedTuple):
Index: int
longitude: float
latitude: float
x: tuple[Any, ...]
y: GeoRow
y = x
does anyone know why mypy might complain about this? i can't reproduce it in an isolated snippet like this, but it's complaining in my actual code:
error: Incompatible types in assignment (expression has type "tuple[Any, ...]", variable has type "GeoRow") [assignment]
what's weird is that i have the same assignment in a later section of code, and mypy is perfectly fine with it there
ah, the later section is because mypy thinks something else is Any... separate problem
If you look at item (2) in the traceback in #type-hinting message, the py312 traceback only has return all( given for the source code of that frame. The py313 traceback prints out the full source code (a 3-line generator expression) for that frame
Discord is the easiest way to communicate over voice, video, and text. Chat, hang out, and stay close with your friends and communities.
I believe Pablo et al. also managed to achieve all this while reducing the amount of C code involved in printing tracebacks now, as well β it all just calls Python code in traceback.py now
also: there's still currently no way to append an argument to a method without copying the whole signature from a parent class, right?
class B(A):
let's say A init has 5 overloads and 20 parameters. i just want to add a single foo: bool = False kwarg at the end. do i need to go find the .pyi file where A is defined and copy its entire init method signature, or do we have a better way to write that now?
No better way that I'm aware of, I'm afraid
is there some open thread about this on the mailing list or discourse forum?
it's a really really painful case
PEP-692 might help you, but it's hard to know without seeing the specific code you're working with (it probably won't help if you do actually have overloads)
!pep 692
i'm trying to write a type stub for geopandas.GeoSeries which is a subclass of pandas.Series
i still can't even figure out where the type parameters for Series are defined...
and yes, the init method has > 10 parameters and overloads
If you didn't have overloads, with PEP-692 you could use one TypedDict for typing the kwargs in your base class A, then subclass the TypedDict (adding a single field) and use the TypedDict subclass for annotating the kwargs in your subclass B(A)
that would still require me to copy the init method from a 3rd party's source code into a TypedDict, along with all the necessary imports and whatever cursed internal stuff they import in order to make their pyi file work
Not sure, I can't remember anything about this specifically but it feels like the kind of thing somebody would have raised at some point
i'll make a thread then.
ideally we'd be able to have something like this:
class B(A):
def __init_(self, *args: InheritArgs, **kwargs: InheritKwargs, foo: bool = False) -> None:
i've wanted similar things in the past, like the ability to create a protocol by copying the signature of a class
FooLike = ProtocolFrom("FooLIke", Foo)
or at least by copying individual methods/attributes
its yeah under a proposed Super type (which'd be a paramspec) or something
extending a union type would fall into the same category
oh? i'd like to see that
https://github.com/python/typing/issues/1471 its just like this right?
or an overload that doesnt get rid of the super's overloads has also been discussed
oh @heady flicker https://github.com/python/typing/issues/1273
yeah, ideally we'd have both behaviors here
maybe i'll write up my use case in the first thread, it seems like people aren't considering this as an important tool for managing complexity
https://github.com/python/typing/discussions/1412 thats it @heady flicker
Thanks!
Does it work if you swap them? ``None | "AbstractDefinition"`
Ah, I see. TIL. Well... the fallback "X | None" should work? Doesn't feel nice though.
And huh. For some reason, I didn't think to ask about why the type is not used directly.
the better option would be to use 3.12 type aliases
Ah. Didn't know about that. Doubt I will use it any time soon, but that's good to know!
How to type hint a custom list class just for a model?
class JobList(list):
def __contains__(self, job: Job):
return any(job.url == existing_job.url for existing_job in self)
In that example ^ existing_job doesn't highlight as a job. What can I do?
Are there examples of where you might use the JobList class?
this probably isnt necessary if you just add eq to Job
No, the eq for the job doesn't determine if it exists in a list in this case.
the eq for Job checks if all fields are the same
for this I just want to check url only
thats interesting design
also you cant have the job param be Job because it should be able to take anything, you should first check if its an instance of Job
anyway your problem is that you arent subclassing list[Job]
# save job
session = Session() #sqlalchemy
def save_jobs():
exiting_jobs = db.get_jobs()
new_jobs = platform.get_jobs()
for job in new_jobs:
if job in existing_jobs:
# I was thinking about these 2 lines
existing_job = existing_jobs[existing_jobs.index(job)]
if job != existing_job:
existing_job.update(job)
job = existing_job
else:
session.add(job)
session.commit()
I'm working with basically an api client & database. The above is technically a method, I just wrote it as a function for example.
I'm trying to avoid a unique constraint error π
job.url is the unique contstraint field
Basically I'm saying:
For each job, I want to see if the job exists, otherwise it's new and I can save it. If it does exist, if it has new data that the current job doesn't, update it's data. Otherwise, ignore it.
(I know it's outside the scope of typehinting but just answering her boni's question haha)
I think... I would just use filter.
As for the typing issue, my first thought was actually Protocol if .url was all you needed, but if you need to append, then list[Job] does make a lot more sense.
Inherit from list[Job]
Thanks! That seems to be the overall consensus haha. It worked, thanks guys!
class ThingA:
prop: bool
class ThingB:
pass
for thing in lots_of_different_things:
thing: ThingA|ThingB
try:
if thing.prop: # cannot access member prop for type ThingB
break
except AttributeError: # to account for ThingB, which doesnt have prop as an attribute
pass
vscode typechecker doesnt like this :(
even with the try/catch, it gives "cannot access member prop for type ThingB"
is there a way to appease the type checker without using isinstance or something similar?
mypy can narrow the type with if isinstance(thing, ThingA): ...
ah, without isinstance? maybe you need a runtime-checkable protocol
i don't think mypy has any support for "discriminated union" like what you describe
May be in an assert.
Would casting the value to ThingB in the try work for a less costly option?
Depends a bit on if the check is running in a hot loop and if the code is running with optimization on (since that eliminates asserts and makes them a *type checking timeβonly tool for this case).
Hey there Python friends, would anyone have an idea why mypy is struggling with the following (see comments for details):
from enum import Flag, auto
class Problem(Flag):
FIRST_ITEM = auto()
SECOND_ITEM = auto()
BOTH_ITEMS = FIRST_ITEM | SECOND_ITEM
def friendly_name(self) -> str:
# mypy error
# Item "None" of "str | None" has no attribute "lower"
#
# self.name inferred as follows:
# mypy: Union[builtins.str, None]
# pyright: Literal['FIRST_ITEM', 'SECOND_ITEM', 'BOTH_ITEMS']
return self.name.lower().replace("_", "-")
This doesn't seem to be a problem when using enum.Enum but specifically when using enum.Flag
Okies, found this issue which seems to explain it: https://github.com/python/mypy/issues/16177
Lets say I wan't to provide a type-hint for any generic callable with takes a single argument of type Thing
With no return value
We'd be looking at Callable[[Thing], None]
Does this work for objects with custom __call__ methods which implicitly accept a self argument?
Yes
I need a sanity check
T_Atomic = None | bool | int | float | str
T_Set = set['T_Atomic' | 'T_Tuple']
T_List = list['T_Data']
T_Dict = dict[str, 'T_Data']
T_Hash = tuple[T_Atomic, ...]
T_Tuple = tuple['T_Data', ...] | T_Hash
As a recursive type
This is anything that can be put into an html element's dataset
Its more or less "anything JSON encodable"
Seems to make sense on the first glance
Though the invariance of mutable types will likely diminish the benefits you are trying to receive from this
Could you dumb that down a bit for me?
Also - it occurs to me that dataset items don't update when they change. In theory you can place a list or some such into a dataset, but when it changes, the value in the dataset won't (its frozen).
Mutable types are invariant.
This means that a function that accepts list[int | str] won't accept a list[int]
This is because there is a possibility that you append a str to that list within your function, causing the original list to change its type β without type checker noticing.
So type hinting with data structures such as the one you have here can prove very inconvenient.
My advice would probably be to use immutable types
Hmmmmmmmmm
XD
In javascript, everything in the dataset is converted to a string
I was hoping to avoid that
I've got to start getting ready for work, but I'll keep my eye on this an reply as able
if you want covariance use Sequence and Mapping in place of list and dict
"covariance" is when list[int | str] accepts list[str]
Someday I'll learn to dumb down better :(
and as a concrete example of why this won't work for mutable data:
def append(data: list[int | str], newval: int | str) -> None:
return data.append(newval)
x: list[int] = [1, 2, 3]
append(x, "q")
y: list[int] = [n + 1 for n in nums] # Runtime error! Cannot add a string to an int.
it allows str to sneak into a list that should only contain int
ol087v
i'm just recapping your description π
change your password!
No no! This made perfect sense
Thank you
I guess I should do some investigation as what can actually be put into an element's dataset, and how its represented
as another example
class Employee:
id: int
class Manager(Employee):
reports: list[Employee]
def append(employees: list[Employee], new_employee: Employee) -> None:
employees.append(new_employee)
employee_456: Employee = ...
managers: list[Manager] = [...]
append(managers, employee_456)
for mgr in managers:
print(mgr.reports) # Runtime error! employee_456 is in thie list, but has no .reports attribute!
are you looking to support arbitrary modifications and in-place updates? if not, you can make your life easier and use the immutable types from collections.abc like Sequence
that way you don't have to worry so much about these "variance" concerns, you get the intuitive covariance behavior
at the same time, if you don't expect users to be passing around specialized containers like a "a DIV containing only numbers" then the invariance is fine
that is, you can choose between:
-
allow users to specialize the type of the data inside the container (like
Div[int]instead ofDiv[HtmlElem]), but accept the constraint of type invariance OR prohibit in-place mutation -
do not allow users to specialize the type of the data inside the container (inserting an int inside a div causes you to lose knowledge of what you just inserted), but allow both covariance and in-place mutation
at least, those are the choices as i understand them. maybe someone with more formal knowledge in this area can correct any errors
Does anyone have any experience with pydantic and FastAPI request-response serialization? I'm trying to make it so that my response model allows me to not only leave a value blank, but to not show the value (key-pair) at all if I choose. Is there a way to do that?
you want to pass in a specific value that results in the attribute being omitted from the output? not just omitting a default value?
(this isn't really a type-hinting question, it's about runtime behavior)
you can definitely figure fastapi and pydantic to exclude default values / unset fields from the output
I don't think it's possible without hacks, as far as I researched.
As far as I understand, the simplest approach would be:
- Make the attribute a PrivateAttr.
- Capture it at schema insurance initialization (
__init__) and set it as an attribute on the instance.
Why do you need something like this?
I'm currently following a spec detailed at https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0012.md#customer-put-verification, but the issue is that the client request may not (and is not required to by the spec) to input all of the keys to that particular request
I see, I'll look into that
response_model_exclude_defaults=True https://fastapi.tiangolo.com/tutorial/response-model/
I see
actually response_model_exclude_unset seems like what you want
pydantic tracks whether the value was set by the input, or by default
so it can distinguish when serializing
pydantic has config options for this as well, for more general-purpose use outside of fastapi
Thanks for showing me that doc, I think thats what Im looking for
Just to remove something on a case
rather than generally
@bold wind is there any way to call response_model_exclude_unset conditionally?
So if you hit some case, remove a particular tag
that's what i was trying to understand, it sounds like you have an additional case of actually removing a certain value
you can use response_model_exclude_default instead, where the default value is some sentinel or placeholder value
class Thing:
quantity: int | None = None
if you use response_model_exclude_default=True, you can assign thing.quantity = None and it should be excluded, even if previously it was set to something else
i was looking at my old code and found these cute type-hints:
[int]
(int, int)
(int, ...)
lambda: bool
lambda int: bool
``` i like them more than normal type-hints
PEP 677 unfortunately
ocaml and haskell are available today!
python DESTROYED with FACTS and LOGIC
think they use with (...) -> ReturnType
Hi there guys, suppose I have:
class NamedTag(Flag, metaclass=ABCEnumMeta):
@abstractproperty
def description(self) -> str:
raise NotImplementedError
T = TypeVar("T", bound="NamedTag")
@dataclass
class Section(Generic[T]):
name: str
tags: T
def run(sections: list[Section[T]]) -> None:
...
How can I obtain the type of T within the run() function?
Presently, I'm doing this but just wonder if there's a more elegant method:
def run(sections: list[Section[T]]) -> None:
tag_type = sections[0].tags.__class__
you dont really get any better than that unfortunately
and youve added to my list of problems with runtime reification >:(
I don't think there's a way to do that at runtime π€ The best you can do is get it's type: type(section.tags)
Also tag_type = sections[0].tags.__class__ could not work properly if your Sections have different tags types
e.g. Section[A], Section[B], Section[C] would give you just A
Ah yes although the generic should ensure that all of the types are the same I would hope.
Thanks heaps for the reply π
well theres nothing stopping you passing list[Section[A] | Section[B]] as they pointed out
Yeah, generics are completely erased at runtime
how to create a new type after combining dictionaries?
lets say I have 2 TypedDict
import typing as t
class MainStat(t.TypedDict):
DEX: int
STR: int
INT: int
class PersonalStat(t.TypedDict):
LUK: int
CRT: int
TEC: int
and if I declare a dictionary MainStat
sample: MainStat = {"DEX": 1, "INT": 1, "STR": 1}
I get the expected type hints, same with PersonalStat
however I want to combine together both dictionaries and have their type hints combined like
sample: IdkTheType = {"DEX": 1, "INT": 1, "STR": 1, "LUK": 1, "CRT": 1, "TEC": 1}
I've tried Union[MainStat, PersonalStat], but it only includes the member keys of the first declared key, and will not include the keys on another dict
any workarounds on this?
class Both(MainStat, PersonalStat):
pass
Union does the opposite, it means it is either a MainStat or a PersonalStat
alright thanks!
Or using inheritance 
Actually this should only apply to inheritance, but I think it should be Section[A | B]
either would break it here
am I missing something or is it really annoying when you initialize anything with a literal value and now pylance/right continues to think it can only be that literal and not an int/str
i.e.
x = [(0, 0)]
# pylance thinks x: list[tuple[Literal[0], Literal[0]]]
x.append((1, 1)) # complains
even if I go like
x: list[tuple[int, int]] = [(0, 0)] it still does not work
Oh I see, I will have to rethink my solution a little then, thanks a lot!
That does work, maybe you need to update pyright?
Code sample in pyright playground
from typing import reveal_type
a = [(0,0)]
reveal_type(a)
b: list[tuple[int, int]] = [(0,0)]
reveal_type(b)
A question to the type experts:
Pydantic schemas validate data. Recently, we started needing to analyze whether some schemas are subsets of other schemas. I.e. If data generated by A.json() (A.model_dump_json() in V2) is also parseable by B.parse_raw() (B.model_validate_json() in V2).
I have noticed a few quirks about this relationship. A field of A can be parsed by an equivalently named field in B if and only if its type is the same or wider (i.e. str | int in B can parse str from A but str in B cannot parse str | int in A). However, in terms of fields themselves, this relationship is reversed: B can only parse A if and only if it has the same number of required fields or less required fields than A (let's assume that all existing fields in B can be mapped to fields in A and let's assume there are no default values or validators that alter values).
This effectively yields the following relationship: "the set of fields in B must be a subset of the set of fields in A while each field type in B must be the superset of the respective field in A". This feels so much like Callable contravariance on arguments and covariance on return type but I cannot seem grasp two things:
- Is there a relationship between
Callablevariance and these relationships in pydantic schemas? - If so, why? How does it work -- from a type theory standpoint?
Is there a way to annotate something like this?
def f() -> int: return 127
def g() -> str: return "SRD"
def run(name: str):
return globals()[name]()
v = run("f") # v: Any, should be int
v = run("g") # v: Any, should be str
Well, two @overloads would work.
like, def run(name: Literal["f"]) -> int and similarly for the other.
def f() -> int: return 127
def g() -> str: return "SRD"
@overload
def run(name: Literal["f"]) -> int: ...
@overload
def run(name: Literal["g"]) -> str: ...
def run(name: str):
return globals()[name]()
v = run("f") # v: int
v = run("g") # v: str
This seems very repetetive. Not feasable for hundreds of f's and g's.
Esp considering the f's and g's are all in different files.
not completely
there is __orig_class__ attribute that is being set (if possible) on instances that are created through GenericAlias
@tranquil turtle :white_check_mark: Your 3.12 eval job has completed with return code 0.
001 | {}
002 | {'__orig_class__': __main__.X[int]}
no, and a type checker shouldn't really be expected to know what's definitely in globals anyways
Why do you need to pass in strings?
Why not pass the function itself, for example?
v = run(f)
I mean Im not sure where I need to update, but Im using pylance vscode extension, and I think Im up to date there
The function names come from csv files.
So if it's dynamic and not provided with literals, why bother with the types?
It's essentially def run(name: str) -> int | str
Yeah, that'd be no good.
Why?