Python-Tutorial


Versionsgeschichte

-   23-Mai-2010:

    Erweiterung um *GUI-Programmierung mit wxPython*

-   03-Mai-2010:

    Erstversion.

Allgemeines

Python ist eine dynamisch typisierte Programmiersprache - der Typ der Variable ergibt sich aus dem Ausdruck auf der rechten Seite des Istgleichzeichens.

Auf der anderen Seite ist Python eine stark typisierte Sprache, d.h. es werden keine impliziten Konversionen vorgenommen wie bsw. bei Perl.

Weitere Informationen und Links finden Sie unter Dynamische Typisierung.

Datentypen

int, float, string, sowie die Container-Typen dict, list und tuple.

Beispiel für list:

print "Languages w/ static typing: "
some_static_type_languages = ["C++", "C"]
for language in my_favorite_programming_languages:
    print language

print "Languages w/ dynamic typing - aka duck typing: "
some_dynamic_type_languages = ["Perl", "Python"]
for language in my_favorite_programming_languages:
    print language

print "Languages w/ mixed typing: "
some_mixed_type_languages = ["Cobra", "Boo"]
for language in my_favorite_programming_languages:
    print language

Diese Beispiele kann man auch kürzer schreiben:

print "Languages w/ static typing: "
for language in ["C++", "C"]:
    print language

print "Languages w/ dynamic typing - aka duck typing: "
for language in ["Perl", "Python"]:
    print language

print "Languages w/ mixed typing: "
for language in ["Cobra", "Boo"]:
    print language

Oder noch kürzer:

for caption, languages in \
    [ ["Languages w/ static typing: ", ["C++", "C"]] \
    , ["Languages w/ dynamic typing - aka duck typing: ", ["Perl", "Python"]] \
    , ["Languages w/ mixed typing: ", ["Cobra", "Boo"] \
    ]:

    print caption
    for language in languages:
        print language

Beispiel für dict:

class P_Controller:
    def __init__( self, KP):
        self._KP = KP

    def name( self): return "P"

class PI_Controller:
    def __init__( self, KP, KI):
        self._P = P( KP)
        self._KI = KI

    def name( self): return "PI"

class PID_Controller:
    def __init__( self, KP, KI, KD):
        self._PI = PI( KP, KI)
        self._KD = KD

    def name( self): return "PID"

controller_classes_by_name = {"P": P_Controller, "PI", PI_Controller, "PID": PID_Controller}

controller_name = raw_input( "Bitte Namen (Abkürzung wie P, PI usw.) des Reglers eingeben, den wir verwenden sollen [P] > ")

if controller_name and controller_name in controller_classes_by_name.keys():
    controller = controller_classes_by_name[controller_name]
else:
    controller = controller_classes["P"]

print "Sie haben einen %s-Regler gewählt. " % controller.name()

Beispiel für tuple:

#   Importe aus Library
#
import sys, time

#   Aktionen definieren, die im Menü angeboten werden
#
def on_exit():
    sys.exit()

def on_run():
    print "Analysis running..."
    time.sleep( 2.5)

#   Menü definieren
#
menuline_tuples_dict = ("x": ("Exit", on_exit), "r": ("Run analysis", on_run)}
                                # Die Keys des dict sind die Zeichen, mit der User seine Wahl trefen
                                #   kann, die Values sind Tuples mit Konstantdaten - *der*
                                #   Anwendungsfall für Tuples.

#   Endloas Menü ausgeben, auf User warten und gewählte Aktion durchführen
#
while True:
    #   Menü ausgeben
    for choice in menuline_tuples_dict:
        text, func = menuline_tuples_dict[choice]
        print "%s...%s" % (choice, text)

        print "-" * max( [len( x[0]) for x in menuline_tuples_dict.values()])
                                        # Wir geben eine Linie aus, die das Menü abschließt, d.h. das
                                        #   ganze Menü sieht dann so aus:
                                        #
                                        #       r...Run analysis
                                        #       x...Exit
                                        #       ----------------
                                        #
                                        #   Die Länge der Linie ist als Länge des längsten Textes berechnet
                                        #   worden.
    #   Auf User warten
    #
    choice = raw_input( "> ")
                                    # Wir warten auf die Wahl, die der User trifft, wobei wir das
                                    #   durch ein "> " anzeigen Die gesamte Bildschirmausgabe sieht
                                    #   also so aus:
                                    #
                                    #       r...Run analysis
                                    #       x...Exit
                                    #       ----------------
                                    #       >

    #   Aktion ausführen, die zu dieser Wahl gehört
    #
    if choice in menuline_tuples_dict.keys():
        func = menuline_tuples_dict[choice]
        func()
                                        # War die Wahl "x", dann wird "on_exit" aufgerufen
                                        #   und das Programm verlassen. Es bedarf also keiner
                                        #   Abbruchbedingun für die for-Loop.

Programmflusssteuerung

Anmerkung: In den folgenden Beispielen stehen drei Doppelpunkte untereinander für ausgelassenen Code.

if-then-else

:
:
:
if current_value < 123456789012345678901234567890:
    print "No need to panic, yet. "

else:
    print "Well, I guess we should panic *now*: Panic! Panic! Panic!"
:
:
:

Man sieht hier zweierlei:

  • Whitespace matters, d.h. die Einrückung bestimmt, was zum jeweiligen Block gehört.
  • Ganzzahlen können bliebig groß sein, es gibt keinen Overflow.

Wenn wir also ein File ifthenelse.py anlegen und den Text

#!/usr/bin/env python

current_value = 12345567890

if current_value < 123456789012345678901234567890:
    print "No need to panic, yet. "

else:
    print "Well, I guess we should panic *now*: Panic! Panic! Panic!"

eintippen und es dann per

python ifthenelse.py

ausführen, erhalten wir folgende Ausgabe am Bildschirm:

No need to panic, yet.

for

for name in ("Shrek", "Fiona", "Donkey"):
    print name

for i in range( 42):
    print "%d != 42" % i

for i, name in enumerate( ("Shrek", "Fiona", "Donkey")):
    print "Name %d is %s" % (i, name)

while

while True:
    print "Looping endlessly. "

file_finder = FileFinder( "~/", "*.tex")
path_name = file_finder.path_name_first()
while path_name:
    print "File %s seems to exist. " % path_name

    path_name = file_finder.path_name_next()

Programmstart

Programme startet man per python my_program.py auf der Konsole.

Möchte man aus einer graphischen Oberfläche heraus starten, dann muss das File ausführbar sein und als erste Zeile #!/usr/bin/env python enthalten.

Programmorganisation

Größere Programme organisiert man natürlich in mehreren Files, die dann aufeinander zugreifen. Beispielsweise könnte ein File controllers.py die Regler enthalten, die dann in main.py verwendet werden. Der Code in main.py würde so aussehen können:

import controllers

pid = controllers.PID( KP=1, KI=0.5, KD=0.001)
pid.e( 0.001)
print pid.u()

Im File controllers.py wäre neben anderen Reglerklassen PID definiert. Code, der dort zum Testen auch enthalten sein kann, soll natürlich nicht ausgeführt werden, wenn das Modul importiert wird. Das kann im File controllers.py verhindern wie folgt:

:
:
:
class PID(_Controller):

    def __init__( self, KP, KI, KD):
        self._KP = KP
        self._KI = KI
        self._KD = KD
        return

    :
    :
    :

_Default_Controller = PID( 1, 0.1, 0.01)
                                # Wird auch bei Import ausgeführt

def Test_PID():
    pid = PID( 1, 0.1, 0.001)
    pid.e( 0.0001)
    assert pid.u() == 123

if __name__ == '__main__':
                                # True, wenn echter Aufruf per "python controller.py"
                                #   und nicht Import.
                                #   Tests werden also bei einem Import nicht ausgeführt.
    Test_P()
    Test_PI()
    Test_PID()

GUI Bindings

Selbstverständlich können mit Python nicht nur Konsolenprograme geschrieben werden, sondern auch solche mit einem GUI. Dabei schreibt Python aber kein bestimmtes GUI vor, es existieren vielmehr etliche GUI-Bindings, für die man sich je nach Anwendung oder Vorlieben entscheiden kann.

  • wxPython

    wxPython ist so aufgebaut, wie MFC(TM) es hätte sein sollen. Wie Python ist es plattformunabhängig, bietet aber “native look an feel”.

  • Tkinter

    Tkinter ist der Python-Wrapper zu Tk, dem GUI zu Tcl (daher Tcl/Tk) von J. Ousterhout. Etwas älter zwar, aber sehr mächtig.

GUI-Programmierung mit wxPython

MainFrame

Das folgende File könnte Ihnen als Vorlage für das Hauptprogramm Ihrer wx-Applikation dienen, die Sie dann natürlich nach Ihren Erfordernissen anpassen.

Was Sie jedenfalls z. V. stellen sollten (nicht müssen!), sind die zwei Files splash.png und icon.ico. Ersters wird verwendet, um - wie der Name schon andeutet - den Splash-Screen anzuzeigen und letzteres dient dazu, den MainFrame mit einem Icon - dem Icon Ihrer Applikation - zu versehen.

Wie auch immer, wenn Sie den folgenden Code-Block in eine File demo.py kopieren, dann müsste das bereits funktionieren - und zwar egal ob Sie auf Windows oder Linux sind.

# -*- coding: utf8 -*-

import logging; from logging import handlers

import os
import string
import sys
if hasattr( sys, "setdefaultencoding"):
    sys.setdefaultencoding( "iso8859_15")

import threading
import traceback
import time
import wx
import wx.lib.newevent

_PADDING = 5

_APP_NAME = u"Your App's Name Goes Here"
_APP_VERSION_NUMBERS = (0, 0, 0)
_APP_VERSION = u"%d.%d.%d" % _APP_VERSION_NUMBERS
_APP_CAPTION = u"%s Ver. %s" % (_APP_NAME, _APP_VERSION)

def Excepthook( typ, val, tbk):
    tbk_formatted = ""
    for file, line, module, info in traceback.extract_tb( tbk):
        tbk_formatted += "\tFile:\t%s\n\tLine:\t%d\n\tModule:\t%s\n\tInfo:\t%s\n\n" % (file, line, module, info)

    if tbk_formatted:
        tbk_formatted = tbk_formatted[:-1]

    additional_infos = "\tCurrent working directory = %s\n\tsys.argv = %s" % (os.getcwd(), sys.argv)
    logging.critical( "An uncaught exception has occurred:\ntype = '%s'\nvalue = '%s'\ntraceback:\n%s\nadditional infos:\n%s" \
                      % (typ, val, tbk_formatted, additional_infos)
                    )
    sys.__excepthook__( typ, val, tbk)
    return
#sys.excepthook = Excepthook
# ### Not convenient during development. Activated in releases only.

def _InitLogging_():
    logger = logging.getLogger()
    hdlr = handlers.RotatingFileHandler("./%s.log" % _APP_NAME, "a", 10000000, 10)
    formatter = logging.Formatter("%(asctime)s %(process)d %(thread)d %(levelname)10s %(message)s")
    hdlr.setFormatter(formatter)
    logger.addHandler(hdlr) 
    #logger.setLevel(logging.CRITICAL)
    #logger.setLevel(logging.ERROR)
    #logger.setLevel(logging.WARNING)
    #logger.setLevel(logging.INFO)
    logger.setLevel(logging.DEBUG)
    return
_Logger = logging.getLogger()

def _OPJ_( path):
    """Convert paths to the platform-specific separator.

    .. todo:: Is this still needed?!
    """
    s = apply( os.path.join, tuple( path.split('/')))

# HACK: on Linux, a leading / gets lost...
#
    if path.startswith('/'):
        s = '/' + s

    return s

class MainFrame(wx.Frame):

    def __init__( self, parent, id, caption):

        this_name = u"MainFrame.__init__"
                                        # The name of this method, just in case 
                                        #   we needed it for logging.
    # Init the base class
    #
        wx.Frame.__init__( self, parent, id, caption, wx.DefaultPosition
                           , size=(700, 500)
                           #, style=wx.MAXIMIZE
                          )

    # Build this MainFrame
    #
        self._build_()
                                        # Build the MainFrame
        self.Maximize()
                                        # Let the MainFrame fill the whole screen
        self.Layout()
                                        # 2DO: Is this still needed?

    # Activate all events we will need
    #
        wx.EVT_IDLE( self, self._on_idle_)
        #  ^         ^     ^
        #  |         |     |
        #  |         |     Event target's event handler
        #  |         Event target
        #  Event
                                        # Activate IDLE events. These are called, 
                                        #   if the GUI is - well - IDLE. You may
                                        #   then do things you wouldn't want to do 
                                        #   if the GUI is processing.
        wx.EVT_CLOSE( self, self._on_close_window_)
                                        # Activate CLOSE event. You may want to 
                                        #   do some cleanup in case we are 
                                        #   closing down.

    # Activate the IDLE-timer
    #
        self._idle_timer = wx.Timer( self)
        self.Bind( wx.EVT_TIMER, self._on_idle_timer_)
        self._idle_timer.Start( 100)

    # Some attributes
    #
        self._is_closing = False

        return

    def _build_( self):
        '''Builds the MainFrame, i.e. creates all widgets and puts them into sizers.
        '''
    # Put the App's icon in the MainFrame
    #
        if os.path.exists( "icon.ico"):
            icon = wx.EmptyIcon()
            bmp = wx.Image( "icon.ico").ConvertToBitmap()
            icon.CopyFromBitmap( bmp)
            self.SetIcon( icon)

    # Create all the other widgets
    #
        pass

        return

    def _on_close_window_( self, event):

        self._is_closing = True
        busy = wx.BusyInfo( "Just a moment, please...")
        wx.SafeYield( None, True)
        time.sleep( 1.0)

        self.Destroy()

    def _on_idle_( self, event):
        this_name = "MainFrame::_on_idle_"

        event.Skip()
        return

    def _on_idle_timer_( self, event):
        """Ensures that idle events get handled.

        .. note::
            To do it this way is more efficient than a call to event.RequestMore() in each and every idle 
            event handler.

            Every idle event handler should call event.Skip() now.
        """
        if not self._is_closing:
            wx.WakeUpIdle()

        return

    def _on_timer_( self, event):
        if self._is_closing:
            return

        return

class _SplashScreen(wx.SplashScreen):

    def __init__( self, main_frame):
        if os.path.exists( "splash.png"):
            bmp = wx.Image( "splash.png").ConvertToBitmap()
        else:
            bmp = wx.EmptyBitmap( 500, 350)

        bmp = wx.Image( "splash.png").ConvertToBitmap()
        wx.SplashScreen.__init__( self, bmp
                                , wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT
                                , 5000 
                                , None, -1
                                , size=(800,-1) 
                                )
        self._main_frame = main_frame
        self.Bind( wx.EVT_CLOSE, self.OnClose)
        self._fc = wx.FutureCall( 2000, self._showMain_)
        return

    def OnClose(self, evt):

    # Make sure the default handler runs too so this window gets
    # destroyed
    #
        evt.Skip()
        self.Hide()

    # If the timer is still running then go ahead and show the
    # MainFrame now
    #
        if self._fc.IsRunning():
            self._fc.Stop()
            self._showMain_()

        return

    def _showMain_(self):
        self._main_frame.Centre( wx.BOTH)
        self.Show( False)
        self._main_frame.Show( True)
        #if self._fc.IsRunning():
        #    self.Raise()
        # ### Guess, this isn't needed, is it?

        return

class App(wx.App):

    def OnInit(self):
        main_frame = MainFrame( None, -1, _APP_CAPTION)
        splash = _SplashScreen( main_frame)
        splash.Show()
        self.SetTopWindow( main_frame)
        return True

def main():

# First we change the path to where the executable resides
#
    if sys.platform.startswith( "win"):
        try:
            dirname = os.path.split(sys.argv[0])[0]
            os.chdir( dirname)
        except ValueError:
            pass

# Now we start logging
#
    _InitLogging_()
    _Logger.critical( u"******** %s has started. ********" % _APP_NAME)
    _Logger.info( "Program has started with these args: '%s'. " % sys.argv)

# And finally we start the app
#
    redirect_stderr_and_stdout = 0
    app = App( redirect_stderr_and_stdout)
    app.MainLoop()

if __name__ == '__main__':
    main()

Wenn Sie dieses Programm laufen lassen, dann erscheint an Ihrem Bildschirm ein leerer MainFrame.

Um das Programm Ihren Bedürfnissen anzupassen, fangen Sie am besten mit MainFrame an. Beachten Sie alles Andere jetzt einfach noch nicht.

Anmerkung zu wx.Panel

wx.Panel ist der Container für andere Widgets, die Sie gerne angezeigt haben möchten. Wenn Sie einmal selber Widgets bauen, indem Sie Basis-Widgets zu einem größeren Ganzen zusammenstellen, dann wird wx.Panel der Container, die "Grundplatte" für Ihr Composite-Widget sein.

Lassen Sie uns nun gemeinsam weitermachen!

Wir platzieren einen Button auf dem MainFrame

Also, wir wollen jetzt einen Button auf dem MainFrame platzieren. Dazu muss man wissen, dass die Platzierung von Widgets (oder Controls) auf das grundlegende Widget wx.Panel erfolgen sollte. wx.Panel ist der Container, in den man beliebig Widgets platzieren kann, den mit Sizern beliebig "formen" kann usw.

Wir ergänzen also

def _build_( self):
    '''Builds the MainFrame, i.e. creates all widgets and puts them into sizers.
    '''
# Put the App's icon in the MainFrame
#
    if os.path.exists( "icon.ico"):
        icon = wx.EmptyIcon()
        bmp = wx.Image( "icon.ico").ConvertToBitmap()
        icon.CopyFromBitmap( bmp)
        self.SetIcon( icon)

# Create all the other widgets
#
    pass

    return

dort, wo es # Create all the other widgets heißt, um

panel = wx.Panel( self, -1)
b = wx.Button( panel, -1, "Drück mich!")

Die Zeile mit pass, die als Platzhalter diente, löschen wir.

Lassen Sie das Programm mit der neuen Änderung laufenund Sie sehen links ober einen Button, der Drück mich! heißt, aber noch nichts tut, wenn man ihn drückt. Zwei Dinge also, die wir in Ordnung bringen müssen:

1.  Der Button soll "Aua!" ausgeben.
2.  Der Button soll in der Mitte des Frames positioniert sein.

Die Aktion, die hinter dem Button steht

Um (1) zu ändern, fügen Sie zu Ihrem def _build_( self)

b.Bind( wx.EVT_BUTTON, self._on_BUTTON_)

hinzu, sodass es jetzt so aussieht:

def _build_( self):
    '''Builds the MainFrame, i.e. creates all widgets and puts them into sizers.
    '''
# Put the App's icon in the MainFrame
#
    if os.path.exists( "icon.ico"):
        icon = wx.EmptyIcon()
        bmp = wx.Image( "icon.ico").ConvertToBitmap()
        icon.CopyFromBitmap( bmp)
        self.SetIcon( icon)

# Create all the other widgets
#
    panel = wx.Panel( self, -1)
    b = wx.Button( panel, -1, "Drück mich!")
    b.Bind( wx.EVT_BUTTON, self._on_BUTTON_)

    return

Den Event-Handler _on_BUTTON_ platzieren Sie einfach hinter Ihrem _build_:

def _on_BUTTON_( self, wxE):
    """:param wxE: wx-Event, sent by a button.
    """

    wx.MessageBox( "Aua!", "Message from BUTTON...", wx.OK|wx.ICON_INFORMATION)
    return

Alles zusammen sieht also so aus:

def _build_( self):
    '''Builds the MainFrame, i.e. creates all widgets and puts them into sizers.
    '''
# Put the App's icon in the MainFrame
#
    if os.path.exists( "icon.ico"):
        icon = wx.EmptyIcon()
        bmp = wx.Image( "icon.ico").ConvertToBitmap()
        icon.CopyFromBitmap( bmp)
        self.SetIcon( icon)

# Create all the other widgets
#
    panel = wx.Panel( self, -1)
    b = wx.Button( panel, -1, "Drück mich!")
    b.Bind( wx.EVT_BUTTON, self._on_BUTTON_)

    return

def _on_BUTTON_( self, wxE):
    """:param wxE: wx-Event, sent by a button.
    """
    wx.MessageBox( "Aua!", "Message from BUTTON...", wx.OK|wx.ICON_INFORMATION)
    return

Das Layout

Nun kümmern wir uns um (2), das Layout. Wenn wir unser _build_ um die Zeilen

sizer = wx.BoxSizer( wx.VERTICAL)

sizer.Add( b, 0, wx.ALIGN_CENTER, 0)

panel.SetSizerAndFit( sizer)

ergänzen, _build_ also so

def _build_( self):
    '''Builds the MainFrame, i.e. creates all widgets and puts them into sizers.
    '''
# Put the App's icon in the MainFrame
#
    if os.path.exists( "icon.ico"):
        icon = wx.EmptyIcon()
        bmp = wx.Image( "icon.ico").ConvertToBitmap()
        icon.CopyFromBitmap( bmp)
        self.SetIcon( icon)

# Create all the other widgets
#
    panel = wx.Panel( self, -1)
                                    # The panel, that will contain all our
                                    #   widgets.
    sizer = wx.BoxSizer( wx.VERTICAL)
                                    # The sizer, that will achieve a decent
                                    #   layout.
    b = wx.Button( panel, -1, "Drück mich!", size=(200, 100))
    b.Bind( wx.EVT_BUTTON, self._on_BUTTON_)
    sizer.Add( b, 0, wx.ALIGN_CENTER, 0)
                                    # Add the button to the sizer
    panel.SetSizerAndFit( sizer)
                                    # Tell the panel that there's a sizer.
    return

aussieht, dann wird der Button in der Mitte des MainFrame angezeigt. Damit der Button nicht so klein aussieht, haben wir ihm noch eine size mitgegeben.