Saturday, 30 September 2017

Tuples - a fixed alternative to lists

Tuples are a data type in Python that are in many ways similar to lists. The big difference is that tuples cannot be changed without recreating the data object. Like strings they are immutable and cannot be changed in-place - they can only be changed by reassigning the variable name to a new value.
Tuples in code look like lists, but use round brackets () rather than square ones []
For example on the Python command line:
>>> xtuple = (5, 7, 9, 11, 13)
>>> print(type(xtuple))
<class 'tuple'>
>>> xlist = list(xtuple)
>>> print (xlist)
[5, 7, 9, 11, 13]
>>> for y in xtuple:
 print (y)

5
7
9
11
13

>>> xtuple = xtuple + (15, 17)
>>> print (xtuple)
(5, 7, 9, 11, 13, 15, 17)
>>> ytuple = tuple(xlist)
>>> print (ytuple)
(5, 7, 9, 11, 13)

>>> ztuple = (34, 10, 15, 11, 75, 3)
>>> sorted(ztuple)
[3, 10, 11, 15, 34, 75]
>>> print (ztuple)
(34, 10, 15, 11, 75, 3)
>>> ztuple = tuple(sorted(ztuple))
>>> print (ztuple)
(3, 10, 11, 15, 34, 75)
>>> if 11 in ztuple:
   print ('11 is found')

11 is found
>>> ztuple.count(15)
1
>>> ztuple.index(34)
4
>>>
 print(ztuple[0])3
This shows a number of things:
The list() function can convert a tuple to a list, and the tuple() function can convert a list to a tuple.
Tuples, like lists, ranges and even strings, can be used in a for loop.
Tuples can be concatenated, like strings and lists.
Tuples can be searched using in, like lists
Tuples can use count() function and index() function like lists
You can find the value of an element at a given index by tuplename[index] like lists

However, there are a lot of functions that lists can do that a tuple won't do.
You can't use a sort() function on a tuple (though there are ways around it as shown above)
You can't append() onto a tuple.
You can't use clear() to remove all elements from a tuple as you can with lists.

So why use tuples? Mainly because it is less easy to change them accidentally.
Here is an example script:
#!/usr/bin/python3
mondaystaff = ['Bob', 'Charlie', 'Eric']
tuesdaystaff = ['Alice', 'Charlie', 'Daria']
wednesdaystaff = ['Alice', 'Bob', 'Eric']
thursdaystaff = ['Bob', 'Charlie', 'Daria']
fridaystaff = ['Alice', 'Daria', 'Eric']
weekday = (('Monday', mondaystaff), ('Tuesday', tuesdaystaff), ('Wednesday', wednesdaystaff), ('Thursday', thursdaystaff), ('Friday', fridaystaff))
for day in weekday:
    print ('On', day[0], 'the office is manned by')
    staffstring = ''
    for staff in day[1]:
        if staff != day[1][-1]:
            staffstring = staffstring + staff + ', '
        else:
            staffstring = staffstring + 'and ' + staff
    print (staffstring)
and here is the output:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test05_tupledemo.py On Monday the office is manned by
Bob, Charlie, and Eric
On Tuesday the office is manned by
Alice, Charlie, and Daria
On Wednesday the office is manned by
Alice, Bob, and Eric
On Thursday the office is manned by
Bob, Charlie, and Daria
On Friday the office is manned by
Alice, Daria, and Eric

>>>  
This has lists within tuples within an overall tuple. The weekday variable holds a tuple containing tuples. These are unlikely to change - the strings for days of the week are fixed, and they should be associated with the list for that day of the week.
However, the lists of strings (for the staff on each day of the week) are mutable.
This has unexpectedly turned into a demonstration of referring to collections within collections (here lists within tuples). The line
if staff != day[1][-1]:
uses day[1][-1], which is the Python way of saying find the second element in this, and then find the last element in whatever that is - i.e. it assumes that not only day is an indexed collection (i.e. list or tuple), but also day[1] is also a list or tuple and Python should find the last element [-1] of that. 
I don't know if I've explained that clearly. I hope so. And I feel I could have got the commas right but I'm not sure how.

Friday, 29 September 2017

Corrections and Clarifications: Built-in Functions and Keywords

In my exuberance of starting a new blog, I may have got some terminology mixed up.

Keywords

Keywords are not the same as built-in functions. Keywords are reserved by Python and cannot be used as names (such as variable names), overwritten or redefined. A full list is found by going onto the Python command line and:
>>> import keyword
>>> print (keyword.kwlist)
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
>>>
and include the following which I have already mentioned in this blog:
  • break - to get out of loops early
  • elif - an alternative in if structures
  • else - used in if structures
  • False - one of two possible Boolean values
  • for - for loops go through a range of numbers or a sequence of items one at a time
  • if - if structures make decisions based on whether something (such as a comparison) is true or not
  • in - both used in for loops and for searching lists
  • True - one of two possible Boolean values
  • while - used to create while loops that loop around while a condition or comparison is true

Built-in Functions

Built-in functions are always available in Python - you don't need to import them. It is also a bad idea to use them as names/identifiers. Technically you can override them and assign them as variable names or redefine their function, but it is a really bad idea, for example:
>>> print = 'Hello World'
>>> print (print)
Traceback (most recent call last):
  File "<pyshell#64>", line 1, in <module>
    print (print)
TypeError: 'str' object is not callable

>>>
And thus we have messed up the print() function for this command line session.
A proper list of built-in functions for Python 3.4 can be found here:
https://docs.python.org/3.4/library/functions.html

These include some which I have already mentioned:
  • close() - closes a file
  • float() - tries to convert a string or integer to a floating point number
  • input() - for taking user-typed information from the command line and using it 
  • int() - tries to convert a string or float to an integer
  • len() - gives the length (number of elements) of whatever collection is in the brackets
  • list() - tries to convert whatever is in the brackets into a list
  • open() - tries to open a file
  • print() - outputs whatever is printed onto the command line
  • range() - gives a range of numbers, typically used in a for loop.
  • str() - tries to convert a floating point or integer to a string

Actually, going through the page given in the link, I've discovered a number of potentially very useful functions. I hope to make use of them in later posts.

Thursday, 28 September 2017

Opening, Writing to and Reading from a File

Why bother with files? The main reason is persistence. When you close a Python IDLE session, you lose any information that you have created in memory. If it is in a file on the computer's storage (usually hard disk), it will survive until the next session.
Another reason is passing information between apps. A wide range of computer programs can handle a simple text file, but not so many can easily and seamlessly integrate with Python. So files (particularly plain text) can be useful intermediaries.

At the moment I will assume the files I will be opening, writing to, reading from and closing will be stored in the same folder/directory as the Python scripts. It is possible to navigate around a computer's folder structure, but I am not ready for that yet.

So to open a file, I create a variable that contains a file object, sometimes called a file handle, with the built-in function open and then the filename as it appears in the operating system and then specify why I am opening the file: 'w' for write, 'r' for read, 'a' for append. 
For example,
#!/usr/bin/python3
foo = open('test.txt', 'w')
foo.write('Hello World!\n')
foo.write('This is a Test!')
foo.close() 
foo = open('test.txt', 'r')
print(foo.read())
foo.close()

gives the output:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test07_filetest.py
Hello World!
This is a Test!
Furthermore, you can open up your folder explorer (in Windows 10 this is File Explorer) and you should find the file test.txt in the same folder as this script.
As you might be able to tell, the first part of the script opens up the file for writing.
If the file does not exist, then Python creates it.
If the file does exist, then writing to it will overwrite any data currently in the file.
When you no longer need access to the file on the disk it is a good idea to close() the file object, which I have done on the last line of this script.

Using 'a' for append when opening the file means that any data written to the file is added to the end, preserving the old data. For example, we can add a few lines to the script to add a few lines to test.txt:
#!/usr/bin/python3
foo = open('test.txt', 'w')
foo.write('Hello World!\n')
foo.write('This is a Test!\n')
foo.close()
foo = open('test.txt', 'r')
print(foo.read())
foo.close()
foo = open('test.txt', 'a')
foo.write('Here are some more lines \n')
foo.write('Appended to the end\n')
foo.close()
foo = open('test.txt', 'r')
print(foo.read())
foo.close()
This gives the output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test07_filetest.py
Hello World!
This is a Test!
Hello World!
This is a Test!
Here are some more lines
Appended to the end
>>>
And again you can check the text file in the folder with the python script. 

Simply telling it to filename.read() will read all the data in the file at once. If you assign it to a variable, that variable will contain a string of the entire file contents.
This is useful sometimes. But more often than not, you want to go through the file line by line, and Python allows us to do that, using our old friend the for loop. Rather than treating the file contents as one big lump, Python can treat it as a collection or list of lines. And for loops are good for working their way through a sequence or list.
#!/usr/bin/python3
foo = open('test.txt', 'r')
linecount = 0
linelist = []
for line in foo:
    linecount += 1
    print(linecount, line)
    linelist.append(line)
foo.close()
print (linelist)
with the output:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test07_filetest2.py 1 Hello World! 
2 This is a Test! 
3 Here are some more lines  
4 Appended to the end 
['Hello World!\n', 'This is a Test!\n', 'Here are some more lines \n', 'Appended to the end\n']
>>>
Why the extra spaces between the lines? Because write() does not automatically add newlines (end of line characters) so I added them manually in the strings written to the file (you noticed the '\n' at the end?) but then print() automatically adds newlines. Hey presto, two newlines per line.

Wednesday, 27 September 2017

Importing Modules and Random Stuff

So far all the functions we have covered have been part of Python's core.
However, to increase the range of functions we can import modules. Modules can be thought of as extensions to Python, bringing along new functions and capabilities. There are a huge number of modules available, some bundled with the Python interpreter so you know they are ready to be imported, including
  • random (generates random numbers and outputs)
  • math (allows advanced maths, including trigonometry & logarithms)
  • os (lets the python program interact with your computer's operating system
  • re (lets the programmer use Regular Expressions in the code)
  • time (provides date, time and similar functions)
  • sys (allows modification at the level of Python interpreter)
The full list is here.
Some other modules (usually third party) need to be downloaded and installed, typically using a program called pip (Python Installation Program? The acronym is not explained).

Importing modules is best done at the very start of a program, and the simplest way is import modulename.
Here is an example script that uses the random module:
#!/usr/bin/python3
import random
print ('Example of random float between 0 and 1')
print (random.random())
print ('Example of random integer between 1 and 100')
print (random.randrange(100) +1)
print ('Example of random element from a list')
samplelist = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta']
print ('Sample list = ', samplelist)
print (random.choice(samplelist))
print ('Random shuffle of list')
shuffledlist = samplelist
random.shuffle(shuffledlist)
print (shuffledlist)
And the results are
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test05_randomtest.py
Example of random float between 0 and 1
0.3802338941554101
Example of random integer between 1 and 100
95
Example of random element from a list
Sample list =  ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta']
beta
Random shuffle of list
['delta', 'epsilon', 'zeta', 'gamma', 'eta', 'beta', 'alpha', 'theta']

>>>
 Of course, should you run it yourself, you won't get exactly the same results, otherwise it wouldn't be random.
A few point that I found out for myself while doing this
For the randrange() function, I included the +1 for it to be from 1 to 100 rather than 0 to 99.
shuffle() function works in a kind of opposite to sort() (it sorts the list in a random rather than logical way). But similar to sort(), it changes the list in-place and returns None, so if I change the line at the end of the script
print (random.shuffle(samplelist))
what I get printed is not a shuffled samplelist but None
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test05_randomtest.py
Example of random float between 0 and 1
0.7661205325736707
Example of random integer between 1 and 100
74
Example of random element from a list
Sample list =  ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta', 'theta']
theta
Random shuffle of list
None

>>>
In business, randomness is not really wanted - its main use is in testing and sampling from large sets of data. However, it can provide some amusement. For example, this silly program
#!/usr/bin/python3
import random
print("Quick Murder Mystery Generator")
suspectlist = ['Cook', 'Butler', 'Colonel', 'Doctor', 'Detective', 'Disinherited Son', 'Divorcee', 'Failed Actress', 'Mad Scientist', 'Python Programmer']
weaponlist = ['Revolver', 'Garrotte', 'Dagger', 'Candlestick', 'Spanner', 'Hatchet', 'Voodoo Curse', 'Submachinegun', 'Shotgun', 'Poisoned drink']
locationlist = ['Conservatory','Vegetable Patch', 'Kitchen', 'Dining Room', 'TV Room', 'Bedroom', 'Bathroom', 'Wine Cellar', 'Study', 'Library', 'Flower Garden']
print('A murder has been committed - the lord of the manor has been found dead. But who did it, with what, and where?')
print ('Miss Marbles the amateur sleuth has come along and after asking some awkward questions, has decided')
suspectchoice = random.choice(suspectlist)
weaponchoice = random.choice(weaponlist)
locationchoice = random.choice(locationlist)
print ('It was the', suspectchoice, 'using the', weaponchoice, 'in the', locationchoice,'!!')
give the output
>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test05b_cluedo.py
Quick Murder Mystery Generator
A murder has been committed - the lord of the manor has been found dead. But who did it, with what, and where?
Miss Marbles the amateur sleuth has come along and after asking some awkward questions, has decided
It was the Disinherited Son using the Dagger in the Flower Garden !!

>>>
 And unlike some murder mysteries, this one is quite difficult to predict how it will turn out....

Tuesday, 26 September 2017

Caesar's Cipher in Python

Back in the olden days of the Roman Empire, cryptography and cryptanalysis was not as sophisticated as it is these days. So Julius Caesar's brilliant cipher consisted of taking each letter in the words he was encrypting and swapping it for the letter a certain number of steps ahead or behind in the alphabet. If he was shifting the letters by one step then H becomes I, E becomes F, L becomes M and O becomes P. Thus the word HELLO is encrypted as IFMMP. If you know the key, you decrypt by shifting each letter back by that many steps in the alphabet.

So here's a program I've done.
#!/usr/bin/python3.5
alphabet_list = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
print ("Please enter code setting (integer 1-25)")
shift = input("?")
shift = int(shift)
initialstring = input("Please enter plain text string: ")
initialstring = initialstring.lower()
shiftedstring = ""
for initialchar in initialstring:
    if (initialchar in alphabet_list):
        initialplace = alphabet_list.index(initialchar)
        shiftplace = initialplace + shift
        if (shiftplace > 25):
            shiftplace = shiftplace - 26
        elif (shiftplace < 0):
            shiftplace = shiftplace + 26
        shiftedchar = alphabet_list[shiftplace]
    else:
        shiftedchar = initialchar
    shiftedstring = shiftedstring + shiftedchar
print (shiftedstring)
This is longer than some previous scripts and there are few things I would like to point out, There are a number of functions and techniques I haven't mentioned before, and perhaps I am jumping ahead with this script :
  • At its heart is the list of alphabet letters, and increasing or decreasing the index does the job of changing the letter by a number of steps. It should be on one long line, but word-wrapping on this blog makes it appear to cover multiple lines.
  • initialstring = initialstring.lower() converts the string to lower case. This keeps things simple and I don't have to worry about a separate list for upper case letters. This function works on strings. 
  • The for loop treats the initialstring as a sequence of characters, similar to a list. Each time the loop repeats, it picks up the next character in the string until it reaches the end. 
  • if (initialchar in alphabet_list) checks whether or not the selected character is in the alphabet_list. Like other if...else... statements. if the statement is true then the lines below the if statement are run. Otherwise the line below the else: statement is run. 
  • alphabet_list.index(initialchar) returns the index (i.e. the position in the list) of the selected letter in the initialchar variable. We know that it will return one because the previous if statement just checked that it's there in the list. 
  • The deeply nested if...elif... statements are for when shifting along the list goes off either end. It sort of joins the ends the list so that z +1 gives a, and a -1 gives z. They use the < (less than) and > (greater than) comparators. 
  • shiftedstring = shiftedstring + shiftedchar is a demonstration of string concatentation. Just as with lists, you can use + to join two different strings together. Here it is adding each newly-encoded character onto the end of the encoded string. 
So what is the output?
>>>
 RESTART: C:\Users\John\Dropbox\Misc Programming\Python\python3\test05_rot13.py
Please enter code setting (integer 1-25)
?
3
Please enter plain text string: Hail Julius Caesar! And Hello World!
kdlo mxolxv fdhvdu! dqg khoor zruog!
>>>
 RESTART: C:\Users\John\Dropbox\Misc Programming\Python\python3\test05_rot13.py
Please enter code setting (integer 1-25)
?
23
Please enter plain text string: kdlo mxolxv fdhvdu! dqg khoor zruog!
hail julius caesar! and hello world!
>>>
The second run was to show how to decrypt - use the code setting of 26 minus the original code setting. This will reverse any letter changes from the original encryption but not restore lost capital letters.  You could also decrypt by using a code setting of the negative of the original code (so here -3).
Footnote: The code works fine with code settings of -52 up to 26, so actually the prompt for the code setting should be modified. Any number beyond that range will cause an out-of-range error.
I was puzzled that it should cope with such numbers of negative magnitude. Then I realised that it would be looking for an index of [-26] or higher. This is then interpreted by Python as start at the end of the list and work backwards. I don't know if I'm a subconscious genius or just lucky...

Monday, 25 September 2017

The While Loop and Break

The while loop is a very useful structure in Python, combining the looping of the for loop (doing some instructions over and over) with the True/False comparison of if...elif... statements. The while loop tells Python while something is true, keep doing this set of instructions.

For example, in this script:
#!/usr/bin/python3
print ('List Accumulator')
samplelist = []
print ('Please enter items for list below. When list is finished, just enter nothing instead of item.')
item = 'x'
while item != '':
    item = input('Next item? ')
    if item != '':
        samplelist.append(item)
    print ('Currently', len(samplelist), 'items in list')
print ('Complete list is:')
print (samplelist)
The while loop tells Python to keep prompting for input as long as some string other than '' is entered.
The output is as follows:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test05_whileloop.py
List Accumulator
Please enter items for list below. When list is finished, just enter nothing instead of item.
Next item?
Hello
Currently 1 items in list
Next item?
World
Currently 2 items in list
Next item?
Currently 2 items in list
Complete list is:
['Hello', 'World']

>>>
 Apart from the while loop, there is nothing particularly new in this script.
It's the first time I've used the != (not equal to) comparison on this blog.
item = 'x' is a necessary workaround to initialise the item variable - Python does not like trying to compare something it does not have a value for. 'x' is discarded with the first input.
The if structure inside the while loop is there to prevent '' being the last item in the finished list.

A while loop can be used instead of a for loop.
#!/usr/bin/python3
x = 0
while x < 10:
    print (x, 'Hello World')
    x += 1
gives the output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test05_simpleWhile.py
0 Hello World
1 Hello World
2 Hello World
3 Hello World
4 Hello World
5 Hello World
6 Hello World
7 Hello World
8 Hello World
9 Hello World

>>>

Trapped in an Infinite Loop?

while loops can create a situation where the program and user cannot break out of the loop - it just goes round and round. This is a form of logical error - the computer is just doing as it is told.
The way to escape this is ctrl-c on the keyboard (ctrl key + c key at same time). This will break out of many (most?) running programs.
This script demonstrates:
#!/usr/bin/python3
x = 1
while (x):
    print ('Infinite loop! Ctrl-C to escape!')
However, I have found that Ctrl-C is not always reliable (maybe it tries to interrupt at the wrong point in the script?), and that it may be necessary (or at least quicker) to close the Python/IDLE window. Generally it's best not to get into that situation in the first place. Maybe I should have mentioned that before offering the above script...

break

Generally speaking, when you want to finish a while loop, you include one or more conditions in the while statement at the top of the loop, so it stops looping when the condition is no longer true. Similarly with a for loop, you expect it to run for however many iterations you state at the start or until it reaches the end of a list or other collection.  However, there may be situations when you want to stop the loop and continue with the rest of the program. This is where break comes in. It allows the program to break out of the loop prematurely. Here's a script that uses break.
#!/usr/bin/python3
b = input("Please input number of repetitions:")
b = int(b)
for a in range(b):
    print (a, " Hello World!")
    if a >= 50:
        print ('Sorry, this is getting tedious.')
        break

   
 So if I run it and enter a high number:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test05_for_break.py
Please input number of repetitions:56
0  Hello World!
1  Hello World!
2  Hello World!
......
47  Hello World!
48  Hello World!
49  Hello World!
50  Hello World!
Sorry, this is getting tedious.

>>>
You could conceivably use break as a check against infinite loops, such as in the script above. It would be up to the programmer to decide when a loop is not serving any purpose and has become a nuisance, and thus create an if statement and break to catch this.

Sunday, 24 September 2017

More About Strings

I feel like I have given short shrift to strings so far, yet they are a common data type in Python with a number of functions.

Concatenating Strings

You can join strings up by using the + sign. This is basically the same as concatenating lists together. In previous scripts I have used a comma in print statements where there are strings mixed with variables. This has the effect of adding a space between them - normally this is fine but there may be circumstances where you don't want a space. Concatenation gets around that. However, it does require all parts to be strings, otherwise it throws an error. See str() below.
Here is an example on the Python command line.
>>> a = 'Hello'
>>> b = 'World'
>>> c = 42
>>> print (a, b, c)
Hello World 42
>>> print (a,b,c)
Hello World 42
>>> print (a+b)
HelloWorld
>>> print (a+b+c)
Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    print (a+b+c)
TypeError: must be str, not int

>>> print(a+b+str(c))
HelloWorld42
>>>

str()

This function turns other data types back into strings. There are various reasons for doing this - one reason is if you are concatenating outputs into one big string, Python expects all the components to be strings. Another is if you want to sort a list ASCIIbetically, but there are numbers scattered in there, you can convert numbers to strings.
>>> samplelist = ['This', 'is', 'a', 'sample', 'list', 42, 3.1415]
>>> print (samplelist)
['This', 'a', 'is', 'list', 'sample', 42, 3.1415]
>>> for x in samplelist:
   y = str(x)
   z = samplelist.index(x)
   samplelist[z] = y

>>> print (samplelist)
['This', 'a', 'is', 'list', 'sample', '42', '3.1415']
>>>
Forgive the longwinded workaround - it's one way I could use a for loop to change in-situ the list it is working through. Anyway, the point is that the list is now full of strings. str() works for integers, floats and Boolean values, though not for lists.


Changing Case with lower(), upper() and capitalize()

This is more than just presentation - Python (and a few other computer languages and programs) are case sensitive, so changing the case will change how the string is evaluated. For example on the Python command line:
>>> var = 'HEllO woRLd'
>>> print (var.lower())
hello world
>>> print (var.upper())
HELLO WORLD
>>> print (var.capitalize())
Hello world
>>>


split()

Put simply, split() is how you turn a string into a list. There should be a delimiter, a character or series of characters that tells the split() function where one element ends and another element begins - this delimiter is given as a string in the brackets. For example:
>>> print('Hello world this is a random string'.split(' '))
['Hello', 'world', 'this', 'is', 'a', 'random', 'string']
>>> linuxpath = '/home/john/Dropbox/Misc Programming/Python/python3'
>>> print (linuxpath.split('/'))
['', 'home', 'john', 'Dropbox', 'Misc Programming', 'Python', 'python3']
>>>
Note that the first element on the last line is an empty string, '', as the string starts with a delimiter '/'.
You might wonder why, if I generally use Windows, I didn't give an example of a Windows file path being split up. I actually had a go at that, but then realised that the delimiter in Windows is not forward slash '/' (which Python handles like any other character) but backslash '\' which is the escape character. This escape character \ tells Python to interpret a string differently from how it normally would, so Python has difficulty dealing with the unmodified string of a Windows file path. Python modules and functions that deal with directory paths have to take this into account. 
You can also split() a string without any delimiter. In this case, it assumes a default of spaces. 
>>> print (linuxpath.split())
['/home/john/Dropbox/Misc', 'Programming/Python/python3']
>>>
 If you really want to split it into a list of individual characters, one character per element, you can use list() which is an built-in function:
>>> print (list(linuxpath))
['/', 'h', 'o', 'm', 'e', '/', 'j', 'o', 'h', 'n', '/', 'D', 'r', 'o', 'p', 'b', 'o', 'x', '/', 'M', 'i', 's', 'c', ' ', 'P', 'r', 'o', 'g', 'r', 'a', 'm', 'm', 'i', 'n', 'g', '/', 'P', 'y', 't', 'h', 'o', 'n', '/', 'p', 'y', 't', 'h', 'o', 'n', '3']
>>>
 

Strings as Sequences

Strings can be viewed as either single entities, or a sequence of characters. And as a sequence, like lists, you can put strings into a for loop, and it will be looped over, character by character.
Also, as shown previously, I can measure the length of a string with the len() function.

Characters as elements

In a similar way to picking out the elements of lists, you can look at the individual characters in a string, using the same syntax. As usual with Python, the index of the characters starts at 0 and ends at length -1.
As in lists, you can tell Python to return the character at a specified index
>>> print (linuxpath)
/home/john/Dropbox/Misc Programming/Python/python3
>>> print (linuxpath[7])
o
>>> print (linuxpath[6])
j
>>>
 or use index() to find the first position of a character in the string
>>> print (linuxpath.index('M'))
19
>>>
 and of course use len() to find the length (in characters) of the string
>>> print(len(linuxpath))
50
>>>

 count()

You learn something new every day, and today I've found the function count() which works for both lists and strings (and I suspect a few other data types). It works as
sequencename.count(searchitem)
and returns the number of times the search item turns up in the sequence or collection.
>>> print(linuxpath.count('/'))
6
>>> zlist = list(linuxpath)
>>> print (zlist.count('/'))
6
>>> print (zlist.count('z'))
0
>>>
 


Saturday, 23 September 2017

Comparing with >, < and !=

In a previous post I showed how an if structure could make a program do one of several possible things, depending on whether an input was equal to some value. I used == throughout that post to test equality.
However, that is only one possible way of comparing two values. There are other comparisons:
  • > (greater than)
  • >= (greater than or equal to)
  • < (less than)
  • <= (less than or equal to)
  • != (not equal to)
  • in (whether a value is also an element in a list or other collection)
In each case, Python checks whether or not the comparison is True or False. Why have I capitalised these? Because they are keywords in Python, and are the two possibilities of the Boolean data type. As a data type (along with strings, lists, floats & integers), Boolean data can be assigned to a variable (and even included in lists), but more often than not it is used in decision-making in the structure of the program - typically if...elif... structures, and while loops (which I haven't covered yet but will get round to).  

A word of warning about data types: Python can and usually will throw a type error if you try to compare two different data types - this is actually the same fundamental error as when Python cannot sort lists containing different types. Python cannot compare strings with numbers, or numbers with lists etc.  Integers and floats can be compared with each other because they are both numbers.
Interestingly Boolean values can be compared to numbers, with True being treated as 1 and False being treated as 0. For example on the Python command line:
>>> print(4.777 > 4)
True
>>> print(4.777 < 4)
False
>>> print(0.5 > True)
False
>>> print(0.5 > False)
True
>>> print(0 == False)
True
>>> print(True == 1)
True
>>> print(0.5 == 'String')
False
>>> print(0.5 < 'String')
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    print(0.5 < 'String')
TypeError: '<' not supported between instances of 'float' and 'str'

>>>

Strings can be compared to each other - in this case they are compared alphabetically/ASCIIbetically rather than numerically, the same way as they are sorted if in a list. As you can see here, Python is case sensitive - upper case letters are considered different characters from their lower-case counterparts. This is also true for variable names, and can be a source of errors.
>>> print('Hello World' == 'hello world')
False
>>> print('Hello World' < 'hello world')
True
>>> print('Hello World' == 'Hello World')
True
>>>
Lists are equal to each other if they are the same length and the elements in each index are the same as in the other list - i.e. the contents of the list are identical. Whether a list is greater or less than another list is based on first the length of the list (i.e. number of elements) and then the contents of the elements.

The final comparator in the bullet-point list is in. Strictly speaking it is not a comparator but an operator, and is also found in for loops, though here the context is different. It is useful for finding if something being searched for matches one or more elements in a list. For example on the command line:
>>> x = [10, 12, 15, 17]
>>> print(10 in x)
True
>>> print(11 in x)
False
>>> y = ['This', 'is', 'a', 'list', 'of', 'strings']
>>> print ('is' in y)
True
>>> print ('Hello' in y)
False
>>>


Friday, 22 September 2017

Paper, Rock, Scissors, Lizard, Spock

Courtesy of the Big Bang Theory, I bring you a computerised version of the extended version of Rock, Paper, Scissors.

#!/usr/bin/python
import random
print ("Paper, Rock, Scissors, Lizard, Spock")
print ("====================================")
print ("Please choose P(aper), R(ock), S(cissors), L(izard) or (Spoc)K")
player_choice = input()
player_choice = player_choice.upper()
choice_list = ["P", "R", "S", "L", "K"]
computer_choice = random.choice(choice_list)
print ("Player choice is ", player_choice)
print ("Computer choice is ", computer_choice)
if player_choice == "P":
   if computer_choice == "P":
      print ("Two Papers - Draw!")
   elif computer_choice == "R":
      print ("Paper covers Rock - Player Wins!")
   elif computer_choice == "S":
      print ("Scissors cuts Paper - Computer Wins!")
   elif computer_choice == "L":
      print ("Lizard eats Paper - Computer Wins!")
   elif computer_choice == "K":
      print ("Paper disproves Spock - Player Wins!")
elif player_choice == "R":
   if computer_choice == "P":
      print ("Paper covers Rock - Computer Wins!")
   elif computer_choice == "R":
      print ("Two rocks - Draw!")
   elif computer_choice == "S":
      print ("Rock blunts Scissors - Player Wins!")
   elif computer_choice == "L":
      print ("Rock crushes Lizard - Player Wins!")
   elif computer_choice == "K":
      print ("Spock vapourises Rock - Computer Wins!")
elif player_choice == "S":
   if computer_choice == "P":
      print ("Sissors cut Paper - Player Wins!")
   elif computer_choice == "R":
      print ("Rock blunts Scissors - Computer Wins!")
   elif computer_choice == "S":
      print ("Two Scissors - Draw!")
   elif computer_choice == "L":
      print ("Scissors decapitates Lizard - Player Wins!")
   elif computer_choice == "K":
      print ("Spock smashes Scissors - Computer Wins!")
elif player_choice == "L":
   if computer_choice == "P":
      print ("Lizard eats Paper - Player Wins!")
   elif computer_choice == "R":
      print ("Rock crushes Lizard - Computer Wins!")
   elif computer_choice == "S":
      print ("Scissors decapitates Lizard - Computer Wins!")
   elif computer_choice == "L":
      print ("Two Lizards - Draw!")
   elif computer_choice == "K":
      print ("Lizard poisons Spock - Player Wins!")
elif player_choice == "K":
   if computer_choice == "P":
      print ("Paper disproves Spock - Computer wins!")
   elif computer_choice == "R":
      print ("Spock vapourises Rock - Player Wins!")
   elif computer_choice == "S":
      print ("Spock smashes Scissors - Player Wins!")
   elif computer_choice == "L":
      print ("Lizard poisons Spock - Computer Wins!")
   elif computer_choice == "K":
      print ("Two Spocks - That is illogical! (but still counts as a draw)")
else:
   print ("Problem with Player Choice")
And it gives the results:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test06_rockPaperScissors.py
Paper, Rock, Scissors, Lizard, Spock
====================================
Please choose P(aper), R(ock), S(cissors), L(izard) or (Spoc)K

r
Player choice is  R
Computer choice is  S
Rock blunts Scissors - Player Wins!

>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test06_rockPaperScissors.py
Paper, Rock, Scissors, Lizard, Spock
====================================
Please choose P(aper), R(ock), S(cissors), L(izard) or (Spoc)
K
l
Player choice is  L
Computer choice is  P
Lizard eats Paper - Player Wins!

>>>

 This is not sophisticated - it simply has an if/elif option for each possible combination.
It does introduce the random module - I will discuss importing modules in a later post.
I may come back to this with a more efficient version that does not rely on such long-winded coding. Just as a challenge to any budding Python coders, how would you improve this?

Thursday, 21 September 2017

So why did I choose Python? What about other Programming Languages?

The short answer is: Python seems to be the best compromise between power and simplicity.
There is also the advantage that, at my current stage of learning, Object-Oriented Programming in Python is optional.

Choosing and championing programming languages can seem like religion rather than computer science. There are proselytisers, explaining why their language is clearly the best, and other languages are fatally flawed. Practitioners have faith in their language, and have scepticism, if not outright prejudice, towards others. My experience of other languages is both small and shallow, so I am certainly not claiming any expertise in this hotly contested subject, so anything I say about each language below is purely from my own viewpoint. But I like to think of myself as open-minded and ecumenical.

Java

I don't think I would have got into Java on my own - it was actually a requirement of my Open University course.
The good: It is a powerful, professional language. A lot of off-the-shelf software is written in Java, including my favourite, Minecraft. Anything that helped create Minecraft must be good.
And yes, I did get into it for a while. I could write simple programs as required by the course.
The bad: Everything is required to be a class, as per OOP. This makes the archetypal "Hello World" program a lot longer and more complicated than in simpler languages. For a noob like me this is quite off-putting.
It's a compiled language, rather than an interpreted language, so when you want to run programs, you have to compile it first. My approach to programming involves a lot of running, testing and debugging - a kind of messy, unstructured version of rapid prototyping - compiling just slows that down.

Perl

I have to say, it was a fairly close choice between Python and Perl for me - this blog might have been titled "Progressing Through Perl" after having a go at 'Learning Perl' by Schwartz, Phoenix & foy (O'Reilly's Llama book) . What tipped the balance was relative popularity, particularly in the job adverts. There are still fairly regular vacancies for Python developers. Requests for Perl developers are very rare.
The good: It's available on any Linux system and can easily be installed on Windows (I used Strawberry Perl).
It's simplicity is comparable to Python, at least at the levels I was at (which was noobishly simple).
It's interpreted, so testing programs is quick, allowing rapid correction of problems.
The bad: Syntax isn't great. Having said that, I don't find it as off-putting as some people do.
Another thing is that it isn't really suited to Object-Oriented Programming. I understand it can be done, but it's more of a work-around than an inbuilt feature. And despite my misgivings about Java, I recognise that if I am to be taken seriously as a programmer in the workplace, I need to do OOP.

QBasic

I know that very few take this one seriously. And I don't think I ever took it that seriously. But it is what it is - a simple language primarily intended for education and training.
My first ever computer was a Commodore 64. That had BASIC on powering up. Although I was never serious about becoming a programmer until I started the OU course, that Commodore 64 gave me my first taste of programming. So when I found I could download and run a version of BASIC, in this case QBasic, on a Windows PC, I did so. I didn't get very far, but it reminded me that I can program (if only "Hello World" at that stage). I think that was more about familiarity and maybe nostalgia than seriously trying programming.
The good: It is deliberately simple, at least to start with. Some advocates (yes there are a few out there) say that QBasic has hidden depths - I haven't got round to exploring those myself, but I'll take its supporters' word for it.
The bad: As one might expect, it was created primarily as a teaching tool rather than a professional language for commercial applications. I don't know if mentioning it on your CV is sensible or not.

Tuesday, 19 September 2017

Manipulating Lists

So previously we've looked at what lists are. Now what can we do with them?
Lists are a mutable data type. This means we can change them without assigning them to a new variable (or recreating the existing variable). However, the order of elements stays put unless we specifically change them. This means you can look at an element at a specific position (also known as the element's index) in a list by the list variable name followed by the position in square brackets. So for example on the command line:
>>> example = [23, 10, 18, 31]
>>> print (example[1])
10
>>>
You might say the computer has got it wrong. That's not the first number in the list, it should be 23.
Remember when using range(10) we actually got a range from 0 to 9? Lists are numbered the same way - from 0 to length-1. So if you wanted the first element, you need [0]
>>> print (example[0])
23
>>>
And just to show you what happens if you try to go beyond the end of a list:
 >>> print(example[5])
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    print(example[5])
IndexError: list index out of range

>>>
This is known as an "out of range error" and may crop up if you aren't careful with lists in programs. As you can see, the position of the element is referred to as its index.
To find the last item in a list you can use [-1], so you don't need to know the actual length:
>>> print (example[-1])
31
>>>
You can change an element's value by using its index in an assignment statement
>>> print (example)
[23, 10, 18, 31]
>>> example[1] = 15
>>> print (example)
[23, 15, 18, 31]
>>>
Lists also have a bunch of useful built-in functions, including index(), sort(), append() and clear().

index()

This function looks for the position (index) of an element within a list with the format listname.index(element). If one or more elements match what it's looking for, it returns the index of the first one. Again remember that the index starts at 0 and ends at number of elements -1.
>>> print (example)
[23, 15, 18, 31]
>>> example.index(23)
0
>>> example.index(31)
3
>>> example.index(0)
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    example.index(0)
ValueError: 0 is not in list

>>>
As you can see, Python throws an error when asked to give the index of something not in the list so this is not good for generally searching for a term in a list - for that use in

sort()

This allows us to sort a list into some order. Going back to our example on the Python command line
>>> example.sort()
>>> print (example)
[15, 18, 23, 31]
>>> print (example.sort())
None
>>>
That print(example.sort()) is a tricky one that has caught me out several times. The sort() function will sort the list, but returns None. There have been times in a program when I've done something like
>>> newlist = example.sort()
>>> print (newlist)
None
>>>

and then been puzzled and annoyed when the newlist is empty. What I should have done is
>>> newlist = example
>>> newlist.sort()
>>> print (newlist)
[15, 18, 23, 31]
>>>
 One final caveat - when sorting, don't mix types - more specifically, don't try to sort a mix of numbers and strings. sort() can sort numbers (including a mix of floats and integers, such as secondexample below) numerically, and it can sort strings ASCIIbetically (like alphabetically but with a wider range of characters, such as thirdexample below) , but it doesn't sort a mix of numerical and ASCIIbetical (like badexample) and comes back with a type error.
>>> secondexample = [102, 52.3, 75, 10.666, 0.01, 10]
>>> secondexample.sort()
>>> print (secondexample)
[0.01, 10, 10.666, 52.3, 75, 102]
>>> print (type(secondexample[0]))
<class 'float'>
>>> print (type(secondexample[1]))
<class 'int'>
>>> thirdexample = ['this', 'is', 'a', 'list', 'of', 'strings']
>>> thirdexample.sort()
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this']
>>> badexample = [0.2, 'Hello', 'world', 50]
>>> badexample.sort()
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    badexample.sort()
TypeError: '<' not supported between instances of 'str' and 'float'

>>>

append()and Concatenation

Adding new elements to the end of a list can be done two ways. One is concatenation (a fancy way of saying sticking two things together, one after the other), the other uses the append() function. So going back to our example on the Python command line:
>>> example = example + [17]
>>> print (example)
[15, 18, 23, 31, 17]
>>> example.append(25)
>>> print (example)
[15, 18, 23, 31, 17, 25]
>>>
At this stage there doesn't seem to be a major difference between the two. However, there are ramifications later on because append() modifies the existing list, while concatenation creates a new list, or at least here recreates the existing list with the addition.
Concatenation has two advantages: firstly you can add new elements onto the front, by doing the concatenation the other way round:
>>> print (example)
[15, 18, 23, 31, 17, 25]
>>> example = [41] + example
>>> print (example)
[41, 15, 18, 23, 31, 17, 25]
>>>

and secondly you can concatenate more than one element at a time, or even join two lists together.
>>> concatexample = secondexample + example
>>> print (concatexample)
[0.01, 10, 10.666, 52.3, 75, 102, 41, 15, 18, 23, 31, 17, 25]
>>>
If you append() one list onto the end of another list, what you get is a list within a list.
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this']
>>> thirdexample.append(badexample)
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this', [0.2, 'Hello', 'world', 50]]
>>>
This might be what you actually want. There are good reasons for having lists within lists. But I need to be careful about this.

del and clear()

Finally you can remove elements from lists. If appending the list into the other list was a mistake, I can use del command and the element's index in the list:
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this', [0.2, 'Hello', 'world', 50]]
>>>
>>> del thirdexample[6]
>>> print (thirdexample)
['a', 'is', 'list', 'of', 'strings', 'this']
>>>
 To totally clear out a list, you can use the clear() function. This doesn't delete the list itself, merely all the elements it contains.
>>> print(badexample)
[0.2, 'Hello', 'world', 50]
>>> badexample.clear()
>>> print (badexample)
[]
>>>


Monday, 18 September 2017

A First Look at Lists

Previously I've mentioned three different data types: strings (enclosed in quotes, and treated as text, such as words and sentences), integers (whole numbers) and floating point numbers (numbers with a decimal point).

Lists are another data type. Whereas strings have quote marks at each end ("" or ''), lists use square brackets ([]) at each end and commas to separate the elements of the list. Element is the term for each item in the list. For example this script:
#!/usr/bin/python3
foo = ['Hello', "World", 4, 6.333, 5, "This is a list"]
for elem in foo:
    print (elem, type(elem))
produces the output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04b_listdemo.py
Hello <class 'str'>
World <class 'str'>
4 <class 'int'>
6.333 <class 'float'>
5 <class 'int'>
This is a list <class 'str'>

>>>
 There are a number of points here:
  • Lists, like other data types, are often assigned to variables, in this case foo
  • for loops can go through each element (item) in a list instead of a range of numbers
  • A list can contain different types of data, including strings, floats & integers (here using the type() function we encountered in a previous post). 
I can modify the script to show that lists can even include other lists, or variables containing lists.
#!/usr/bin/python3
zim = ['list', 'within', 'a', 'list']
foo = ['Hello', "World", 4, 6.333, 5, "This is a string", zim]
for elem in foo:
    print (elem, type(elem))
with the output:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04b_listdemo.py
Hello <class 'str'>
World <class 'str'>
4 <class 'int'>
6.333 <class 'float'>
5 <class 'int'>
This is a string <class 'str'>
['list', 'within', 'a', 'list'] <class 'list'>

>>>
 The len() function returns the length of whatever is in the brackets. So to demonstrate I can add a few lines to the script:
#!/usr/bin/python3
zim = ['list', 'within', 'a', 'list']
foo = ['Hello', "World", 4, 6.333, 5, "This is a string", zim]
for elem in foo:
    print (elem, type(elem))
print ('elements in foo = ', len(foo))
print ('elements in zim = ', len(zim))
with the resulting output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04b_listdemo.py
Hello <class 'str'>
World <class 'str'>
4 <class 'int'>
6.333 <class 'float'>
5 <class 'int'>
This is a string <class 'str'>
['list', 'within', 'a', 'list'] <class 'list'>
elements in foo =  7
elements in zim =  4

>>>
Although you may have noticed, I'll just point out that in calculating the length of foo, len(foo) does not  include the elements within zim - zim is treated as a single element.

Just as an aside, len() also works on strings, counting then returning the number of characters in the string. For example, on the Python command line:
>>> len("Hello World")
11
>>> example = "This is an example string"
>>> len(example)
25
>>>
 Back to lists. If a list just contains numbers, then you can use a for loop to do maths on them. Here's another script.
#!/usr/bin/python3
# initialising the list variable
x = [45, 32, 46, 21, 40, 39, 28, 55]
total = 0
for num in x:
    total = total + num
print ('List is ', x)
print ('Total for list is: ', total)
avg = total / len(x)
print ('Average for list is ', avg)
which gives the output:
  RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test04c_numberlist.py
List is  [45, 32, 46, 21, 40, 39, 28, 55]
Total for list is:  306
Average for list is  38.25

>>>
 As you can see, len() is useful for tasks such as finding averages as you don't need to work out for yourself how many elements you need to divide the total by.

Sunday, 17 September 2017

If, Else and Comparing with ==

So far I've really only used one control structure (that is a part of the program that controls how the program flows) - the for loop. This is useful up to a point and previous script in this blog have demonstrated the for loop very well.
The if structure allows the computer to do stuff once depending on whether or not a statement is true rather than a number of times.
So a very quick script to show this might look like:

#!/usr/bin/python3
username = input("Hello, what is your name? ")
print("Hello ", username)
if username == "John":
    print ("Nice to have you back!")

  
A couple of points:
Something that often catches me out is that Python uses a single = for assigning values to variables, but when comparing to see if things are the same, it uses == (double =). Here because it is checking to see if the inputted string is the same as "John", it uses ==. Fortunately in Python (unlike some other languages), == is good for comparing both strings and numbers.
There is a colon after the comparison or the thing that should be true. This tells Python that the indented line(s) that follow the colon should only be run if the thing being checked is true.

So if I run it twice, giving it two different inputs, this is what we get:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? Bob
Hello  Bob
>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? John
Hello  John
Nice to have you back!
>>>

 But what if the comparison is false (i.e. not what the if statement is looking for), and you want the program to do something else? This is where else comes in. It is not used on its own, but immediately after an if statement, as an alternative set of instructions.
I can add a few lines to the script:
#!/usr/bin/python3
username = input("Hello, what is your name? ")
print("Hello ", username)
if username == "John":
    print ("Nice to have you back!")
else:
    print("I don't think we've met before.")
And so the output from two different runs of this script:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? John
Hello  John
Nice to have you back!

>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? Mike
Hello  Mike
I don't think we've met before.

>>>

 Note that like if, else also has a colon after it, and the alternative set of instructions are also indented. However, it does not have any comparison or checking if something is true or false - that is done in the preceding if statement.

There is a third option - elif. This checks a different comparison if the first one (in the if statement) is not true. The elif statement comes in between the if and the else statements, and you can have as many of them as you want. They form a cascade of checks to see which, if any, conditions apply.
Let's modify the script again.
#!/usr/bin/python3
username = input("Hello, what is your name? ")
print("Hello ", username)
if username == "John":
    print ("Nice to have you back!")
elif username =="Sara":
    print ("You're looking nice today!")
elif username =="Englebert":
    print("Is your second name Humperdink?")
elif username =="Algernon":
    print("Go away Algernon, you stupid idiot")
else:
    print("I don't think we've met before.")
So running the script a few times with different inputs gives the output
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? Englebert
Hello  Englebert
Is your second name Humperdink?

>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? Algernon
Hello  Algernon
Go away Algernon, you stupid idiot

>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? Alison
Hello  Alison
I don't think we've met before.

>>>
 In these control structures, indentation becomes very important, as it tells both Python and human readers whether a line is part of the control structure (i.e. linked to the if, else or elif statement preceding it) or not. This is the same principle as the for structure. Any lines after the if/elif/else statement that are not indented are run regardless of comparisons. For example:
#!/usr/bin/python3
username = input("Hello, what is your name? ")
print("Hello ", username)
if username == "John":
    print ("Nice to have you back!")
elif username =="Sara":
    print ("You're looking nice today!")
else:
    print("I don't think we've met before.")
print ("This line gets printed anyway!")
The last line is not indented, so does not belong to the else statement, and is run after Python has gone through the if/elif/else part. And if we run it several times with different inputs:
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? John
Hello  John
Nice to have you back!
This line gets printed anyway!

>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? Sara
Hello  Sara
You're looking nice today!
This line gets printed anyway!

>>>
 RESTART: C:/Users/John/Dropbox/Misc Programming/Python/python3/test02_if.py
Hello, what is your name? Andy
Hello  Andy
I don't think we've met before.
This line gets printed anyway!

>>>