Torxed Torxed - 5 months ago 10
Python Question

Class that inherits `int` gets odd ghost like variable assignments

For lack of a better title, this is what I got so far.

class cordinate(int):
def __new__(self, *args, **kwargs):
self.x = args[0]
self.y = args[1]

## Try to get pixel [0,0] and see what it got:
if self.x == 0 and self.y == 0:
print('New Here')
print(self.x, self.y, self.angle, self.distance_from_edge)

self.angle = 225
self.distance_from_edge = 13

args = [self.distance_from_edge,]
return super(cordinate, self).__new__(self, *args, **kwargs)

cordinates = [cordinate(0,0), cordinate(2,10), cordinate(3,8)]


As expected, this code throws an error:

New Here
Traceback (most recent call last):
File "test.py", line 17, in <module>
cordinates = [cordinate(0,0), cordinate(2,10), cordinate(3,8)]
File "test.py", line 9, in __new__
print(self.x, self.y, self.angle, self.distance_from_edge)
AttributeError: type object 'cordinate' has no attribute 'angle'


For whatever reason, I tried the following:

if self.x == 2 and self.y == 10:


This code will output:

New Here
2 10 225 13


Now, I am sure there's a simple explanation to this and there's no need for panic or start to believe in ghosts..

But I've played around with it but can't make sense of it. What's the explanation of this behavior - does it have a name? How come a newly created instance can have a value that is about to be set 2 rows under?

Expected value: Always crashing - Because I purposely placed a print with a not-defined key at the top.

Python: 3.5.1 (Windows 8)

Answer

As you can see from the data model documentation, the first argument to __new__ is the class (conventionally cls), not the instance (conventionally self). Therefore you are setting class attributes on the cordinate class (note that that's a typo, and class names should be CamelCased) not each instance. As long as the first call to __new__ succeeds, those attributes are set on the class for all subsequent calls.

If you want to set instance attributes in __new__, do it once you have the instance, e.g.:

class Coordinate(int):

    def __new__(cls, *args, **kwargs):
        self = super(Coordinate, cls).__new__(cls, *args[2:], **kwargs)
      # ^ or 'inst' or whatever you like
        self.x, self.y = args[:2]
        ...
        return self