user14612542
user14612542

Reputation:

Why should we use -> in def __init__(self, n) -> None:?

Why should we use -> in def __init__(self, n) -> None:? I read the following excerpt from PEP 484, but I am unable to understand what it means.

(Note that the return type of __init__ ought to be annotated with -> None. The reason for this is subtle. If __init__ assumed a return annotation of -> None, would that mean that an argument-less, un-annotated __init__ method should still be type-checked? Rather than leaving this ambiguous or introducing an exception to the exception, we simply say that __init__ ought to have a return annotation; the default behavior is thus the same as for other methods.)

What's the subtle difference between using def __init__(self, n) -> None: and def __init__(self, n):? Can someone explain the quoted excerpt in simple words?

Upvotes: 27

Views: 62119

Answers (5)

Reinderien
Reinderien

Reputation: 15273

The critical reading on this matter is the thread including Guido in MyPy issue 604, where he opines

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

concluding in

gvanrossum added a commit that referenced this issue on Oct 1, 2018 [...] Make return type implicitly None for type checked __init__

Long story short, if a linter determines that static type checking is possible for a given __init__ (in your case by adding an n: int), it will not - and should not - complain on the absence of a -> None and will infer that.

Upvotes: 3

chepner
chepner

Reputation: 532003

The main reason is to allow static type checking. By default, mypy will ignore unannotated functions and methods.

Consider the following definition:

class Foo:
    def __init__(self):
        return 3

f = Foo()

mypy, a static type analysis tool, sees nothing wrong with this by default:

$ mypy tmp.py
Success: no issues found in 1 source file

but it produces a runtime TypeError (note that python here is Python 3.8.6):

$ python tmp.py
Traceback (most recent call last):
  File "tmp.py", line 5, in <module>
    f = Foo()
TypeError: __init__() should return None, not 'int'

If you add the annotation -> None, then mypy will type-check the method and raise an error:

$ mypy tmp.py
tmp.py:3: error: No return value expected
Found 1 error in 1 file (checked 1 source file)

mypy will even complain if you try to circumvent the the check by declaring def __init__(self) -> int: instead:

$ mypy tmp.py
tmp.py:2: error: The return type of "__init__" must be None
Found 1 error in 1 file (checked 1 source file)

It's also worth noting that any annotation will make mypy pay attention; the lack of a return type is the same as -> None if you have at least one annotated argument:

def __init__(self, x: int):
     return x

will produce the same "No return value expected" error as an explicit -> None. The explicit return type, though, is often easier to provide than any artificial argument type hints, and is arguably clearer than trying to type self.

Upvotes: 29

tdelaney
tdelaney

Reputation: 77387

It comes down to the fist sentence in PEP 484 - The meaning of annotations Any function without annotations should be treated as having the most general type possible, or ignored, by any type checker. def __init__(self, n): won't be checked but def __init__(self, n) -> None: will. Even though we know that __init__ should only return None, a checker is not supposed to special-case it. The reason is that you don't know if the author intended to have def __init__(self): checked or not.

Upvotes: 1

Wesley Atwood
Wesley Atwood

Reputation: 54

In python 3.5 appeared type annotation option. def __init__(self, n) -> None: means that __init__ should always return NoneType and it can be quite helpful if you accidentally return something different from None especially if you use mypy or other similar things. But you can ignore it if you prefer the old way to do it.

Upvotes: 2

Hultner
Hultner

Reputation: 3780

This only matters if you care about type-annotations and can be skipped if you don't. It doesn't change functionality in runtime (other than adding information in the private __annotations__ property).

What does it do then?
-> Is used to document the type of the data that a function returns.
The type then follows after, in your case the type is None.

So it says that the method doesn't return anything, or if it does return something it's always None.

If you would return something you'd change it to the type of the return set.

Upvotes: 0

Related Questions