Posts mit dem Label Programmierung werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Programmierung werden angezeigt. Alle Posts anzeigen

Montag, 19. Mai 2025

"A Language A Day" von Andrew Shitow - Buchbesprechung

Der Buchtitel "A Language A Day" kann relativ viel bedeuten, im IT Kontext kann man aber davon ausgehen, dass es im Buch um Programmiersprache(n) geht. Mit dem Untertitel des Buchs wird das dann ziemlich klar: "A brief introduction to 21 programming languages". Auf deutsch: eine kurze Einführung in 21 Programmiersprachen.

In dem vorliegenden, englischsprachigen Buch stellt der Autor Andrew Shitow 21 Programmiersprachen vor. Und, um das schon mal vorwegzunehmen, auf eine sehr gute Art und Weise.

Die 21 Sprachen sind, in alphabetischer Reihenfolge (in der sie auch im Buch vorkommen):
C++, Clojure, Crystal, D, Dart, Elixir, Factor, Go, Hack, Hy, Io, Julia, Kotlin, Lua, Mercury, Nim, OCaml, Raku, Rust, Scala und TypeScript.
Nach welchen Kriterien der Autor die Sprache ausgewählt hat, wird nicht erwähnt - ist für das Buch aber letztendlich auch nicht weiter relevant. Jedenfalls ist die Spannbreite der Sprachen ziemlich weit, es werden eigentlich alle Arten von Sprache behandelt, sowohl was das Paradigma als auch deren Bekanntheitsgrad angeht. Für mich kann ich jedenfalls sagen, dass ich bis zum Lesen des Buchs von vier der 21 Sprachen noch nie etwas gehört hatte und vier weitere Sprachen nur dem Namen nach kannte, ohne jemals irgendwelchen Code gesehen zu haben.

Jeder Sprache ist ein eigenes Kapitel gewidmet, welches ca. ein Dutzend Seiten umfasst. In diesen Kapiteln werden für jede der Programmiersprache, nach einer kurzen Einführung zur Sprache selbst, dieselben Aufgaben programmtechnisch umgesetzt: Ausgabe von "Hallo Welt", Berechnung der Fakultät einer zu übergebenden, natürlichen Zahl, Erstellen eines polymorphen Arrays von Objekten sowie Implementierung eines Sleep Sort Algorithmus, mit dem dann 10 Integerwerte sortiert werden. Im Rahmen der Beispiele wird dann auf relevante Themen wie Variablendefinition (inklusive Typisierung, wo notwendig / zutreffen), Definition von Funktion, objektorientiert Programmierung bzw. das, was die Programmiersprache als vergleichbaren Ansatz anbietet, Fehlerbehandlung sowie nebenläufige Programmierung eingegangen. Den Abschluss jedes Kapitels bilden ein paar Links zur weiterführenden Einführung und Lernressourcen für die jeweilige Programmiersprache.

Was der Autor wirklich gut macht, ist, zwar dem Schema zu folgen, aber trotzdem auf die individuellen Ansätze der jeweiligen Sprache eingeht. Dabei wird auch, wenn immer notwendig bzw. sinnvolle, kurze Exkurse in die jeweiligen Spezifika gemacht, um eine der Aufgaben besser umsetzen zu können. Z.B. gibt es bei Rust ein kurzes Unterkapitel zu Macros oder bei Lua ein kurzes Unterkapitel zu Metatables.

Insgesamt ist das Buch gut geschrieben und liest sich sehr flüssig. Jedes Kapitel zu jeder Sprache ist in sich abgeschlossen und lässt sich aufgrund der Länge gut in 15-20min Lesen, wenn man sich die jeweiligen Codebeispiel mit anschaut. Damit sich das Buch bzw. die Kapitel auch für die kurze Lektüre zwischendurch oder am Abend geeignet.

Lernt man mit dem Buch eine der 21 Programmiersprachen? Nein, sicher nicht, dazu sind die Kapitel viel zu kurz. Hilft das Buch, einen kompakten Einblick in die jeweilige Sprache zu bekommen, der eventuell Lust auf mehr macht? Ja, absolut. Ich denke, dass das auch die Intention des Autors ist, den Leser dazu zu motivieren, sich auch einmal - zumindest kurz - mit anderen Programmiersprachen zu beschäftigen, außerhalb derer, die man vielleicht schon kann. Man muss meiner Meinung nach auch nicht wirklich Ahnung von verschiedenen Programmierparadigmen und -sprachen haben, um das Buch zu lesen. Es reicht, wenn man ein bisschen Programmieren kann - in welcher Sprache auch immer - und natürlich ein generelles Interesse an Programmierung und Programmiersprachen hat.

Jedenfalls ist das Buch eines der interessantesten und kurzweiligsten IT-Büchern, die ich in den letzten Jahren zu Programmiersprachen gelesen habe. Für alle, die sich auch nur ansatzweise für verschieden Programmiersprachen interessieren und auch Lust haben, mal in vielen verschieden Richtungen über den Tellerrand zu blicken, ist das Buch eine absolute Empfehlung. Zumal es sich auch ganz hervorragend kapitelweise lässt und es auch egal ist, in welcher Reihenfolge man die Kapitel liegt und ob eventuell auch mal ein paar Tage mehr zwischen der Lektüre von Kapiteln liegen.

Buchdaten:

  • Titel: A Language A Day - A brief introduction to 21 programming languages
  • Autor: Andrew Shitow
  • Verlage: Deep Text
  • Buchart: Taschenbuch
  • ISBN: 979-8344731834
  • Erscheinungsdatum: 28.10.2024 (1. Auflage)
  • Preis: ca. 16 Euro (bei Amazon)

 

Mittwoch, 8. Juni 2022

Python und Geschwindigkeit auf verschiedenen Intel und AMD CPUs - und einem Apple Bionic Chip

Vor ein paar Jahren gab es hier im Blog einen Post, der die Geschwindigkeit eines Pythonprogramms zum Number-Crunching (in Form eines Primzahlentests) beschriebt (Link zum Artikel).

Ich hatte jetzt kürzliche die Gelegenheit, die Ausführungsgeschwindigkeit auf vier verschiedenen Mobil CPUs verschiedener Hersteller und Generation zu vergleichen, nämlich:

Die Prozessoren sind also eine zum Zeitpunkt des Schreibens des Artikels eine ca. 7 Jahre alte CPU (der Core i5-5200U, erschienen 2015), ein Core i7 Prozessor der 10. Generation (erschienen zweite Jahreshälfte 2019), ein Core i7 Prozessor der 11. Generation (erschienen 2. Jahreshälfte 2021)  sowie die ebenfalls 2021 erschienene Ryzen 7 5700U CPU.

Außerdem habe ich noch einen Test auf einem iPad Air gemacht. Mehr dazu am Ende des Artikels.

Verglichen habe ich nur die Ausführungsgeschwindigkeit des Python-Programms ohne Optimierungen / Beschleunigungen, also ohne PyPy, Cython, JIT Compiler etc.

Als Betriebssystem diente für den Core i5-5200U und den AMD Ryzen 7 5700U Ubuntu 22.04 mit Python 3.10.4 aus den offiziellen Ubuntu Quelle und für die beiden Intel Core i7 Windows 11, ebenfalls mit Python 3.10.4 aus dem Windows App Store, also die Python-Version, die direkt von der PSF bereit gestellt wird.
Das ganze wurde immer bei ruhendem Desktop gemessen, d.h. außer dem Terminal, in dem das Python-Programm läuft, läuft kein weiteres Programm. Es wurde jedes Skript 3x ausgeführt, die weiter unten auf geführten Zeitangabe sind der Mittelwert der Ausführungszeiten.

Als erste habe ich die Ausführungsgeschwindigkeit mit nur einem Prozess gemessen, das Programm sieht wie folgt aus:

import math
from time import time

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419,
    777777722155555333,
    777777722155555335,
    9999999900000001,
    2327074306453592351,
    2327074306453592353]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    for number in PRIMES:
        prime = is_prime(number)
        print('{} is prime: {}'.format(number, prime))

if __name__ == '__main__':
    start = time()
    main()
    end = time()
    print(f'Time: {end - start}')

Die Ergebnisse sind wie folgt:

  • Core i5-5200U: 204 Sekunden
  • Core i7-10510U: 111 Sekunden
  • Core i7-1165G7: 68 Sekunden
  • Ryzen 7 5700U: 78 Sekunden

Wie zu erwarten war, sind die neueren CPUs deutlich schneller. Interessant ist auch die relative große Unterschied zwischen dem Core i7 10. Generation und 11. Generation, letzterer ist etwas 60% schneller. Hier hat Intel deutlich in Sachen Leistung nachgelegt, zumindest was die Performance von nur einem Kern angeht.
Zweite Beobachtung: im weiter oben verlinkten Blogpost wurde der gleiche Code ebenfalls auf dem gleichen Core i5-5200U ausgeführt, nur damals unter Python 3.6. Die Geschwindigkeit war damals quasi identisch mit der hier gemessenen. Auch wenn Python (bzw. genau genommen die Referenzimplementierung CPython) einige Verbesserungen in Sachen Geschwindigkeit bekommen hat, spielen diese zumindest für dieses Szenario keine Rolle, der Code ist auf der gleichen CPU bzw. dem gleichen Rechner wie damals quasi gleich schnell.

Im zweiten Test wurde das Programm modifiziert, so dass mit Hilfe des concurent.futures Moduls mehrere Prozesses zum Rechnen gestartet. Da die Core i5 5200U CPU und auch die neueren Core i7 CPUs "nur" vier Kerne haben, wurde als Prozessanzahl vier gewählt, damit im Idealfall jeder Prozess auf einem Kern läuft.

Der Code sieht wie folgt aus:

import math
from time import time
import concurrent.futures

WORKERS = 4

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419,
    777777722155555333,
    777777722155555335,
    9999999900000001,
    2327074306453592351,
    2327074306453592353]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor(max_workers=WORKERS) as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))


if __name__ == '__main__':
    print(f'using {WORKERS} workers for calculation')
    start = time()
    main()
    end = time()
    print(f'Time: {end - start}')

Die Ergebnisse für den Multiprozessansatz sind:

  • Core i5-5200U: 111 Sekunden
  • Core i7-10510U: 89 Sekunden
  • Core i7-1165G7: 50 Sekunden
  • Ryzen 7 5700U: 51 Sekunden

Auch hier ist der Leistungsunterschied zwischen dem Core i7 10. und 11. Generation erheblich, der neuere ist ca. 80% schneller. Der Core i7-1168G7 und der Ryzen 7 5700U sind quasi gleich schnell.

Ein zusätzlicher Test mit dem Ryzen 7 und acht Workern brachte keine weitere Beschleunigung, das Ergebnis war quasi identisch mit dem mit vier Workern. Dies liegt im gegebenen Test aber ziemlich sicher daran, dass die ersten sechs Prüfungen relativ schnell abgeschlossen sind, d.h. man zieht für die restlichen fünf Zahlen keinen echten Nutzen aus mehr Kernen, weil nicht mehr so viele Prozesse laufen.

Festzuhalten bleibt aber auch: natürlich sind neuere Prozessoren schneller, aber bringen (bei weitem) nicht so viel wie der Einsatz von schnelleren Python-Implementierungen und JIT Compilern.
Alleine der simple Einsatz von PyPy statt CPython auf dem "alten" Core i5 Prozessor führt den Code doppelt so schnell aus wie die Core i7 CPU der 11. Generation oder der Ryzen 7 5700U.

Zusätzlich zu den diversen Intel und der AMD CPU habe ich den nicht-parallelen Code auf einem iPad Air 3. Generation, iOS 15.5 und Python 3.6.1 aus der Pythonista ausgeführt. Dieses iPad hat eine A12 Bionic CPU. Der parallelisierte Code funktioniert ja leider nicht unter iOS, weil, zumindest zum Zeitpunkt des Schreibens des Artikels, ein Prozess keine weiteren Prozesse starten darf.

Der A12 Bionic braucht für den obigen Code ca. 104 Sekunden - und ist damit ca. 10% schneller der getestet Intel Core i7 der 10. Generation. Schon ganz ordentlich für einen Prozessor, der ca. ein Jahr vor dem Intel Prozessor erschienen ist, wobei allerdings auch der Takt der leistungsstärkeren Kerne des A13 mit 2,,49 GHz (deutlich) höher ist als die 1,80 GHz Core i7-10510U.

Sonntag, 17. Dezember 2017

Tkinter und Threads

Der Raspberry Pi ist ein recht populärer Minirechner, besonders für Hobbybastler und Leute, die gerne mit elektrischen Schaltung, welche vom Rechner gesteuert werden, herumspielen.
In Supportforen taucht öfters die Frage auf, wie man Messwerte von Sensoren am besten in graphischen Oberfläche (GUI) visualisiert werden können. Da Tkinter bei Python standardmäßig an Bord ist, wird dieses gerne eingesetzt (auch, wenn Qt oder GTK+ leistungsfähiger sind).

Das grundsätzliche Problem: Das Auslesen von Sensoren kann, je nach Sensor, mehr oder minder lang dauern. Liest man den Sensor über eine Funktion in der GUI aus, kann es sein, dass das Warten auf den Wert des Sensor die GUI bzw. deren Mainloop blockiert. Dies kann man recht einfach dadurch um gehen, dass das Auslesen der Sensoren in einen eigenen Thread auszulesen und die Werte über eine Queue an die GUI zu senden.

Der entsprechende Code kann z.B. wie folgt aussehen:

import tkinter as tk
from queue import Queue
from threading import Thread, Event
from time import sleep
from random import random

def generate_data(my_queue, event):
    while not event.is_set():
        data ={}
        data['value_sensor_1'] = random()
        data['value_sensor_2'] = random()
        my_queue.put(data)        
        sleep(0.7)
      

class Application(tk.Frame):
    def __init__(self, my_queue, thread_kill_event, master=None):
        super().__init__(master)
        self.my_queue = my_queue        
        self.thread_kill_event = thread_kill_event
        self.pack()
        self.create_widgets()
        self.update_labels()

    def create_widgets(self):
        self.label_sensor_1 = tk.Label(self, bg='purple', height=5, width=10, 
                                       text='Sensor 1')
        self.label_sensor_1.pack(side='left')
        self.label_sensor_2 = tk.Label(self, bg='yellow', height=5, width=10,
                                       text='Sensor 2')
        self.label_sensor_2.pack(side='right')
        self.quit = tk.Button(self, text='QUIT', fg='red',
                              command=self.exit_cleanup)
        self.quit.pack(side='bottom')

    def update_labels(self):
        self.master.after(500, self.update_labels)
        if not self.my_queue.empty():
            data = self.my_queue.get()
            if data['value_sensor_1'] < 0.5:
                self.label_sensor_1['bg'] = 'purple'
            else:
                self.label_sensor_1['bg'] = 'pink'
            if data['value_sensor_2'] < 0.5:
                self.label_sensor_2['bg'] = 'yellow'
            else:
                self.label_sensor_2['bg'] = 'blue'

    def exit_cleanup(self):
        self.thread_kill_event.set()
        self.master.destroy()

def main():
    my_queue = Queue()
    my_event = Event()
    my_thread = Thread(target=generate_data, args=(my_queue, my_event, ))
    my_thread.start()
    root = tk.Tk()
    app = Application(my_queue, my_event, master=root)
    app.master.title('TKinter Thread Demo')
    app.mainloop()

if __name__ == '__main__':
    main()

Von Prinzip her ist das eigentlich recht einfach:
Die Funktion generate_data , welche in einem eigenen Thread läuft, produziert alle 0,7 Sekunden einen zufälligen Wert zwischen 0 und 1 für zwei fiktive Sensoren, schreibt die Werte in ein Dictionary, welches dann in die Queue "geschoben" wird. Die 0,7 Sekunden Wartezeit "simulieren" dabei dir Trägheit des Sensors.
Die Klasse Application ist die eigentliche Tkinter-Anwendung. Diese besteht nur aus zwei Labeln und einem Quit-Button.
Die Methode update_labels ist die, welche die Werte aus der Queue holt und in Abhängigkeit vom Wert data['value_sensor_1'] bzw. data['value_sensor_2'] die Farbe des jeweiligen Labels ändert. Wichtig ist die Zeile

self.master.after(500, self.update_labels)

mit der festgelegt wird, dass die Methode alle 500 Millisekunden aufgerufen wird, so dass die Labels periodisch aktualisiert werden.
In der Funktion exit_cleanup wird dann noch der Thread sauber beendet, indem ein Event an die Funktion generate_data gesendet wird, worauf hin diese sich beendet, dann wird noch das Tkinter Fenster gekillt.

Wie zu sehen ist, ist das Entkoppeln von Messwerterfassungen und Messwertvisualisierung nicht weiter schwierig. Hat man mehrere verschiedene Sensoren, lässt sich das obige Beispiel auch einfach auf mehrere Threads erweitern.

Sonntag, 24. September 2017

Learning Concurrency in Python - Buchrezension

Der Name des englischsprachigen Buchs ist Programm: "Learning Concurrency in Python" hat das Ziel, eine Einführung in die nebenläufige Programmierung mit Python 3 zu geben.

Das ganze erfolgt recht umfassend auf rund 340 Seiten. Das Buch beginnt mit einem knappen Überblick über die verschiedenen Möglichkeiten, die Python bzw. zusätzliche Python-Module in Sachen nebenläufiger Programmierung bieten. Danach folgt ein allgemeiner (Python-unabhängiger) Überblick, welche Möglichkeiten und Limitierungen die Hardware bezüglich Nebenläufigkeit haben kann.
Ab dem 3. Kapitel wird es dann konkreter: es werden die Themen Threads in Python, Synchronisation, Ressourcenteilung und Kommunikation von Threads und Multiprocessing behandelt. Threads und Multiprocessing werden dabei über die jeweiligen Module threading und multiprocessing erläutert, als auch (etwas kürzer) über das Modul concurrent.futures.
Das folgenden Kapitel heißt "Event-Driven Programming" und behandelt primär das asyncio-Modul, am Ende des Kapitels gibt es aber auch (relativ kurze) Abschnitte zu den Modulen Twisted und Gevent.
Die folgenden beiden Kapitel behandeln "Reactive Programming" mittels der Module RxPY und PyFunctional sowie die Nutzung der GPU über die Module PyCUDA, Numba, Theano und PyOpenCL.
Zum Abschluss des Buchs gibt es nochmal auf rund zehn Seiten eine Zusammenfassung inklusive der Erläuterung der Vor- und Nachteile der verschiedene Wege zur nebenläufigen Programmierung mit Python.

Es werden also so ziemlich alle gängigen Bereich in Sachen Python und nebenläufiger Programmierung abgedeckt. Gut ist, dass das Buch sehr viel Bespiele enthält, welche der Autor auch alle erläutert, so dass eigentlich kaum Fragen offen bleiben. Persönlich fand ich das Kapitel zu asyncio sehr gut, da es zumindest mir (enorm) geholfen hat, dass Thema (endlich) besser zu verstehen.

Trotz der sehr guten und umfassenden Erklärungen hat das Buch aber einen ziemlich großen Mangel: in vielen Listings stimmen die Einrückungen nicht. Was so viel heißt wie, dass dort, wo syntaktisch Einrückungen sein müssen, keine sind (womit der Code in der abgedruckten Form nicht lauffähig wäre). Die Fehler sind zwar leicht zu erkennen, da immer "nur" eine Ebene fehlt. Aber für ein Fachbuch zu Python, wo Einrückungen essentiell wichtig sind, ist dieser Fehler schwer zu entschuldigen.

Nichts desto trotz ist das Buch durchaus empfehlenswert, wenn man einen umfassenden Überblick, Einblick und Einstieg ins Thema "concurrent programming" mit Python sucht. Um das Buch zu verstehen und effektiv nutzen zu können, sollte man fortgeschrittene Kenntnisse haben oder zumindest ein fortgeschrittener, ambitionierter Einsteiger sein (vorher braucht man wahrscheinlich so wie keine nebenläufigen Programme...).
Das Buch liest sich recht flüssige und sollte auch mit "normalen" Englischkenntnissen gut zu verstehen sein.
Wer sich für das Thema des Buchs interessiert, der sollte einen Blick hinein werfen.

Buchinfos:

Titel: Learning Concurrency in Python
Autor: Elliot Forbes
Verlag: Packt Publishing
ISBN: 9781787285378
Preis: 42,99 Euro (gedruckte Ausgabe + E-Book), 29,15 Euro (nur E-Book)
Erstauflage: August 2017

Montag, 3. August 2015

Python: Ausrichten von Paragraphen in Platypus / ReportLab

Paragraphen von Reportlabs Playtypus sind dazu da, "normalen" Text aufzunehmen und dann im PDF-Dokument darzustellen. Und wie in jeder anderen Textdarstellung auch hat der Text in diesen Paragraphen natürlich auch einen Ausrichtung. Diese ist per Voreinstellung linksbündig.

Nun gibt es aber auch genug Anwendungsfälle, wo man den Text nicht linksbündig ausrichten möchte. So bietet es sich bei längern Textpassagen an, den Text als Blocksatz zu formatieren, damit das rechte Textende nicht "flattert".

Das ist natürlich auch mit ReportLab / Platypus auch ohne Probleme möglich. Es werden alle vier gängigen Textformatierungen unterstützt: linksbündig, rechtsbündig, zentriert und Blocksatz.

Wie das geht, ist im folgenden Beispiel gezeigt. Für rechtsbündig, zentriert und Blocksatz wird einfach das bestehenden Stylesheet um neue Styles erweitert, die die entsprechenden Ausrichtung haben:

from reportlab.platypus import Paragraph, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
from reportlab.lib.pagesizes import A4

text = '''Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam
nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam 
voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita 
kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'''

text_short = 'Lorem ipsum dolor sit amet'

styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name='RightAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_RIGHT))
styles.add(ParagraphStyle(name='CenterAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_CENTER))
styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_JUSTIFY))
doc = SimpleDocTemplate('align_test.pdf', pagesize=A4)
story = []
story.append(Paragraph(text_short, styles['Normal']))
story.append(Paragraph(text_short, styles['RightAlign']))
story.append(Paragraph(text_short, styles['CenterAlign']))
story.append(Paragraph(text, styles['JustifyAlign']))
doc.build(story)

Führt man den obigen Code aus, dann enthält dieser vier Paragraphen: der erste ist linksbündig ausgerichtet, der zweite rechtsbündig, der dritte zentriert und der vierte als Blocksatz.

Intern verwendet ReportLab für die Ausrichtung Integer-Werte, die importierten Konstanten TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY stellen diese einfach nur dar, wie man leicht im interaktiven Python-Interpreter sehen kann:

>>> from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT, TA_JUSTIFY
>>> TA_CENTER
1
>>> TA_LEFT
0
>>> TA_RIGHT
2
>>> TA_JUSTIFY
4

>>>

Im obigen Codebeispiel könnte man also statt

styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=TA_JUSTIFY))
auch schreiben:

styles.add(ParagraphStyle(name='JustifyAlign',
                          parent=ParagraphStyle('Normal'),
                          alignment=4))

Das Ergebnis ist das gleich und man würde den Import der TA_* Konstanten "sparen".

Samstag, 1. August 2015

WTForms: Daten für SelectField nachträglich einfügen

WTForms gehört in der Python-Welt zu den populäreren Modulen zum Generieren und Validieren von HTML-Formularen für Webanwendungen.

Hier im Blogeintrag geht es aber nicht um WTForms im gesamten - die offizielle Doku ist ja so wie so recht ausführlich - sondern "nur" um das nachträgliche Hinzufügen von Auswahlmöglichkeiten für ein SelectField (=Auswahlfeld). Wobei das im folgenden gezeigt auch für das RadioField gilt.

Vorab noch: Alle folgenden Beispiele sind mit WTForms 2.0.2 unter Python 3.4 getestet. Der Einfachheit wird alles im interaktiven Python-Interpreter auf der Kommandozeile durchgespielt. Das Vorgehen in realen Webanwendungen ist aber identisch.

Eine einfaches Formular mit einem SelectField sieht z.B. so aus:

>>> from wtforms import Form, StringField, SelectField
>>> class TestForm(Form):
...     name = StringField('name')
...     gender = SelectField('gender',

            choices=[('m', 'male'),
                     ('f', 'female')])
>>>

Zuerst werden die benötigten Klassen importiert, dann wird eine eigene Formularklasse Namens TestForm definiert, welche die zwei Felder name und gender hat. gender ist dabei ein  Auswahlfeld.

Die Auswahlmöglichkeiten werden über das choices-Attribute festgelegt. Diesem wird eine Liste von Tupeln zuwiesen, wobei das erste Element der value der Option ist und das zweite Element der angezeigte Text.

Erzeugt man nun eine neue Instanz der Klasse

>>> my_form = TestForm()

kann man sich das HTML-Markup, welche das Formular erzeugt, auch direkt auf der Kommandozeile anzeigen lassen, z.B. für gender:

>>> str(my_form.gender)
'<select id="gender" name="gender"><option value="m">male</option><option value="f">female</option></select>'

>>>

Jetzt gibt es aber natürlich auch genug Anwendungsfälle, wo man in einer Webanwendung ein Auswahlfeld dynamisch befüllen will und somit die Werte bei der Definition der Klasse noch nicht kennt.

Auch das ist natürlich mit WTForms problemlos möglich und zwar indem man das choices-Attribut erst später mit Daten füttert. Dies wird im folgenden Beispiel gezeigt:

>>> class TestForm2(Form):
...     name = StringField('name')
...     fav_color = SelectField('favourite color')
...
>>> form2 = TestForm2()


Wie zu sehen ist, wird für das SelectField kein Wert für choices übergeben. Folglich kann auch kein HTML generiert werden, stattdessen wird ein Fehler geworfen:

>>> str(form2.fav_color)
Traceback (most recent call last):
...
TypeError: 'NoneType' object is not iterable
>>>


Versorgt man das choices-Attribute mit Daten, dann funktioniert alles:

>>> my_choices = [('w', 'white'),
                  ('r', 'red'),
                  ('b', 'blue')]
>>> form2.fav_color.choices = my_choices
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'

>>>

In einer realen Anwendung würde die Liste natürlich nicht hard-coded im Quelltext stehen, sondern z.B. aus einer Datenbankabfrage kommen.

Wie andere Field-Klassen auch kennt SelectField ebenfalls einen Default-Wert, was sich im HTML-Formular dann so darstellt, dass diese Option vorausgewählt ist.

Wollte man z.B. im ersten Beispiel female als Default-Wert haben, dann müsste die Zeile im Code so geändert werden:

gender = SelectField('gender',
            choices=[('m', 'male'),
                     ('f', 'female')],
            default='f')

Will man beim 2. Beispiel  die Farbe weiß als Default-Wert setzen, dann muss man das default-Attribute ebenfalls mit einem Wert versorgen:

>>> form2.fav_color.default='w'
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'
>>> form2.fav_color.default
'w'
>>>


Das alleine reicht aber nicht, wie im obigen Code zu sehen ist, hat sich am generierten HTML aber nichts geändert, obwohl der Default-Wert korrekt gesetzt ist.

Damit das HTML wie gewünscht ist, muss ein weiterer Schritt durchgeführt werden, und zwar der Aufruf der process-Methode der Klasse: :

>>> form2.process()
>>> str(form2.fav_color)
'<select id="fav_color" name="fav_color"><option selected value="w">white</option><option value="r">red</option><option value="b">blue</option></select>'
>>>


So ist auch der Default-Wert korrekt gesetzt.

Dienstag, 17. September 2013

sostore Version 0.4 erschienen

Am 25.8. hatte ich einen Blogpost zu sostore geschrieben. sos ist ein simple Objekt-Datenbank für Python, Details werden im verlinkten Artikel erklärt.

Anfang September ist eine neue Version 0.4. erschienen, welche eine von mir gemachten Verbesserungsvorschlag enthält: randomized ist jetzt ein Klassenattribute und wurde im Gegenzug aus der insert-Funktion entfernt.

Das macht in so fern Sinn, als dass man mit randomize im Rahmen der insert-Funktion nur genau für diesen einen, neuen Datensatz eine zufällige ID generiert hat, bei folgenden Aufrufen von insert ohne randomize die IDs aber sequentiell weiter hochgezählt wurden. In so fern hat man so eher einen Offset bei der ID erzeugt als eine echte zufällige Nummerierung.

In der Default-Einstellung steht das Attribute für randomized in der Klasse auf False. Wer also durchgehend zufällige IDs für seine Instanz der Collection-Klasse von sostore benötigt, der instanziert dies so:

>>> c = sostore.Collection('people', randomized=True)


Das ist auch die einzige grundlegenden Änderung der Version 0.4 von sostore. Ansonsten funktioniert das Modul noch so, wie im oben verlinkten Blogpost beschrieben.

Sonntag, 25. August 2013

sostore - eine simple Objekt-Datenbank für Python

Der Python Cheeseshop ist schon ein echte Fundgrube - zumindest mir geht es so, dass man da was sucht und dann zufällig auf was ganz anderes stößt, was man zwar eigentlich nicht braucht, aber trotzdem interessant ist.

Neulich bin ich so auf sostore gestoßen. Der Name steht für "SQLite Object Store" und das Projekt beschreibt sich als "An absurdly simple object "database" for Python".

Der Name ist vielleicht etwas irreführend weil: sostore speichert nicht wirklich Objekte (im Sinne von Python-Objekten), sondern "nur" Python Dictionaries.  Gespeichert wird dabei in eine SQLite Datenbank. Das Modul besteht nur aus zwei Dateien und hat keine Abhängigkeiten außer Python selber.

Dieser Blogeintrag beschreibt die Nutzung der zur Zeit aktuellste, stabilen Version 0.3.
(Nachtrag 17.9.2013: es gibt inzwischen die Version 0.4. Ein Update zu diesem Blogpost gibt es hier: Link)

Wie andere Python-Module auch lässt sich sostore via pip installieren:

$ pip install sostore

Danach steht das Modul unter dem Namen sostore zur Verfügung:

>>> import sostore

Die einzige Klasse, mit der man als Nutzer interagiert, heißt Collection. Diese enthält alle Daten und stellt alle Funktionen bereit.

Als erstes wird eine neue Collection angelegt:

>>> c = sostore.Collection('people')

Per Voreinstellung wird die im Hintergrund laufende SQLite-Datenbank als "in-memory" angelegt. Wer Daten persistent speichern möchte, der muss explizit das db-Argument angeben:

>>> c2 = sostore.Collection('people2', db='people.db')

Eine dritte Möglichkeit ist, beim Anlegen einer Instanz einer Collection als Argument eine bestehende DB-Connection anzugeben. Details hierzu sind in den Docstrings von sostore zu finden.

Als erstes werden der Collection ein paar Datensätze hinzugefügt:

>>> d = {'name':'Peter', 'surname':'Punk', 'job':'musician'}
>>> c.insert(d)
{'job': 'musician', '_id': 1, 'surname': 'Punk', 'name': 'Peter'}
>>> c.insert({'name':'Peter', 'surname': 'Pan', 'job': 'fairyhero'})
{'job': 'fairyhero', '_id': 2, 'surname': 'Pan', 'name': 'Peter'}
>>> c.insert({'name':'Otto', 'surname': 'Normal', 'job': 'lawyer'})
{'job': 'lawyer', '_id': 3, 'surname': 'Normal', 'name': 'Otto'}
>>> c.insert({'name':'Volker', 'surname': 'Racho', 'job': 'musicien'})
{'job': 'musicien', '_id': 4, 'surname': 'Racho', 'name': 'Volker'}


Wie zu sehen ist, wird für jedes hinzugefügt Objekt das Objekt sowie die automatisch angelegt ID (welches das Objekt in der SQLite-Datenbank als Primärschlüssel hat) zurück gegeben. Wer möchte, kann auch eine zufällige ID erzeugen, indem bei insert zusätzlich das Argument randomize=True angibt.

Das 'Aktualisieren eines Objekts innerhalb der Collection ist natürlich auch möglich. Im folgenden wird der Rechtschreibfehler beim Job des 4. Datensatzes korrigiert:

>>> d = c.get(4)
>>> d['job'] = 'musician'
>>> c.update(d)
{u'job': 'musician', '_id': 4, u'surname': u'Racho', u'name': u'Volker'}


Das Löschen eines Datensatzes erfolgt wie folgt:

>>> c.remove(3)

Die Anzahl der Datensätze in der Collection liefert count

>>> c.count
3


sostore bietet verschiedene Möglichkeiten, die Daten auszulesen. Um alle Datensätze zu sehen, genügt der Befehl:

>>> c.all()
[{u'job': u'musician', '_id': 1, u'surname': u'Punk', u'name': u'Peter'}, {u'job': u'fairyhero', '_id': 2, u'surname': u'Pan', u'name': u'Peter'}, {u'job': u'musician', '_id': 4, u'surname': u'Racho', u'name': u'Volker'}]


Aber natürlich sind die Datensätze auch einzeln selektierbar. Wer die ID kennt, kann sich diese Daten wie folgt anzeigen lassen:

>>> c.get(1)
{u'job': u'musician', '_id': 1, u'surname': u'Punk', u'name': u'Peter'}


Es ist auch möglich, mehr als eine ID anzugeben:

>>> c.get_many([1, 2])
[{u'job': u'musician', '_id': 1, u'surname': u'Punk', u'name': u'Peter'}, {u'job': u'fairyhero', '_id': 2, u'surname': u'Pan', u'name': u'Peter'}]


Optional kann man auch zusätzlich die Felder angeben, die zurück geliefert werden sollen:

>>> c.get_many([1, 2], fields='surname')
[{u'surname': u'Punk', u'name': u'Peter'}, {u'surname': u'Pan', u'name': u'Peter'}]


Das Argument field=... funktioniert übrigens auch beim weiter oben gezeigten Befehl all().

Nun besteht bei "real-life" Applikation natürlich auch öfters die Notwendigkeit, Daten anhand von Werten in den Datensätzen zu finden. Dies funktioniert so:

>>> c.find_one('surname', 'Punk')
{u'job': u'musician', '_id': 1, u'surname': u'Punk', u'name': u'Peter'}


Diese Funktion liefert immer nur den ersten Treffer zurück, wie der folgenden Befehl zeigt:

>>> c.find_one('name', 'Peter')
{u'job': u'musician', '_id': 1, u'surname': u'Punk', u'name': u'Peter'}


Jetzt gibt es aber zwei Datensätze mit dem Feld "name=Peter" in unserer Collection. Um alle Datensätze, die einem Suchkriterium entsprechen, zu finden, dient der Befehl:

>>> c.find_field('name', 'Peter')
[1, 2]


Das Ergebnis ist eine Liste von IDs. find_field kennt als optionales Argument noch compare_function, welches als Wert eine Vergleichsfunktion enthalten kann. Details findet man in den Docstrings.

Zwei weitere Funktionen von Collection sind random_entry() und random_entries(count=X), welche einen bzw. X zufällige gewählte Datensätze zurück liefern.

Das SQLite Object Store ist mit Sicherheit kein komplexes Modul. Aber es kann doch recht praktisch sein, wenn man Python-Dicts bzw. JSON-Objekt Speichern und Durchsuchen möchte. Außerdem bietet sostore die Möglichkeit die Daten persistent zu sichern.

Montag, 21. Januar 2013

Das 1. Softwareprojekt: ein paar Erfahrungen

Anfang Dezember 2012 habe ich mich dazu entschieden, mein erstes Softwareprojekt zu starten, welches ich der Öffentlichkeit zugänglich mache. Zwar habe ich vorher schon ein paar "mittelgroße" Sachen programmiert, das sind aber alles Webapplikation, welche im Intranet der Firma laufen und das Arbeitsleben für meine Kollegen und mich vereinfachen.

Das neue Projekt Namens "pygcstats" ist zwar öffentlich, ist aber nur nützlich für Leute, welche Geocaching betreiben, bei geocaching.com registriert sind und dort einen Premium-Account haben (sonst kommt man nicht an die Daten, die pygcstats auswertet). Gut, das sind weltweit geschätzt ca. immer noch 3 - 3,5 Millionen potentielle Nutzer... Programmiersprache der Wahl ist wie immer bei mir Python.

Aber um das Programm an sich geht es gar nicht, sondern eher, was ich bis jetzt bei dem Projekt generell gelernt habe:
  • Nutze ein Versionskontrollsystem: Zu Beginn habe ich noch ohne programmiert, aber spätestens dann, wenn man veröffentlichen will und ggf. zusätzlich zum Release einen Entwicklungszweig hat, kommt man nicht ohne aus. Und auch wenn moderne VCS nicht wirklich schwer zu verstehen braucht es ein bisschen Einarbeitungszeit. Besonders dann, wenn man zum ersten Mal einen Branch erstellen, nutzen und veröffentlichen will. In meinem Fall bestand das "learning by doing" zum Glück "nur" aus einem falsch ausgeführten Push ins Repositry auf Launchpad. Der ließ sich aber problemlos rückgängig machen.
  • Beschäftige dich mit der Plattform, die den Code hostet: Wie weiter oben bereits erwähnt nutze ich Launchpad als Plattform und Bazaar als VCS. Aus zwei ziemlich platten Gründen: Zum einem, weil ich eh' einen Account bei launchpad.net habe, zum anderen, weil ich die Doku im Wiki bei ubuntuusers.de zu Bazaar ziemlich kompakt und gut verständlich finde. Was auch schön ist: bei Launchpad kann man ziemlich viel über die Weboberfläche konfigurieren. Aber: auch das kostet Zeit, besonders am Anfang, wenn man noch nicht weiß, wo was ist. Inzwischen geht alles ganz flüssig.
  • Doku schreiben kostet Zeit: gute Software mit schlechter Doku (oder keiner Doku) ist keine gute Software. Von daher habe ich von Anfang an Wert auf Doku gelegt. Sowohl in Form von Anleitungen für die Nutzer als auch in Form von Docstrings im Quelltext. Einen großen Teil der Nutzerdoku habe ich erst in Englisch und dann noch in Deutsch geschrieben. Ich habe zwar nicht die Zeit genau gemessen, aber ich würde schätzen, dass mindestens 1/3 der Gesamtarbeitszeit des Projekts bis jetzt für Dokumentation aufgewendet wurde.
  • Halte dich von Anfang an an die PEP8: Die PEP8 ist der allgemein akzeptierte Code Style Guide für Python. Dieser war mir zwar geläufig, aber nicht in allen Details. Na ja, jedenfalls waren die ersten Ausgaben des PEP8 Checkers für meinen Code ziemlich lang, was die Fehler anging. Der größte Teil waren Leerzeichen in Leerzeilen oder am Zeilenende. Nicht tragisch, aber trotzdem kostet es Zeit, diese alle zu eliminieren. Also lieber direkt beim coden darauf achten direkt löschen bzw. erst gar keine "entstehen" lassen.
Rückblickend würde ich zwar nicht sagen, dass ich etwas wirklich falsch gemacht habe, aber man hätte Sachen besser machen können. In erster Linie wäre das, sich vorab mit der Plattform und dem VCS seiner Wahl zu beschäftigen. Dann hat man im laufenden Projekt mehr Zeit, auf die Programmierung und das Testen zu fokusieren.
Und, der Tipp / Hinweis an alle: unterschätzt nicht den Zeitaufwand, den eine gute / ausführliche Doku braucht.

Ach ja: den ersten "öffentlichen" Release der Software gab's übrigens vor ein paar Tagen.

Mittwoch, 18. Januar 2012

To GUI or not to GUI...

Schaut man in den Programmierforen nach, so kommt von Einsteiger öfters die Frage: "Ich möchte mein erstes Programm schreiben, das auch eine GUI haben soll.". Eine oft gehörte Antwort ist dann: "Programmier' erst Mal ohne GUI".

Das mag befremdlichen klingen, da seit vielen Jahren eine grafische Benutzeroberfläche absoluter Standard ist. Warum soll man also nun für das (erste) eigene Programm darauf verzichten? Diese Frage wird im folgenden ein wenig beleuchtet.

Dazu gibt es folgende kleine Aufgabe: Es soll ein Programm geschrieben werden, welches eine ganze Zahl als Eingabe nimmt und das Quadrat dieser Zahl als Ausgabe zurück liefert. Um das Programm einfach zu halten und den Fokus auf "To GUI or not to GUI" zu legen, soll das Programm lediglich prüfen, ob die Eingabe eine ganze Zahl ist. Wenn nicht soll eine Fehlermeldung ausgegeben werden. Weiterhin enthalten die Quelltexte keinerlei Kommentare.

Als Programmiersprache kommt Python zum Einsatz, wobei sich die Beispiele  sicherlich auch recht einfach auf andere Sprachen übertragen lassen.

Zurück zu "Programmier' erst Mal ohne GUI": Selbst Linux-Nutzer, welchen die Kommandozeile tendenziell geläufiger ist als z.B. Windows-Nutzer, erscheint es vielleicht doch ein wenig antiquiert "nur Text" zu Arbeiten. Der Grund für den Rat hierzu ist aber recht simpel: Selbst eine minimale GUI (wie im folgenden zu sehen) verlangt einiges mehr an Codezeilen. Plus, und das ist der wichtigere Punkt, die Einarbeitung in eine entsprechende Bibliothek mit entsprechend umfangreicher Dokumentation kann zeitaufwendig sein. Das heißt, dass der geneigte Programmieranfänger nicht nur die Grundlagen einer für ihn neuen Programmiersprache lernen soll, sondern auch direkt die Nutzung der GUI-Bibliothek - was die Sache definitiv nicht einfacher macht. Außerdem wäre es ja auch Schade, wenn er frustriert aufgibt, weil er (als Anfänger) nicht mit der GUI-Programmierung zurecht kommt.

Bevor wir zu den verschiedenen Versionen des Programms kommen noch eine Anmerkung vorab: Vor den Listings ist immer die Anzahl der Codezeilen angegeben. Die beinhalten nicht den Shebang und die Coding-Info (welche bei allen Listings so wie so gleich ist), weiterhin werden Leerzeilen nicht mit gezählt. Codezeilen, welche Zeilenumbrüche zur Verbesserung der Lesbarkeit enthalten, werden ebenfalls als eine Zeile gezählt.

Alle Programm sind unter Ubuntu 11.10 Oneiric getestet, die verwendete Python-Version bzw. GUI-Bibliotheken und Python-Anbindungen stammen alle aus den offiziellen Paketquellen.

Der erste Kandidat ist ein reines Kommandozeilen-Programm. Dieses erwartet, dass die zu quadrierende Zahl als Argument beim Programmaufruf mit übergeben wird. Heißt das Programm z.B. "quadrat.py", so lautet der Aufruf:

python quadrat.py 12

Hier das Programm für die Kommandozeile, 12 Zeilen:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

if len(sys.argv) < 2:
    print 'Es wurde kein Wert vorgegeben!'
    sys.exit()

eingabe = sys.argv[1]
try:
    zahl = int(eingabe)
except ValueError:
    print 'Der Wert ist keine ganze Zahl!'
    sys.exit()
else:
    print zahl * zahl


Update 21.1.2012:
Das obige Beispiel läuft zu ziemlich unter allen Python-Version, selbst unter sehr alten. Wer Python 2.7  bzw. 3.2 und neuer nutzt, der kann den Code noch kürzer gestalten, und zwar unter Verwendung des argparse-Moduls:

Kommandozeile mit argparse, 5 Zeilen:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse

parser = argparse.ArgumentParser('Programm zum Quadrieren von Integerzahlen.')
parser.add_argument('value', type=int)
args = parser.parse_args()
print args.value * args.value


Danke an Hyperion für den Hinweis hierzu in deutschen Python-Forum!


Nun ist der Vergleich eines reinen Kommandozeilen-Programms vielleicht nicht ganz fair, weil dieses für jede Rechnung erneut aufgerufen werden muss. Die Programme mit grafischer Oberfläche müssen nur ein Mal gestartet werden, dann können beliebig viele Rechnungen durchgeführt werden.

Doch auch auf der Kommandozeile gibt es dafür ein pythonische Lösung, das cmd-Modul. Eine Einführung in dieses Modul ist im Blogeintrag cmd - Python-Modul für text-basierte Programme hier bei mir im Blog zu finden.

Die Umsetzung sieht dann so aus:

cmd-Modul, 14 Zeilen:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cmd

class SimpleCmd(cmd.Cmd):
      
    def do_quadrat(self, line):
        try:
            zahl = int(line)
        except ValueError:
            print 'Der eingegebene Wert ist keine ganze Zahl!'
        else:
            print zahl*zahl

    def do_EOF(self, line):
            print 'Programm beenden...'
            return True

simplecmd=SimpleCmd()
simplecmd.cmdloop()

Kommen wir zu den GUI-Bibliotheken. Betrachtet werden hier die beiden wohl populärsten Frameworks, nämlich Qt4 und GTK+. Erster wird dabei über PySide genutzt, letzter über das PyGObject.
Pyside + Qt4, 27 Zeilen:

#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~

import sys
from PySide import QtGui

class SimpleQt(QtGui.QDialog):
    def __init__(self,parent=None):
        super(SimpelQt, self).__init__(parent)
        self.setWindowTitle('Qt4')
        layout = QtGui.QVBoxLayout()
        self.eingabe = QtGui.QLineEdit()
        layout.addWidget(self.eingabe)
        self.button = QtGui.QPushButton(u'Quadrieren')
        layout.addWidget(self.button)
        self.label = QtGui.QLabel(u'Ergebnis...')
        layout.addWidget(self.label)
        self.setLayout(layout)
        self.button.clicked.connect(self.rechnen)

    def rechnen(self):
        wert = self.eingabe.text()
        try:
            zahl = int(wert)
        except ValueError:
            msgBox = QtGui.QMessageBox()
            msgBox.setText(u'Der eingegebene Wert ist keine ganze Zahl')
            msgBox.exec_()
        else:
            self.label.setText(unicode(zahl*zahl))

app=QtGui.QApplication(sys.argv)
simple_qt=SimpleQt()
simple_qt.show()
sys.exit(app.exec_())

Und hier noch die Version mit grafischer Oberfläche, welche auf GTK+ setzt:

PyGObject + GTK+, 29 Zeilen:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
from gi.repository import Gtk
 
class SimpelGtk(object): 
    def Gtk.Window.__init__(self, title='Simpel Gtk+')
        self.window.set_default_size(200, 100)
        vbox = Gtk.Box(False, 20)
        vbox.orientation = Gtk.Orientation.VERTICAL
        self.window.add(vbox)
        self.eingabe = Gtk.Entry()
        vbox.pack_start(self.eingabe,True,True,0)
        button = Gtk.Button("Quadrieren")
        button.connect("clicked", self.rechnen)
        vbox.pack_start(self.button,True,True,0)    
        self.label = Gtk.Label("Ergebnis...")
        vbox.pack_start(self.label,True,True,0)
        
    def rechnen(self,data=None):
        try:
            wert = int(self.eingabe.get_text())
        except ValueError:
            text = 'Die Eingabe ist keine ganze Zahl!'
            dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR,
            Gtk.ButtonsType.CANCEL, 'Fehler bei der Eingabe')
            dialog.format_secondary_text(
                'Der eingegebene Wert ist keine ganze Zahl!')
            dialog.run()
        else:
            self.label.set_text(str(wert*wert))

    def main(self):
        win = Gtk.Window()
        win.connect("delete-event", Gtk.main_quit)
        win.show_all()
        Gtk.main()
 
if __name__ == "__main__":
    e = SimpelGtk()
    e.main()
    
#http://python-Gtk-3-tutorial.readthedocs.org/en/latest/introduction.html

Wie zu sehen ist, sind die GUI-basierten Varianten deutlich länger, nämlich rund 2,5x länger als die Kommandozeile-Version und circa 2x so lange wie die Version, welche das cmd-Modul nutzt. Das mehr an Codezeilen ist dabei natürlich durch das Hinzufügen der GUI-Elemente bedingt. Weiterhin ist der Quelltext für Anfänger (wahrscheinlich) deutlich schwieriger nach zu vollziehen.

Nur damit keine Missverständnisse entstehen: Dies ist kein Plädoyer gegen Programme mit GUI. Überhaupt nicht. Es soll lediglich gezeigt werden, wodurch der oft gehört und gut gemeinte Rat "Programmier' erst Mal ohne GUI" motiviert ist.

Abgesehen davon: beherrscht man die Grundlagen einer Progammiersprache ist es oft deutlich einfacher, die APIs weiterer Bibliotheken wie z.B. den zur GUI-Erstellung, nachzuvollziehen.