XClose

An introduction to research programming with Python

Home
Menu

Functions

Definition

We use def to define a function, and return to pass back a value:

In [1]:
def double(x):
    return x * 2
In [2]:
double(5)
Out[2]:
10
In [3]:
double([5])
Out[3]:
[5, 5]
In [4]:
double("five")
Out[4]:
'fivefive'

Default Parameters

We can specify default values for parameters:

In [5]:
def jeeves(name="Sir"):
    return "Very good, " + name
In [6]:
jeeves()
Out[6]:
'Very good, Sir'
In [7]:
jeeves("James")
Out[7]:
'Very good, James'

If you have some parameters with defaults, and some without, those with defaults must go later.

In [8]:
def product(x=5, y=7):
    return x * y
In [9]:
product(9)
Out[9]:
63
In [10]:
product(y=11)
Out[10]:
55
In [11]:
product()
Out[11]:
35

Side effects

Functions can do things to change their mutable arguments, so return is optional.

In [12]:
def double_inplace(vec):
    vec[:] = [element * 2 for element in vec]


z = [1, 2, 3, 4]
double_inplace(z)
print(z)
[2, 4, 6, 8]

In this example, we're using [:] to access into the same list, and write it's data.

vec = [element * 2 for element in vec]

would just move a local label, not change the input.

Let's remind ourselves of this behaviour with a simple array:

In [13]:
x = 5
x = 7
x = ["a", "b", "c"]
y = x
In [14]:
x
Out[14]:
['a', 'b', 'c']
In [15]:
x[:] = ["Hooray!", "Yippee"]
In [16]:
y
Out[16]:
['Hooray!', 'Yippee']

Early Return

In [17]:
def isbigger(x, limit=20):
    if x > limit:
        return True
    print("Got here")
    return False


isbigger(25, 15)
Out[17]:
True
In [18]:
isbigger(40, 15)
Out[18]:
True

Return without arguments can be used to exit early from a function

In [19]:
def extend(to, vec, pad):
    if len(vec) >= to:
        return
    vec[:] = vec + [pad] * (to - len(vec))
In [20]:
x = [1, 2, 3]
extend(6, x, "a")
print(x)
[1, 2, 3, 'a', 'a', 'a']
In [21]:
z = list(range(9))
extend(6, z, "a")
print(z)
[0, 1, 2, 3, 4, 5, 6, 7, 8]

Unpacking arguments

If a vector is supplied to a function with a *, its elements are used to fill each of a function's arguments.

In [22]:
def arrow(before, after):
    return str(before) + " -> " + str(after)


print(arrow(1, 3))
1 -> 3
In [23]:
x = [1, -1]

print(arrow(*x))
1 -> -1

This can be quite powerful:

In [24]:
charges = {"neutron": 0, "proton": 1, "electron": -1}
In [25]:
charges.items()
Out[25]:
dict_items([('neutron', 0), ('proton', 1), ('electron', -1)])
In [26]:
for particle in charges.items():
    print(arrow(*particle))
neutron -> 0
proton -> 1
electron -> -1

Sequence Arguments

Similiarly, if a * is used in the definition of a function, multiple arguments are absorbed into a list inside the function:

In [27]:
def doubler(*sequence):
    return [x * 2 for x in sequence]


print(doubler(1, 2, 3, "four"))
[2, 4, 6, 'fourfour']

Keyword Arguments

If two asterisks are used, named arguments are supplied as a dictionary:

In [28]:
def arrowify(**args):
    for key, value in args.items():
        print(key + " -> " + value)


arrowify(neutron="n", proton="p", electron="e")
neutron -> n
proton -> p
electron -> e