Some Black Magic Python for n00bs
I had lunch with an old friend yesterday and we were discussing Python. He had a background in Perl and PHP so I knew some of the higher-order aspects of Python wouldn’t be clear to him yet. He also had rudimentary knowledge of Python decorators, a tool I use all the time.
In an effort to help, I wrote up some code that demonstrates some of these concepts. I think it will be useful to readers of this blog too.
Higher Order Uses of Python
Python gives us some features found in functional languages. I make use of the higher-order features all the time.
The basic idea is that you can write code that operates on code similarly to how it operates on data. For example, I can define a function and then pass it to some other function to use later.
def foo():
print 'foo'
def bar():
print 'bar'
list_of_funs = [foo, bar, foo, foo, bar]
for fun in list_of_funs:
fun()
def call_fun(fun):
fun()
call_fun(foo)
call_fun(bar)
On Duck Typing
If it looks like a dict and smells like a dict, well, let’s just treat it like a dict! (more here)
class Foo(object):
def __init__(self, *args, **kwargs):
self.bar = 'bar'
self._dict = dict()
## Make our Foo object behave like a dictionary
def __setitem__(self, key, value):
self._dict[key] = value
def __getitem__(self, key):
return self._dict[key]
## Create a foo instance
f = Foo()
## Treat it like a dictionary
f['some_key'] = 'some value'
print f['some_key']
## Set some more values
f['whatevz'] = 'meow meow meow'
f['dude'] = 'wheres your car?'
f['wheres your car?'] = 'dude'
A Caveat
We have only implemented the code to handle get and set like a dictionary.
for k,v in f.items():
print 'array[%s] => %s' % (k, v)
Hey wait… that loop didn’t work, did it? It’s because f isn’t a complete dictionary implementation. We only implemented the part that lets you set key-value pairs or retrive a value by key.
Read more here: http://docs.python.org/release/2.6.6/howto/descriptor.html
On List Comprehensions and Generators
First, some code using list comprehensions
some_list = [1,2,3,4,5]
squares = [x**2 for x in some_list]
print squares
another_list = [9,8,7,6,5]
added_squares = [x**2 + y**2 for x in some_list for y in another_list]
print added_squares
And now for the same idea with a generators.
some_list = [1,2,3,4,5]
squares = (x**2 for x in some_list) # similar syntax
print squares
We didn’t get a list back that time. But check out what it does when we treat it like a list.
for square in squares:
print square
It printed the list! But why isn’t squares itself a list?
A generator is a way to delay computation of some code until a later point. Consider the idea of searching through files on a file system. You can write a generator that will yield the name of every file in the file system when you iterate over it. Rather than compute the list of files ahead of time, you generate the next file when you’re about to use it.
First, we’ll introduce the concept of yield with an example.
def generator_function():
x = [1,2,3,4,5]
for i in x:
yield i
def print_remaining(generator):
## now print the remaining
for i in generator:
print i
## Instanitate the generator
generator = generator_function()
## Print the first two yields
print generator.next()
print generator.next()
## Finish off the generator
print_remaining(generator)
So what you see is that a generator is essentially a function that behaves like an iterator. You yield a value and the execution stops until you ask it for the next value.
We can build the concept of an infinite list by using a generator. Here is an example.
def infinite_list():
i = 1
while True:
yield i
i = i+1
long_list = infinite_list()
long_list.next()
1
long_list.next()
2
long_list.next()
3
On Decorators
A decorator is basically a function that wraps another one. Consider it from the perspective of authentication. If a user isn’t authenticated, you don’t want them to be able to call a funciton.
def auth_wrapper(method):
"""This function creates a new function that decides whether or not
method can be called. It is intended to wrap a class funciton, thus
the use of `self`. If self._auth isn't present, and exception will
be thrown instead of calling the wrapped function.
"""
def wrapped(self):
if self._auth:
return method(self)
else:
raise Exception('Auth failed!')
return wrapped # return the function we just defined above
We can see by the use of self that this decorator expects to be used on a class method. Let’s write a class that uses it!
class SomeThing(object):
def __init__(self):
self._auth = False
def make_authed(self):
self._auth = True
@auth_wrapper
def some_fun(self):
print 'Some fun!'
If we instanitate SomeThing, we can try it out.
st = SomeThing()
try:
st.some_fun() # throws an exception
except Exception,e:
print 'ERROR: ', e
st.make_authed()
st.some_fun()