Ciasto piekarz
Ciasto piekarz

Reputation: 8277

How to set value of class object argument as if its a dict using __setItem__

I have implemented a sample class PointLocation,

import collections as _collections

Point = _collections.namedtuple("Point", ("x", "y", "z"))

class PointLocation(object):
    def __init__(self, x, y, z):
        self._x = x
        self._y = y
        self._z = z
        self._location = Point(x, y, z)
        print "First Item: %s " % self._location[0]

    def __repr__(self):
        return "%s(%r, %r, %r)" % (
            self.__class__.__name__,
            self._x,
            self._y,
            self._z,
            )


    def __getitem__(self, key):
        """
            to make the object to be used in a manner similar to a tuple or list
        """
        return self._location.__getitem__(key)

    def __setitem__(self, key, value):
        if key == 0:
            self._location = Point(self._location.x, value)
        else:
            self._location = Point(value, self._location.y)

and I am trying to set value of an argument x using:

pointLocationObj[0] = 1

but I keep getting error

Traceback (most recent call last):
  File "/usr/san/Desktop/testScripts/classObject_returnsList.py", line 40, in <module>
    pointLocationObj[0] = 7
  File "/usr/san/Desktop/testScripts/classObject_returnsList.py", line 32, in __setitem__
    self._location = Point(self._location.x, value)
TypeError: __new__() takes exactly 4 arguments (3 given)

Upvotes: 0

Views: 231

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1121654

Your Point named tuple requires x, y and z arguments:

Point = _collections.namedtuple("Point", ("x", "y", "z"))

while you only provided two of those; you probably wanted to pass in self._location.z as well:

def __setitem__(self, key, value):
    if key == 0:
        self._location = Point(self._location.x, value, self._location.y)
    else:
        self._location = Point(value, self._location.y, self._location.z)

You can also use the namedtuple._replace() method to replace a specific attribute (a new instance is returned):

def __setitem__(self, key, value):
    if key == 0:
        self._location = self._location._replace(y=value)
    else:
        self._location = self._location._replace(x=value)

If you wanted to use indices to refer to x, y and z, create a dictionary to apply as a keyword argument to namedtuple._replace():

def __setitem__(self, key, value):
    coordinate = self._location._fields[key]   # can raise IndexError, we want to propagate that
    self._location = self._location._replace(**{coordinate: value})

Here I assume you meant 0 to be x, not 1.

Upvotes: 1

Ciasto piekarz
Ciasto piekarz

Reputation: 8277

just by updating the below method, it worked as expected. Thanks @Martijin for opening my eyes.

def __setitem__(self, key, value):
    if key == 0:
        self._location = Point(value, self._location.y, self._location.z)
    elif key == 1:
        self._location = Point(self._location.x, value, self._location.z)
    elif key == 2:
        self._location = Point(self._location.x, self._location.y, value)
    else:
        raise IndexError("%s takes %s has arguments. You are trying to update %s argument." % (
            self.__class__.__name__, 
            len(self._location), key+1)
            )

2nd Update:

def __setitem__(self, key, value):
    """ update the value of argument like a dictionary
    """
    try:
        self._location = self._location._replace(**{'xyz'[key]: value})
    except IndexError:
        raise IndexError("%s takes %s has arguments. You are trying to update %s argument." % (
            self.__class__.__name__, 
            len(self._location), key)
            )

Upvotes: 0

Related Questions