constructing an object from another instance of the same class

Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Fri Jun 18 11:34:10 EDT 2010


On Fri, 18 Jun 2010 16:30:00 +0200, Christoph Groth wrote:

> If other is of type Base already, just "pass it on".  Otherwise,
> construct an instance of Base from it.
> 
> **************************************************************** import
> numpy as np
> 
> class Base:
>     def __init__(self, other):
>         if isinstance(other, type(self)):
>             self = other
>             return

This does not do what you think it does. I wonder whether you've actually 
tried it?

>>> import numpy as np
>>> a = np.identity(4)
>>> b = Base(a)  # works
>>> c = Base(b)  # doesn't work
>>> b.a
array([[ 1.,  0.],
       [ 0.,  1.]])
>>> c.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: Base instance has no attribute 'a'


In your __init__ method, the line:

self = other

does NOT do what you think. All it does is re-bind the name "self" to 
refer to the other object, while leaving the instance untouched. 
Consequently, the rest of the initialisation code never gets executed and 
your instance is uninitialised.

To do what you are trying to do, you need to do two things:

(1) Use a new-style class, not a classic (old-style) class; and

(2) Use the constructor __new__ and not the initialiser __init__.

And then you should change your mind and not do it, because you will be 
opening a huge can of horrible bugs that will be really hard to debug. 
The reason being, your class is *mutable*, and you will find yourself 
having code where you think you have two different instances but you 
actually only have one instance with two different names.

Here's a simple example demonstrating why this is a bad idea:


>>> class Recycler(object):
...     def __new__(cls, other):
...             if isinstance(other, cls):
...                 return other
...             else:
...                 instance = super(Recycler, cls).__new__(cls, other)
...                 return instance
...     def __init__(self, other):
...             # Don't re-initialise when self has been recycled.
...             if self is other:
...                 return
...             self.attr = other
...
>>> a = Recycler(42)
>>> a.attr
42
>>> b = Recycler(a)
>>> b.attr
42
>>> b.attr = 23
>>> a.attr
23


a and b are the same object, and whatever you do to one, you do to the 
other. Object constructors should construct new instances, not give you 
back an existing instance which is already in use elsewhere.

However, if you make the class *immutable*, then it is perfectly safe, 
because you can't modify immutable objects and therefore it doesn't 
matter whether a and b refer to two different instances or the same 
instance. For example, Python caches small integers and reuses them, as a 
memory optimization:

>>> a = int("42")
>>> b = int("42")
>>> a is b
True

but doesn't bother for larger integers to avoid filling the cache will 
billions of ints that will never be re-used:

>>> a = int("4200207")
>>> b = int("4200207")
>>> a is b
False

This is safe, since you can't change the value of the object 42. (You can 
rebind the name "a", but not modify the object itself.)

So, to recap:

* you aren't doing what you think you're doing;

* even if you were, you shouldn't;

* unless you make the class immutable.



-- 
Steven



More information about the Python-list mailing list