{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Python basics 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Objectives" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Define a function that takes some input\n", "- Return a value from a function\n", "- Set default values for function parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions in Python serve very much the same purpose as in other programming languages, allowing you to tidy up your code and repeat parts of it as necessary. Unlike some other languages though, Python only has one kind of function structure. There are no subroutines or procedures - functions serve in place of both of these." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Definition" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A function definition in Python has four parts: \n", "- definition of the function name and arguments\n", "- a docstring - optional, but strongly recommended!\n", "- function contents\n", "- `return` statement - also optional, but necessary for the function to give output\n", "\n", "These components are arranged in a very similar way to loops and if statements." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def function_name(arg1, arg2, arg3): # Function name and argument(s) definition. Note the colon.\n", " \"\"\"\n", " Optional docstring.\n", " \n", " This should describe what the function does and what the input should be.\n", " This string will be displayed when you call help(function_name) and in some help interfaces.\n", " \"\"\"\n", " # Function contents.\n", " # Just the code that does what you want the function to do.\n", " # Note the indentation, as with loops and ifs.\n", " \n", " return # Optional return statement." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the return statement is omitted, or is present but with no variables, the output of the function is None. Otherwise, it exits the function immediately and returns returns any variables given to the main code.\n", "\n", "Since output from the function is dealt with by the return statement, arguments only need to specify input. Functions can have any number of arguments, including none." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Define an example function which returns the square of 5 and takes no input." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NameError", "evalue": "name 'square_five' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mprint\u001b[0m \u001b[0msquare_five\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'square_five' is not defined" ] } ], "source": [ "print square_five()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Define an example function" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Call our function with some input\n", "print square(5), square(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Positional arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When a function has multiple arguments, the function call must provide those arguments in the right order (unless they're keyword arguments - we'll get to them in a minute). These are called positional arguments." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Define another function with two input arguments" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NameError", "evalue": "name 'power' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# Call the function with some input\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mprint\u001b[0m \u001b[0mpower\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mpower\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'power' is not defined" ] } ], "source": [ "# Call the function with some input\n", "print power(5, 2), power(5, 3)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NameError", "evalue": "name 'power' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;31m# Call the function again with arguments the other way around\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mprint\u001b[0m \u001b[0mpower\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'power' is not defined" ] } ], "source": [ "# Call the function again with arguments the other way around\n", "print power(2, 5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Keyword arguments" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keyword arguments define a default value for a function argument. When calling the function, these can be omitted and the default value will be used." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Redefine the previous function making x and pwr keyword arguments with a default values of 5 and 2." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Call the new function with default argument values\n", "print power(), power(x=6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keyword arguments can be given in any order as long as the keyword is also given. Otherwise they are just positional arguments and have to be given in the same order as in the function definition." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Call the function with keyword arguments in arbitrary order\n", "print power(pwr=3), power(pwr=3, x=6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also mix keyword and positional arguments." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Call the function with both keyword and positional arguments\n", "print power(6, pwr=3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, when doing this the positional arguments should always come before the keyword arguments." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Demonstrate incorrect function call\n", "print power(pwr=3, 6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Challenges

\n", "
    \n", "
  1. Define a function which takes as input a list and an integer. The function should return a new list containing all the items in the old list raised to the power of the integer. Give the integer argument a default value.
  2. \n", "
  3. Define a new function which converts an input temperature value from degrees Celsius to degrees Fahrenheit.
  4. \n", "
  5. Redefine this second function so that it takes the temperature unit of the original temperature as an additional input, with a default value of 'C'. Have the function return this temperature in Celsius, Fahrenheit and Kelvins.
  6. \n", "
\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# 1\n", "\n", "# Test that the function works\n", "print list_to_power(range(5)), list_to_power(range(0, 10, 2), pwr=3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# 2\n", "\n", "# Test that the function works\n", "print convert_temp(0), convert_temp(100), convert_temp(-273.15)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# 3\n", "\n", "# Test that the function works\n", "print convert_temp(0), convert_temp(32, 'F'), convert_temp(-273.15, unit='K')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Object-Oriented Programming" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Objectives" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Understand what Python objects and instances are\n", "- Understand attributes and methods of objects\n", "- Understand the basic principles of inheritance\n", "- Create a class which inherits from another class" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Python is an object-oriented programming language. This means it uses objects, which are a way of grouping variables together. Objects have associated with them attributes and methods, and can be as complex as a really complex thing or as simple as a single number.\n", "\n", "Understanding OOP is not strictly necessary for Python unless you intend to write your own classes (types of object), because Python is specifically designed that way. However, EVERYTHING in Python is an object of some kind, so it is worth having a basic knowledge of OOP principles and terminology." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Classes and Instances" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A class is a type of object, which is defined to have particular properties. An instance is an individual object of a class. For instance," ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [], "source": [ "x = [1, 2, 3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "defines an object which is an instance of the list class, and binds that object to the name 'x'. This list object also contains three instances of the int type. When they are created, all list variables will have the properties of the list class - i.e.:, you can reference items in an array by indexing, add items using .append(), and so on, because these are written in to the class definition as things that lists can always do.\n", "\n", "The example above is fairly simple and uses built-in variable types, but classes can also be user-defined, and can be as complicated as you want them to be." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Attributes and Methods" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Attributes are objects which are associated with another object. These can be simple variables - individual numbers, strings, etc. - or they can be complex objects in their own right, with their own attributes. Methods are simply object attributes which are functions. They can be used to change their parent object or its attributes in some way, or they can return information about the object, and so on. Attributes and methods are of an object are accessed using the syntax `object_name.attribute`. We have already seen this syntax when using `list.append()` and similar functions in lesson 1. This is because `append()` is a method of list objects.\n", "\n", "Most classes define an `__init__` method, which is called automatically when an instance of that class is created. This allows the user to provide input to the new object, which is usually used to determine the object's attributes. Let's look at a class definition:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Define a new kind of object\n", "class Animal():\n", " \"\"\"Basic class which (vaguely) describes an animal.\"\"\"\n", " def __init__(self, n_legs, warmblooded):\n", " \"\"\"\n", " The function which initiates this class.\n", " \n", " Takes an int and a bool as input and defines corresponding attributes.\n", " \"\"\"\n", " self.n_legs = n_legs\n", " self.warmblooded = warmblooded\n", " \n", " def printlegs(self):\n", " print 'This creature has {} legs.'.format(self.n_legs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the syntax is again very similar to that of function definitions: a name declaration followed by parentheses and a colon, with the class contents indented. The parentheses are not strictly necessary at this point, but will be needed later, so they are included here for consistency. \n", "\n", "Also note that both the methods defined in the animal class take an argument called self. This argument refers to the parent object of the methods, and allows the method to access and change other attributes of the object. When calling the method, this argument is passed automatically and does not need to be supplied by the user.\n", "\n", "An animal object can be created from the the Animal class above like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Create a new Animal instance for a warmblooded, four-legged creature\n", "cat = Animal(4, True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and the printlegs() method is called like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Print the number of legs the cat has\n", "cat.printlegs()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Inheritance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A useful aspect of OOP is that classes can be defined such that they build upon the properties of another class. In this case the new class is said to _inherit_ from the base class. A common use for this feature is to take a class which is fairly generic and build a more specialised class from it. For example, we can create a new class based on the animal class above which more closely describes a particular type of animal:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that we are able to call the printlegs method of dog even though it is not defined for the Mammal class, because it has inherited it from the Animal class.\n", "\n", "Classes can also inherit attributes from more than one base class, but this is more complicated and not worth covering here." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Challenges

\n", "
    \n", "
  1. Create a Human class which inherits from the Mammal class above. This class should:
  2. \n", "
      \n", "
    • automatically set the `n_legs` attribute to 2 in the `__init__` method;
    • \n", "
    • have a new attribute, `name`, which is defined by the user when a new instance is created.
    • \n", "
    \n", "
  3. Create a new instance of this of the Human class. Print its name and number of legs.
  4. \n", "
\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "#1" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# 2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Namespaces and Modules" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Objectives" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Understand the basic principles and purpose of namespaces in Python\n", "- Import and rename a module\n", "- Import specific names from a module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Namespace principles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Namespaces are another topic that you will typically not need to worry about at the level of this workshop. Again though, it is useful to have a basic understanding of the concept even though it may not significantly change how you write your code.\n", "\n", "A namespace is the mapping of names to objects. The set of built-in Python variables and functions is an example of a namespace. Modules have their own namespaces, as do functions, and the names defined within these namespaces are not accessible from outside.\n", "\n", "For instance, remember that the functions above defined a variable called `x_to_pwr`. But if we try to use this variable outside the function, we get an error telling us that it is undefined:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print x_to_pwr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is because it is defined within the function's namespace, and does not exist outside that namespace.\n", "\n", "Similarly, the Mammal class from earlier defines a variable, `warmblooded`, and two functions, `__init__()` and `printlegs()`. Again, these are inaccessible from outside the namespaces of Mammal objects:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "printlegs()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The difference here is that we can access the names defined in the namespace of an instance object. When we use the `object.attribute` syntax to get a variable, we are really retrieving the attribute from within the namespace of the object." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The names defined in the current namespace can be accessed using the `dir()` function:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "['In',\n", " 'Out',\n", " '_',\n", " '__',\n", " '___',\n", " '__builtin__',\n", " '__builtins__',\n", " '__doc__',\n", " '__name__',\n", " '_dh',\n", " '_i',\n", " '_i1',\n", " '_i2',\n", " '_i3',\n", " '_i4',\n", " '_i5',\n", " '_i6',\n", " '_i7',\n", " '_i8',\n", " '_ih',\n", " '_ii',\n", " '_iii',\n", " '_oh',\n", " '_sh',\n", " 'exit',\n", " 'function_name',\n", " 'get_ipython',\n", " 'quit',\n", " 'x']" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "dir()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`dir()` can also be passed an object as an argument, in which case it will print the names defined within the namespace of that object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "dir(dog)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When any new object is created, a new namespace is created for it at the same time. This means that although different instances of the same class define the same names, they are within separate namespaces and do not interfere with each other. Similarly, a new namespace is created for a function every time it is called, so variables used within functions are not effected by previous calls to the same function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Importing modules" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As has been mentioned before, Python is a modular language. This means that although the core language itself is relatively simple, we can access a huge number of functions and classes designed for various purposes by importing them from another script which contains their definitions. These scripts are called modules.\n", "\n", "Let's take a look at a simple example." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This command imports the NumPy module into the current namespace, allowing us to use the names it defines. However, modules are objects, just like everything else, and have their own namespaces. The names defined in NumPy are therefore within that namespace and NOT available in the current namespace. Fortunately, we can access them as attributes of the NumPy module object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Try to print a varable from numpy" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Actually print a variable from numpy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default the name given to the module object when you import it is simply the name of the module. However, we will often be using the module many times in one code and may wish to abbreviate the name. For instance, numpy is usually abbreviated to `np`. To do this we can use the `as` keyword when importing." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Import and rename numpy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On the other hand, if we only wish to use one or two names from the module, importing the whole thing may be inefficient. In that case we can use the `from ... import ...` syntax to specify particular names we want." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Import pi and the sine function from NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that in the above example the variable `pi` and the function `sin()` can be called without appending the name of the module. This is because the `from ... import ...` structure imports the specified names into the current namespace, not the whole module, so they are immediately accessable. Any number of objects can be imported from a module using this syntax." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can even combine the `from` and `as` keywords to import some names from a function and rename them." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Import and rename pi, sine and cosine" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, as an extension of `from ... import ...`, we can import ALL of the names from a module into the current namespace by using `*`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Import everything from NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, this is STRONGLY DISCOURAGED, because it ignores the whole purpose of namespaces, which is to keep conflicting name definitions separate. If you blindly import everything from a module, you may well be redefining one of your own variables, or you may end up redefining a variable the module needs to work properly. This is especially the case when you are importing a large, third-party module like NumPy, and you are unlikely to know everything it contains. The wildcard import should therefore be used with extreme caution - and ideally not at all." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "

Challenges

\n", "
    \n", "
  1. Import the NumPy, SciPy and matplotlib modules. Rename each of them.
  2. \n", "
  3. The matplotlib module contains a submodule called pyplot. Import this submodule and rename it plt.
  4. \n", "
\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "#1" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "#2" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.9" } }, "nbformat": 4, "nbformat_minor": 0 }