4

I am new in Python and in OOP in general. I have an error "...instance has no attribute '__getitem__'", and I understand that the object I have created is not a list. How can I make to be a list object. Here is the class file:

#!/usr/bin/python -tt

import math, sys, matrix, os

class Point:
    'Class for points'
    pointCount = 0

    def __init__(self, x, y, z):
        'initialise the Point from three coordinates'
        self.x = x
        self.y = y
        self.z = z
        Point.pointCount += 1

    def __str__(self):
        'print the Point'
        return 'Point (%f, %f, %f)' %(self.x, self.y, self.z)

    def copyPoint(self, distance):
        'create another Point at distance from the self Point'
        return Point(self.x + distance[0], self.y + distance[1], self.z + distance[2])

    def __del__(self):
        'delete the Point'
        Point.pointCount -= 1
        #print Point.pointCount
        return '%s deleted' %self

I need to have it as a point with three coordinates inside (x, y, z), and those coordinates must be "callable" like in a list instance with [].

I have read similar topics but did not understand much. Please describe it in simple words and with examples.

Manuel Allenspach
  • 12,467
  • 14
  • 54
  • 76
  • what are you passing in as distance? Something indexable? Are you actually trying to use `Point` as a list ie doing `point[0]` etc? – Doug T. Jul 19 '12 at 14:48
  • 1
    You need to implement __getitem__: http://stackoverflow.com/questions/2936863/python-implementing-slicing-in-getitem – Simon Hibbs Jul 19 '12 at 15:08
  • Thanks to everybody for fruitful (and not much) comments and suggestions. For this moment I understand the answer from ecatmur. But I will study also others. @DougT.: yes, you are right. I am trying to make "indexable" class. Distance is just another indexable variable. It is just a testing example and the "copyPoint" will be redefined later. –  Jul 20 '12 at 07:48
  • somebody deleted my "Thank you" at the end of the original post. Why? –  Jul 20 '12 at 10:46

3 Answers3

5

Write a __getitem__ method:

def __getitem__(self, item):
    return (self.x, self.y, self.z)[item]

This constructs a tuple of x, y, and z, and uses Python's own indexing facilities to access it.

Alternatively you could switch your own internal storage to be a tuple, and create properties for x, y and z:

def __init__(self, x, y, z):
    self.coords = (x, y, z)

@property
def x(self):  # sim. for y, z
    return self.coords[0]

def __getitem__(self, item):
    return self.coords[item]
ecatmur
  • 152,476
  • 27
  • 293
  • 366
0

Yes, you need to define __getitem__, but I would probably design the class as follows, which allows attribute and index access to the co-ordinates.

from collections import namedtuple

class Point(object):
    def __init__(self, x, y, z):
        self._coords = namedtuple('Coords', 'x y z')._make( (x, y, z) )

    @property
    def coords(self):
        return self._coords

    def __getitem__(self, item):
        return self.coords[item]

    def copy_point(self, distance):
        return Point(*(el + distance for el in self.coords))

    def __repr__(self):
        return 'Point: {}'.format(self.coords)

p = Point(1, 2, 3)
p.copy_point(20), p.coords[0], p.coords.x
# (Point: Coords(x=21, y=22, z=23), 1, 1)
martineau
  • 119,623
  • 25
  • 170
  • 301
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
0

I suggest you consider making your Point class using the collections.namedtuple factory function which will make it a subclass of the the built-in tuple class. This will save you some boiler-plate work. namedtuple class have attributes that can be accessed both by name, such as p.x and indexed, like p[0].

They are also very memory efficient like tuples, which may be important if you're going to have a lot of class instances.

You can further specialize what is returned by subclassing it, or use the verbose option to capture the source code and modify that as necessary.

There's an example in the documentation linked to above showing it being used to create a 2D Point class, which seems like it could be very helpful in your specific use-case.

Here's an example showing how one could define a custom 3D Point class via subclassing:

from collections import namedtuple

class Point(namedtuple('Point', 'x y z')):
    __slots__ = ()  # prevent creation of instance dictionaries to save memory
    point_count = 0  # instance counter

    def __init__(self, *args):
        super(Point, self).__init__(*args)
        Point.point_count += 1

    def distance(self, other):
        return sum((self[i]-other[i])**2 for i in xrange(len(self))) ** 0.5

    def copy_point(self, distance):
        'create another Point at distance from the self Point'
        return Point(*[dimension+distance for dimension in self])

p1 = Point(0, 0, 0)
print 'p1:', p1
p2 = p1.copy_point(20)
print 'p2: Point(%s)' % ', '.join(str(p2[i]) for i in xrange(len(p2)))
print 'distance p1 <-> p2: %.3f' % p1.distance(p2)

Output:

p1: Point(x=1, y=2, z=3)
p2: Point(21, 22, 23)
distance p1 <-> p2: 34.641

Note that by using namedtuple you don't have to implement a __getitem__() yourself, nor write a __str__() method. The only reason an __init__() was needed was because of the need to increment the class instance counter which was added -- something that namedtuples don't have or do by default.

martineau
  • 119,623
  • 25
  • 170
  • 301
  • @AlexPi: You're welcome. FWIW, I'd also like to point out (no pun intended) that from the sample output you can see that what your `copy_point()` method does is **not** actually creating another point the specified distance away from itself. In other words `Point(21,22,23)` is not 20 units away from `Point(1,2,3)`. – martineau Aug 04 '12 at 02:03
  • Yes, I know. It was just for testing. It is not there anymore ;) –  Aug 07 '12 at 09:19