21

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?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437

5 Answers5

24

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.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    Thanks! Do programmers prefer ```def __init__(self, n) -> None:``` to ```def __init__(self, n):```? –  Nov 20 '20 at 17:43
  • I would say most programmers are in the *habit* of not providing an explicit return type. In the case of `__init__`, at this time, most people that are using `mypy` already know that `__init__` doesn't need to return a value (and may not even be aware that doing so is an error, since they've never tried to), so adding the return type might seem pedantic. (1/2) – chepner Nov 20 '20 at 17:46
  • 1
    But for someone new to Python, it might be a good idea to start using type annotations and `mypy` from the beginning, because following this advice *would* catch any attempts to return a value, should they decide to try. (2/2) – chepner Nov 20 '20 at 17:47
  • I will add that it's crucially more important to type `n: int` than to `-> None`. The second is easily inferrable and the first is not. – Reinderien May 25 '21 at 18:49
  • Since the `__init__(self,)` magic is already hardcoded by the language, why mypy should require an explicit `-> None` to work? It's fairly safe to hardcode this assumption, no? – Compl Yue Jul 31 '23 at 12:53
  • Without *any* annotations, `mypy` simply won't apply type-checking to the function. `-> None` is a simple way to force checking of `__init__`, as it doesn't require any thought. (While the types of the arguments for `__init__` may vary depending on the definition, *every* `__init__` method returns `None`, so you don't have to "figure out" what the return type should be.) – chepner Jul 31 '23 at 18:33
3

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.

Reinderien
  • 11,755
  • 5
  • 49
  • 77
2

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.

DeepSpace
  • 78,697
  • 11
  • 109
  • 154
1

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.

tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • _a checker is not supposed to special-case it_ - There is in fact a special case for `__init__` so long as - through information other than the return - the checker knows that the function is typed. – Reinderien May 25 '21 at 18:46
  • @Reinderien - what other means are you thinking of? The PEP says that --> None should not be assumed as a special case for for `__init__`. If you want return type checking on `__init__`, be explicit. – tdelaney May 25 '21 at 19:29
  • The other means: in this case `n: int`. Also, the PEP and reality now diverge; current mypy semantics do support an implicit `None`. https://github.com/python/mypy/issues/604 for nuance. – Reinderien May 25 '21 at 19:31
  • @Reinderien - Input parameter annoations don't say anything about return type. The PEP says "ought" not "must" and simply tells us to be explicit. Perhaps some clever or ill-advised bit of code does return a value. Doing so is harmless. – tdelaney May 25 '21 at 19:39
  • _Input parameter [annotations] don't say anything about return type_ - They actually do in this case. Try it yourself: run `__init__(self):`, `__init__(self, x) -> None:`, `__init__(self, x: int)`, and `__init__(self, x: int) -> None` through mypy and observe the difference. – Reinderien May 25 '21 at 19:53
  • @Reinderien - why? mypy seems to have ignored the "ought". It doesn't say what type checkers in general do (or should do). – tdelaney May 25 '21 at 19:55
  • Perhaps so, but on the balance this one case of ignoring the PEP seems to be in the name of favouring usability. Issue 604 illustrates the discussion well. – Reinderien May 25 '21 at 19:56
0

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.

Hultner
  • 3,710
  • 5
  • 33
  • 43