4

I need to create a class that mimics this behavior (in mathematics, we say list, dict, are "idempotent"):

>>> list(list([3,4]))
[3, 4]
>>> dict({'a':1,'b':2})
{'a':1,'b':2}

So, if A is my class, I want to write

>>> a = A(1)
>>> b = A(a)
>>> b == a
True

I imagine my class A has to look like this :

class A(object):
   def __init__(self,x):
       if isinstance(x, A) : 
           self = x
       else : 
           self.x = x
           self.y = 'hello'

I try it

>>> A(1).x
1
>>> A(A(1)).x
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'A' object has no attribute 'x'

It does not work !

I don't want to copy x attributes in self, i just want self to BE x or "point" x

Some idea ? Thanks

  • 3
    `self = x` merely changes the value of the local variable `self` to refer to a different object. (This reference goes away at the end of the method anyway.) It does not *replace* the object that is being pointed to by `self` with the other object. – kindall Nov 16 '12 at 15:19
  • In the case of `b = A(a)`, is it also OK that any later changes to `a` affect the value of `b`? – martineau Nov 16 '12 at 16:38

1 Answers1

11

What you are looking for is the __new__() method, which takes is run before the class is constructed, as opposed to __init__(), which takes place after. With __new__() you can hook in and replace the object being created.

def __new__(cls, x):
    if isinstance(x, A):
        return x
    else:
        return object.__new__(cls, x)

You can't do this in __init__() as the object has already been created. Changing self simply changes the value of the local variable, it doesn't affect the object.

It's also worth noting that type-checking is almost always the wrong thing to do in Python. Instead, check to see if the class has the information/attributes you need. This way, someone can create a class that acts like yours and works with your code.

As a final word of warning, this is pretty confusing behaviour - people won't expect your class to act like this and it's generally not a great idea. Your example of list() and dict() isn't accurate to what you are doing here, as list(some_list) does not give some_list, it gives a new list which is a copy of some_list - the same is true for dict():

>>> x = [1, 2, 3]
>>> list(x) is x
False 

When you call a constructor, it's natural to expect a new object, rather than a reference to the existing one. I would recommend making A(some_a) copy some_a, and restructure your calling code not to rely on A(some_a) is some_a).

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • 1
    +2 if I could for not just answering the question, but also for the excellent advice and explanation. – Steven Rumbalski Nov 16 '12 at 15:29
  • Great answer, especially the warning in the last part with a recommendation not do it. It is my understanding that idempotence is a property of certain operations, like an adjective, not an object itself (and since I don't consider _list_ & _dict_ mathematical operations it makes little sense to call them idempotent). – martineau Nov 16 '12 at 19:08
  • Ok, very clear answer. I think you are right, the expected behavior of a constructor is to construct some new object. I just have to take care not to call twice my constructor... Thank you – Pierre Puiseux Nov 17 '12 at 08:59