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