Skip to content

Subclass of dict doesn't inherit special signature #1010

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

Closed
gvanrossum opened this issue Nov 25, 2015 · 4 comments
Closed

Subclass of dict doesn't inherit special signature #1010

gvanrossum opened this issue Nov 25, 2015 · 4 comments
Labels
bug mypy got something wrong

Comments

@gvanrossum
Copy link
Member

Consider:

class C(dict):
    pass
x = C(a=1, b=2)

Python accepts this, but mypy complains:

/Users/guido/mypy_tests/mypy_dict_subclass.py:3: error: No overload variant of "C" matches argument types [builtins.int, builtins.int]

But dict(a=1, b=2) works.

@JukkaL JukkaL added the bug mypy got something wrong label Nov 25, 2015
@JukkaL
Copy link
Collaborator

JukkaL commented Nov 25, 2015

Yeah, that was one of the limitations of my quick fix to the dict(x=y, ...) problem.

@JukkaL JukkaL added the priority label Jan 8, 2016
@ddfisher
Copy link
Collaborator

ddfisher commented Mar 2, 2016

If we want to fix this in a non-hacky way we could potentially augment __init__ methods to allow return values which specify the type of the class, and then use these methods for dict:

def __init__() -> Dict[K, V]
def __init__(d: Dict[K, V]) -> Dict[K, V]
def __init__(iterable: Iterable[Tuple[K, V]]) -> Dict[K, V]
def __init__(iterable: Iterable[Tuple[str, V]], **kwargs: V) -> Dict[str, V]
def __init__(**kwargs: V) -> Dict[str, V]

@ddfisher ddfisher added this to the 0.4.0 milestone Mar 2, 2016
@gnprice gnprice removed the priority label Mar 2, 2016
@gvanrossum
Copy link
Member Author

I'm not sure that doesn't qualify as "hacky"... But it does seem reasonable given the absolute meaninglessness of the return type of __init__.

It would require a PEP 484 change since the PEP currently explicitly says that __init__ must be marked with -> None.

I think there's another form that you'll need:

def __init__(iterable: Iterable[Tuple[K, V]], **kwargs: V) -> Dict[[Union[K, str], V]

Interestingly, for __new__ the return type naturally already is an instance of the class so it should work there too without changes.

@JukkaL
Copy link
Collaborator

JukkaL commented Mar 2, 2016

I'm not a fan of this approach, especially if we'd also support the -> None return type as well. But it might be the least bad way to do it. Here are the things that I'm worried about:

  1. Would a None return be still valid as well? If yes, would the new syntax be valid in non-generic classes as well? If that's the case, some users might prefer to use the -> C variant everywhere, while others would prefer to use -> None (as classes where this actually makes a difference will be very rare). I strongly think that we should recommend a single convention to be used as default in case there are multiple alternatives (perhaps mention this in PEP 8). If we deprecate the -> None return type, almost all existing code would have to be updated.

  2. In subclasses the return type would be an instance of the subclass instead of the one given in the annotation, in case __init__ gets inherited. This would be subtly different form other methods, where this kind of variation does not happen. (We might want to support SelfType or similar to support this functionality. However, SelfType wouldn't work here as it wouldn't support indexing.) Example:

class A:
    def __init__(self) -> A: ...

class B(A): pass

b = B()  # type is B even though annotation says `A`
  1. __init__ could have a return type even though it can't return a value of the given type using a return statement. This would be inconsistent with other functions (though __init__ special in other respects as well).

Another way to implement would be to provide a special syntax for just this construct. A function decorator would work, though it would still be ugly. However, this is a rare issue so it might be acceptable. Perhaps something like this (the name is not the greatest):

from typing import instance_type

def C(...):
    ...
    @instance_type(C[str, T])
    def __init__(self, **kwargs: T)

JukkaL added a commit that referenced this issue Apr 17, 2016
This is a stop-gap solution until we implement a general plugin
system for functions, or similar.

Fix #984. Also mostly fix #1010 (except for some special cases).
JukkaL added a commit that referenced this issue Apr 17, 2016
This is a stop-gap solution until we implement a general plugin
system for functions, or similar.

Fix #984. Also mostly fix #1010 (except for some special cases).
bdarnell added a commit to tornadoweb/tornado that referenced this issue Apr 24, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

4 participants