Skip to content

Allow __init__ with signature but no return type #604

Description

@JukkaL

Code:

class Visitor:
    def __init__(self, a: int):
        pass

Error:

x.py: In member "__init__" of class "Visitor":
x.py, line 2: Cannot define return type for "__init__"

The return type is Any (implicitly), and Any should be a valid return type for __init__.

This was reported by Guido.

Activity

  1. gvanrossum commented on Mar 18, 2015

    @gvanrossum
    Member

    Hm, I think the return type for init should be None. A "return " inside an init is almost certainly caused by a misunderstanding. (Maybe you're thinking of new, which does return the object it created?)

  2. JukkaL commented on Mar 19, 2015

    @JukkaL
    CollaboratorAuthor

    Ah, the issue is that the error message is wrong. The message should say that the return type must be None? The message may be a relict from times before Python-compatible syntax.

  3. added a commit that references this issue on Mar 19, 2015
    559640d
  4. gvanrossum commented on Mar 19, 2015

    @gvanrossum
    Member

    But why do I have to specify the return type at all? Can't it default to
    None here?

    On Wednesday, March 18, 2015, Jukka Lehtosalo notifications@github.com
    wrote:

    Closed #604 #604 via 182a70b
    182a70b
    .

    —
    Reply to this email directly or view it on GitHub
    #604 (comment).

    --Guido van Rossum (on iPad)

  5. JukkaL commented on Mar 20, 2015

    @JukkaL
    CollaboratorAuthor

    Because of consistency. Consider __init__ that takes no arguments, other than self:

    def __init__(self):
        ...
    

    Would this be dynamically typed or statically typed? The current approach is to treat any function that has no annotation (no argument or return types) as dynamically typed, and statically typed (i.e. type checked) otherwise. The None return type marks the function as type checked.

    Same goes for functions in general:

    def f():   # Dynamically typed
        ...
    def f() -> None:   # Statically typed
        ...
    def g(x: int):  # Statically typed, Any return type
        ...
    
  6. gvanrossum commented on Mar 20, 2015

    @gvanrossum
    Member

    Hm. I think it'd suck if we'd have to develop the habit of adding explicit
    "-> None" to all init methods just to make mypy happy.

    On Thu, Mar 19, 2015 at 8:11 PM, Jukka Lehtosalo notifications@github.com
    wrote:

    Because of consistency. Consider init that takes no arguments, other
    than self:

    def init(self):
    ...

    Would this be dynamically typed or statically typed? The current approach
    is to treat any function that has no annotation (no argument or return
    types) as dynamically typed, and statically typed (i.e. type checked)
    otherwise. The None return type marks the function as type checked.

    Same goes for functions in general:

    def f(): # Dynamically typed
    ...
    def f() -> None: # Statically typed
    ...
    def g(x: int): # Statically typed, Any return type
    ...

    —
    Reply to this email directly or view it on GitHub
    #604 (comment).

    --Guido van Rossum (python.org/~guido)

  7. JukkaL commented on Mar 20, 2015

    @JukkaL
    CollaboratorAuthor

    Okay, let's reopen this. Here's a proposed new spec. Let me know if it matches your thinking.

    -> None would only be needed for type checked __init__ methods that take no arguments:

    def __init__(self) -> None:
        ...
    

    (In my Python corpus, these account for about 15% of all __init__ methods. __init__ methods seem to cover about 10% of all methods.)

    An __init__ method with any argument types would be type checked, and the return type would always be None implicitly, but you can also give an explicit None return type if you want.

    What about other functions and methods that have arguments with types but don't have an explicit return type? Should they always default to an Any return type? I see a few potential alternatives to this:

    1. Default to a None return type, similar to __init__.
    2. Default to a None return type if there is no return statement with an explicit value in the body. Give an error otherwise.
    3. Default to a None return type if there is no return statement with an explicit value in the body. Default to an Any return type otherwise.
    4. Try to infer the return type automatically. I don't like this much because it adds complexity, reduces consistency and "explicit is better than implicit" in this case, in my opinion.
  8. reopened this on Mar 20, 2015
  9. gvanrossum commented on Mar 20, 2015

    @gvanrossum
    Member

    It's a tricky issue, since we need to balance the desire to catch errors in the body of the function (what if a return type was intended but accidentally omitted?) with the need to derive the most useful return type for the benefit of checking call sites, as well as making the system be pleasant to use. I think for methods in general the status quo is fine, but (2) and (3) have some slight advantage, and some even slighter downside.

    However I still think __init__ is a special case -- its return value is determined by how Python uses it, not by what the user might want it to return. I think the absence of -> None should never result in an error message here (and in fact -> something_else should be considered an error, even -> Any). The heuristic over whether a function is considered type-checked or not is somewhat unfortunate but I can live with the requirement to add a dummy -> None to type-check an __init__ method that has no arguments besides self.

  10. JukkaL commented on Mar 21, 2015

    @JukkaL
    CollaboratorAuthor

    I'm leaning towards using the same convention everywhere (2) -- missing return type (if the signature has argument types) is the same as -> None. One reason is that in many examples of PEP 484 / mypy code I've seen, people tend to omit return types, even though their intention was probably not to have an Any return type -- they just did the easiest/shortest thing. Programmers are lazy, and we probably shouldn't make the rare case (Any return) easier to write than the common case (None return) when we have a choice.

    Still, a convention of using an explicit -> None even when it's not needed (except for __init__, where it isn't useful, as you argued) may be a reasonable thing. However, perhaps it shouldn't be enforced.

    Here's some more stats (these are actually pretty interesting): In my corpus about half of all methods have signature (self) -- but only a small fraction of these are __init__ methods. Approx. 2/3 of (self) methods seem to have None as the expected return type. Top-level functions that take no arguments but have a None return type are much less frequent.

  11. gvanrossum commented on Mar 24, 2015

    @gvanrossum
    Member

    I'm assuming those argument-less None-returning methods are some kind of pattern to set/clear specific flags? E.g. set_debug(). Would be interesting to look at these a bit more, since the stats look a bit suspicious. OTOH it's understandable that there aren't many top-level functions like that -- changing global state is frowned upon more than changing instance state.

  12. gvanrossum commented on Mar 24, 2015

    @gvanrossum
    Member

    I also realized that I'm not sure what your proposal is, exactly. Do you propose to assume ->None for all functions and methods that don't have an explicit return annotation? Or only for those that have at least one argument annotation? Or only for those that have no "return " in their body? (And how would the latter differ?)

  13. 32 remaining items

  14. mristin commented on Jul 31, 2018

    @mristin

    (In case somebody is looking for a script how to add None as return type to every __init__ in the codebase: https://gist.github.com/mristin/073a67f19e4133b9c97ed2269637b2f9

    The script handles also cases when the return type has been already specified. I couldn't make regular expressions work, so I resorted to AST parsing with asttokens.)

  15. onlined commented on Sep 24, 2018

    @onlined
    Contributor

    What is the status? Is final decision made? Is somebody assigned with the implementation? I can work on it, if there is no one available.

  16. JukkaL commented on Sep 25, 2018

    @JukkaL
    CollaboratorAuthor

    @onlined We are happy to accept a PR that implements the proposal. Nobody is working on this yet.

  17. emmatyping commented on Oct 1, 2018

    @emmatyping
    Member

    This was resolved in #5677. Thank you @onlined!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions