9

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.

Sklavit
  • 2,225
  • 23
  • 29
  • 1
    Why not have a non-member function to parse the file, and create a new instance? It seems you'd have to re-write the class each time you wanted to initialise it from a different source, which violates the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle). – Peter Wood Nov 14 '16 at 12:16
  • @Peter Wood, what about `__init__` in `__setstate__`? – Sklavit Nov 14 '16 at 12:33

3 Answers3

15

__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).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
5

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"))
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
0

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
Sklavit
  • 2,225
  • 23
  • 29
  • 1
    Yes, of course the new instance will be cleared; an exception during construction (which includes calling the `__init__` initialiser) means the new object can't be returned; the exception broke the normal flow, no assignment takes place, and no references are left. If you used `inst = A.__new__(A)` and `inst.__init__()` you would still have a reference to the object and `__del__` would *not* be called. This has nothing to do with `__init__`, it is a normal method. – Martijn Pieters Nov 14 '16 at 15:44
  • I'm not sure what all this has to do with reusing the `__init__` method on an existing instance however. – Martijn Pieters Nov 14 '16 at 15:45
  • Also, the `TypeError` is thrown by the code that calls `__init__`; the method is not special, only the caller is (but no more so that other code that uses special methods that break their agreed-to contract; try returning something other than a string from `__str__` for example). Call the `__init__` method directly and nothing happens if you returned something from it other than `None`. – Martijn Pieters Nov 14 '16 at 15:49
  • @ Martijn Pieters, you are right. Just add this comment to your answer and it will be the best and accepted. – Sklavit Nov 15 '16 at 09:25
  • I'm not sure that details on how `__init__` is being called when a new instance is created matter to your style question. Your question certainly didn't try to return something from `__init__`, and any code that tried to conditionally return something would be rather confusing. At that point factoring out the portion that needs to return something would be a much better choice. – Martijn Pieters Nov 15 '16 at 09:36