{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "![header](header.png)\n", "\n", "# What's a function?\n", "\n", "We have been using just a small handful of functions so far. For our present purposes, a function is a command that takes some inputs, called arguments, and returns an output (this definition will be expanded in the next guide). The function \"evaluates\" to whatever it returns as its output, in the same way that a mathematical expression evaluates to a number, and boolean expression evaluates to True or False. Executing a function is called calling the function. We always know that a function is being called if there is a word followed by opening and closing brackets, which contain the arguments (remember, \"inputs\") to the function. For example:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "323\n" ] } ], "source": [ "a = max(1, 323, 3)\n", "print(a)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The print() and max() commands are both functions. The max() function returns whatever the maximum input is, and in the sense described above, evaluates to this value.\n", "\n", "The print() function does not return anything! This may be confusing. Doesn't print() show us a result? Yes, but that's not the same as returning. A printed result is visible to the user, but it is not part of the program. The printed text cannot be assigned to a variable, or used in any computation. It's just a thing for us to read. A returned value replaces that function call inside the code, once the function has finished calculating:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "223\n" ] } ], "source": [ "print(max(1, 323, 3) - 100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Hello\n" ] } ], "source": [ "b = print(\"Hello\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now when we assigned to b, the print() function did its thing (and this is why Hello was displayed on the screen), and b was then assigned to whatever the print() function returned. What did it return?" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] } ], "source": [ "print(b)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zilch." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Built-in functions and standard library functions\n", "\n", "At time of writing, Python has 68 built-in functions which you can read about [here](https://docs.python.org/3/library/functions.html). There is no need to memorize this list; you can obviously keep it open as you work. As you complete common tasks, your memory for the most useful built-in functions will grow.\n", "\n", "There are far more functions available than just these that are built-in. A library is a collection of code that is designed to be used in other programs, to improve the functionality of that program without the programmer having to do it all from scratch. Most programming languages have a \"standard library\", included wherever basic software (such as the interpreter) is installed, and Python is no exception. You can also create your own libraries, and use libraries not included in the standard library. If you installed Python using Anaconda, you will already have access to a large collection of non-standard libraries.\n", "\n", "Making use of a library takes the form of importing a module. A module is just a file containing Python code, and the syntax for doing this is easy. Let's import a module from the standard library. Suppose we wish to get some basic statistical facts about a set of data points." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import statistics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's all there is to it. When a module is imported, the code contained in that module is executed, and any functions, variables, or other objects it provides are loaded into the memory. To access them, we use the dot operator in the following way: modulename.thingwewant. For example, we wish to find the mean in the following data set:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "96.2200289611252\n" ] } ], "source": [ "datapoints = [122.4405632654331, 115.75254005489799, 92.53967075824983, 103.53590194401652, 81.27219333775719, 101.61965193699073, 78.30111045299329, 114.11585074905481, 114.76222147968035, 94.74755847301873, 125.01631527683821, 105.17195190259096, 128.33700175849353, 89.38673085168398, 90.40490836715898, 74.35979914723771, 78.9020965751661, 71.23417050705106, 63.745428686885354, 78.75491369730545]\n", "\n", "average = statistics.mean(datapoints)\n", "print(average)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we anticipate that we will be using the statistics module a lot, we might want to give it a nickname for ease of typing:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "93.64361461563428\n" ] } ], "source": [ "import statistics as stats\n", "med = stats.median(datapoints)\n", "print(med)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If it is just the case that we want only a small number of the functions provided by a module, we can import just that function. In this case, we no longer have to specify the module that function has been imported from:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "376.7948073834579\n" ] } ], "source": [ "from statistics import variance\n", "var = variance(datapoints) # didn't need to write stats.variance\n", "print(var)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Python standard library is large and impressive. Most common tasks computing tasks are aided by standard library modules, such as downloading information from the internet, performing common mathematical operations, reading certain kinds of file from your computer, and so on. You can read about every module available in the standard library in this intimidating list [here](https://docs.python.org/3/library/)." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Where does Python import the modules from?\n", "\n", "This depends on your Python installation and operating system. With a standard Python installation on a UNIX-like (Mac or Linux) system, there is an environment variable called $PYTHONPATH. On different operating systems, or with an Anaconda installation, this might differ (I think Anaconda just looks inside its own directory although I could be mistaken about this, the documentation isn't clear).\n", "\n", "Two places that Python always looks is the current working directory, and the location where the script itself is saved. Recall from the command-line article/video that the user always has a current working directory when navigating using the shell. Whenever Python is running, it also has a current working directory (by default the working directory of the shell from which the script was launched)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Splitting up projects into many files\n", "\n", "The other key use of the import command is to allow your work to be spread over multiple files, which can all be brought together by importing them into a \"main\" file. This is essential for making large projects more manageable, as different aspects of the program can be handled independently.\n", "\n", "As an amusing anecdote and warning, Mark R Johnson, the sole developer of a game in-progress called Ultima Ratio Regum, had never written a line of code in his life when he started development. The game is staggeringly complex as a first programming project, and happens to be written in Python. While he clearly learned a lot of programming along the way, he missed the tip that programmers tend to split up their code across several files, as well as keeping data files (information the code works on) separated from program files (the actual code). At one point he admitted he was now working from a single Python file several hundred thousand lines long and that as well as being unmanageable from a human psychology perspective was causing his text editor to crash frequently under the strain of such a large file.\n", "\n", "So don't be like Mark. If you have to write a large program, split the main parts of the problem up into different files and code them separately. Then import them into a main file. Also, consider if any parts of the program you are writing could be useful in a future program! Then you can put them into one file and just import that file as and when needed in other programs." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Modules that can act as libraries or stand-alone programs\n", "\n", "There is a useful line of Python that can be used when writing a Python file, to give it a different behaviour if it is run as a stand-alone program, or if it is imported as a module.\n", "\n", "As an example, let's suppose I am a physics student who wants to write a very simple module that acts in two possible ways. The module provides some physical constants: if I import the module, I gain access to those physical constants in my other program. Alternatively, if I run the module by itself, say, from the command line, it prints out a list of all the physical constants defined therein.\n", "\n", "Here is the example code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# these constants are available if the module is imported\n", "speed_of_light = 299792458\n", "planck = 6.62607004e-34\n", "electron_charge = -1.6021766208e-19\n", "electron_mass = 9.10938356e-31\n", "\n", "if __name__ == '__main__':\n", " # this code will ONLY be executed if the module\n", " # is launched independently, not imported.\n", " print(\"The speed of light in a vacuum is\", speed_of_light, \"ms⁻ⁱ\")\n", " print(\"Planck's constant is\", planck, \"J·s\")\n", " print(\"An electron has charge\", electron_charge, \"C\")\n", " print(\"An electron has mass\", electron_mass, \"kg\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now if I save this as, say, constants.py, I can import it like so:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Electron rest energy = 8.187105649650028e-14 J\n" ] } ], "source": [ "import constants\n", "\n", "print(\"Electron rest energy =\",\n", " constants.electron_mass * constants.speed_of_light**2, \"J\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, going to my command line and typing python constants.py displays:" ] }, { "cell_type": "raw", "metadata": {}, "source": [ "$ python constants.py\n", "The speed of light in a vacuum is 299792458 ms⁻ⁱ\n", "Planck's constant is 6.62607004e-34 J·s\n", "An electron has charge -1.6021766208e-19 C\n", "An electron has mass 9.10938356e-31 kg\n", "$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Namespaces\n", "\n", "It is not uncommon for a large project to import many, many files. As we have learned, Python files usually involve naming lots of things, using variables. But there's no guarantee that those names will be unique. How do I know a variable in one file doesn't share a name with a variable in another file? Is it a problem if this does occur?\n", "\n", "This is where namespaces come in. A namespace system is a way of avoiding this potential ambiguity. Basically, the dot operator . makes it clear which namespace a particular variable belongs to, and only certain parts of the program can access the variable without using the . (for instance, parts of the program that are already contained within the module in question don't need to provide this part of the address). So, using import X requires us to provide an address to the namespace of each module to use the functions, but from X import Y brings Y into the namespace of the main file, so no . is needed.\n", "\n", "This concept will get a deeper looking in the next chaptor, in the section on scope." ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "# Exercises\n", "\n", "Head to the standard library [documentation](https://docs.python.org/3/library/). Sections 6 through to the end are standard library modules that can be imported. Don't be alarmed that you don't understand what most of them even mean. I don't understand what most of them do either. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.1" } }, "nbformat": 4, "nbformat_minor": 1 }