First steps in Python, variables

In this notebook we'll get to know the syntax of the Python language, and some basic data types and structures. Later we'll use these types and structures everywhere.

Comments

In Python, comments begin with a # symbol, and last until the end of the line. A comment can be at the beginning of a line, or can follow a space or a tab. But if you put it inside a string, it will not be a comment anymore. A # inside of a string only means a single #.

In [1]:
# this is the first comment
SPAM = 1                 # this is the second comment
                         # ... this is the third one.
STRING = "# This is not a comment, because it is between quotation marks."

Comments are for the programmer as a reminder, or they can help other programmers in understanding the code. After a while, even our own code will be a faint memory, thus, it pays off to comment our own code for ourselves extensively.

Python as a calculator

We can use the notebook as a simple calculator. We can write an expression, and it calculates its value. The syntax of the expression goes as usual: +, -, * and / operations work as in most other languages (such as Matlab, Fortran or C/C++). Brackets are for grouping operations, or for overriding the default order of operations.

In [2]:
2 + 2
Out[2]:
4
In [3]:
50 - 5*6
Out[3]:
20
In [4]:
(50 - 5*6) / 4
Out[4]:
5.0

Integer numbers such as 2, 4 or 20 are of the type int, real numbers such as 5.0 or 1.6 are of the type float. In Python 3, division (/) always returns a floating point value. To implement integer division, we can use the // operator, to get the remainder, we can use the % operator.

In [5]:
8 / 5  # division returns a float even in the case of integers
Out[5]:
1.6
In [6]:
17 // 3  # integer division
Out[6]:
5
In [7]:
17 % 3  # the % operator returns the remainder
Out[7]:
2
In [8]:
5 * 3 + 2  # check
Out[8]:
17

We can use the ** operator for exponentiation. (Attention, in most languages it is the ^ operator!)

In [9]:
5 ** 2  # square of 5
Out[9]:
25
In [10]:
2 ** 7  # 2 to the 7th power
Out[10]:
128

Python prefers floating point numbers to integers in certain operations. Those operations that mix types will always convert integers to floats.

In [11]:
3 * 3.75 / 1.5
Out[11]:
7.5
In [12]:
7.0 / 2
Out[12]:
3.5

Sometimes we have to use integers, but we got floats. Then, conversion can be done using the functions int() and float().

In [13]:
int(3.1415)
Out[13]:
3
In [14]:
float(42)
Out[14]:
42.0

Python can work with complex values. We can denote the imaginary part by j or J. Complex numbers can be written as a sum of the real and imaginary parts.

In [15]:
1j * 1J
Out[15]:
(-1+0j)
In [16]:
3+1j*3
Out[16]:
(3+3j)
In [17]:
(3+1j)*3
Out[17]:
(9+3j)
In [18]:
(1+2j)/(1+1j)
Out[18]:
(1.5+0.5j)

Complex numbers are sometimes represented by a pair of floating point numbers. We can address the real and imaginary parts of a z complex number by the z.real and z.imag commands. (For those reader who have already covered the topic of classes: real and imag are two attributes of the complex class.)

In [19]:
(1.5+0.5j).real
Out[19]:
1.5
In [20]:
(1.5+0.5j).imag
Out[20]:
0.5

Conversion to int or float by the functions int() and float() do not work for complex numbers. But we can get the absolute value of a complex number by using the abs() function.

In [21]:
float(3.0+4.0j) # ez nem működik
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-21-fb8153164495> in <module>()
----> 1 float(3.0+4.0j) # ez nem működik

TypeError: can't convert complex to float

Here we've seen our first Python error message! It is rather telling.

In [22]:
abs(3.0+4.0j)  # sqrt(a.real**2 + a.imag**2)
Out[22]:
5.0

Variables

When programming, we usually store information in useful variables. Our code will not refer to concrete numeric values, but to variables. This enables us to run the same algorithm for different input values as well. Variable names can only contain the characters of English language, and some other special characters of which _ is the most common.

width        # only contains letters
corridor_length # contains the _ character as well

Choose variable names such that they are meaningful.

As in most other programming languages, we can assign a value to our variables by using the = symbol. After such an assignment, the interpreter is waiting for a new command, it seems to have done nothing:

In [23]:
width = 20
height = 5*9

But in the memory, the variables width and height have been created, we can use them later on. Let us calcaulte the product o the two variables!

In [24]:
width * height
Out[24]:
900

If a variable is not yet defined (we have not assigned a value to it yet), then we get an error message if we want to use it. Note that the error message tells you about the problem quite verbosely!

In [25]:
n  # we still have not defined the variable n
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-25-bc06a8a81de0> in <module>()
----> 1 n  # we still have not defined the varable n

NameError: name 'n' is not defined

The value of the last printed expression is stored in the _ variable in Jupyter. Thus, if you use Python as a calculator, then you can chain your calculations in the following way:

In [26]:
tax = 12.5 / 100
In [27]:
price = 100.50
In [28]:
price * tax
Out[28]:
12.5625
In [29]:
price + _
Out[29]:
113.0625
In [30]:
round(_, 2) # rounding to two decimal values
Out[30]:
113.06

Use this variable only as readable. Do not assign a value to it, because assigning a value to it creates a local variable with the same name, the prevents the access to the built-in variable which behaves in this magic way. (If we create a local variable with the name of a global variable, the interpreter will use te local copy instead of the global one)

In a Jupyter Notebook, we can refer to not jsut the latest output of a cell, but former ones as well. For example, the output of the second cell was:

In [31]:
Out[2]
Out[31]:
4

Boolean variables and conditional execution

For a program, to be able to deal with different inputs, or to make its pards dependable on former values, we have be able to evaluate certain conditions. Let us think of the solution of a quadratic equation! Depending on the sign of the discriminant, real or complex roots exist. Thus, a program that calculates the solutions has to 'decide' based on tis input values. The decision is based on the evaluation of a condition (e.g. is the discriminant positive?), which can be true or false. A variable that can store true or false values is called boolean. (This is a type - more advanced readers can think of a class - like int or float were before.)

To test whether two objects are equal, we use the == operator.

In [32]:
1 == 1  # is 1 equal to 1?
Out[32]:
True
In [33]:
1 == 2 # is 1 equal to 2?
Out[33]:
False

To test whether two objects are not equal, we use the != operator.

In [34]:
1!=2 # is 1 not 2?
Out[34]:
True

In the case of numbers, we can decide which one is greater with the help of the <> operators.

In [35]:
1>2
Out[35]:
False
In [36]:
1<2
Out[36]:
True
In [37]:
1j+3>2 # this relation cannot be used for complex numbers, as the error message nicely tells us
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-37-79ad4ec18abf> in <module>()
----> 1 1j+3>2 # this relation cannot be used for complex numbers, as the error message nicely tells us

TypeError: '>' not supported between instances of 'complex' and 'int'

We can store a true or false value in a variable.

In [38]:
trueorfalse = 2*2==5
In [39]:
trueorfalse
Out[39]:
False

We may chain relations as well.

In [40]:
b=2
1 < 2 == b
Out[40]:
True
In [41]:
b=3
1 < 2 == b
Out[41]:
False

We can create longer logical expressions with the and and the or operators. We can change the result of any logical expression or logical variable with the help of the not operator.

In [42]:
not 2*2==5
Out[42]:
True

The and, or and not operators are evaluated after the relational expressions, that is, we do not use any grouping by brackets.

Out of the three logical operations, not is evaluated first, and or is evaluated last.

Thus, A and not B or C is the same as (A and (not B)) or C. We can use brackets of course to override default precedence.

In [43]:
b=3
((1==2) and (3==4)) or (b==3)
Out[43]:
True

Strings

Apart from numbers, we can do operations on strings such as words in Python. Strings have to be placed between single ('...') or double ("...") quotes. Between the two notations, there are no remarkable differences. The \symbol can be used to escape quotes inside a string.

In [44]:
'spam eggs'
Out[44]:
'spam eggs'
In [45]:
'doesn\'t'
Out[45]:
"doesn't"
In [46]:
"doesn't"
Out[46]:
"doesn't"
In [47]:
'"Yes," he said.'
Out[47]:
'"Yes," he said.'
In [48]:
"\"Yes,\" he said."
Out[48]:
'"Yes," he said.'
In [49]:
'"Isn\'t," she said.'
Out[49]:
'"Isn\'t," she said.'

Output strings are displayed between quotes, special characters escaped by \ symbols. The print() command creates a much more readable output, it leaves the quotes and displays special characters.

In [50]:
print('"Isn\'t," she said.')
"Isn't," she said.
In [51]:
s = 'First line.\nSecond line.'  # \n means a newline
In [52]:
s  # without print(), \n stays in the output
Out[52]:
'First line.\nSecond line.'
In [53]:
print(s)  # with print, \n creates a newline
First line.
Second line.

If you don't want characters after a \ symbol to be interpreted as special, then you may use a raw string by putting an r letter in front of the first quotation mark.

In [54]:
print('C:\some\name')  # \n means a newline
C:\some
ame
In [55]:
print(r'C:\some\name')  # r is in front of the first quote
C:\some\name

Sometimes we would like to create strings exactly as we type them. A pair of a triple """ or ''' makes it possible. Then, newlines will automatically added to the strng, but this behaviour can be escaped by a \ at the end of the line.

In [56]:
print("""\
Usage: thingy [OPTIONS]
     -h                        Display this usage message
     -H hostname               Hostname to connect to
""")
Usage: thingy [OPTIONS]
     -h                        Display this usage message
     -H hostname               Hostname to connect to

We can concatenate string with the + operation, and we can multiply them by the * operation.

In [57]:
3 * 'un' + 'ium'
Out[57]:
'unununium'

Two literal strings (that are not stored in a variable, or that are not created on the fly by a function) are concatenated by the interpreter by default.

In [58]:
'Py' 'thon'
Out[58]:
'Python'

For variables, it gives an error.

In [59]:
prefix = 'Py'
prefix 'thon'  # it cannot concatenate a varable and a literal string
  File "<ipython-input-59-08ffc84f3814>", line 2
    prefix 'thon'  # it cannot concatenate a varable and a literal string
                ^
SyntaxError: invalid syntax

If you want to concatenate a literal string with a variable or two variable, then use the + operator.

In [60]:
prefix = 'Py'
prefix + 'thon'
Out[60]:
'Python'

Concatenating literals is very useful for handling muliline strings.

In [61]:
text = ('We can put multiple strings into the brackets '
            'to be able to concatenate them.')
text
Out[61]:
'We can put multiple strings into the brackets to be able to concatenate them.'

Python assigns an index to each character in the string. The index 0 corresponds to the first character, the index 1 to the next etc. (This convention is similar to that of C.)

In [62]:
word = 'Python'
In [63]:
word[0]  # character at position 0
Out[63]:
'P'
In [64]:
word[5]  # character at position 5
Out[64]:
'n'

Indices may be negative as well. Then, we cound from the right:

In [65]:
word[-1]  # last character
Out[65]:
'n'
In [66]:
word[-2]  # last but one character
Out[66]:
'o'
In [67]:
word[-6]
Out[67]:
'P'

Moreover, slicing is also supported on top of indexing. While indexing returns one character, slicing returns a substring:

In [68]:
word[0:2]  # characters from position 0 (included) to position 2 (not included)
Out[68]:
'Py'
In [69]:
word[2:5]  # characters from position 2 (included) to position 5 (not included)
Out[69]:
'tho'

First index is always included in the result, last index is not. This makes it possible for s[:i] + s[i:] to be equal to s.

In [70]:
word[:2] + word[2:]
Out[70]:
'Python'
In [71]:
word[:4] + word[4:]
Out[71]:
'Python'

Indices of slices have useful default values. The default value of a left out first index is 0, the default value of a left out second index is the length of the string.

In [72]:
word[:2]  # # characters from the start position to position 2 (not included)
Out[72]:
'Py'
In [73]:
word[4:]  # # characters from position 4 (included) to the end position
Out[73]:
'on'
In [74]:
word[-2:] # characters from the last but one until the end
Out[74]:
'on'

Let us note, that -0 equals to 0, thus it won't start counting from the right!

In [75]:
word[-0]     # mivel -0 és 0 egyenlőek
Out[75]:
'P'

In the case of nonnegative indices, the length of the slice is the difference of the indices, if both are inside the string boundaries. For example, the length of word[1:3] is equal to 2.

If we use too big indices, the result is an error message:

In [76]:
word[42]  # the string word has only 7 characters
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-76-0d607b984cf7> in <module>()
----> 1 word[42]  # the string word has only 7 characters

IndexError: string index out of range

Python's strings are immutable, we cannot rewrite any of their parts. Thus, if we assign a value to a position of a certain index, we get an error:

In [77]:
word[0] = 'J'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-77-91a956888ca7> in <module>()
----> 1 word[0] = 'J'

TypeError: 'str' object does not support item assignment

If we need another string, we may create a new one.

In [78]:
'J' + word[1:]
Out[78]:
'Jython'
In [79]:
word[:2] + 'py'
Out[79]:
'Pypy'

The built-in len() function returns the length of the string.

In [80]:
s = 'legeslegelkáposztásíthatatlanságoskodásaitokért'
len(s)
Out[80]:
47

Lists

Python knows several complex data types that group different values. A very useful and multi-faceted complex datatype is the list, where we can add inputs as comma separated values in a square bracket. The values of a list don't have to be of the same datatype, but in most cases, they will be.

In [81]:
a = ['spam', 'eggs', 100, 1234]
a
['spam', 'eggs', 100, 1234]
Out[81]:
['spam', 'eggs', 100, 1234]

Just as strings, lists can be indexed and sliced.

In [82]:
a[0]
Out[82]:
'spam'
In [83]:
a[3]
Out[83]:
1234
In [84]:
a[-2] 
Out[84]:
100
In [85]:
a[1:-1]   # slicing returns a new list
Out[85]:
['eggs', 100]

Lists support concatenation:

In [86]:
a[:2] + ['ham', 2*2]
Out[86]:
['spam', 'eggs', 'ham', 4]
In [87]:
3*a[:3] + ['Boe!']
Out[87]:
['spam', 'eggs', 100, 'spam', 'eggs', 100, 'spam', 'eggs', 100, 'Boe!']

As opposed to strings, lists are mutable. We can modify any of their elements.

In [88]:
a[2] = a[2] + 23
a
Out[88]:
['spam', 'eggs', 123, 1234]

We can give values to slices, this can even change the number of elements in a list.

In [89]:
a[0:2] = [1, 12] # rewriting some elements
a
Out[89]:
[1, 12, 123, 1234]
In [90]:
a[0:2] = [] # deleting some elements
a
Out[90]:
[123, 1234]
In [91]:
a[1:1] = ['bletch', 'xyzzy'] # inserting some elements
a
Out[91]:
[123, 'bletch', 'xyzzy', 1234]
In [92]:
a[:0] = a  # inserts a copy of itself into the first position
a
Out[92]:
[123, 'bletch', 'xyzzy', 1234, 123, 'bletch', 'xyzzy', 1234]

The built-in len() function may also be used for lists.

In [93]:
len(a)
Out[93]:
8

Lists are embeddable, a list may contain other lists.

In [94]:
a = ['a', 'b', 'c']
n = [1, 2, 3]
x = [a, n]
x
Out[94]:
[['a', 'b', 'c'], [1, 2, 3]]
In [95]:
x[0]
Out[95]:
['a', 'b', 'c']
In [96]:
x[0][1]
Out[96]:
'b'
In [97]:
x[0][1]
Out[97]:
'b'

Sometimes we would like to know the position of a given element inside a list, this can be done with the .index() function (method).

In [98]:
cars=['skoda','vw','merci','mazda']
In [99]:
cars.index('merci')
Out[99]:
2

As we've seen, strings are also indexable. We can also ask the position of a character inside a string.

In [100]:
letters='abcdef'
In [101]:
letters.index('e')
Out[101]:
4

Another common problem is to decide whether an element is in a list. We can use the in operator for such questions.

In [102]:
Tage_der_Woche=['Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag','Sonntag']
In [103]:
'hetfo' in Tage_der_Woche
Out[103]:
False
In [104]:
'Montag' in Tage_der_Woche
Out[104]:
True

Dictionaries

Sometimes we would like to store data not in a certain order, rather according to certain labels. For example, in foreign language dictionary, where we pair English expressions with expressions from the other language. For these structures, the ordering is not very meaningful, such as for properties of some objects. Let us have a car, that is

  • red
  • made in 1976
  • 3 m long
  • has a plate number AOK-621

These properties define the car clearly, independent of their order. Such a data structure is a dictionary (in shorthand dict) in Python. Creating dictionaries with the help of keys and corresponding values goes as follows:

In [105]:
tel = {'John': 4098, 'Simon': 4139}
tel
Out[105]:
{'John': 4098, 'Simon': 4139}

Here, we can refer to an element by its key.

In [106]:
tel['John']
Out[106]:
4098

We can add a new element to the dict also with a similar syntax.

In [107]:
tel['George'] = 4127
tel
Out[107]:
{'George': 4127, 'John': 4098, 'Simon': 4139}

We cannot only give numbers as values:

In [108]:
tel['Jane']='I DON\'T KNOW HER NUMBER'
tel
Out[108]:
{'George': 4127,
 'Jane': "I DON'T KNOW HER NUMBER",
 'John': 4098,
 'Simon': 4139}

We delete an element like

In [109]:
del tel['Simon']
tel
Out[109]:
{'George': 4127, 'Jane': "I DON'T KNOW HER NUMBER", 'John': 4098}

We can access all the keys by using the .keys() function (method).

In [110]:
tel.keys()
Out[110]:
dict_keys(['John', 'George', 'Jane'])

Sometimes it is worth to convert the above keys to a list with the list() command.

In [111]:
list(tel.keys())
Out[111]:
['John', 'George', 'Jane']

Is George in the dictionary?

In [112]:
'Géza' in tel
Out[112]:
False

Note that even if Jane is in the dictonary, we still may not know her number.

In [113]:
'Jane' in tel
Out[113]:
True
In [114]:
tel['Jane']
Out[114]:
"I DON'T KNOW HER NUMBER"

Thus, in checks if a string is among the keys of a dict.

We can defince dictionaries with the dict() function using another syntax as well. It will prove useful when dealing with keyword arguments in functions, or the plotly module.

In [115]:
tel2=dict(Lydia=1234,Mary=4321,Kitty=5567)
tel2
Out[115]:
{'Kitty': 5567, 'Lydia': 1234, 'Mary': 4321}

Booleans and other variables

As we have already seen, we can convert an object to an integer with the int() function, or to a float with the float() function, and we have seen an example of converting something to a list. We can convert objects to boolean variables as well with the bool() function. In the following section, we will have a look at some examples of the behaviour of objects on boolean conversion.

A number or a string gives True.

In [116]:
bool(1.0)
Out[116]:
True
In [117]:
bool('szoveg')
Out[117]:
True

A nonempty list or an existing object gives True as well.

In [118]:
bool([1,'Bela'])
Out[118]:
True

An empty string, an empty list and the number 0 gives False.

In [119]:
bool(0)
Out[119]:
False
In [120]:
bool(0.0)
Out[120]:
False
In [121]:
bool('')
Out[121]:
False
In [122]:
bool([])
Out[122]:
False

A seemingly wrong example.

In [123]:
'a' == ('a' or 'b') 
Out[123]:
True
In [124]:
'b' == ('a' or 'b') 
Out[124]:
False
In [125]:
'a' == ('a' and 'b') 
Out[125]:
False
In [126]:
'b' == ('a' and 'b') 
Out[126]:
True

Why is the second example False, if the first is True? Why is the third example False, if apparently, the similar fourth gives True?

At this point, it may seem that and and or are not working properly. If we delve into the steps the Python interpreter makes, it will turn out that it functions as it should. But it is not what we first expected! The first two examples do not check whether the character in front of == is inside the brackets. What happens exactly?

When Python meets an or inside the brackets, it goes though all of the objects connected by the or, and gives back the value of the first element that can be evaluated as True. Thus, it won't give True or False, but tha value of the variable! Whereas and returns the value of the last variable if all objects were evaluated as True. This behaviour of boolean operators is called short circuit, and it is similat in many other programming languages as well.

Thusm if we would like to test whether an expression is equal to any of a set of other expressions, we have to formulate it as follows.

In [127]:
(('a' == 'a') or ('a' == 'b'))
Out[127]:
True
In [128]:
(('b' == 'a') or ('b' == 'b'))
Out[128]:
True