-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Defaults for Generics? #4236
Comments
#4227 is somewhat related, but the underlying issue is that since mypy doesn't know what the return type of the It seems to me we might be able to fall back to the bound type, but I'm not sure about that. |
Would having a "default" type for |
I've run into a similar problem, where I intuitively wanted to add a default value in my stubbed function to affect the outcome: M = TypeVar('M', bound=Mapping)
def asdict(inst: Any, recurse: bool = ..., dict_factory: M = dict) -> M: ... reveal_type(asdict(foo)) # should be `dict` |
There seem to be two separate things going on here:
I think that the first change is not very controversial, but pretty low priority since there is a workaround. For the second change we'd need more evidence of it being generally useful. |
I can agree with this as long as this use case is taken into consideration during the overflow overhaul (henceforth known as the overflowhaul). Currently I can't solve this without running into overlapping overload errors. |
If I understand correctly, the first suggestion would also allow us to write the M = TypeVar("M", bound=Message)
class FeedParser(Generic[M]):
def __init__(self, _factory: Callable[[], M] = Message) -> None: ...
def close(self) -> M: ... I think that's a fine solution. |
It looks like this pattern is used extensively in SQLAlchemy. A simplified example: class Null: ...
class Clause(Generic[T]):
def __init__(self, type: Optional[Type[T]] = None) -> None:
if type is None:
type = Null # Something like this happens at runtime
self.type = type
reveal_type(Clause(int)) # OK, `Clause[int]`
reveal_type(Clause()) # Bad, `Clause[<nothing>]`, should be `Clause[Null]` A potential solution is to use an overloaded I think this can be raised to at least normal priority. |
IMO, I think the class Clause<T = Null> {
type: T;
constructor(type?: T) {
this.type = type ? type() : Null();
}
}
let c = new Clause()
c.type // type is Null
let d = new Clause(Integer);
d.type // type is Integer This could also potentially be combined with the class Null: ...
T = TypeVar('T', default=Null)
class Clause(Generic[T]):
type: T
def __init__(self, , type: Optional[Type[T]] = None) -> None:
if type is None:
type = Null # Something like this happens at runtime
self.type = type()
reveal_type(Clause(int)) # OK, `Clause[int]`
reveal_type(Clause()) # Ok, `Clause[Null]` Then a developer (of either stubs or a library) wouldn't need to mess with |
I forgot to give an example of class Comparator(Protocol):
def compare(self, other: Any) -> bool: ...
class Null:
def compare(self, other: Any) -> bool: ...
class Integer:
def compare(self, other: Any) -> bool: ...
T = TypeVar('T', bound=Comparator, default=Null)
class Clause(Generic[T]):
type: T
def __init__(self, , type: Optional[Type[T]] = None) -> None:
if type is None:
type = Null # Something like this happens at runtime
self.type = type()
reveal_type(Clause(Integer)) # OK, `Clause[Integer]`
reveal_type(Clause()) # Ok, `Clause[Null]`
reveal_type(Clause(int)) # Value of type variable "T" of "Clause" cannot be "int"
reveal_type(c = cast(Clause[Integer], {})) # Ok, `Clause[Integer]`
reveal_type(c = cast(Clause, {})) # Ok, `Clause[Null]`
reveal_type(c = cast(Clause[int], {})) # Type argument "builtins.int" of "Clause" must be a subtype of "Comparator" |
For what it's worth, the workaround suggested in the first bullet of #4236 (comment), namely using overloads to handle the default argument, doesn't work if the function has a catch-all In particular, I'm looking into making the |
This came up again (if I'm not mistaken) in #6525. Raising priority to high. |
I don’t think the
But on what grounds is mypy to reason that the On the other hand, if you could write T = TypeVar('T')
class Clause(Generic[T]):
type: T
def __init__(self, type: Type[T] = Null) -> None:
self.type = type() then mypy ought to be able to infer that I’m coming here from a more complicated case in Zulip, where we have three type variables |
Mypy already allows overloading T = TypeVar('T')
class Clause(Generic[T]):
@overload
def __new__(cls) -> Clause[Null]: ...
@overload
def __new__(cls, type: Type[T]) -> Clause[T]: ... I think we can just add support for overloading on class Clause(Generic[T]):
@overload
def __init__(self: Clause[Null]) -> None: ...
@overload
def __init__(self: Clause[T], type: Type[T]) -> None: ... |
Now that both overloading |
Type variable defaults should be discussed on typing-sig@ or the typing issue tracker, since they require changes to the |
To close the loop, PEP 696 – Type Defaults for Type Parameters was accepted and will land in Python 3.13. edit: here is the updated issue number: #14851 |
Consider the following (simplified) typeshed definition for
email.feedparser.FeedParser
:FeedParser allows a message factory to be passed in and will use it to construct the return value to
close()
. Unfortunately you have to cast the return value ofclose()
to the correct type:The following type definition fixes this problem:
Now the example above does not require the cast to make mypy happy. But unfortunately using the default factory will not work anymore:
This will cause mypy to complain
error: Need type annotation for variable
. Is there any solution for this conundrum?The text was updated successfully, but these errors were encountered: