Methods
Methods, also called functions, take some input and return some output. We have
already used lots of methods, like dir
, help
, and len
, and in this lesson
we will start creating our own.
As we have seen, methods can do a lot of stuff with very little typing. Methods are normally used to encapsulate small pieces of code that we want to reuse.
Let’s rewrite len
as an example.
>>> def length(obj):
... """Return the number of elements in obj.
...
... obj must be iterable.
... """
... i = 0
... for _ in obj:
... i += 1
... return i
>>> length
<function length at 0x7f83b2bc56e0>
>>> help(length)
Help on function length in module __main__:
length(obj)
Return the number of elements in obj.
obj must be iterable.
>>> length('A b c!')
6
>>> length(range(5))
5
There’s a lot going on here, so we will break it down line-by-line.
def length(obj)
: methods are defined usingdef
, followed by a space, and then the name you want to give the method.1 Inside the parentheses after the name, we list the inputs, or arguments, that we want our method to accept. In this case, we only need a single input: the thing we want to compute the length of. Finally, there’s a colon at the end, just like with afor
orif
, which means a block of code follows (which must be indented)."""Return the number of elements in obj."""
: This is the docstring. It’s just a documentation string, defined literally with three double quotes so that we can include linebreaks. By placing a string here, Python makes the string available to use when we pass our function tohelp
. Documenting your functions is a very good idea! It makes it clear to others, and to future-you, what the method is supposed to do.- The method block. This is the code that will run whenever you call your
method, like
length([1])
. The code in the block has access to the arguments and to any variables defined before the method definition.
In general, you should try to minimise the number of variables outside your method that you use inside. It makes figuring out what the method does much harder, as you have to look elsewhere in the code to find things out.>>> x = 1 >>> def top_function(): ... """Do something silly.""" ... print x ... print y ... >>> y = 2 >>> top_function() 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in top_function NameError: global name 'y' is not defined
return i
: This defines the output of the method, the thing that you get back when you call the method. You don’t have to return anything, in which case Python will implicitly make your function returnNone
, or you can return multiple things at once.
You can see that returning multiple things implicitly means returning a tuple, so we can choose to assign one variable per value while calling the method.>>> def no_return(): ... 1 + 1 ... >>> no_return() >>> no_return() == None >>> def such_output(): ... return 'wow', 'much clever', 213 ... >>> such_output() ('wow', 'much clever', 213) >>> a, b, c = such_output() >>> b 'much clever'
1. Names are conventionally in lowercase, with underscores separating words. ↩
Methods can be called in several ways.
>>> def add(x, y):
... """Return the sum of x and y."""
... return x + y
...
>>> add(1, 2)
>>> add(x=1, y=2)
>>> add(1, y=2)
>>> add(y=2, x=1)
>>> add(y=2, 1)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
>>> add(y=2, =1)
File "<stdin>", line 1
add(y=2, =1)
^
SyntaxError: invalid syntax
Specifying the argument’s name explicitly when calling a method is nice because it reminds you what the argument is supposed to do. It also means you don’t have to remember the order in which the arguments were defined, you can specify keyword arguments in any order. You can even mix positional arguments with keyword arguments, but any keyword arguments must come last.
Using keyword arguments is particularly useful for arguments which act as
on/off flags, because it’s often not obvious what your True
or False
is
doing.
>>> def add(x, y, show):
... """Return the sum of x and y.
...
... Optionally print the result before returning it.
... """
... if show:
... print x + y
... return x + y
...
>>> _ = add(1, 2, True) # Hmm, what is True doing again?
3
>>> _ = add(1, 2, show=True) # Aha! Much clearer
Always having to specify that flag is annoying. It would be much nicer if
show
had a default value, so that we don’t have to provide a value when
calling the method, but can optionally override it.
>>> def add(x, y, show=False):
... """Return the sum of x and y.
...
... Optionally print the result before returning it.
... """
... if show:
... print x + y
... return x + y
...
>>> _ = add(1, 2) # No printing!
>>> _ = add(1, 2, show=True)
3
Perfect.
Of course, function arguments can be anything, even other functions!
>>> def run_method(method, x):
... """Call `method` with `x`."""
... return method(x)
...
>>> run_method(len, [1, 2, 3])
3
Methods returning methods
What does this method do?
>>> def make_incrementor(increment):
... def func(var):
... return var + increment
... return func
SolutionClick to expand
What if you like to accept an arbitrary number of arguments? For example, we
can also write a total
method that takes two arguments.
>>> def total(x, y):
... """Return the sum of the arguments."""
... return x + y
...
>>>
But what if we want to allow the caller to pass more than two arguments? It would be tedious to define many arguments explicitly.
>>> def total(*args):
... """Return the sum of the arguments."""
... # For seeing what `*` does
... print 'Got {0} arguments: {1}'.format(len(args), args)
... return sum(args)
...
>>> total(1)
Got 1 arguments: (1,)
1
>>> total(1, 2)
Got 2 arguments: (1, 2)
3
>>> total(1, 2, 3)
Got 3 arguments: (1, 2, 3)
6
The *args
syntax says “stuff any arguments into a tuple and call it args
”.
This let’s us capture any number of arguments. As args
is a tuple, one could
loop over it, access a specific element, and so on.
We can also expand lists into separate arguments with the same syntax when calling a method.
>>> def reverse_args(x, y):
... return y, x
...
>>> l = ['a', 'b']
>>> reverse_args(l)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: reverse_args() takes exactly 2 arguments (1 given)
>>> reverse_args(*l)
('b', 'a')
A similar syntax exists for keyword arguments.
>>> def ages(**people):
... """Print people's information."""
... # For seeing what `**` does
... print 'Got {0} arguments: {1}'.format(len(people), people)
... for person in people:
... print 'Person {0} is {1}'.format(person, people[person])
...
>>> ages(steve=31)
Got 1 arguments: {'steve': 31}
Person steve is 31
>>> ages(steve=31, helen=70, zorblax=9963)
Got 3 arguments: {'steve': 31, 'zorblax': 9963, 'helen': 70}
Person steve is 31
Person zorblax is 9963
Person helen is 70
As you can see from the debug print statement, **people
is a dictionary
containing the keyword arguments we passed to the ages
method. The keys of
the dictionary are the names of the argument as strings, and the values are the
values of the arguments. Just like for the *
syntax, **
can also be used to
expand a dictionary into keyword arguments.
>>> d = {'thor': 5000, 'yoda': -1}
>>> ages(**d)
Got 2 arguments: {'yoda': -1, 'thor': 5000}
Person yoda is -1
Person thor is 5000
The order of the keyword arguments used to call the method are not necessarily
the same as those that the function block sees!
This is because dictionaries are unordered, and the **
syntax effectively creates a dictionary.
The most generic method
The most generic method would take any number of positional arguments and any number of keyword arguments. What would this method look like?
SolutionClick to expand
Inline methods
Some methods take other methods as arguments, like the built-in map
method.
>>> map(str, range(5))
['0', '1', '2', '3', '4']
map
takes a function and an iterable, and applies the function to each element in
the iterable. It returns a new list with the results. We can define and then pass
our own functions.
>>> def cube(x):
... """Return the third power of x."""
... return x*x*x
...
>>> map(cube, range(5))
[0, 1, 8, 27, 64]
For such a simple method, this is a lot of typing! We can use a lambda
function to
define such simple methods inline.
>>> map(lambda x: x*x*x, range(5))
[0, 1, 8, 27, 64]
The syntax of defining a lambda
is like this:
lambda <args>: <return expression>
<args>
is a command-separate set of variables that the lambda
can take as
arguments, and <return expression>
is the code that is run. A lambda
automatically returns whatever the result of the expression is, you don’t need
a return
(the return
is implicit).
Writing a lambda
statement defines a method, which you can capture as a
variable just like any other object.
>>> div2 = lambda x: x/2
>>> div2
<function <lambda> at 0x7fc6b2207758>
>>> map(div2, range(5))
[0.0, 0.5, 1.0, 1.5, 2.0]
Note that we got real numbers back because we are using Python 2 with from __future__ import division
.
Sum in quadrature
Write a method that accepts an arbitrary number of arguments, and returns the
sum of the arguments computed in quadrature. A “sum in quadrature” is the
square root of the sum of the squares of each number. You should use lambda
to define a squaring and a square root function, and map
to apply the
squaring method.
SolutionClick to expand
Another use case for lambda
is the built-in filter
method (see:
help(filter)
).
>>> filter(lambda x: x % 2 == 0, range(10)) # filter and return the even numbers only
[0, 2, 4, 6, 8]
List comprehension
How would you rewrite the filter
example above using a list comprehension?
SolutionClick to expand
Generally, you should only use lambda
methods to define little throw-away
methods. The main downside with using them is that you can’t attach a docstring
to them, and they become unwieldy when there’s complex logic.