You are hereBlogs / marv's blog / Python Coolness 2: Tips and Tricks

Python Coolness 2: Tips and Tricks


By marv - Posted on 19 April 2008

Welcome back Pythoneers! Last time we learned the underlying concepts of the Python programming language and runtime environment. In this second part, I would like to show you some of the Python tricks, that make this programming language so special. I will also briefly highlight some of the areas, which prove to be more difficult or dangerous than in other common languages, such as Java and C++. After this tutorial you will be able to write lines of code that will definitely impress your fellow geeks.

So, let's get started. Recall the tuple type we learned about last time. Let's do a few things with tuples to learn what they can and can't do:

# Lets make some tuples
point = (3.0, 2.0)
 
# We can mix types
client = ("marv", "male", 27, 1.80)
 
# We can omit the parentheses in non-ambiguous cases
four = "four", 4, 4.0
 
# We can access tuples using brackets
client[0]
> 'marv'
 
# Trick: Access the last elements with negative values
client[-1]
> 1.8
 
client[-2]
> 27
 
# Trick: We can access slices
client[1:3]
> ('male', 27)
 
client[:2]
> ('marv', 'male')
 
client[2:]
> (27, 1.8)
 
# Concatenation works with '+'
client + point
> ('marv', 'male', 27, 1.8, 3.0, 2.0)
 
# We can check if an element is in a tuple with 'in'
'male' in client
> True
 
# But: Tuples are IMMUTABLE
point[0] = 4.0
> TypeError: 'tuple' object does not support item assignment

It is best you experiment more with tuples, so that you get a feel for them. So far, we have always used tuples on the right side of assignments. One great trick in Python is that you can use tuples on the left side. For instance, swapping values can be done in one step:

# Assign some values
x = 3.0
y = 5.0
 
# Swap
x,y = y,x

Neat, huh? Using this technique we could have also done the two x and y assignments at the beginning in a single line.

Now let's move on to lists. Basically, most of what can be said for tuples, applies to lists, and vice-versa. However, lists are highly mutable. We cannot only change values at certain indexes, but also add and remove values at will. Let's have some fun with lists:

# Create lists with brackets
sentence = ['i', 'like', 'python']
 
# Concatenation works like before
sentence = sentence + ['very']
 
# You can also append a non-list object
sentence.append('much')

Okay, so nothing all too special yet. So let's look at a few other ways to construct lists. What if, for instance, we knew beforehand that our list has a specific length? Appending elements one by one is inefficient. For this, we can use the multiplication operator:

# Create a list with 10 zeros
counts = [0] * 10
 
# Now we can set elements in that range
counts[2] = 4

Often, we would like to create lists that enumerate over a certain range. This is especially useful for for loops:

# Create a list of numbers from 1 to 9
nums = range(1,10)
 
# Loop over them
for num in nums:
	print "I can count to %d!" % num

One of the most powerful features of Python are list comprehensions. They allow to create new lists from existing lists in a single line. Lets show how they work:

# Lets double every number in our nums list
nums2 = [x * 2 for x in nums]
 
# Lets get a list of all 2D positions in the range 0-4
positions = [(i,j) for i in range(0,5) for j in range(0,5)]
 
# We could use this list to convert a 1D index
# to a 2D index over the range 0-4,0-4
positions[8]
> (1, 3)
 
# We can also filter lists
even = [x for x in nums if (x % 2) == 0]
 
# Finally, as a more complex example, let's
# find all prime numbers between 3 and 100
def multiples_of(x, max):
	return [x * i for i in range(2, max/x + 1)]
 
# Get all numbers in range
prime = range(3, 100)
 
# Remove all multiples of 2...49
for i in range(2, 50):
	prime = [x for x in prime if not x in multiples_of(i, 100)]

Finally, I would also like to highlight the special relationship between lists and strings. Lets see what cool stuff we can do with the two:

# Strings work a lot like lists/tuples
greeting = "hello"
greeting[:4]
> 'hell'
 
"heaven" in greeting
> False
 
# Recall our sentence list
sentence
> ['i', 'like', 'python', 'very', 'much']
 
# String objects have a join() method
" ".join(sentence)
> 'i like python very much'
 
# We can iterate through strings character by character
["(%s)" % c for c in "PYTHON"]
> ['(P)', '(Y)', '(T)', '(H)', '(O)', '(N)']
 
# For instance, we can make a filter
garbled = "t!hi(s se/nten?ce h=as so?me? p&robl?ems"
alphabet = "abcdefghijklmnopqrstuvwxyz "
 
# Output only alphabet characters
"".join([c for c in garbled if c in alphabet])
> 'this sentence has some problems'

Enough with lists. Let's take a look at dictionaries. Dictionaries are a base type in python, and are very easy to use:

# Make a dictionary
ages = { 'marv': 27, 'meck': 25, 'ph': 42 }
 
# Access them like lists
ages['marv']
> 27
 
# Iterating through keys:
for name in ages:
	print "%s is %d years old!" % (name, ages[name])
 
# Or better: Using items()
for name, age in ages.items():
	print "%s is %d years old!" % (name, age)

Remember how everything in Python is a mapping object? Well, there are ways to convert these objects to a dictionary. One such function is locals(), which returns a dictionary with all variables at the local scope. Let's try it:

# Get all local variables, not starting with '_'
myvars = [var for var in locals() if var[:1] != '_']

The value myvars should now contain all variables that we have created in this tutorial so far. One downside of Python, is that arguments passed to a function need to be type checked. Let's make a function that will simplify this process for us:

# This is our helper function to check types. What it
# does will become clearer when we use it.
def check_types(locals, desired):
	for name, type_needed in desired.items():
		if not isinstance(locals[name], type_needed):
			raise ValueError("Wrong type for %s!" % name)
 
# A test function
def person_string(name, age):
	# Here comes the helper function
	check_types(locals(), { 'name': str, 'age': int })
 
	# The actual function implementation
	return "%s is %d years old." % (name, age)
 
# Test
person_string("marv", 27)
> 'marv is 27 years old.'
 
# But:
person_string("marv", 1.8)
> ValueError: Wrong type for age!

New to Python are properties. Objective-C 2.0 has added these to the language as well, and they are a very powerful concept. Properties allow attribute access, that are actually handled by functions. Lets see how they can be used:

class Person(object):
	def __init__(self, first, last):
		self.first = first
		self.last = last
 
	# Accessors to full name
	def get_fullname(self):
		return "%s %s" % (self.first, self.last)
 
	def set_fullname(self, full):
		self.first, self.last = full.split()
 
	# Provide attribute like access using properties
	fullname = property(get_fullname, set_fullname)
 
# Lets try it out
marv = Person('Marius', 'Renn')
 
marv.first
> 'Marius'
marv.last
> 'Renn'
 
marv.fullname
> 'Marius Renn'
 
marv.fullname = 'Marius Ju'
 
marv.last
'Ju'

Although it may not seem like much at first, properties allow powerful patterns to be implemented. For instance, using an observer pattern, another object could track the changes to the attributes of a certain object. Property set-methods could then inform observers of any change.

WARNING: There is a common pitfall of properties. They ONLY work if your class is a subclass of object (which you should always do anyway). If your class does not inherit from any class, you will not get an error, but properties simply won't work.

One final thing I would like to show are lambda functions. These are constructs that allow you to quickly define a function, without having to actually declare it. Lambda functions have no name, and can therefore not be reused. Lets see how they work:

# Lets say we have this list
numbers = range(1, 10)
 
# We can get there sum simply by
sum(numbers)
> 45
 
# What if we wanted the product? There is the reduce function, which
# applies a binary operation to all values in a list, starting from 
# the lowest index up to the highest.
# Its signature is: reduce(FUNCTION, LIST)
 
# So we could go ahead and do it this way:
def mult(x, y):
	return x * y
 
reduce(mult, numbers)
> 362880
 
# Or we could simply use a lambda function
reduce(lambda x,y: x * y, numbers)
> 362880

Alright, that's all for this episode. I hope you learned a few things, and find that Python has a lot to offer. There are many things we have not covered, but the many tutorials on the web should help out here. Next time, we will program our first cool application, which should give you an idea of how Python can help you accomplish useful tasks with only a few lines of code.

Tags