4
\$\begingroup\$

I decided to write a Literate Programme Tutorial of Python in Python, i.e. a document that contains executable code as well as very extensive comments about the basic functionalties of the code. This "programme" is quite big, 431 lines, but most of it is text explaining what is happening. Is it ok to post the tutorial/programme here on Code Review? For completness I include the script down here.

"""
Welcome to this Python tutorial.
This document is both a tutorial and a piece of code: you can read it and
you can also execute it!
if you are curious just press F5 to see what it does.
At the end of this tutorial you will understand how all the functions that 
you see in this file are created. And you will know some basic concept of programming.

---

Traditionally the first programme is an hello world programme, let us
start with that:
"""
def hello_world():
    print("Hello world!")

"""
'def' is the most important keyword in Python: it defines a function.
A function in short is a piece of code that:
1) manipulates an input or
2) gives an output or performs some IO.
The 'print' function just outputs something.

--------

We may want to keep track of a value under a name, this is called 
creating a variable. Creating a variable is very straightforward:
Just type a name the '=' sign and a value.
"""

greeting = "Hello"
place = "World"
answer = 42

"""
Now that we have covered the basics of output and variables, we can get 
down to the real job that computers can do well: calculating things.
Lets make a simple example:
"""

result = 2 + 2

"""
If I ask "How much is the result?" you can easily reply 4.
But let's make another example (a**b means a to the power of b)
"""

result = 9**13

"""
Now it is not easy to mentally calculate that the result is
2541865828329, is it? Instead a computer can calculate it in less
than a tenth of a second.

---
It is very rare to create a programme that will execute all the statements
it contains no matter what, in other words we may want to branch the programme,
do a thing if a condition is True, do another if the condition is False.
I am going to illustrate this with a simple age asking example:
"""

try:
    input = raw_input
except NameError:
    pass

"""
Please ignore the lines of code above, they are there to make this
programme compatible with the old Python 2, they are outside the scope of
this tutorial
"""

def decide_if_teenager(age):
    age = int(input("How old are you? "))
    if 13 <= age <= 19:
        print("You are a teenager")
    else:
        print("You are not a teenager")

"""
The input(text_to_display) function asks the user for a value
and 'returns' that value. For now just rememember that you can use
my_user_decided_value = input("Enter your value: ")
The age input is then converted to an integer to compare it with numbers,
because all input defaults to text and then an
if: else: statement is used to print an appropriate message.

---

We now run into an obvious question:
"If computers are so fast how can I get my computer to execute a big
number of operations? Do I have to type them all one by one?"
Luckly you can tell the computer to execute multiple statements with
'loops'. A loop is nothing more than repeating something.
"""

def count_to_ten():
    for number in range(1,10):
        print(number)

"""
This function will output all the numbers from one to ten.

---

We can now create our first programme the really uses the computational
power of modern computers: a factorial programme.
(The factorial of a number is the product of all the numbers from 1 to
that number).
"""

def factorial(n):
    result = 1
    for i in range(1,n+1):
        result *= i
    return result

"""
Let us consider the most peculiar parts of this function:
* range(1,n+1): ranges in Python are non-inclusive, that is the last 
element is considered outside of the range.
* 'a *= b' is the same as 'a = a * b' but is shorter, so it is used to
be clearer and to avoid too much tipying.
* 'return' this keyword is very important, it allows us to create functions
that 'give back' a value.

---

There is also another kind of looping, the while loop, that loops while
the condition remains True. It should be used rarely but for completeness
sake I am going to implement a programme that uses it.

The collatz conjecture says that the Collatz sequence of all the numbers
will arrive to one.

The collatz sequence of a number is definde as folows
"""

def next_collatz(n):
    if is_even(n):
        return n//2
    else:
        return 3*n + 1

def collatz_sequence(n):
    steps = 1
    while n != 1:
        n = collatz_sequence(n)
        steps += 1
    return steps

"""
The above function will compute the collatz sequence of a number and will
return the number of collatz_steps taken to arrive to 1

---

Let's make another function that computes numerical values, this time a
little harder, the GCD (Greatest Common Divisor). Here we will understand 
the huge power of functions: complex functions can be built from simple
ones.

Our plan is to decompose the problem in the following sub-problems:
1) Find the greatest or max of a list of numbers.
2) Find the common numbers between two lists.
3) Find the divisors of a number.

1) Find the greatest or max of a list of numbers.
We are very lucky because we don't have to solve this problem ourselves:
there is a built-in function ready to use called max that will do just this.

2) Find the common numbers between two lists.
The best way to approach this task is list comprehension.
List comprehension is a simple way to change lists.
A list is a series of comma separated values.
"""

example_lst = [3,5,7,2,8]

"""
There are two ways to change lists, one is 'filtering' them, taking only the 
elements that satisfy a condition
"""

def is_even(n):
    return n % 2 == 0

even_only = [i for i in example_lst if is_even(i)]

"""
The other is 'mapping' them, applying a function that changes the value:
"""

to_the_3rd_power = [i**3 for i in example_lst]

"""
Finally we should remember that these can be applied together:
"""

even_only_to_the_3rd_power = [i**3 for i in example_lst if is_even(i)]

"""
Now defining a function that takes the common elements of two
lists is trivial:
"""

def common(list1,list2):
    return [i for i in list1 if i in lst2]

"""
3) Finding the divisors of a number.
This task is very interesting: you can either choose a very simple 
function that is slow or a very fast one that is harder. In this tutorial
I will use the simple one.
Often you can translate a mathematical concept in code directly:
 ' The divisors (or factors) of a number are all the natural numbers (integers)
less than it  if the remainder of the division number / divider equals 0.'
"""

def divisors(n):
    return [i for i in range(1,n+1) if n % i == 0]

"""
We have been able to translate the concept to code verbatim!
(Technical side note: '%' is the reaminder-of-division (modulo) operator.
"""

"""
And now that we have all the pieces we can assemble the final GCD
proramme.
"""

def GCD(a,b):
    return max(common(divisors(a),divisors(b)))

"""
In the above function the parenthesis work as they do in the
algebraic expressions i.e. you start evaluating what is in the innermost
parenthesis first and then you proceed to the outer parenthesis.

---

We now know some Math techniques, so it is time to learn about files IO
and text processing techniques.
"""

def get_text_of_self():
    with open(__file__) as f:
        return f.read()

"""
In the code above I introduced 2 new concepts:
1) 'with open(filename) as f:' is the best way to handle files IO:
just call it, than do what you want with f and then forget about it:
it handles closing automatically.
2) '__file__' as you can see this variable has __double__undercores__,
it means that it is 'magic': the Python interpreter (the programme that 
runs the Python programmes) gives a value to all this __double__underscored__variables__
at runtime. '__file__' is the name of the script.

---

Now we may be curious to know what are the most common words in this file.
Yet another time, we can use a built-in called counter
"""

from collections import Counter

"""
Imports should always be placed at the start of files but for you to better
read this tutorial, I have put this import in the midddle of the file.

Now you may start wondering: "How can I understand what is built-in and what 
is not?" Actually it is not easy: the Python Standard Library (i.e. the
place that contains all the built-ins) is huge. A reasonable rule of thumb
is that 95% of what you can think of is built-in, you just have to search for
it. A good page to see all the built-ins listed nicely is 
https://docs.python.org/3/library"

---

So let's return to goal of getting the most common words of this file.
We are going to divide the task in smaller functions:
1) Getting the content of this file
2) Making a list of words out of it
3) Using Counter to count them

1) Is already done, just see the get_text_of_self function above.

2) To make a list of words out of it we must use the important 
'.split()' built-in, it allows youto split a text at a char and gives you a list.
As words are separated by spaces we can split on the spaces
"""

def get_wordlist(text):
    return text.split(" ")

"""
3) Counter is built-in.

---
We can now assemble the pieces:
"""

def count_words_in_this_file():
    words = get_wordlist(get_text_of_self())
    return Counter(words)

"""
This will return all the words in the file with the number of times they 
appear, scroll to the top to see the most common ones.

---

So far we have covered the basics of calculating with numbers, IO and
processing text, now I will show you how to create a GUI, a graphical user
interface, where the user can press buttons and enter values in 
'widgets'. "Is a GUI module built-in?" The answer unsuprisingly is yes,
it is called Tkinter.
This GUI will be a number guessing game:
The user moves a slider to select a number, he then presses a button and if
the number is equal to the computer's number, he will win, otherwise the computer
will tell him lower or bigger.
"""

"""
First of all we import the needed libraries, as I want this programme
to be compatible with the old Python 2.x I have to make the imports code a
bit more complicated, anyway it is just trying to import the modules for
Python 3, if it fails it imports the modules for Python 2
"""
try:
    import tkinter as tk
    import tkinter.messagebox as pop_up
except ImportError:
    import Tkinter as tk
    import tkMessageBox as pop_up

import random

"""
As always we decompose the problem into functions,
as creating a number guessing game is easy we only need 3 of them
"""
def generate_message(user_number,cpu_number):
    if user_number < cpu_number:
        return "Too low"
    elif user_number == cpu_number:
        return "Right!"
    else:
        return "Too big"

def number_guessing_game_GUI():
    """
    Tkinter GUI programmes are usually Object Oriented, but I
    consider that an advanced topic, also it is not compulasory.
    As this is a beginner course I decided not to use Objects but simple
    functions and variables.
    """

    """
    The following line is a convention always followed
    when writing a tkinter programme.
    """
    root = tk.Tk()

    root.wm_title("Number guessing game")

    """
    Sadly inside tkinter we can't use the Python built-ins types,
    we are forced to use IntVar and StringVar. These kind of types
    must be assigned with the set keyword and read with the get keyword.
    cpu_number is the only IntVar that we will use in this simple programme.
    """
    cpu_number = tk.IntVar()
    cpu_number.set(random.randint(1,10))

    def reset():
        cpu_number.set(random.randint(1,10))

    def show_message():
        pop_up.showinfo("The number you chose is...",
             generate_message(number_selector.get(),cpu_number.get()))

    """
    Down here you can see the code creating the widgets i.e. the things that you
    see in the GUI. It is quite long but pretty easy to undersatnd.
    """

    number_selector = tk.Scale(root,from_=1, to=10,orient = tk.HORIZONTAL)
    number_selector.pack()

    confirm_button = tk.Button(root,text = "Check this number",command = show_message)
    confirm_button.pack()

    play_again_button = tk.Button(root, text = "Play again",command = reset)
    play_again_button.pack()

    """
    In order to actually display the GUI you need to call the mainloop function
    """
    tk.mainloop()

"""
Now we are going to display a welcome message to the user (to you in this case :))
and show him the list of the functions in this module
"""
from inspect import getmembers, isfunction

functions_list = [hello_world, decide_if_teenager, count_to_ten,
                  factorial, next_collatz, collatz_sequence,
                  is_even, common, divisors, GCD, get_text_of_self,
                  get_wordlist, count_words_in_this_file,
                  generate_message,number_guessing_game_GUI,
                  ]
def main():
    print("""Welcome to the interactive shell, you can experiment with the following
functions defined in this tutorial.""")
    print([i.__name__ for i in functions_list])

"""
When writing a medium/big Python script it is good practice to use the following 'guard'
"""
if __name__ == "__main__":
    main()

"""
This way the Python script will only execute the main funciton if it called directly,
if you put the UI code in the main function this means that you can use the script not
only standalone but also as a library importing it
"""

P.S. I would like to post the programme in this wiki is the copyright of the reviews ok?

| |
\$\endgroup\$
  • \$\begingroup\$ Well... I can't think of a reason it wouldn't be. I assume the code works or you wouldn't be posting it on a site that touts runnable tutorials. Being a tutorial, you obviously want it to be good code. The definition of good code just might be a little different than we're used to in this case. \$\endgroup\$ – RubberDuck Jan 5 '15 at 21:14
11
\$\begingroup\$

In general, literate programming would be OK. This particular question, though, is off-topic, I think. The code doesn't do any coherent task, and is all for illustration. In that sense, any code would do, making it rather hypothetical.

Alternatively stated, what kind of answers would you expect? There should really be several independent questions; we couldn't meaningfully review all of the code in one answer.

| |
\$\endgroup\$
5
\$\begingroup\$

The wiki that you would like to post to uses a CC0 license, which is essentially public domain. That license is incompatible with the CC BY-SA 3.0 license used by Stack Exchange, as it drops the requirements for attribution and for relicensing derivative works under the same terms.

Therefore, if you were to receive answers, you would not be able to republish them on the Literate Programming wiki unless you got explicit authorization from the contributor(s).

| |
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .