Why isn’t my class initialized by “def __int__” or “def _init_”? Why do I get a “takes no arguments” TypeError, or an AttributeError?

What do the exception messages mean, and how do they relate to the problem?

As one might guess, a TypeError is an Error that has to do with the Type of something. In this case, the meaning is a bit strained: Python also uses this error type for function calls where the arguments (the things you put in between () in order to call a function, class constructor or other “callable”) cannot be properly assigned to the parameters (the things you put between () when writing a function using the def syntax).

In the examples where a TypeError occurs, the class constructor for Example does not take arguments. Why? Because it is using the base object constructor, which does not take arguments. That is just following the normal rules of inheritance: there is no __init__ defined locally, so the one from the superclass – in this case, object – is used.

Similarly, an AttributeError is an Error that has to do with the Attributes of something. This is quite straightforward: the instance of Example doesn’t have any .attribute attribute, because the constructor (which, again, comes from object due to the typo) did not set one.

Why didn’t a problem occur earlier, for example, with the class definition itself?

Because the method with a wrongly typed name is still syntactically valid. Only syntax errors (reported as SyntaxError; yes, it’s an exception, and yes, there are valid uses for it in real programs) can be caught before the code runs. Python does not assign any special meaning to methods named _init_ (with one underscore on each side), so it does not care what the parameters are. While __int__ is used for converting instances of the class to integer, and shouldn’t have any parameters besides self, it is still syntactically valid.

Your IDE might be able to warn you about an __int__ method that takes suspicious parameters (i.e., anything besides self). However, a) that doesn’t completely solve the problem (see below), and b) the IDE might have helped you get it wrong in the first place (by making a bad autocomplete suggestion).

The _init_ typo seems to be much less common nowadays. My guess is that people used to do this after reading example code out of books with poor typesetting.

How else might the problem manifest?

In the case where an instance is successfully created (but not properly initialized), any kind of problem could potentially happen later (depending on why proper initialization was needed). For example:

BOMB_IS_SET = True
class DefusalExpert():
    def __int__(self):
        global BOMB_IS_SET
        BOMB_IS_SET = False
    def congratulate(self):
        global BOMB_IS_SET
        if BOMB_IS_SET:
            raise RuntimeError("everything blew up, gg")
        else:
            print("hooray!")

If you intend for the class to be convertible to integer and also wrote __int__ deliberately, the last one will take precedence:

class LoneliestNumber:
    def __int__(self):
        return 1
    def __int__(self): # was supposed to be __init__
        self.two = "can be as bad"

>>> int(LoneliestNumber())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __int__ returned non-int (type NoneType)

(Note that __int__ will not be used implicitly to convert instances of the class to an index for a list or tuple. That’s done by __index__.)

How might I guard against the problem in the future?

There is no magic bullet. I find it helps a little to have the convention of always putting __init__ (and/or __new__) as the first method in a class, if the class needs one. However, there is no substitute for proofreading, or for training.

Leave a Comment