`dataclasses.Field.type` isn't always a type
I don't know whether this is a bug in Python or a bug in typeshed, but it's a bug in at least one. (And I'm kind of worried that both are going to say it's the other one with the bug.)
in dataclasses typeshed has this
class Field(Generic[_T]):
name: str
type: Type[_T]
But that doesn't always match the Python implementation.
from __future__ import annotations
from dataclasses import dataclass, fields
class B:
pass
@dataclass
class C:
b: B
def f() -> None:
for field in fields(C):
if issubclass(field.type, B):
print("found a b field")
if __name__ == "__main__":
f()
This type checks without any errors, but run time fails with TypeError: issubclass() arg 1 must be a class
field.type is not a type, it's a str
I think this is because of from __future__ import annotations
Does typeshed need to change to this?
type: Type[_T] | str
Typeshed is wrong here. The type field can also be some other type form that is not actually a type, such as a union object. We should say it is of type Any.
Would Any make it give a type checking error in if issubclass(field.type, B):?
I was hoping for something that would tell me there's a problem with that line.
Any would not give a type checking error. It is a "please don't complain" marker that lets you do anything without type checking errors. But it is possible to achieve what you want with type[_T] | str, or even better, type[_T] | str | Any. You would get an error message that basically says you need to handle strings.
Unlike you would expect, type[_T] | str | Any isn't same as just using Any. It would give an error for if issubcass(field.type, B) like you want. See "the Any trick" in our CONTRIBUTING.md.
Here's an example of what the .type can be:
>>> from dataclasses import dataclass, fields
>>> @dataclass
... class Foo:
... a: str
... b: 'Foo'
... c: str | int
...
>>> [f.type for f in fields(Foo)]
[<class 'str'>, 'Foo', str | int]
>>> [type(f.type) for f in fields(Foo)]
[<class 'type'>, <class 'str'>, <class 'types.UnionType'>]
So the fields can be types (Type[_T]), strings (str), or some other special things that would be difficult to describe accurately (Any).