Close

## Using Functions¶

### Calling functions¶

We often want to do things to our objects that are more complicated than just assigning them to variables.

In [1]:
len("pneumonoultramicroscopicsilicovolcanoconiosis")

Out[1]:
45

Here we have "called a function".

The function len takes one input, and has one output. The output is the length of whatever the input was.

Programmers also call function inputs "parameters" or, confusingly, "arguments".

Here's another example:

In [2]:
sorted("Python")

Out[2]:
['P', 'h', 'n', 'o', 't', 'y']

Which gives us back a list of the letters in Python, sorted alphabetically (more specifically, according to their Unicode order).

The input goes in brackets after the function name, and the output emerges wherever the function is used.

So we can put a function call anywhere we could put a "literal" object or a variable.

In [3]:
len('Jim')*8

Out[3]:
24
In [4]:
x = len('Mike')
y = len('Bob')
z = x+y

In [5]:
print(z)

7


### Using methods¶

Objects come associated with a bunch of functions designed for working on objects of that type. We access these with a dot, just as we do for data attributes:

In [6]:
"shout".upper()

Out[6]:
'SHOUT'

These are called methods. If you try to use a method defined for a different type, you get an error:

In [7]:
x = 5

In [8]:
type(x)

Out[8]:
int
In [9]:
x.upper()

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-328ac508ff1b> in <module>
----> 1 x.upper()

AttributeError: 'int' object has no attribute 'upper'

If you try to use a method that doesn't exist, you get an error:

In [10]:
x.wrong

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-29321da545fa> in <module>
----> 1 x.wrong

AttributeError: 'int' object has no attribute 'wrong'

Methods and properties are both kinds of attribute, so both are accessed with the dot operator.

Objects can have both properties and methods:

In [11]:
z = 1+5j

In [12]:
z.real

Out[12]:
1.0
In [13]:
z.conjugate()

Out[13]:
(1-5j)
In [14]:
z.conjugate

Out[14]:
<function complex.conjugate>

### Functions are just a type of object!¶

Now for something that will take a while to understand: don't worry if you don't get this yet, we'll look again at this in much more depth later in the course.

If we forget the (), we realise that a method is just a property which is a function!

In [15]:
z.conjugate

Out[15]:
<function complex.conjugate>
In [16]:
type(z.conjugate)

Out[16]:
builtin_function_or_method
In [17]:
somefunc=z.conjugate

In [18]:
somefunc()

Out[18]:
(1-5j)

Functions are just a kind of variable, and we can assign new labels to them:

In [19]:
sorted([1,5,3,4])

Out[19]:
[1, 3, 4, 5]
In [20]:
magic = sorted

In [21]:
type(magic)

Out[21]:
builtin_function_or_method
In [22]:
magic(["Technology", "Advanced"])

Out[22]:
['Advanced', 'Technology']

### Getting help on functions and methods¶

The 'help' function, when applied to a function, gives help on it!

In [23]:
help(sorted)

Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.

A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.



The 'dir' function, when applied to an object, lists all its attributes (properties and methods):

In [24]:
dir("Hexxo")

Out[24]:
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__iter__',
'__le__',
'__len__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rmod__',
'__rmul__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__',
'capitalize',
'casefold',
'center',
'count',
'encode',
'endswith',
'expandtabs',
'find',
'format',
'format_map',
'index',
'isalnum',
'isalpha',
'isdecimal',
'isdigit',
'isidentifier',
'islower',
'isnumeric',
'isprintable',
'isspace',
'istitle',
'isupper',
'join',
'ljust',
'lower',
'lstrip',
'maketrans',
'partition',
'replace',
'rfind',
'rindex',
'rjust',
'rpartition',
'rsplit',
'rstrip',
'split',
'splitlines',
'startswith',
'strip',
'swapcase',
'title',
'translate',
'upper',
'zfill']

Most of these are confusing methods beginning and ending with __, part of the internals of python.

Again, just as with error messages, we have to learn to read past the bits that are confusing, to the bit we want:

In [25]:
"Hexxo".replace("x", "l")

Out[25]:
'Hello'
In [26]:
help("FIsh".replace)

Help on built-in function replace:

replace(...) method of builtins.str instance
S.replace(old, new[, count]) -> str

Return a copy of S with all occurrences of substring
old replaced by new.  If the optional argument count is
given, only the first count occurrences are replaced.



### Operators¶

Now that we know that functions are a way of taking a number of inputs and producing an output, we should look again at what happens when we write:

In [27]:
x = 2 + 3

In [28]:
print(x)

5


This is just a pretty way of calling an "add" function. Things would be more symmetrical if add were actually written

x = +(2, 3)



Where '+' is just the name of the name of the adding function.

In python, these functions do exist, but they're actually methods of the first input: they're the mysterious __ functions we saw earlier (Two underscores.)

In [29]:
x.__add__(7)

Out[29]:
12

We call these symbols, +, - etc, "operators".

The meaning of an operator varies for different types:

In [30]:
"Hello" + "Goodbye"

Out[30]:
'HelloGoodbye'
In [31]:
[2, 3, 4] + [5, 6]

Out[31]:
[2, 3, 4, 5, 6]

Sometimes we get an error when a type doesn't have an operator:

In [32]:
7-2

Out[32]:
5
In [33]:
[2, 3, 4] - [5, 6]

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
----> 1 [2, 3, 4] - [5, 6]

TypeError: unsupported operand type(s) for -: 'list' and 'list'

The word "operand" means "thing that an operator operates on"!

Or when two types can't work together with an operator:

In [34]:
[2, 3, 4] + 5

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-34-67b01a5c24ab> in <module>
----> 1 [2, 3, 4] + 5

TypeError: can only concatenate list (not "int") to list

To do this, put:

In [35]:
[2, 3, 4] + [5]

Out[35]:
[2, 3, 4, 5]

Just as in Mathematics, operators have a built-in precedence, with brackets used to force an order of operations:

In [36]:
print(2+3*4)

14

In [37]:
print((2+3)*4)

20