Saturday, December 22, 2012

Python Lists part I

A list is one of the most important data structures in Python. In this (and subsequent) posts I will go over the methods, tips, and examples of using lists in your programs.

I will be using Python 3.3 but most of the examples should work with Python 2.7.

There are already some excellent tutorials that cover usage of lists:

http://docs.python.org/3.3/tutorial/datastructures.html#more-on-lists

and

http://effbot.org/zone/python-list.htm

In this series of posts my goal is to cover some helpful utility functions for list handling, go into more detail on list sorting and give some examples of list use in my game tutorials:

http://lightbird.net/larks/

Customizing lists

Lists are a kind of a default go-to container structure in Python: if you’re not sure what to use, it’s always a safe choice to start with and to change later as needed.

It’s very easy to make a list-like container to add custom functionality. If you only need to iterate over the list, you need to add the special __iter__() method:

# (star, brightness)
starlst = [["Deneb", 1.25], ["Aldebaran", 0.85], ["Betelgeuse", 0.42], ["Fomalhaut", 1.16]]

class Stars(object):
    def __init__(self, stars) : self.stars = stars
    def __iter__(self)        : return iter(self.stars)

stars = Stars(starlst)
for star in stars:
    print(star)

You can also use the ‘in’ test:

print(starlst[0] in stars)

Once you have your custom container, it’s easy to add convenience methods:

class Stars(object):
    def __init__(self, stars) : self.stars = stars
    def __iter__(self)        : return iter(self.stars)

    def names(self):
        return [s[0] for s in self.stars]

    def brightness(self):
        return [s[1] for s in self.stars]

    def bright_stars(self):
        return [s[0] for s in self.stars if s[1] < 1]

print(stars.names())
print(stars.bright_stars())

The way visual brightness is measured is that the lower the value.

Of course, you might also want to make a Star class to wrap each star with name and brightness attributes, but this is a good example of making a wrapper around a list of tuples which may be more than enough in many cases.

Now, if I need to access items by index, I’ll just need to add one more special method – __getitem__() (naturally, if I did not need to iterate but ONLY needed access-by-index, I could add this special method alone – my point is that it’s VERY easy to add these special methods as you experiment with your code as you go and worry about the rest later):

def __getitem__(self, i): return self.stars[i]

... letting me do print(stars[0]):

["Deneb", 1.25]

Often, you’ll want to accompany your __getitem__ with a __setitem__:

def __setitem__(self, i, val): self.stars[i] = val

Now I can change the star’s brightness (ok not the real star’s but the one in my program):

i, brightness = input("Enter star # and brightness: ").split()
i = int(i)
stars[i][1] = float(brightness)
print(stars[i])

If I enter ‘1 0.9’, I get the following:

['Aldebaran', 0.9]

In this manner, I can get little bits and pieces of the full list’s functionality as I need them. If I wanted to have a full set of list’s methods, I need to inherit from collections.MutableSequence and override the following special methods:

class Stars(collections.MutableSequence):
    def __init__(self, stars)     : self.stars = stars
    def __len__(self)             : return len(self.stars)
    def __getitem__(self, i)      : return self.stars[i]
    def __delitem__(self, i)      : del self.stars[i]
    def __setitem__(self, i, val) : self.stars[i] = val
    def __str__(self)             : return str(self.stars)

    def insert(self, i, val)      : self.stars.insert(i, val)

Now you’ll be able to use methods like append, count, extend, pop, reverse, sort, et cetera.

My guide to Python has many examples of usage of lists, built-in and customized, used in many simple games along with helpful explanations:

http://lightbird.net/larks/

In my next post I’ll go over some of the uses of lists in my guide’s games.