Sklavit
Sklavit

Reputation: 2341

May __init__ be used as normal method for initialization, not as constructor?

Sometimes it looks reasonable to use __init__ as initialization method for already existing object, i.e.:

class A():
    def __init__(self, x):
        self.x = x

    def set_state_from_file(self, file):
        x = parse_file(file)
        self.__init__(x)

As alternative to this implementation I see the following:

class A():
    def __init__(self, x):
        self.init(x)        

    def init(self, x):
        self.x = x

    def set_state_from_file(self, file):
        x = parse_file(file)
        self.init(x)

It seems to me as over-complication of code. Is there any guideline on this situation?

Update: There is a case when it is definitely not an alternate constructor case: unpickling. During unpickling, pickle first creates an instance and only then sets its state.

Upvotes: 9

Views: 1933

Answers (3)

Sklavit
Sklavit

Reputation: 2341

I found some differences between __init__ and 'normal' methods:

1., __init__ is not allowed to return anything: TypeError will be raised.

2., If __init__ raises error, __del__ will be called: UPDATE by Martijn Pieters: this is only for constructor calls, not for generic usage, see comments below.

 class A(object):
     def __init__(self):
           print('__init__')
           raise ValueError('__init__ error')
           pass

    def method(self):
        raise ValueError('method error')

    def __del__(self):
        print("__del__")

def main():
    try:
        a = A()
        a.method()
    except ValueError as e:
        print(e)
    print('exit main')

if __name__ == '__main__':
    main()
    print('end of file')

will output:

__init__
__init__ error
__del__
exit main
end of file

Upvotes: 0

Martijn Pieters
Martijn Pieters

Reputation: 1123270

__init__ is not a constructor. It is an initialisation method, called after the instance was already constructed for you (the actual constructor method is called __new__()).

You can always call it again from your code if you need to re-initialise, this isn't a style violation. In fact, it is used in the Python standard library; see the multiprocessing.heap.Heap() implementation for example:

def malloc(self, size):
    # return a block of right size (possibly rounded up)
    assert 0 <= size < sys.maxsize
    if os.getpid() != self._lastpid:
        self.__init__()                     # reinitialize after fork

or the threading.local implementation, which uses a context manager to defer initialisation.

There is otherwise nothing special about the __init__ method itself. It is merely automatically called by type.__call__ (after creating the instance with instance = cls.__new__(cls, *args, **kwargs), cls.__init__(instance, *args, **kwargs) is called if it is available).

Upvotes: 15

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

In addition to Martjin's answer: a common pattern in Python is to use classmethods as factory methods, ie:

class A():
    def __init__(self, x):
        self.x = x

    @classmethod
    def from_file(cls, file):
        x = parse_file(file)
        return cls(x)


a1 = A(42)
a2 = A.from_file(open("/path/to/file"))

Upvotes: 5

Related Questions