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.

2
  • 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. Commented Nov 14, 2016 at 12:16
  • @Peter Wood, what about __init__ in __setstate__? Commented Nov 14, 2016 at 12:33

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

Comments

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"))

9 Comments

@Sklavit then obviously the "classmethod as alternate constructor" pattern is not what you need <g>
What has this got to do with reusing the __init__ method on an existing instance?
@Martijn Pieters this answer is also the answer for the question above.
@MartijnPieters I've seen peoples reusing __init__ from another method when what they really wanted was an alternate constructor...
@brunodesthuilliers: sure, but that issue is orthogonal to wether or not there is a style issue with reusing the method.
|
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

5 Comments

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.
I'm not sure what all this has to do with reusing the __init__ method on an existing instance however.
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, you are right. Just add this comment to your answer and it will be the best and accepted.
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.