wtd
|
Posted: Wed Apr 06, 2005 5:04 pm Post subject: [Python-tut] Functions: arguments, objects, decorators |
|
|
Understanding functions is critical to understanding Python.
To introduce a function definition, we use the "def" keyword. Let's look at a simple function.
code: | >>> def hello():
... print "hello"
...
>>> hello()
hello
>>> |
This is about as straightforward as it gets.
Now, let's greet a person. For this, we'll need to get the name of the person as an argument.
code: | >>> def greet(name):
... print "Hello, " + name
...
>>> greet("Bob")
Hello, Bob
>>> |
Again, this is quite straightforward. We can take more than one argument.
code: | >>> def greet(first, last):
... print "Hello, " + first + " " + last
...
>>> greet("Bob", "Smith")
Hello, Bob Smith
>>> |
But, what happens now if the person being greeted doesn't have a last name (or is ahamed to make it known, lest his family's longstanding feud with the McCoy clan come back to haunt him)?
In that case, we'll have simply printed an extra space after the first name for nothing. We can use a conditional to catch this.
code: | >>> def greet(first, last):
... if last is "":
... print "Hello, " + first
... else:
... print "Hello, " + first + " " + last
...
>>> greet("Bob", "")
Hello, Bob
>>> |
But, in this case, why bother even supplying the empty string as an argument? Well, we've little choice. Python doesn't allow us to overload functions by argument count. However, we can make arguments optional by providing default values.
code: | >>> def greet(first, last=""):
... if last is "":
... print "Hello, " + first
... else:
... print "Hello, " + first + " " + last
...
>>> greet("Bob")
Hello, Bob
>>> |
Now, here's a question: what if I forget which argument comes first? Well, in that case I can use named arguments.
code: | >>> greet(last="Smith", first="Bob")
Hello, Bob Smith
>>> |
But, going back to single names, what if I want to greet lots of people? Well, in this case we need to slurp up all of the arguments that aren't otherwise accounted for into a list.
code: | >>> def greet(*names):
... for name in names:
... print "Hello, " + name
...
>>> greet("Bob", "John", "Chris")
Hello, Bob
Hello, John
Hello, Chris
>>> |
Earlier I demonstrated that named arguments can be used when a function is called. Well, what if I provide names that aren't arguments? I can collect those as well, and then deal with them in my function. In this case, let's use that to collect names, and aliases.
code: | >>> def greet(*names, **others):
... for name in names:
... print "Hello, " + name
... for name, alias in others.iteritems():
... print "Hello, " + name
... print "Or should I call you... " + alias + "!"
...
>>> greet("Bob", "John", Chris="Masked Hacker")
Hello, Bob
Hello, John
Hello, Chris
Or should I call you... Masked Hacker!
>>> |
Now, for the fun stuff. Functions in Python are objects. What does this mean, you say? Well, it means that we can pass them around like any other value.
The value of this is especially easily seen with functions like filter, which uses the output of a function supplied to it to determine if it should discard a value or not from a list.
First we create a list of numbers from 0 to 9 with the range function. Then we create a function which determines if a number is odd. Then we create another list based on filtering the original list.
code: | >>> foo = range(10)
>>> foo
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> def is_odd(number):
... return number % 2 is 1
...
>>> is_odd(3)
True
>>> is_odd(5)
True
>>> bar = filter(is_odd, foo)
>>> bar
[1, 3, 5, 7, 9]
>>> |
What other interesting things can we do with functions as objects? Well, we can create local functions.
code: | >>> def add(n):
... def temp(m):
... return m + n
... return temp
...
>>> a = add(4)
>>> a(3)
7
>>> a
<function temp at 0xb7df7fb4>
>>> |
Since "add(4)" returned a function, "a" is simply a function which adds 4 to any argument it's given. There's only one problem. The name of the "a" function is still "temp," since that's what it started out as. Fortunately we can fix that up.
code: | >>> def add(n):
... def temp(m):
... return m + n
... temp.func_name = "add(%d)" % n
... return temp
...
>>> a = add(4)
>>> a(3)
7
>>> a
<function add(4) at 0xb7df7f0c>
>>> |
Now, let's use something like this to comes up with another example of filter in action.
code: | >>> def is_less_than(n):
... def temp(m):
... return m < n
... temp.func_name = "is_less_than(%d)" % n
... return temp
...
>>> foo = range(10)
>>> bar = filter(is_less_than(6), foo)
>>> bar
[0, 1, 2, 3, 4, 5]
>>> |
Starting with Python 2.4, an interesting feature was added called "decorators." But first, let's look at the way the same things were accmplished before.
code: | >>> def print_before(a):
... def temp(func):
... def execute_func(*args, **kwargs):
... print a
... return func(*args, **kwargs)
... execute_func.func_name = func.func_name
... return execute_func
... return temp
...
>>> |
This looks pretty bizarre, but it's easier to understand when you go through it step by step. At the top is a function called "print_before" which takes an argument to print. This is the only function involved whose name we really care about.
That function defines another function called "temp" which takes a function as an argument.
Inside that we define yet another function called "execute_func" which takes a list of arguments. Inside that function we print "a" that was supplied to "print_before" and call the function it was supplied with using the arguments provided to execute_func, returning the output.
The "temp" function then changes the name of execute_func to match the function passed in, and returns execute_func.
The "print_before" function then returns "temp."
Now, let's look at using this to "wrap" another function.
code: | >>> def foo():
... print "foo"
...
>>> foo = print_before("bar")(foo)
>>> foo()
bar
foo
>>> |
But that's pretty messy, so Python's designers decided to make it nicer to look at.
code: | >>> @print_before(42)
... def foo():
... print "foo"
...
>>> foo()
42
foo
>>> |
Now, that's not a terribly useful example, but hopefully demonstrates the power of decorators. Let's look at a more useful example.
code: | >>> def attributes(**attrs):
... def temp(func):
... for attr_name, attr_value in attrs.iteritems():
... setattr(func, attr_name, attr_value)
... return func
... return temp
...
>>> @attributes(author="wtd",
... date_created="4/6/2005",
... version="1.0")
... def bar():
... print "bar"
...
>>> bar()
bar
>>> bar.author
'wtd'
>>> bar.date_created
'4/6/2005'
>>> bar.version
'1.0'
>>> bar
<function bar at 0xb7e0109c>
>>> |
Questions? Comments? |
|
|