constructing an object from another instance of the same class

Bruno Desthuilliers bruno.42.desthuilliers at websiteburo.invalid
Fri Jun 18 11:08:23 EDT 2010


Christoph Groth a écrit :
> Bruno Desthuilliers <bruno.42.desthuilliers at 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



More information about the Python-list mailing list