__setitem__() implementation in Python for Point(x,y) class
Question:
I’m trying to make a Point class in python. I already have some of the functions, like __ str__ , or __ getitem__ implemented, and it works great.
The only problem I’m facing is that my implementation of the __ setitem__ does not work, the others are doing fine.
Here is my Point class, and the last function is my __ setitem__()
:
class point(object):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "point(%s,%s)" % (self.x, self.y)
def __getitem__(self, item):
return (self.x, self.y)[item]
def __setitem__(self, x, y):
[self.x, self.y][x] = y
It should work like this:
p = point(2, 3)
p[0] = 1 # sets the x coordinate to 1
p[1] = 10 # sets the y coordinate to 10
Am I even right, should the `setitem() work like this?
Thanks!
Answers:
Let self.data
and only self.data
hold the coordinate values.
If self.x
and self.y
were to also store these values there is a chance self.data
and self.x
or self.y
will not get updated consistently.
Instead, make x
and y
properties that look up their values from self.data
.
class Point(object):
def __init__(self,x=0,y=0):
self.data=[x, y]
def __str__(self):
return "point(%s,%s)"%(self.x,self.y)
def __getitem__(self,item):
return self.data[item]
def __setitem__(self, idx, value):
self.data[idx] = value
@property
def x(self):
return self.data[0]
@property
def y(self):
return self.data[1]
The statement
[self.x, self.y][x]=y
is interesting but problematic. Let pick it apart:
[self.x, self.y]
causes Python to build a new list, with values self.x
and self.y
.
somelist[x]=y
causes Python to assign value y
to the x
th index of somelist
. So this new list somelist
gets updated. But this has no effect on self.data
, self.x
or self.y
. That is why your original code was not working.
Let’s strip this down to the bare minimum:
x, y = 2, 3
[x, y][0] = 1
print(x)
This will print out 2
.
Why?
Well, [x, y]
is a brand-new list containing two elements. When you do reassign its first member to 1
, that just changes the brand-new list, so its first element is now 1
instead of 2
. It doesn’t turn the number 2
into the number 1
.
Since your code is essentially identical to this, it has the same problem. As long as your variables have immutable values, you can’t mutate the variables.
You could fix it by doing something like this:
x, y = [2], [3]
[x, y][0][0] = 1
print(x[0])
Now you’ll get 1
.
Why? Well, [x, y]
is a new list with two elements, each of which is a list. You’re not replacing its first element with something else, you’re replacing the first element of its first element with something else. But its first element is the same list as x
, so you’re also replacing x
‘s first element with something else.
If this is a bit hard to keep straight in your head… well, that’s usually a sign that you’re doing something you probably shouldn’t be. (Also, the fact that you’re using x
for a parameter that means “select x
or y
” and y
for a parameter that means “new value” makes it a whole lot more confusing…)
There are many simpler ways to do the same thing:
- Use an
if
/else
statement instead of trying to get fancy.
- Use a single
list
instead of two integer values: self.values[x] = y
. (That’s unutbu’s answer.)
- Use a
dict
instead of two integer values: self.values['xy'[x]] = y
.
- Use
setattr(self, 'xy'[x], y)
.
- Use a
namedtuple
instead of trying to build the same thing yourself.
What’s happening in setitem is it builds a temporary list, sets the value, then throws away this list without changing self.x or self.y. Try this for __setitem__
:
def __setitem__(self,coord,val):
if coord == 0:
self.x = val
else:
self.y = val
This is quite an abuse of __setitem__
, however… I’d advise figuring out a different way of setting the x/y coordinates if possible. Using p.x and p.y is going to be much faster than p[0] and p[1] pretty much no matter how you implement it.
This works in python 2.6 i guess it works for 2.7 as well
The __setitem__ method accept 3 arguments (self, index, value)
in this case we want to use index as int for retrive the name of the coordinate from __slots__ tuple (check the documentation of __slots__ is really usefull for performance)
remember with __slots__ only x and y attributes are allowed! so:
p = Point()
p.x = 2
print(p.x) # 2.0
p.z = 4 # AttributeError
print(p.z) # AttributeError
This way is faster respect using @property decorator (when you start to have 10000+ instances)
class Point(object):
@property
def x(self):
return self._data[0] # where self._data = [x, y]
...
so this is my tip for you 🙂
class Point(object):
__slots__ = ('x', 'y') # Coordinates
def __init__(self, x=0, y=0):
'''
You can use the constructor in many ways:
Point() - void arguments
Point(0, 1) - passing two arguments
Point(x=0, y=1) - passing keywords arguments
Point(**{'x': 0, 'y': 1}) - unpacking a dictionary
Point(*[0, 1]) - unpacking a list or a tuple (or a generic iterable)
Point(*Point(0, 1)) - copy constructor (unpack the point itself)
'''
self.x = x
self.y = y
def __setattr__(self, attr, value):
object.__setattr__(self, attr, float(value))
def __getitem__(self, index):
'''
p = Point()
p[0] # is the same as self.x
p[1] # is the same as self.y
'''
return self.__getattribute__(self.__slots__[index])
def __setitem__(self, index, value):
'''
p = Point()
p[0] = 1
p[1] = -1
print(repr(p)) # <Point (1.000000, -1.000000)>
'''
self.__setattr__(self.__slots__[index], value) # converted to float automatically by __setattr__
def __len__(self):
'''
p = Point()
print(len(p)) # 2
'''
return 2
def __iter__(self):
'''
allow you to iterate
p = Point()
for coord in p:
print(coord)
for i in range(len(p)):
print(p[i])
'''
return iter([self.x, self.y])
def __str__(self):
return "(%f, %f)" % (self.x, self.y)
def __repr__(self):
return "<Point %s>" % self
Your may find it a lot easier to use namedtuple for this:
from collections import namedtuple
Point= namedtuple('Point', ['x','y'])
fred = Point (1.0, -1.0)
#Result: Point(x=1.0, y=-1.0)
The main drawback is that you can’t poke values into a namedtuple – it’s immutable. In most applications that’s a feature, not a bug
This is pretty old post, but the solution for your problem is very simple:
class point(object):
def __init__(self,x=0,y=0):
self.x=x
self.y=y
def __str__(self):
return "point(%s,%s)"%(self.x,self.y)
def __getitem__(self,item):
return self.__dict__[item]
def __setitem__(self,item,value):
self.__dict__[item] = value
Each class has his own dictionary with all properties and methods created inside the class. So with this you can call:
In [26]: p=point(1,1)
In [27]: print p
point(1,1)
In [28]: p['x']=2
In [29]: print p
point(2,1)
In [30]: p['y']=5
In [31]: print p
point(2,5)
It is more readable then your “index” like reference.
Here’s an example:
from collections import namedtuple
Deck = namedtuple('cards',['suits','values'])
class FrenchDeck(object):
deck = [str(i) for i in range(2,11)]+list('JQKA')
suits = "heart clubs spades diamond".split()
def __init__(self):
self.totaldecks = [Deck(each,every) for each in self.suits for every in self.deck]
def __len__(self):
return len(self.totaldecks)
def __getitem__(self,index):
return self.totaldecks[index]
def __setitem__(self,key,value):
self.totaldecks[key] = value
CardDeck = FrenchDeck()
CardDeck[0] = "asdd" # needs`__setitem__()`
print CardDeck[0]
If you don’t use the __setitem__()
, you will get an error
TypeError: 'FrenchDeck' object does not support item assignment
I’m trying to make a Point class in python. I already have some of the functions, like __ str__ , or __ getitem__ implemented, and it works great.
The only problem I’m facing is that my implementation of the __ setitem__ does not work, the others are doing fine.
Here is my Point class, and the last function is my __ setitem__()
:
class point(object):
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "point(%s,%s)" % (self.x, self.y)
def __getitem__(self, item):
return (self.x, self.y)[item]
def __setitem__(self, x, y):
[self.x, self.y][x] = y
It should work like this:
p = point(2, 3)
p[0] = 1 # sets the x coordinate to 1
p[1] = 10 # sets the y coordinate to 10
Am I even right, should the `setitem() work like this?
Thanks!
Let self.data
and only self.data
hold the coordinate values.
If self.x
and self.y
were to also store these values there is a chance self.data
and self.x
or self.y
will not get updated consistently.
Instead, make x
and y
properties that look up their values from self.data
.
class Point(object):
def __init__(self,x=0,y=0):
self.data=[x, y]
def __str__(self):
return "point(%s,%s)"%(self.x,self.y)
def __getitem__(self,item):
return self.data[item]
def __setitem__(self, idx, value):
self.data[idx] = value
@property
def x(self):
return self.data[0]
@property
def y(self):
return self.data[1]
The statement
[self.x, self.y][x]=y
is interesting but problematic. Let pick it apart:
[self.x, self.y]
causes Python to build a new list, with values self.x
and self.y
.
somelist[x]=y
causes Python to assign value y
to the x
th index of somelist
. So this new list somelist
gets updated. But this has no effect on self.data
, self.x
or self.y
. That is why your original code was not working.
Let’s strip this down to the bare minimum:
x, y = 2, 3
[x, y][0] = 1
print(x)
This will print out 2
.
Why?
Well, [x, y]
is a brand-new list containing two elements. When you do reassign its first member to 1
, that just changes the brand-new list, so its first element is now 1
instead of 2
. It doesn’t turn the number 2
into the number 1
.
Since your code is essentially identical to this, it has the same problem. As long as your variables have immutable values, you can’t mutate the variables.
You could fix it by doing something like this:
x, y = [2], [3]
[x, y][0][0] = 1
print(x[0])
Now you’ll get 1
.
Why? Well, [x, y]
is a new list with two elements, each of which is a list. You’re not replacing its first element with something else, you’re replacing the first element of its first element with something else. But its first element is the same list as x
, so you’re also replacing x
‘s first element with something else.
If this is a bit hard to keep straight in your head… well, that’s usually a sign that you’re doing something you probably shouldn’t be. (Also, the fact that you’re using x
for a parameter that means “select x
or y
” and y
for a parameter that means “new value” makes it a whole lot more confusing…)
There are many simpler ways to do the same thing:
- Use an
if
/else
statement instead of trying to get fancy. - Use a single
list
instead of two integer values:self.values[x] = y
. (That’s unutbu’s answer.) - Use a
dict
instead of two integer values:self.values['xy'[x]] = y
. - Use
setattr(self, 'xy'[x], y)
. - Use a
namedtuple
instead of trying to build the same thing yourself.
What’s happening in setitem is it builds a temporary list, sets the value, then throws away this list without changing self.x or self.y. Try this for __setitem__
:
def __setitem__(self,coord,val):
if coord == 0:
self.x = val
else:
self.y = val
This is quite an abuse of __setitem__
, however… I’d advise figuring out a different way of setting the x/y coordinates if possible. Using p.x and p.y is going to be much faster than p[0] and p[1] pretty much no matter how you implement it.
This works in python 2.6 i guess it works for 2.7 as well
The __setitem__ method accept 3 arguments (self, index, value)
in this case we want to use index as int for retrive the name of the coordinate from __slots__ tuple (check the documentation of __slots__ is really usefull for performance)
remember with __slots__ only x and y attributes are allowed! so:
p = Point()
p.x = 2
print(p.x) # 2.0
p.z = 4 # AttributeError
print(p.z) # AttributeError
This way is faster respect using @property decorator (when you start to have 10000+ instances)
class Point(object):
@property
def x(self):
return self._data[0] # where self._data = [x, y]
...
so this is my tip for you 🙂
class Point(object):
__slots__ = ('x', 'y') # Coordinates
def __init__(self, x=0, y=0):
'''
You can use the constructor in many ways:
Point() - void arguments
Point(0, 1) - passing two arguments
Point(x=0, y=1) - passing keywords arguments
Point(**{'x': 0, 'y': 1}) - unpacking a dictionary
Point(*[0, 1]) - unpacking a list or a tuple (or a generic iterable)
Point(*Point(0, 1)) - copy constructor (unpack the point itself)
'''
self.x = x
self.y = y
def __setattr__(self, attr, value):
object.__setattr__(self, attr, float(value))
def __getitem__(self, index):
'''
p = Point()
p[0] # is the same as self.x
p[1] # is the same as self.y
'''
return self.__getattribute__(self.__slots__[index])
def __setitem__(self, index, value):
'''
p = Point()
p[0] = 1
p[1] = -1
print(repr(p)) # <Point (1.000000, -1.000000)>
'''
self.__setattr__(self.__slots__[index], value) # converted to float automatically by __setattr__
def __len__(self):
'''
p = Point()
print(len(p)) # 2
'''
return 2
def __iter__(self):
'''
allow you to iterate
p = Point()
for coord in p:
print(coord)
for i in range(len(p)):
print(p[i])
'''
return iter([self.x, self.y])
def __str__(self):
return "(%f, %f)" % (self.x, self.y)
def __repr__(self):
return "<Point %s>" % self
Your may find it a lot easier to use namedtuple for this:
from collections import namedtuple
Point= namedtuple('Point', ['x','y'])
fred = Point (1.0, -1.0)
#Result: Point(x=1.0, y=-1.0)
The main drawback is that you can’t poke values into a namedtuple – it’s immutable. In most applications that’s a feature, not a bug
This is pretty old post, but the solution for your problem is very simple:
class point(object):
def __init__(self,x=0,y=0):
self.x=x
self.y=y
def __str__(self):
return "point(%s,%s)"%(self.x,self.y)
def __getitem__(self,item):
return self.__dict__[item]
def __setitem__(self,item,value):
self.__dict__[item] = value
Each class has his own dictionary with all properties and methods created inside the class. So with this you can call:
In [26]: p=point(1,1)
In [27]: print p
point(1,1)
In [28]: p['x']=2
In [29]: print p
point(2,1)
In [30]: p['y']=5
In [31]: print p
point(2,5)
It is more readable then your “index” like reference.
Here’s an example:
from collections import namedtuple
Deck = namedtuple('cards',['suits','values'])
class FrenchDeck(object):
deck = [str(i) for i in range(2,11)]+list('JQKA')
suits = "heart clubs spades diamond".split()
def __init__(self):
self.totaldecks = [Deck(each,every) for each in self.suits for every in self.deck]
def __len__(self):
return len(self.totaldecks)
def __getitem__(self,index):
return self.totaldecks[index]
def __setitem__(self,key,value):
self.totaldecks[key] = value
CardDeck = FrenchDeck()
CardDeck[0] = "asdd" # needs`__setitem__()`
print CardDeck[0]
If you don’t use the __setitem__()
, you will get an error
TypeError: 'FrenchDeck' object does not support item assignment