Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

constructing an object from another instance of the same class

4 views
Skip to first unread message

Christoph Groth

unread,
Jun 18, 2010, 6:51:39 AM6/18/10
to pytho...@python.org
Dear all,

sometimes it is handy to have a function which can take as argument
anything which can be converted into something, e.g.

def foo(arg):
arg = float(arg)
# ...

I would like to mimic this behavior of float for a user-defined type,
e.g.

def bar(arg):
arg = My_type(arg)
# ...

Now I wonder what is the most pythonic way to write the __init__ method
of My_type? The following comes to my mind:

class My_type:
def __init__(self, other):
if isinstance(other, type(self)):
self.a = other.a
self.b = other.b
return
# initialize self in some other way

It seems to me that in this way I might get problems when I pass an
instance of Derived_from_my_type to bar, as it will become an instance
of My_type.

What is a good way to express this? In C++ (which I know better than
python) I would make bar accept a const reference to My_type. Then I
could use it directly with instances of My_type, Derived_from_my_type
and other types which can be converted into My_type.

thanks
Christoph

Bruno Desthuilliers

unread,
Jun 18, 2010, 8:25:20 AM6/18/10
to
Christoph Groth a �crit :

> Dear all,
>
> sometimes it is handy to have a function which can take as argument
> anything which can be converted into something, e.g.
>
> def foo(arg):
> arg = float(arg)
> # ...
>
> I would like to mimic this behavior of float for a user-defined type,
> e.g.
>
> def bar(arg):
> arg = My_type(arg)
> # ...
>
> Now I wonder what is the most pythonic way to write the __init__ method
> of My_type? The following comes to my mind:
>
> class My_type:
> def __init__(self, other):
> if isinstance(other, type(self)):
> self.a = other.a
> self.b = other.b
> return
> # initialize self in some other way
>
> It seems to me that in this way I might get problems when I pass an
> instance of Derived_from_my_type to bar, as it will become an instance
> of My_type.

The instance you pass to bar won't "become" anything else. You create a
new My_type instance from the Derived_from_my_type one's values, and
rebinding the _local_ name 'arg' only affects the local namespace.

<OT>
BTW, the convention in Python is to use TitleCase for class names
(except - for historical reasons - for most builtin types which are
lowercase).
</OT>

> What is a good way to express this?

Depends on what are the possible initializers / arguments for My_type.
There are a few possible solutions but which one is best depends on the
concrete case and personal tastes.

> In C++

Forget about C++ - Python is a different beast !-)

>(which I know better than
> python) I would make bar accept a const reference to My_type. Then I
> could use it directly with instances of My_type, Derived_from_my_type
> and other types which can be converted into My_type.

If you only worry about being able to use any "My_type like" object -
that is, any object implementing a given subset of My_type's interface,
then just document your expectations in the function's docstring and use
whatever object is passed in as if it was a My_type instance. Period. As
long as you document what your function expects, it's the caller's
responsaibility to make sure it provides something compatible. If he
don't, well he'll get a nice traceback.

I know this might look very freestyle and a bit frightening when coming
from one of these B&D languages, but that's really the Python way, and
from experience, it JustWork(tm).

Christoph Groth

unread,
Jun 18, 2010, 10:30:00 AM6/18/10
to pytho...@python.org
Bruno Desthuilliers <bruno.42.de...@websiteburo.invalid> writes:

>> It seems to me that in this way I might get problems when I pass an
>> instance of Derived_from_my_type to bar, as it will become an
>> instance of My_type.
>
> The instance you pass to bar won't "become" anything else. You create
> a new My_type instance from the Derived_from_my_type one's values, and
> rebinding the _local_ name 'arg' only affects the local namespace.

I understand that it won't become an instance of My_type globally. But
it will become locally and this breaks polymorphism. (See code example
I provide at the end)

>> In C++
>
> Forget about C++ - Python is a different beast !-)

Still, it is useful and interesting to compare languages. Like in the
real world it is also insightful to compare the grammar of quite
different languages.

>> (which I know better than python) I would make bar accept a const
>> reference to My_type. Then I could use it directly with instances of
>> My_type, Derived_from_my_type and other types which can be converted
>> into My_type.
>
> If you only worry about being able to use any "My_type like" object -
> that is, any object implementing a given subset of My_type's
> interface, then just document your expectations in the function's
> docstring and use whatever object is passed in as if it was a My_type
> instance. Period. As long as you document what your function expects,
> it's the caller's responsaibility to make sure it provides something
> compatible. If he don't, well he'll get a nice traceback.

This is not what I am worrying about. I will try to be more explicit.

I would like to have a class for a "special matrix". This is an
ordinary 2n by 2n matrix which can be thought of as consisting of four n
by n sized blocks.

At this moment, I just use normal numpy arrays (or anything which
behaves like them). But I have very good reasons to actually have a
class for these special matrices. Still, I would like to be able to
call functions which work with "special matrices" with plain numpy
arrays as arguments. In that case, the arguments which are plain
matrices should be converted to "special" ones such that the main part
of the function can assume to always work with "special matrices".

The code attached in the end (which is a complete runnable script)
should illustrate what I mean.

This example works as I want except that when bar is called with a an
argument of type Derived, it behaves as Base. Also, I am not sure
whether there is a nicer way to achieve the following functionality for
Base.__init__:

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
n = other.shape[0]
assert n == other.shape[1]
assert n % 2 == 0
n //= 2
self.a = other[0 : n, 0 : n]
self.b = other[n : 2*n, 0 : n]
self.c = other[0 : n, n : 2*n]
self.d = other[n : 2*n, n : 2*n]

def hello(self):
print 'hello from Base'

class Derived(Base):
def hello(self):
print 'hello from Derived'

def bar(arg):
arg = Base(arg)
# Do something useful with arg.{a..d}
arg.hello()

# This works.
a = np.identity(4)
bar(a)

# And this also.
a = Base(np.identity(4))
bar(a)

# But this prints "hello from Base"!
a = Derived(np.identity(4))
bar(a)

Bruno Desthuilliers

unread,
Jun 18, 2010, 11:08:23 AM6/18/10
to
Christoph Groth a écrit :

> Bruno Desthuilliers <bruno.42.de...@websiteburo.invalid> writes:
>
>>> It seems to me that in this way I might get problems when I pass an
>>> instance of Derived_from_my_type to bar, as it will become an
>>> instance of My_type.
>> The instance you pass to bar won't "become" anything else. You create
>> a new My_type instance from the Derived_from_my_type one's values, and
>> rebinding the _local_ name 'arg' only affects the local namespace.
>
> I understand that it won't become an instance of My_type globally. But
> it will become locally and this breaks polymorphism.

Then don't do it !-)

> (See code example
> I provide at the end)
>
>>> In C++
>> Forget about C++ - Python is a different beast !-)
>
> Still, it is useful and interesting to compare languages.

Indeed. But you have to understand enough of a language to compare it
with another one. The old "I can write C in any language" syndrom...


>>> (which I know better than python) I would make bar accept a const
>>> reference to My_type. Then I could use it directly with instances of
>>> My_type, Derived_from_my_type and other types which can be converted
>>> into My_type.
>> If you only worry about being able to use any "My_type like" object -
>> that is, any object implementing a given subset of My_type's
>> interface, then just document your expectations in the function's
>> docstring and use whatever object is passed in as if it was a My_type
>> instance. Period. As long as you document what your function expects,
>> it's the caller's responsaibility to make sure it provides something
>> compatible. If he don't, well he'll get a nice traceback.
>
> This is not what I am worrying about. I will try to be more explicit.

Ok.

> I would like to have a class for a "special matrix". This is an
> ordinary 2n by 2n matrix which can be thought of as consisting of four n
> by n sized blocks.

Right.

> At this moment, I just use normal numpy arrays (or anything which
> behaves like them). But I have very good reasons to actually have a
> class for these special matrices. Still, I would like to be able to
> call functions which work with "special matrices" with plain numpy
> arrays as arguments. In that case, the arguments which are plain
> matrices should be converted to "special" ones such that the main part
> of the function can assume to always work with "special matrices".

Ok. So you want to build a "special matrice" like object from the numpy
array.

> The code attached in the end (which is a complete runnable script)
> should illustrate what I mean.
>
> This example works as I want except that when bar is called with a an
> argument of type Derived, it behaves as Base.

Ok.

> Also, I am not sure
> whether there is a nicer way to achieve the following functionality for
> Base.__init__:
>
> If other is of type Base already, just "pass it on". Otherwise,
> construct an instance of Base from it.

You can't do this in the initializer, you have to use either a factory
function or the proper constructor (or an alternate constructor).

> ****************************************************************
> import numpy as np
>
> class Base:

If you're using Python 2.x, make this:

class Base(object):

> def __init__(self, other):
> if isinstance(other, type(self)):
> self = other

'self' is a local name. Rebinding a local name only impact the local
namespace.

> return
> n = other.shape[0]
> assert n == other.shape[1]
> assert n % 2 == 0
> n //= 2
> self.a = other[0 : n, 0 : n]
> self.b = other[n : 2*n, 0 : n]
> self.c = other[0 : n, n : 2*n]
> self.d = other[n : 2*n, n : 2*n]


Question : is there any case where you may want to instanciate this
class with explicit values for a, b, c and d ?


Anyway: the simplest solution here is to replace the call to your Base
class with a call to a factory function. I'd probably go for something
like (Q&D untested code and other usual warnings) :

class Base(object):
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d

@classmethod
def from_array(cls, arr):
""" alternate constructor from a numpy array """
n = arr.shape[0]
assert n == arr.shape[1]


assert n % 2 == 0
n //= 2

return cls(
arr[0 : n, 0 : n],
arr[n : 2*n, 0 : n],
arr[0 : n, n : 2*n],
arr[n : 2*n, n : 2*n]
)


def hello(self):
print 'hello from Base'


class Derived(Base):
def hello(self):
print 'hello from Derived'

def coerce(obj, cls=Base):
if isinstance(obj, cls):
return obj
else:
return cls.from_array(obj)


def bar(arg):
arg = coerce(arg)


# Do something useful with arg.{a..d}
arg.hello()

# This works.
a = np.identity(4)
bar(a)

# And this also.
a = Base.from_array(np.identity(4))
bar(a)

# And now this should work too
a = Derived.from_array(np.identity(4))
bar(a)

Does it solve your problem ?-)


Note that if using a plain function hurts your feelings - or just isn't
practical for any other reason - you can also make it another
classmethod of Base, ie :


class Base(object):
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d

@classmethod
def from_array(cls, arr):
""" alternate constructor from a numpy array """
n = arr.shape[0]
assert n == arr.shape[1]


assert n % 2 == 0
n //= 2

return cls(
arr[0 : n, 0 : n],
arr[n : 2*n, 0 : n],
arr[0 : n, n : 2*n],
arr[n : 2*n, n : 2*n]
)

@classmethod
def coerce(cls, obj):
if isinstance(obj, cls):
return obj
else:
return cls.from_array(obj)

def bar(arg):
arg = Base.coerce(arg)


# Do something useful with arg.{a..d}
arg.hello()


HTH

Bruno Desthuilliers

unread,
Jun 18, 2010, 11:11:55 AM6/18/10
to
Bruno Desthuilliers a �crit :
> Christoph Groth a �crit :
>> Bruno Desthuilliers <bruno.42.de...@websiteburo.invalid> writes:
>>
(snip)

>>>> In C++
>>> Forget about C++ - Python is a different beast !-)
>>
>> Still, it is useful and interesting to compare languages.
>
> Indeed. But you have to understand enough of a language to compare it
> with another one. The old "I can write C in any language" syndrom...

Re-reading this it might looks a bit patronizing. Sorry, was not my
intention at all - it was just a general consideration (I mean "mostly
useless blah").

(snip)

Steven D'Aprano

unread,
Jun 18, 2010, 11:34:10 AM6/18/10
to
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

Stephen Hansen

unread,
Jun 18, 2010, 11:34:43 AM6/18/10
to pytho...@python.org
On 6/18/10 3:51 AM, Christoph Groth wrote:
> sometimes it is handy to have a function which can take as argument
> anything which can be converted into something, e.g.
[snip]

> I would like to mimic this behavior of float for a user-defined type,
> e.g.
[snip]

> Now I wonder what is the most pythonic way to write the __init__ method
> of My_type? The following comes to my mind:
[snip]

> It seems to me that in this way I might get problems when I pass an
> instance of Derived_from_my_type to bar, as it will become an instance
> of My_type.
[snip]
> What is a good way to express this? In C++ (which I know better than

> python) I would make bar accept a const reference to My_type. Then I
> could use it directly with instances of My_type, Derived_from_my_type
> and other types which can be converted into My_type.

This kind of polymorphism is anti-Pythonic; you don't go around
converting an object from one 'type' to another 'type' like that, and
especially not for that reason.

Basically, you're thinking about *types* too much. They are much less
important in Python then they are in C++; you achieve polymorphism in
Python by matching behavior and following the protocols of the other type.

Duck typing: if it quacks like a duck, its a duck.

The Pythonic way to approach the problem of, "I want to accept anything
which can be converted into X" is to accept the variable, and then
simply *use* it *as if* it were X. If the value implemented the
appropriate protocols (say, __add__ and __radd__ and friends and such if
you want it to mimic a numeric type) then it will work.

For all intents and purposes, it *is* X, for all you need to worry about.

If it doesn't implement the protocols, then it will error out, yes.
It'll throw an exception. You can be prepared to catch such exceptions
and say, log an error, "Expected value to be a numeric type", or you can
just let it propagate (I almost always prefer this method). Sometimes
you need to be a little more careful, sometimes less: but generally,
only at the boundry of your program do you need to worry about
type-correctness (i.e., where user and data is accepted, you have to
convert stuff there.) Anywhere else, its a bug if you pass the wrote
thing in: unit tests and exceptions/tracebacks are excellent for finding
and fixing such bugs.

--

Stephen Hansen
... Also: Ixokai
... Mail: me+list/python (AT) ixokai (DOT) io
... Blog: http://meh.ixokai.io/

signature.asc

Christoph Groth

unread,
Jun 18, 2010, 6:55:02 PM6/18/10
to pytho...@python.org
Bruno Desthuilliers <bruno.42.de...@websiteburo.invalid> writes:

> Anyway: the simplest solution here is to replace the call to your Base
> class with a call to a factory function. I'd probably go for something
> like (Q&D untested code and other usual warnings) :
>

> (...)

Yeah, that will do what I want.

My confusion arose from the expectation that there had to be some
mechanism to do the conversion automatically. But actually a simple

def bar(arg):
if not isinstance(arg, Base):
arg = Base(arg)
# Do something with arg.

is a simple and explicit solution of the problem.

Thanks
Christoph

Christoph Groth

unread,
Jun 18, 2010, 6:58:17 PM6/18/10
to pytho...@python.org
Steven D'Aprano <st...@REMOVE-THIS-cybersource.com.au> writes:

> 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?

Just quickly. Sorry, I should have written

class Base:
def __init__(self, other):
if isinstance(other, type(self)):

self.a = other.a
self.b = other.b

self.c = other.c
self.d = other.d
return
# ...

Jean-Michel Pichavant

unread,
Jun 21, 2010, 5:38:06 AM6/21/10
to Christoph Groth, pytho...@python.org
Christoph Groth wrote:
> Dear all,

>
> sometimes it is handy to have a function which can take as argument
> anything which can be converted into something, e.g.
>
> def foo(arg):
> arg = float(arg)
> # ...
>
> I would like to mimic this behavior of float for a user-defined type,
> e.g.
>
> def bar(arg):
> arg = My_type(arg)
> # ...
>
> Now I wonder what is the most pythonic way to write the __init__ method
> of My_type? The following comes to my mind:
>
> class My_type:

> def __init__(self, other):
> if isinstance(other, type(self)):
> self.a = other.a
> self.b = other.b
> return
> # initialize self in some other way
>
> It seems to me that in this way I might get problems when I pass an
> instance of Derived_from_my_type to bar, as it will become an instance
> of My_type.
>
> What is a good way to express this? In C++ (which I know better than
> python) I would make bar accept a const reference to My_type. Then I
> could use it directly with instances of My_type, Derived_from_my_type
> and other types which can be converted into My_type.
>
> thanks
> Christoph
>
>
There is no need to do such thing in python most of the time. Python is
strongly typed and won't change the type of an object for you (unlike
perl for instance). That means no matter where you are in your code, you
should know the exact type of your objects.

If you don't, that means you are assigning to the *same name*, object of
different types. You don't want to that.

One possible solution is to make your class also a factory class:
python 2.5

class My_type(object):
@classmethod
def fromInt(cls, anInteger):
"""Return a My_type instance given an integer"""
pass

@classmethod
def fromString(cls, aString):
"""Return a My_type instance given an integer"""
pass


Try to resist the temptation of making one constructor that would handle
any type of parameter, it may look stylish, but from the "I don't care
about the parameter type" you will soon experience the "I don't know
about the parameter type" which is problematic when debugging /
maintaining the code.

JM

Carl Banks

unread,
Jun 21, 2010, 6:26:19 AM6/21/10
to
On Jun 18, 3:55 pm, Christoph Groth <c...@falma.de> wrote:

What if someone wants to call bar with an argument that mimics a Base
but isn't a subclass? Your function would try to convert it to an
actual Base.

Something to think about before you make a habit of this.


Carl Banks

0 new messages