hacatu
hacatu

Reputation: 665

Python assignment to self in constructor does not make object the same

I am making a constructor in Python. When called with an existing object as its input, it should set the "new" object to that same object. Here is a 10 line demonstration:

class A:
    def __init__(self, value):
        if isinstance(value, A):
            self = value
        else:
            self.attribute = value
a = A(1)
b = A(a)#a and b should be references to the same object
print("b is a", b is a)#this should be true: the identities should be the same
print("b == a", b == a)#this should be true: the values should be the same

I want the object A(a) constructed from the existing object a to be a. Why is it not? To be clear, I want A(a) to reference the same object as a, NOT a copy.

Upvotes: 4

Views: 7580

Answers (4)

Alex Martelli
Alex Martelli

Reputation: 881675

self, like any other argument, is among the local variables of a function or method. Assignment to the bare name of a local variable never affects anything outside of that function or method, it just locally rebinds that name.

As a comment rightly suggests, it's unclear why you wouldn't just do

b = a

Assuming you have a sound reason, what you need to override is not __init__, but rather __new__ (then take some precaution in __init__ to avoid double initialization). It's not an obvious course so I'll wait for you to explain what exactly you're trying to accomplish.

Added: having clarified the need I agree with the OP that a factory function (ideally, I suggest, as a class method) is better -- and clearer than __new__, which would work (it is a class method after all) but in a less-sharply-clear way.

So, I would code as follows:

class A(object):

    @classmethod
    def make(cls, value):
        if isinstance(value, cls): return value
        return cls(value)

    def __init__(self, value):
        self.attribute = value

Now,

a = A.make(1)
b = A.make(a)

accomplishes the OP's desires, polymorphically over the type of argument passed to A.make.

Upvotes: 9

jonrsharpe
jonrsharpe

Reputation: 122032

The only way to make it work exactly as you have it is to implement __new__, the constructor, rather than __init__, the initialiser (the behaviour can get rather complex if both are implemented). It would also be wise to implement __eq__ for equality comparison, although this will fall back to identity comparison. For example:

>>> class A(object):
    def __new__(cls, value):
        if isinstance(value, cls):
            return value
        inst = super(A, cls).__new__(cls)
        inst.attribute = value
        return inst
    def __eq__(self, other):
        return self.attribute == other.attribute


>>> a = A(1)
>>> b = A(a)
>>> a is b
True
>>> a == b
True
>>> a == A(1)
True  # also equal to other instance with same attribute value

You should have a look at the data model documentation, which explains the various "magic methods" available and what they do. See e.g. __new__.

Upvotes: 3

oxrock
oxrock

Reputation: 641

If you call a constructor, it's going to create a new object. The simplest thing is to do what hacatu suggested and simply assign b to a's value. If not, perhaps you could have an if statement checking if the value passed in is equal to the object you want referenced and if it is, simply return that item before ever calling the constructor. I haven't tested so I'm not sure if it'd work.

Upvotes: 0

Hugh Bothwell
Hugh Bothwell

Reputation: 56654

__init__ is an initializer, not a constructor. You would have to mess around with __new__ to do what you want, and it's probably not a good idea to go there.

Try

a = b = A(1)

instead.

Upvotes: 2

Related Questions