Cover

Learning Python

1. Intro

1.1. The problem with learning Python

Learning Python is terrible. All tutorials start with the amazement that you can have variables (WOW!) to which you can assign values(Hurray!!). And, although I forced myself to start a number of those courses, I just cannot spend hours going through that kind of basics anymore.

Most courses will stop at some point stop, inviting you to do your own coding to learn further. And that is mostly the point that it gets interesting.

So, none of the tutorials work and all stop where it gets interesting.

Furthermore, Python has a number of inconsistencies and bizarre concepts that make the language harder to learn. Did I just say language? I meant languages, because the changes between version 2 and 3 make it often impossible to exchange programs between them.

As a tutorial exercise, I'll be rewriting my TCL/Tk application for image administration in Python.

1.2. Quick language overview

1.2.1. Basic concepts

Python is an Object language. So most of the code will be in classes or will be calling classes.

Python uses an indent to create blocks, in stead of { }, like in C/C++, Perl, go, java, PHP, Scala etcetera or do/done or the likes. Python is very sensitive to spacing. A block is ended with an empty line.

Comments are marked with #, which is normal for a scripting language.

Python has all the normal flow controls, while, for if-then-else. The syntax is, that an indented block is started with a colon, for example:
for item in iterable_collection:
    # do something

Associative arrays (hashes in Perl) are called dictionaries and behave more or less that same as Perl's hashes:
plaatjes = {
    'molen' = 'mooi',
    'straat' = 'lelijk',
    'cavia' = 'lief',
}
for key, value in plaatjes.iteritems():
    print key, value
molen mooi
straat lelijk
cavia lief

1.2.2. Let's have an argument

Python allows two types of arguments:
  • positional parameters
  • named parameters (called keyword parameters in Python)

Positional parameters are what every language uses. Keyword parameters in general get an initial value and they are optional. As an example:

 

def hello (greet='hi',dest='all the people'):
    print greet, " to ", dest
hello()
hello(dest='you')

 

If you do not know (or care) how many positional parameters and/or keyword parameters you get, you can use the c-style pointers:

 

def print_args(*args, **kwargs):
    print 'Positional:', args
    print 'Keyword:   ', kwargs
    for key in kwargs.keys():
        print "Key=",key,"   Value=",kwargs[key]
        if key == 'foo' :
            print "JA HOOR"
print_args(1, 2, foo='bar', stuff='meep')

Obviously, it's nothing like c's pointers, but it's easier to remember like that.

1.2.3. Lists and references

A special place in hell should be reserved for the one who invented references in Python. Consider the following:
a = [1, 2, 3]
b = a
a = []
print(a)
print(b)
a = [1, 2, 3]
b = a
del a[:]
print(a)
print(b)
a = [1, 2, 3]
b = a[:]
del a[:]
print(a)
print(b)

Which gives as output:
[]
[1, 2, 3]
[]
[]
[]
[1, 2, 3]

Obviously.

This can only be explained by the fact that a = [1, 2, 3] assigns a reference to the list [1, 2, 3] to the variable a and the line b = a assigns the value of a to b. That means that now, a and b both have a reference to the same [1, 2, 3] So when we do a = [] a will be a reference to an empty list and b will still have a reference to the original list. However, if I start doing operations on a list with del a[:] it affects the list and not the reference to the list. So that means that b (which is a reference to the same list) will now also point to the empty list.

Finally, b = a[:] forces a copy of a. So, because it is a copy, emptying a will not empty b.

Pythons garbage collector for orphans seems effective enough, so you don't have to worry about lists that are unrefferencable.

1.2.4. Tuples

Tuples are lists that you cannot change. Because they are static, they are faster. And they use different parentheses. That is all there is to know about them.

Many Python zealots will start talking about mutable and immutable objects. They will also point to semantics, for example:
import time
time.localtime()
(2008, 2, 5, 11, 55, 34, 1, 36, 0)

where, if you delete the days, the minutes become hours. So this is why you have time in tuples instead of lists.

1.2.5. Variable scoping

Because Python does not allow you to scope variables explicitly (PEP 20: Explicit is better than implicit), Python uses the following rules:
  • If a variable is declared global it is assumed to be part of the global namespace.
  • If a variable is declared nonlocal it is part of the parent namespace.
  • If you assign a value to a variable, it is assumed to be part of the local namespace, from the beginning of that context.
  • If you do not assign anything to a variable, it assumed implicitly to be part of the parent namespace
  • Mutating an object (for example deleting parts of a list) is not considered as an assignment

The real fun starts when there are multiple nested namespaces...

1.3. Input Output

In the previous sections, al number of print statements have been used.

Reading stdin is a bit more difficult. If you want to read a number, you can use:
value=input('prompt')

However, if you need a string as input, you must use:
value=raw_input('prompt')

2. Tkinter

Tkinter is a Python module for the TK-toolkit.

2.1. Hello, world

A first "Hello world" looks like this:
#!/usr/bin/python
import Tkinter as tk
root = tk.Tk()
w = tk.Label(root, text="Hello world!")
w.pack()
root.mainloop()

which gives the following window:

Which is nice. But what did we do?
import Tkinter as tk

So we imported a module Tkinter and we say we'd like to call it tk from now on. It is possible to just import Tkinter but then, instead of using root = tk.Tk() you would use root = Tkinter.Tk() and so on.

A lot of the rest is fairly standard Tk. So compare
Python
TCL/Tk
root = tk.Tk()
w = tk.Label(root, text="Hello world!")
label .hello -text "Hello, World!"
w.pack()
pack .hello

and you'll see it is basically the same.

At the end, we do the root.mainloop() which starts the tk event loop. This is the moment that Python starts the tk loop. In TCL, this is done automatically.

2.2. Hello again

Python uses classes. That means that you generally create classes to do something for you.
#!/usr/bin/python
from Tkinter import *
class App:
def __init__(self, master):
    self.frame = Frame(master)
    self.frame.pack()
    self.button = Button(
    self.frame, text="QUIT", fg="red", command=self.frame.quit
    )
    self.button.pack(side=LEFT)
    
    self.hi_there = Button(self.frame, text="Hello", command=self.say_hi)
    self.hi_there.pack(side=LEFT)
def say_hi(self):
    print "hi there, everyone!"
root = Tk()
app = App(root)
root.mainloop()
root.destroy()

Let's analyze what we did. First, we created a class App with two methods,
  • init
  • say_hi

The method __init__ is a standard name. The method gets called after the object is created. __init__ is not (as some less rigorous people seem to think) a constructor. The real constructor is called __new__(cls, *args, **kwargs) In general, Python uses a lot of __init__ and very seldom __new__.

The first argument to all methods is self. The reason for this is that Python likes to state all sort of things explicitly. The fact that there is an inconsistency between the calling of a method (where self is always missing) and the declaration does not seem to be a problem. So we need to learn when explicit is required and when not.

The App object contains a number of Tk objects:
  • frame (which is a Frame)
  • button with quit
  • button with hello

All of these objects get a prefix of self because, in Python, there is no explicit variable declaration.

It is worth noting that the self.frame.quit quits the application (the main loop)

2.3. Tkinter widgets

Most of the widgets are the same as in Perl/Tk or TCL/Tk, and the syntax example with the buttons above should make them usable.
Widget
Description
Button
A simple button
Canvas
Structured graphics.
Checkbutton
Represents a variable that can have two distinct values.
Entry
A text entry field.
Frame
A container widget.
Label
Displays a text or an image.
Listbox
Displays a list of alternatives.
Menu
A menu pane.
Menubutton
A menu button.
Message
Display a text.
Radiobutton
Represents one value of a variable that can have one of many values.
Scale
Allows you to set a numerical value by dragging a “slider”.
Scrollbar
Standard scrollbars for use with canvas, entry, listbox, and text widgets.
Text
Formatted text display.
Toplevel
A container widget displayed as a separate, top-level window.
LabelFrame
A variant of the Frame widget that can draw both a border and a title.
PanedWindow
A container widget that organizes child widgets in resizable panes.
Spinbox
A variant of the Entry widget for selecting values from a range or an ordered set.

As an example, a simle way to display an image is:
from Tkinter import *      
root = Tk()      
canvas = Canvas(root, width = 400, height = 400)      
canvas.pack()      
img = PhotoImage(file="/links/diaadm/images/fullsize/00125.gif")      
canvas.create_image(0,0, anchor=NW, image=img)      
mainloop() 

3. Mariadb

3.1. Intro

All my data is currently stored in a Maria DB and therefore it is necessary to use the Mariadb. The format is simple: a single table holds all the data.
Name
Type
Description
number
INTEGER
Unique key for the image
type
VARCHAR(255)
The directory under /links/diaadm where the image is placed
file
VARCHAR(255)
The file name for the image
year
INTEGER
The year that the picture is made
month
INTEGER
The month that the picture is made
descr
VARCHAR(4096)
A short description, using many keywords
GPS_Latitude
VARCHAR(255)
Location where the picture is taken, if available
GPS_Longitude
VARCHAR(255)
Location where the picture is taken, if available
GPS_Altitude
VARCHAR(255)
Location where the picture is taken, if available
ISO_equiv
VARCHAR(255)
Copy of the JPEG header, if the data is available
Aperture
VARCHAR(255)
Copy of the JPEG header, if the data is available
Exposure_time
VARCHAR(255)
Copy of the JPEG header, if the data is available
Focal_length
VARCHAR(255)
Copy of the JPEG header, if the data is available

All sorts of improvements can be made (a perceptual hash can be created, keywords in the description can get their own table), but that will be a later concern.

3.2. Sample program

From the Internet, I retrieved the following python script.
#!/usr/bin/python
import mysql.connector as Mariadb
mariadb_connection = mariadb.connect(user='diaadm', password='diaadm', database='diaadm', host='127.0.0.1')
cursor = mariadb_connection.cursor()
#retrieving information
some_name = 'l-j'
cursor.execute("SELECT type,file,descr FROM diaadm WHERE descr RLIKE %s", (some_name,))
for tpe, f, descr in cursor:
    print("Type: {}, File: {},  Description: {}").format(tpe,f,descr)

Before I go into the details, I encountered a number of problems. First problem:
Traceback (most recent call last):
  File "maria.py", line 2, in <module>
   import mysql.connector as mariadb
ImportError: No module named mysql.connector

This says that the module is not installed. So that means that you should install the modules, using gslapt (or whatever your distributions flavor is) or using pip install.

Second problem:
Traceback (most recent call last):
  File "maria.py", line 4, in <module>
      mariadb_connection = mariadb.connect(user='diaadm', password='diaadm', database='diaadm', host='127.0.0.1')
    File "/usr/lib64/python2.7/site-packages/mysql/connector/__init__.py", line 179, in connect
      return MySQLConnection(*args, **kwargs)
    File "/usr/lib64/python2.7/site-packages/mysql/connector/connection.py", line 95, in __init__
      self.connect(**kwargs)
    File "/usr/lib64/python2.7/site-packages/mysql/connector/abstracts.py", line 719, in connect
      self._open_connection()
    File "/usr/lib64/python2.7/site-packages/mysql/connector/connection.py", line 206, in _open_connection
      self._socket.open_connection()
    File "/usr/lib64/python2.7/site-packages/mysql/connector/network.py", line 475, in open_connection
      errno=2003, values=(self.get_address(), _strioerror(err)))
  mysql.connector.errors.InterfaceError: 2003: Can't connect to MySQL server on '127.0.0.1:3306' (111 Connection refused)

A MySQL client on Unix can connect to the mysqld server in two different ways:
  • By using a Unix socket file,
  • by using TCP/IP, which connects through a port number.

My TCL/Tk script connected to a socket, which is more secure, because you do not have to expose the TCP-port. Apparently, as a default, Python's module uses a network connection. Searching through the Internet did not turn up a quick solution to make Python behave more securely, therefore, I restarted mariadb with
# SKIP="--skip-networking"

commented out in /etc/rc.d/rc.mysqld.

3.3. Commitment

Contrary to the other languages I use (Perl, TCL) I use frequently, Python turns autocommit off by default. Also, the mysql CLI starts with autocommit on. But Python's PEP0249 states:

 

Note that if the database supports an auto-commit feature, this must be initially off. An interface method may be provided to turn it back on.

 

The problem is that if a session that has autocommit disabled ends without explicitly committing the final transaction, MySQL rolls back that transaction.

So, you have three choices:

  • Turn on autocommit
  • Explicitly commit your changes
  • Loose your data

Turning on autocommit can be done directly when you connect to a database:
import mysql.connector as mariadb
connection = mariadb.connect(user='testdb', password='testdb',
		database='testdb', host='127.0.0.1',autocommit=True)

or separately:
connection.autocommit=True

Explicitly committing the changes is done with
connection.commit()

Note that the commit is done via the connection to the database, not via the cursor.

4. Design issues

Upto here, everything was just more or less copying the orignal TCL/Tk functionality in Python. However, where TCL/Tk code stays relatively compact, with Pithon I now have a script that is about the same number of lines but implements only a very small part of the functionality. This is a strategy that is not sustainable.

4.1. Splitting the GUI

In the TCL/Tk version it was one simple GUi; the tree-structure that the GUI has is natural to that language. In Python, it is not. So, in order to get a better and more readable code, we have to create objects that provide a more high-level view of the GUI, instead of the low-level Tk components.

4.1.1. Entryfields

As a first, I have a number of entry fields that are preceeded by a label that tells what information should be in the entry field.

entryframes.png>

So, the combination of a label with an entry field is a good candidate for an object.
class entryfield(object):
    def __init__(self,parent,label='text'):
        self.fieldframe=tk.Frame (parent)
        self.fieldframe.pack(side=tk.TOP)
        self.fieldname=tk.Label(self.fieldframe, text=label ,width=10,anchor='w')
        self.fieldname.pack(side=tk.LEFT)
        self.fieldvalue=tk.Entry(self.fieldframe, width=50)
        self.fieldvalue.pack(side=tk.RIGHT)
   def set(self,value='text'):
       self.fieldvalue.delete(0,tk.END)
       self.fieldvalue.insert(0,value)
   def get(self):
       return(self.fieldvalue.get())

And with that object, creating the part of the GUI in the picture above becomes:
        self.fieldnumber=entryfield(self.fieldframe,'Number')
        self.fielddir=entryfield(self.fieldframe,'Directory')
        self.fieldfilename=entryfield(self.fieldframe,'Filename')
        self.fieldyear=entryfield(self.fieldframe,'Year')
        self.fieldmonth=entryfield(self.fieldframe,'Month')
        self.fielddescr=entryfield(self.fieldframe,'Description')

That is clearly more readable than the endless repeats of the tk.Frame, tk.Label, tk.Entry and their packs.

You will notice, that their are a number of assumptions that make this object rather specific for my GUI. The size of the entry fields and labels are fixed, packing is always TOP etcetera. This makes the entryfield object good for this GUI, but perhaps a bit less re-usable.

4.1.2. Multilist

The object of the next object is to create a more reusable object. The problem is that we have multiple list boxes that must scroll synchronously and with a scrollbar. This functionality existed in the TCL version and in the one-big-gui version, so the main problem is to create a more generally usable object.

 

class multilist (object) :
    def __init__ (self,parent,columns=1,height=50,width=10) :
        self.listframe=tk.Frame (parent)
        self.listframe.pack()
          #     The listframe contains 4 lists and a scrollbar which are synchronized
          self.sb = tk.Scrollbar(self.listframe, orient='vertical')
          self.cols=[]
          self.select=[]
          for i in xrange(columns):
              self.listnr=tk.Listbox (self.listframe, yscrollcommand=self.yscrollnr)
              self.listnr.config(height=height,width=width)
              self.listnr.bind('<<ListboxSelect>>',self.selectlist)
              self.listnr.pack(side=tk.LEFT, expand=True )
              self.cols.append(self.listnr)
          self.sb.config(command=self.yview)
          self.sb.pack(side=tk.RIGHT, fill='y')
    def width(self,column=1,width=10):
        print "set column ",column-1,' to ',width
        self.cols[column-1].config(width=width)
    def yscrollnr(self, *args):
        for i in xrange(len(self.cols)):
            self.cols[i].yview_moveto(args[0])
        self.sb.set(*args)
    def yview(self, *args):
          for i in xrange(len(self.cols)):
             self.cols[i].yview(*args)
    def subscribe (self,function):
         self.select.append(function)
    def selectlist(self,event):
         widget = event.widget
         sel=widget.curselection()
         if sel != ():
             for f in self.select:
                 f(sel)
    def clear(self):
          for i in xrange(len(self.cols)):
              self.cols[i].delete(0,tk.END)
    def add(self, *args):
          i=0
          for val in args:
            if i < len(self.cols):
                self.cols[i].insert(tk.END,val)
                i=i+1
            else:
                 print "No column for $val"

 

Their are still a number of things that make this object a bit specific, but it should be clear that there are a number of choices to generalize its use.

First of all, the size (width and height) and the number of columns are part of the call to __init__. It is also possible to adjust the column width on a per-column basis. Second, the clear and add methods are used to manipulate the listbox contents.

Perhaps more interesting is the callback for a selection in the list. Instead of calling a specific function, external functions/objects/programs can subscribe to the select in the listbox. So multiple functions can be called, just by subscribing to this event.

5. Style

The observation of Python's authors is that programs are more often read than written. Therefore, they have adopted a specific style guide. If you're writing Pythhon code, you should adher to it, because
  • others that see your programs will nag about it
  • if you're used to the style, it will be easier to read programs by others

There are some strange choices in the PEP8 standard. They make programs needlessly long and, for a beginning Python programmer, harder to read. They are also inconsistent in a number of ways.

This chapter does not explain the complete PEP8. I threw my code through an on-line PEP8 checker and these are the errors that I found in my code and my observation of them.

5.1. Spaces

Equal signs must have spaces around them, for example:
a = 1+1

Unless, the equal sign is used for an assignment of named arguments. Other operators should not have spaces around them.

A comma should have a space after it.
a=function(arg1, arg2)

And parentheses do not get spaces.

The colon that is used for block starts should also not get a space:
def function(arg):

And comments start with '# ' (hash-space).

5.2. Line length

Maximum line length is 80. Why? Because the IBM punch-card format, introduced in 1928 had 80 columns.

5.3. Indent

One of the horrors of Python is it's attitude towards indents. Indents are used to mark blocks. Indents are 4 spaces. Not a tab, four spaces. Together with the 80-column limit, this also means a maximum for nesting blocks (19), but if you hit that limit, you should restructure your code anyway.

6. Common tasks

6.1. Configuration files

6.1.1. In Perl

What we're trying to accomplish ist the Python equivalent of
%config={};
open(CFG,"testfile.txt");
while (<CFG>){
    s/#.*//;
    if (/(\w+)=(.*)/){
        $config{$1}=$2;
        print "config{$1}=$2; $config{$1}\n";
    }
}

So, a simple configuration file, with comments and simple a=b assignments. While reding, we also want some sanity-checks, whitch is done by the matching of the regular expressions. The result should be some form of hash/dictionary in which we can lookup the values by text.

6.1.2. configobj

The Python-way seems to be to google if there is a module that does it for you and then use that module. There are a number of modules that read config files, and configobj seems the most simple one.

If it would work.

Try 1:
import configobj
config=configobj.configobj("testfile.txt")

Result:
Traceback (most recent call last):
  File "configobj.py", line 2, in <module>
      import configobj
    File "/home/ljm/src/learning_python/configobj.py", line 3, in <module>
      config=configobj.configobj("testfile.txt")
  TypeError: 'module' object is not callable

The type of configobj.configobj is a module. That is a bit unexpected. Especially since many answers on the Internet suggest that configobj is the module and configobj.configobj should be the way to use it. But apparently, it is not.
import configobj
print type(configobj)
print type(configobj.configobj)
print type(configobj.configobj.configobj)

gives:
<type 'module'>
<type 'module'>
<type 'module'>
<type 'module'>
<type 'module'>
<type 'module'>

Try 2:
from configobj import configobj

which gives:
Traceback (most recent call last):
  File "configobj.py", line 2, in <module>
      from configobj import configobj
    File "/home/ljm/src/learning_python/configobj.py", line 2, in <module>
      from configobj import configobj
  ImportError: cannot import name configobj

Camel-humping the config obj results in yet another import error:
Traceback (most recent call last):
  File "configobj.py", line 2, in <module>
    from configobj import ConfiGobj
  File "/home/ljm/src/learning_python/configobj.py", line 2, in <module>
    from configobj import ConfiGobj
ImportError: cannot import name ConfiGobj

This also means that none of the configobj examples work. And because I don't understand what is happening (behavior does not comply with any of the solutions that I got), I will abandon this module.

6.1.3. ConfigParser

Another module that does the configuration is ConfigParser. It requires a more complicates configuration file.
import ConfigParser
config = ConfigParser.RawConfigParser()
config.read('testfile.txt')
a=config.get('notes',"do")
print a

You must have a [section header] in the file, otherwise the module will fail.
Traceback (most recent call last):
  File "configparser.py", line 4, in <module>
     config.read('testfile.txt')
    File "/usr/lib64/python2.7/ConfigParser.py", line 305, in read
     self._read(fp, filename)
    File "/usr/lib64/python2.7/ConfigParser.py", line 512, in _read
     raise MissingSectionHeaderError(fpname, lineno, line)
  ConfigParser.MissingSectionHeaderError: File contains no section headers.
  file: testfile.txt, line: 1

eventhough the module is documented ( https://docs.python.org/2/library/configparser.html ), there is still a lot unclear about its workings. Ah well, that also seems to be the Python way.

6.1.4. Import

There are some that suggest that using import and creating valid python code as config file is a good idea. It is not.

Ofcourse, if everything is completely under control, and your users won't put code in the config file, then it may not be so bad. But if your user is anyone else but yourself, don't do this.

6.2. Command line arguments

6.2.1. Sys.argv

If you want to interact with anything beyond the most simplset, you need to import sys. In sys there is an array sys.argv that contains the arguments. Like everywhere else, sys.argv[0] contains the name of the script or program.

6.2.2. Getopt

A more or less standard way of parsing arguments is getopt. This is available in C, Perl, Bashe and many others, and also in Python. However, because it is standard, Python doesn't like it. The official stance is that getopt is not deprecated, but argparse is more actively maintained and should be used for new development.

The following sniplet shows the use of getopt:
#!/usr/bin/python
import sys, getopt
try:
    opts, args = getopt.gnu_getopt(sys.argv[1:],"he:q:",["question=","exclamation="])
except getopt.GetoptError:
   print sys.argv[0], '[-e excalamation ] [ -q question ]'
   sys.exit(2)
for opt, arg in opts:
   if opt == '-h':
      print sys.argv[0], '[-e excalamation ] [ -q question ]'
      sys.exit()
   elif opt in ("-q", "--question"):
      print arg,'?'
   elif opt in ("-e", "--exclamation"):
      print arg,'!'
print 'arg=',args

Note that getopt.gnu_getopt gets sys.argv[1:] as list of options. sys.argv[0] contains the name of the script and would therefore mess-up the argument parsing.

We used gnu_getopt because otherwise, parsing of the flags stops when the first non-flag argument is encoutered.

The result is shown below.
$ python getops.py -q question answer --exclamation yes  oh
question ?
yes !
arg= ['answer', 'oh']

6.2.3. agparse

Agparse is at the moment the prefered option to parse command line arguments. It is more advanced than getops, and it has a nice self-documenting feature.

6.3. Regular expression matching

If there's anything Perl is good in, it is handling regular expressions. In Python, this is made complicated. As if the goal is to discourage the use.

First: it is not standard in Python. It is an add-on that needs to be imported.
import re

7. Evaluation so far

I now have implemented exactly the same functionality in Python as I had available in the TCL/Tk script. It is now time for a short evaluation.

7.1. Speed

The Python script is noticably slower than the TCL/Tk equivalent, epecially at start-up. Something needs to be done about that otherwise Python scripts become unusable.

The speed differnce is mostly in three places:
  • start-up
  • looping over the mysql-cursor
  • adding items to listboxes

The way I found this is by putting time print out in specific functions:
from datetime import datetime
def printnow(string):
    dt = datetime.now()
    print string, dt.second, dt.microseconds

and calling this printnow at specific places in the code. You can ofcourse add dt.minutes but if the execution would require me to add the minutes, I would abandon any further exploration of Python.

The result for the looping over the cursor ('cursor.executed' to 'copied to the list') are 1.3 seconds, as can be seen below:
get_selection 45 470185
cursor.executed 46 249354
copied to the list 47 554510
showlist start 47 554571
list cleared 47 633019
list filled 48 351590

The complete update of the listbox from the database is 2.8 seconds, which is clearly much more than the almost instantanous response from TCL/Tk.

7.2. Code size

Python's code size in lines is significantly larger than TCL/Tk. Python is a language that likes to do everything vertically. With a maximum linesize of 80 characters, the codesize in lines quickly becomes very large. The python version is 549 lines, and the TCL/Tk is 337. In characters, Python is more than 75% larger.

7.3. Readability

Code from Python is marginally better readable. My screen is (vertically) around 80 lines, which makes it impossible to get a good overview of sections in the Python code in one screen. The punch-card restriction of 80 columns, compared with the 180 columns of the terminal screen, makes it feel like Python has made some odd choices.

7.4. Variable typing

One thing that is bewildering is the strong typing of Python in combination with its dynamic typing.

In Perl, you can:
$a=2;
$b="1+1=".$a;
print $b;

to get "1+1=2".

In Python, this results in:
>>> a=2
>>> b='1+1=' + a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  TypeError: cannot concatenate 'str' and 'int' objects

Note that you can assign a='2' even after it once was an integer. That is called dynamic typing.

The combination of dynamic typing and strong typing gives additional constructions like:
b='1+1={0}'.format((a))

which require quite a bit of Python specific reasoning to justify.

7.5. Libraries

Python has a lot of libraries that do different things, and many that do the same thing. There is no way to tell if these libraries will be continued, if they are part of standards or anything like that.

Further more, all sorts of installation methods are used, from slapt-get to easyinstall and pip install and let's not forget compiling from source. The libraries are also all over the place; a standardization in location seems difficult. Add to this the version 2 and version 3 problems and the frameworks, and you'll get XKCD's installation of Python.

8. References

I used a number of resources to learn Python. Some have far more elaborate texts on the subject. there are ofcourse other sources of information, but this is what I used. I have not listsed all the google searches that helped me with specific questions.
http://effbot.org/tkinterbook/

An introducyion to Tkinter
https://www.programiz.com/article/python-self-why

the explanation of self in python classes
http://spyhce.com/blog/understanding-new-and-init

for the difference between __new__ and __init__.
http://www.mysqltutorial.org/python-mysql/

for the mysql interface.
http://pep8online.com/

for checking PEP8 compliancy.