Can __new__ prevent __init__ from being called?

F

Felix Wiemann

Sometimes (but not always) the __new__ method of one of my classes
returns an *existing* instance of the class. However, when it does
that, the __init__ method of the existing instance is called
nonetheless, so that the instance is initialized a second time. For
example, please consider the following class (a singleton in this case):
.... instance = None
.... def __new__(cls):
.... if C.instance is None:
.... print 'Creating instance.'
.... C.instance = object.__new__(cls)
.... print 'Created.'
.... return cls.instance
.... def __init__(self):
.... print 'In init.'
....Creating instance.
Created.
In init.
In init. <---------- Here I want __init__ not to be executed.

How can I prevent __init__ from being called on the already-initialized
object?

I do not want to have any code in the __init__ method which checks if
the instance is already initialized (like "if self.initialized: return"
at the beginning) because that would mean I'd have to insert this
checking code in the __init__ method of every subclass.

Is there an easier way than using a metaclass and writing a custom
__call__ method?
 
S

Steven Bethard

Felix said:
Sometimes (but not always) the __new__ method of one of my classes
returns an *existing* instance of the class. However, when it does
that, the __init__ method of the existing instance is called
nonetheless, so that the instance is initialized a second time.
[snip]

How can I prevent __init__ from being called on the already-initialized
object?

The short answer: you can't:
http://www.python.org/2.2.3/descrintro.html#__new__
Note that in the Singleton example there, subclasses are told to
override init, not __init__ for exactly this reason.

If it's okay that __init__ never be called, you could do something like:

py> class C(object):
.... class __metaclass__(type):
.... def __call__(cls, *args, **kwargs):
.... return cls.__new__(cls, *args, **kwargs)
.... instance = None
.... def __new__(cls):
.... if cls.instance is None:
.... print 'Creating instance'
.... cls.instance = object.__new__(cls)
.... print 'Created'
.... return cls.instance
.... def __init__(self):
.... print 'In init'
....
py> C()
Creating instance
Created
<__main__.C object at 0x011F2E30>
py> C()
<__main__.C object at 0x011F2E30>

If __init__ needs to be called, I might go with something like:

py> class C(object):
.... class __metaclass__(type):
.... def __call__(cls, *args, **kwargs):
.... if cls.instance is None:
.... print 'Creating instance'
.... cls.instance = cls.__new__(cls, *args, **kwargs)
.... print 'Created'
.... cls.instance.__init__(*args, **kwargs)
.... return cls.instance
.... instance = None
.... def __init__(self):
.... print 'In init'
....
py> C()
Creating instance
Created
In init
<__main__.C object at 0x011F2E50>
py> C()
<__main__.C object at 0x011F2E50>

where all the work is done in the metaclass and you don't even define
__new__. I would probably also create the 'instance' attribute as part
of the metaclass work, like:

py> class SingletonMetaclass(type):
.... def __call__(cls, *args, **kwargs):
.... try:
.... return cls.__instance__
.... except AttributeError:
.... cls.__instance__ = cls.__new__(cls, *args, **kwargs)
.... cls.__instance__.__init__(*args, **kwargs)
.... return cls.__instance__
....
py> class C(object):
.... __metaclass__ = SingletonMetaclass
.... def __init__(self):
.... print '__init__'
....
py> C()
__init__
<__main__.C object at 0x011F3210>
py> C()
<__main__.C object at 0x011F3210>
py> C() is C()
True

But none of these solutions is particularly simple... Sorry!

STeVe
 
P

Peter Hansen

Felix said:
Sometimes (but not always) the __new__ method of one of my classes
returns an *existing* instance of the class. However, when it does
that, the __init__ method of the existing instance is called
nonetheless, so that the instance is initialized a second time. For
example, please consider the following class (a singleton in this case):
[snip]
How can I prevent __init__ from being called on the already-initialized
object?

Is this an acceptable kludge?
.... instance=None
.... def __new__(cls):
.... if C.instance is None:
.... print 'creating'
.... C.instance = object.__new__(cls)
.... else:
.... cls.__init__ = lambda self: None
.... return cls.instance
.... def __init__(self):
.... print 'in init'
....creating
in init
(Translation: dynamically override now-useless __init__ method.
But if that works, why do you need __init__ in the first place?)

-Peter
 
M

Michael Spencer

Peter said:
Felix said:
Sometimes (but not always) the __new__ method of one of my classes
returns an *existing* instance of the class. However, when it does
that, the __init__ method of the existing instance is called
nonetheless, so that the instance is initialized a second time. For
example, please consider the following class (a singleton in this case):
[snip]

How can I prevent __init__ from being called on the already-initialized
object?


Is this an acceptable kludge?
... instance=None
... def __new__(cls):
... if C.instance is None:
... print 'creating'
... C.instance = object.__new__(cls)
... else:
... cls.__init__ = lambda self: None
... return cls.instance
... def __init__(self):
... print 'in init'
...creating
in init
(Translation: dynamically override now-useless __init__ method.
But if that works, why do you need __init__ in the first place?)

-Peter
Or this one: use an alternative constructor:

class C(object):
instance = None
@classmethod
def new(cls, *args, **kw):
if cls.instance is None:
print 'Creating instance.'
cls.instance = object.__new__(cls)
print 'Created.'
cls.instance.__init__(*args,**kw)
return cls.instance
def __init__(self):
print 'In init.'
Creating instance.
Created.
In init.
Michael
 
F

Felix Wiemann

Steven said:

What a pity. By the way, I'm just seeing that the web page says:

| If you return an existing object, the constructor call will still call
| its __init__ method. If you return an object of a different class, its
| __init__ method will be called.

However, the latter doesn't seem to be true, or am I missing something?
.... def __init__(self):
.... print 'Init of A.'
.... .... def __new__(self):
.... return instance
.... def __init__(self):
.... print 'Init of B.'
.... Init of B.
<__main__.B object at 0x406243ec>

So there seems to be some type-checking in type.__call__.
Note that in the Singleton example there, subclasses are told to
override init, not __init__ for exactly this reason.

I see.
py> class C(object):
... class __metaclass__(type):
... def __call__(cls, *args, **kwargs):
... if cls.instance is None:
... print 'Creating instance'
... cls.instance = cls.__new__(cls, *args, **kwargs)
... print 'Created'
... cls.instance.__init__(*args, **kwargs)
... return cls.instance

I didn't think of inlining the metaclass; that's really nice.
[...] where all the work is done in the metaclass and you don't even
define __new__.

Yeah, that's good. I think I'll go that way.

Thanks a lot!
 
J

Jack Diederich

Sometimes (but not always) the __new__ method of one of my classes
returns an *existing* instance of the class. However, when it does
that, the __init__ method of the existing instance is called
nonetheless, so that the instance is initialized a second time. For
example, please consider the following class (a singleton in this case):

... instance = None
... def __new__(cls):
... if C.instance is None:
... print 'Creating instance.'
... C.instance = object.__new__(cls)
... print 'Created.'
... return cls.instance
... def __init__(self):
... print 'In init.'
...
How can I prevent __init__ from being called on the already-initialized
object?
Is there an easier way than using a metaclass and writing a custom
__call__ method?

The standard recipe is to define an alternate init method and call it
once when you instantiate the object (I couldn't find it on ASPN though).

Here is a cut-n-paste from production code. The work normally done in
the magic __init__() is done in init() instead.

class Page(context.AppContext):
"""the One True Singleton """
_single = None # our singleton reference
def __new__(cls, *args, **opts):
if (Page._single is None):
Page._single = object.__new__(cls)
Page._single.init(*args, **opts)
return Page._single

def __init__(self, *args, **opts):
"""We are a singleton, so setup is done just once in init() because
__init__() will be called every time the singleton is re-issued
This __init__ just prevents our parent's __init__'s from running other
than when told to by our init()
"""
return

def init(self, req = None):
"""setup Db objects, cgi params etc
Here is also where we decide if we are being run from the command
line or in mod_python"""
context.AppContext.__init__(self, req)
# lots of initialization done here
return

Page is a singleton but it inherits from the class context.AppContext
which is just a regular class. The empty Page.__init__ doesn't call
the context.AppContext.__init__ but the once-only Page.init does.

Hope that helps,

-Jack
 
C

Colin J. Williams

Felix said:
Steven Bethard wrote:
[snip]
This prompts a similar query. __new__ appears to be intended for
immutable objects but it seems to be called as part of constructor
process for all instances.

Regarding the original question. It is not possible to prevent the use
of __init__ but is it possible to prevent __init__ having any effect by
setting a flag when __init__ is called for the first creation of the
instance.

Colin W.
 
S

Steven Bethard

Colin said:
This prompts a similar query. __new__ appears to be intended for
immutable objects but it seems to be called as part of constructor
process for all instances.

That's because Python has no builtin way of determining whether or not a
given type is immutable. If you wanted to, you could define both
__new__ and __init__, the first to set immutable parts and the second to
set mutable parts, e.g.:

py> class PartlyMutableTuple(tuple):
.... def __new__(cls, *args, **kwargs):
.... return super(PartlyMutableTuple, cls).__new__(cls, args)
.... def __init__(self, *args, **kwargs):
.... self.__dict__.update(kwargs)
....
py> t = PartlyMutableTuple(1, 2, 3, a=4, b=5)
py> t
(1, 2, 3)
py> t.a, t.b
(4, 5)
py> t.a, t.b = t.b, t.a
py> t.a, t.b
(5, 4)
py> t[0] = 2
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: object does not support item assignment

I don't think I'd advise this strategy, but by always calling both
__new__ and __init__, Python makes it possible...

Steve
 
M

Martin

Felix said:
Sometimes (but not always) the __new__ method of one of my classes
returns an *existing* instance of the class. However, when it does
that, the __init__ method of the existing instance is called
nonetheless, so that the instance is initialized a second time.
[snip]

How can I prevent __init__ from being called on the already-initialized
object?

I do not want to have any code in the __init__ method which checks if
the instance is already initialized (like "if self.initialized: return"
at the beginning) because that would mean I'd have to insert this
checking code in the __init__ method of every subclass.

Is there an easier way than using a metaclass and writing a custom
__call__ method?

Yes. You could move all your initalization logic from __init__ to a
separate init method and use the following simple recipe that does not
involve metaclasses and overriding __call__.

Although the base class __init__ does have to check to see if the
instance is initialized, you don't have to repeat the code in derived
classes:

class C(object):
def __new__(cls, *args, **kwds):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls)
it.init(*args, **kwds)
return it

def init(self, *args, **kwds): # only called once
print 'In C init.'
pass

def __init__(self): # called each time
print 'In C __init__.'

class D(C):
def init(self, *args, **kwds): # only called once
print 'In D init.'
pass

def __init__(self): # called each time
print 'In D __init__.'
In C init.
In C __init__.
In C __init__.
In D init.
In D __init__.
In D __init__.

-Martin
 
M

Martin

I meant to say:

Although the base class __new__ does have to check to see if the
^^^^^^^
instance is initialized, ...

not:
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,057
Latest member
KetoBeezACVGummies

Latest Threads

Top