<jupyter_start><jupyter_text>Python  Notation When writing computer commands that you can type, the font will change to `command`. For example, `x=2**3.4/5` is a command that can be typed in.When we talk about a general command, or some piece of missing data that you should complete, we will use angle brackets as in `` or ``. For example, `` could mean `"Southampton"`.When showing actual commands as typed into Python, they will start with `In []:`. This is the notation used by the IPython console. The `` allows you to refer to previous commands more easily. The output associated with that command will start with `Out []:`.When displaying code, certain commands will appear in different colours. The colours are not necessary. They highlight different types of command or variable. When using the spyder editor you may find the colours match up and are useful: if not, either ignore them or switch them off.  The console - Python as calculator Start with using Python as a calculator. Look at the *console* in the bottom right part of spyder. Here we can type commands and see a result. Simple arithmetic gives the expected results:<jupyter_code>2+2
(13.5*2.6-1.4)/10.2<jupyter_output><empty_output><jupyter_text>If we want to raise a number to a power, say $2^4$, the notation is `**`:<jupyter_code>2**4<jupyter_output><empty_output><jupyter_text>There is an issue with division. If we divide an integer by an integer, Python `3.X` will do *real* (floating point) division, so that:<jupyter_code>5/2<jupyter_output><empty_output><jupyter_text>However, Python `2.X` will do division in the integers:```pythonIn []: 5/2Out []: 2```If you are using Python `2.X` and want the division operator to behave in this way, start by using the command<jupyter_code>from __future__ import division<jupyter_output><empty_output><jupyter_text>Then:<jupyter_code>5/2<jupyter_output><empty_output><jupyter_text>If you really want to do integer division, the command is `//`:<jupyter_code>5//2<jupyter_output><empty_output><jupyter_text>Further mathematical commands, even things as simple as $\log$ or $\sin$, are not included as part of basic Python:<jupyter_code>log(2.3)
sin(1.4)<jupyter_output><empty_output><jupyter_text>First, note the way that errors are reported: we'll see this a lot, and there's useful information there to understand. It's telling us1. Where the problem occurred2. What the problem isThe language Python uses takes some getting used to, but it's worth the effort to read these messages, and think about what it's trying to say. Here it's pointing to the line where the problem happened, and saying "I don't understand what this command is!". Going back to the mathematics, we obviously want to be able to compute more mathematical functions. For this we need a *module* or *package*.  Importing modules and packages Anything that isn't provided by the base Python can be provided by modules and packages. A module is a file containing functions and definitions that we can include and use in our code. A package is a collection of modules. They're not included by default, to reduce overhead. They're easy to write - we will write our own later - and easy to include. To use a package we must *import* it. Let's look at the `math` package.<jupyter_code>import math
math.log(2.3)
math.sin(1.2)<jupyter_output><empty_output><jupyter_text>To use the package we've typed `import `, where in this case `` is `math`. Then we can use functions from that package by typing `.`, as we have here when `` is either `log` or `sin`. The "dot" notation may seem annoying, and can be avoided by specifying what functions and constants we want to use. For example, we could just get the `log` function and use that:<jupyter_code>from math import log
log(2.3)<jupyter_output><empty_output><jupyter_text>However, the "dot" notation is useful, as we often find the same symbol or name being used for many different purposes. For example, the `math` package contains the mathematical constant $e$ as `math.e`:<jupyter_code>math.e<jupyter_output><empty_output><jupyter_text>But there is also the electron charge, usually denoted $e$, which is in the `scipy.constants` package:<jupyter_code>import scipy.constants
scipy.constants.e<jupyter_output><empty_output><jupyter_text>To avoid these name clashes we can import something *as* a different name:<jupyter_code>from math import e
from scipy.constants import e as charge_e
e
charge_e<jupyter_output><empty_output><jupyter_text>You will often see this method used to shorten the names of imported modules or functions. For example, standard examples often used are:<jupyter_code>import numpy as np
import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>The commands can then be used by typing `np.`, or `plt.`, which saves typing. We would encourage you *not* to do this as it can make your code less clear.  Variables A *variable* is an object with a name and a value:<jupyter_code>x = 2<jupyter_output><empty_output><jupyter_text>In standard programming all variables must have a value (although that value may be a placeholder to say "this variable doesn't have a reasonable value *yet*"). Only symbolic packages can mirror the analytical method of having a variable with no specific value. However, code can be written *as if* the variables had no specific value.For example, we cannot write<jupyter_code>x = y**2<jupyter_output><empty_output><jupyter_text>as `y` does not have a specific value yet. However, we can write<jupyter_code>y = 3.14159627
x = y**2
print(x)<jupyter_output>9.869627123677912<jupyter_text>and get a sensible result, even though we have written the exact same line of code, as now `y` has a specific value.  WarningNote that we have defined the variable `x` twice in rapid succession: first as an *integer* (`x=2`) and next as a *floating point number*, or *float* (the computer's implementation of a real number, using `x=y**2`, where `y` is a float). Not all programming languages allow you to do this. In a *statically typed* language you have to say whether a variable will be an integer, or a float, or another type, before you define it, and then it cannot change. Python is *dynamically typed*, so any variable can have any type, which can be changed as we go.  Variable names A variable is an object with a name, but not just any name will do. Python has rules which *must* be followed, and conventions that *should* be followed, with a few gray areas.Variables *must** not contain spaces* not start with a number* not contain a special character (such as `!@$%^&*()\|`)So the following are valid:<jupyter_code>half = 1.0/2.0
one_half = 1.0/2.0<jupyter_output><empty_output><jupyter_text>but the following are not:<jupyter_code>one half = 1.0/2.0
1_half = 1.0/2.0
one!half = 1.0/2.0<jupyter_output><empty_output><jupyter_text>Variables *should** be descriptive, ie say what their purpose is in the code* be written entirely in lower case* separate different words in the variable name using underscoresMore detail can be found in [PEP8](https://www.python.org/dev/peps/pep-0008/). Variables *may* contain some unicode characters, depending on Python version and operating system. In Python `3` you can include accents or extended character sets in variable names:```pythonrôle = 5π = math.pi``` However, these tricks are not always portable between different Python versions (they aren't guaranteed to work in Python `2`), or different operating systems, or even different machines. To ensure that your code works as widely as possible, and that the methods you use will carry over to other programming languages, it is recommended that variables do not use any extended characters, but only the basic latin characters, numbers, and underscores.  Equality and variable assignment One thing that may seem odd, or just plain *wrong* to a mathematician, is two statements like<jupyter_code>x = 2
x = 3<jupyter_output><empty_output><jupyter_text>How can `x` equal *both* 2 and 3? Mathematically, this is nonsense. The point is that, in nearly every programming language, the `=` symbol is not mathematical equality. It is the *assignment* operation: "set the value of the variable (on the left hand side) equal to the result of the operation (on the right hand side)". This implies another difference from the mathematical equality: we cannot flip the two sides and the line of code mean the same. For example,<jupyter_code>3 = x<jupyter_output><empty_output><jupyter_text>immediately fails as `3` is not a variable but a fixed quantity (a *literal*), which cannot be assigned to. Mathematically there is no difference between $x=3$ and $3=x$; in programming there is a huge difference between `x=3` and `3=x` as the meaning of `=` is not the mathematical meaning. To get closer to the standard mathematical equality, Python has the `==` operator. This compares the left and right hand sides and says whether or not their values are equal:<jupyter_code>x == 3.0<jupyter_output><empty_output><jupyter_text>However, this may not be exactly what we want. Note that we have assigned `x` to be the *integer* 3, but have compared its value to the *float* 3.0. If we want to check equality of value and type, Python has the `type` function:<jupyter_code>type(x)
type(x) == type(3.0)
type(x) == type(3)<jupyter_output><empty_output><jupyter_text>Direct comparisons of equality are often avoided for floating point numbers, due to inherent numerical inaccuracies:<jupyter_code>p = 2.01
p**2-4.0401
p**2 == 4.0401<jupyter_output><empty_output><jupyter_text>We will return to this later.  Debugging Making mistakes and fixing them is an essential part of both mathematics and programming. When trying to fix problems in your code this process is called *debugging*. As you've been following along with the code you will have made "mistakes" as there are intentionally broken commands above to show, for example, why `one!half` is not a valid variable (and you may have made unintentional mistakes as well). There are a number of techniques and strategies to make debugging less painful. There's more detail in later chapters, but for now let's look at the crucial first method.  Reading the error messageWhen you make a mistake Python tells you, and in some detail. When using IPython in particular, there is much information you can get from the error message, and you should read it in detail. Let's look at examples. Syntax errorA *syntax error* is when Python cannot interpret the command you have entered.<jupyter_code>one half = 1.0/2.0<jupyter_output><empty_output><jupyter_text>This example we saw above. The `SyntaxError` is because of the space - `one half` is not a valid variable name. The error message says `invalid syntax` because Python can't interpret the command on the left of the equals sign. The use of the carat `^` points to the particular "variable" that Python can't understand.<jupyter_code>x = 1.0 / ( 2.0 + (3.0 * 4.5)<jupyter_output><empty_output><jupyter_text>This example is still a `SyntaxError`, but the pointer (`^`) is indicating the end of the line, and the statement is saying something new. In this statement `unexpected EOF while parsing`, "`EOF`" stands for "end of file". This can be reworded as "Python was reading your command and got to the end before it expected".This usually means something is missing. In this case a bracket is missing - there are two left brackets but only one right bracket.A similar example would be<jupyter_code>name = "This string should end here...<jupyter_output><empty_output><jupyter_text>In this case the error message includes "`EOL`", standing for "end of line". So we can reword this message as "Python was reading your command and got to the end of the line before the string finished". We fix this by adding a `"` at the end of the line. Finally, what happens if the error is buried in many lines of code? This isn't the way we'd enter code in the console, but is how we'd deal with code in the notebook, or in a script or file:<jupyter_code>x = 1.0
y = 2.3
z = 4.5 * x y + y
a = x + y + z
b = 3.4 * a<jupyter_output><empty_output><jupyter_text>We see that the error message points to the *specific line* that it can't interpret, *and* it says (at first) which line this is. Name ErrorsThis is where Python doesn't know the variable you're trying to refer to:<jupyter_code>my_variable = 6
my_variable_2 = 10
x = my_variable + my_variable_3<jupyter_output><empty_output><jupyter_text>It usually means you've made a typo, or have referred to a variable before defining it (sometimes by cutting and pasting code around). Using tab completion is a good way of minimizing these errors.Note that there is a difference between syntax errors and other errors. Syntax errors are spotted by the code before it is run; other errors require running the code. This means the format of the output is different. Rather than giving the line number and using a carat ("`^`") to point to the error, instead it points to the code line using "`---->`" and adds the line number before the line of code. Numerical errorsA range of "incorrect" mathematical operations will give errors. For example<jupyter_code>1.0/0.0<jupyter_output><empty_output><jupyter_text>Obviously dividing by zero is a bad thing to try and do within the reals. Note that the definition of floating point numbers *does* include the concept of infinity (via "`inf`"), but this is painful to use and should be avoided where possible.<jupyter_code>math.log(0.0)<jupyter_output><empty_output><jupyter_text>A `ValueError` generally means that you're trying to call a function with a value that just doesn't make sense to it: in this case, $\log(x)$ just can't be evaluated when $x=0$.<jupyter_code>10.0**(10.0**(10.0**10.0))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07553747821034282, 'fraction_numerical': 0.013073794305636257, 'mean_word_length': 5.191097122302159, 'pattern_counts': {'":': 0, '<': 124, '<?xml version=': 0, '>': 125, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '31', 'dir': 'jupyter-structured-clean-dedup', 'id': '97245', 'license': 'MIT', 'n_tokens_mistral': 3969, 'n_tokens_neox': 3800, 'n_words': 1955, 'path': '01-python-basics.ipynb', 'repo_name': 'LaGuer/maths-with-python', 'size': '13768'}