7

There are many questions on SO asking why python doesn't always call__init__ after object creation.The answer, of course, is found in this excerpt from the documentation:

If__new__() returns an instance ofcls, then the new instance’s__init__() method will be invoked like__init__(self[, ...]), whereself is the new instance and the remaining arguments are the same as were passed to__new__().

If__new__() does not return an instance ofcls, then the new instance’s__init__() method will not be invoked.

What is the design reason for this?

askedJan 18, 2015 at 0:39
DanielSank's user avatar
3
  • 1
    Why should__init__ always be invoked?CommentedJan 18, 2015 at 0:42
  • I never said it should. I'm just wondering why it is how it is.CommentedJan 18, 2015 at 0:46
  • 3
    My guess would be that, if__new__ doesn't return an instance ofcls, Python doesn't have a value forself to pass to__init__.CommentedJan 18, 2015 at 0:48

4 Answers4

3

__init__ does act like a constructor. It needs an instance to do its job like setting attributes and so on. If__new__ doesn't return explicitly returns an instance, thenNone is returned by default.

Imagine that what will happen when__init__ gets aNone as an input and trying to set attributes? It will raise an exception called"AttributeError: 'NoneType' object has no attribute xxxxx".

So I think it's natural that not to invoke__init__ when__new__ returns None.

answeredJan 18, 2015 at 1:31
Stephen Lin's user avatar
Sign up to request clarification or add additional context in comments.

Comments

2

In Python 2, you can't actually call a regular method with the first argument being anything other than an instance of the class (or a subclass):

class Foo(object):    def __init__(self):        passFoo.__init__()# TypeError: unbound method __init__() must be called with Foo instance as first argument (got nothing instead)Foo.__init__(3)# TypeError: unbound method __init__() must be called with Foo instance as first argument (got int instance instead)

So__init__ isn't called because it cannotpossibly do anything other than immediately raise an exception. Not trying to call it is strictly more useful (though I don't think I've ever seen code take advantage of this).

Python 3 has a slightly simpler method implementation, and this restriction is no longer in place, but the__new__ semantics are the same. It doesn't make a lot of sense to try to run a class'sinitializer on a foreign object, anyway.


For a more designy answer, rather than a "because it's this way" answer:

Overriding__new__ is already a weird thing to do. By default, it returns an uninitialized object, which is a concept that Python tries very hard to hide. If you override it, you're probably doing something like this:

class Foo(object):    def __new__(cls, some_arg):        if some_arg == 15:            # 15 is a magic number for some reason!            return Bar()        else:            return super(Foo, cls).__new__(cls, some_arg)

Let's imagine a Python variant that unconditionally called__init__ on the return value. I immediately see a number of problems.

When you returnBar(), should Python callBar.__init__ (which has already been called in this case) orFoo.__init__ (which is for a completely different type and would break whatever guaranteesBar makes)?

The answer surely has to be thatBar.__init__ is called. Does that mean that you have to return an uninitializedBar instance, using the mouthfulreturn Bar.__new__(Bar) instead? Python very rarely requires you to call dunder methods outside of usingsuper, so this would be highly unusual.

Where wouldBar.__init__'s arguments come from? BothFoo.__new__ andFoo.__init__ are passed the same arguments — those passed totype.__call__, which is what handlesFoo(...). But if you explicitly callBar.__new__, there's nowhere to remember the arguments you wanted to pass toBar.__init__. You can't store them on the newBar object, because that's whatBar.__init__ is supposed to do! And if you just said it gets the same arguments that were passed toFoo, you severely limit what types can be returned from__new__.

Or, what if you wanted to return an object that already exists? Python has no way to indicate that an object is "already" initialized — since uninitialized objects are a transient and mostly-internal thing only of interest to__new__ — so you'd have no way to say not to call__init__ again.

The current approach is a little clumsy, but I don't think there's any better alternative.__new__ issupposed to create storage space for the new object, and returning a different type of object altogether is just a really weird thing to do; this is the least-surprising and most-useful way Python can handle it.

If this limitation is getting in your way, remember that the entire__new__ and__init__ dance is just the behavior oftype.__call__. You're perfectly free to define your own__call__ behavior on a metaclass, or just swap your class out for a factory function.

answeredJan 18, 2015 at 1:39
Eevee's user avatar

3 Comments

This is good but it sort of pushes the question away instead of answering it.Why doesn't python 2 allow passing a different type of object into a class's__init__?
fair! but then, which__init__ should be called — the one on the original type, or the one belonging to the type you returned from__new__? and why? if i were to hazard a guess, it's probably just that it's awkward and unusual to return anuninitialized object of a different type from__new__, whereas returning an uninitialized object of the usual type is the default behavior.
also, keep in mind that the__new__/__init__ dance is just the implementation oftype.__call__, which you are free to completely change in a metaclass if you so desire.
1

This is probably not THE design reason, but a nifty consequence if this design decision is that a class does not HAVE to return an instance of itself:

class FactoryClass():    def __new__(klass, *args, **kwargs):        if args or kwargs:            return OtherClass(*args, **kwargs)        return super().__new__(klass)

This could be useful sometimes. Obviously if you're returning some other class object, you don't want the init method of the factory class to be called.

The real reason which I hope the above example illustrates, and the bottom line, is that any other design decision would be nonsensical. New is the constructor for Python class objects, not init. Init is no different from any other class method (other than it being auto magically called after the object has been constructed); whether or not any of them get called depends on what happens in the constructor. This is just a natural way to do things. Any other way would be broken.

answeredJan 18, 2015 at 1:52
Rick's user avatar

7 Comments

That "obviously" phrase is begging the question :)
I suppose. Why would you want it called? It doesn't make sense. There is no instance of the factory class. In Python, new is the constructor, not init. Would you expect other class methods to be called? Of course not. They're not constructors, either. The call to init is dependent upon what happens when the constructor does its job. Any other situation just wouldn't make sense.
And when your design decision is driven by the alternative implementation being utterly nonsensical, it's probably one you can be confident in.
Indeed. This makes sense and is basically what everyone here is saying.
I haven't marked this, or any other answer as accepted because while everyone is saying that calling__init__ on something other than an instance of the factory class would be broken, nobody has given a real life example of why I would want a classFoo with aFoo.__new__ method that returns something other than aFooand such that this other object is not sensibly initialized byFoo.__init__.
|
0

If__new__ doesn't return an instance ofcls, then passing the return value tocls.__init__ could lead to very bad things.

In particular, Python doesn'trequire you to return an instance of the same type of class.

Take this contrived example:

In [1]: class SillyInt(int):   ...:     def __new__(cls):   ...:         return "hello"   ...:     In [2]: si = SillyInt()In [3]: siOut[3]: 'hello'

So we've created a subclass ofint, but our__new__ method returned astr object.

It wouldn't make a great deal of sense to then pass thatstring to our inheritedint.__init__ method for instantiation.Even if, in the particular case in question, it 'works' (in the sense of not throwing any errors), we may have created an object in an inconsistent state.


Ok, so what about a more concrete (albeit still contrived) example?

Say we create two classes which set the same attribute as part of their initialisation (in the__init__ method):

In [1]: class Tree():   ...:     def __init__(self):   ...:         self.desc = "Tree"   ...:         In [2]: class Branch():   ...:     def __init__(self):   ...:         self.desc = "Branch"   ...:

If we now create a subclass which, in a custom__new__ method, wants to return a different type of object (which may sometimes make sense in practice, even if it doesn't here!):

In [3]: class SillyTree(Tree):   ...:     def __new__(cls):   ...:         return Branch()   ...:

When instantiating this subclass, we can see that we've got an object of the expected type, and that theBranch initialiser was the one that was called:

In [4]: sillyTree = SillyTree()In [5]: sillyTree.descOut[5]: 'Branch'

What would have happened if Python had unconditionally called the inherited initialiser?

Well, we can actually test this by calling the initialiser directly.

What we end up with is an instance ofBranch which was initialised as aTree, leaving the object in a very unexpected state:

In [6]: SillyTree.__init__(sillyTree)In [7]: sillyTree.descOut[7]: 'Tree'In [8]: isinstance(sillyTree, Branch)Out[8]: TrueIn [9]: isinstance(sillyTree, Tree)Out[9]: False
answeredJan 18, 2015 at 1:38
sapi's user avatar

2 Comments

This is a good answer but it's also confusing because init is not the constructor. New is the constructor. Calling init the constructor just confuses things
Ok but this just pushes the question away such that we now want to ask why you would ever want a classFoo such thatFoo.__new__ makes something other thanFoo instancesand such that these other instances are not sensibly initialized byFoo.__init__. I suppose one can imagine reasons for that, but having one real example recorded in the answer would really help.

Your Answer

Sign up orlog in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

By clicking “Post Your Answer”, you agree to ourterms of service and acknowledge you have read ourprivacy 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.