OS

Python / PyS60

1 Andreas Jakl, 2009 v1.2 – 01 March 2009 PyS60 – Examples

● ShakerRacer

www.youtube.com/watch?v=EMjAYdF13cU

2 Andreas Jakl, 2009 PyS60 – Examples

● PyWuzzler (Benjamin Gmeiner, Yen-Chia Lin)

3 Andreas Jakl, 2009 Controlling Super Mario PyS60 – Examples by movements (jumping, walking, ...)

● NiiMe

Playing drums

Controlling flight / racing games with tilting

http://www.niime.com/

4 Andreas Jakl, 2009 PyS60 – Examples Time-lapse Photography http://blog.foozia.com/blog/2007/jan/21/python-s60-time-lapse-photography-using--n80/

aspyplayer Last.fm-Player for PyS60 http://code.google.com/p/aspyplayer/

pyPoziomica Use your phone as a level tool http://www.symbian-freak.com/news/007/12/pypoziomica_freeware_level_tool.htm PyED Edit Python source code on the phone http://sourceforge.net/projects/pyed/

5 Andreas Jakl, 2009 What is it all about? Python

6 Andreas Jakl, 2009 Python

● Older than you might think: 1989 – 1991 by Guido van Rossum (National Research Institute for Mathematics and Computer Science, Netherlands) Named after Monty Python’s Flying Circus

● Today: Microcontrollers Mobile Phones Web servers

7 Andreas Jakl, 2009 Scalable

● Modular architecture

● Easy code reuse

● Huge Python standard library

● Extension with own C/C++ modules

Shell scripts

Huge projects

8 Andreas Jakl, 2009 Mature

Helpful Object Interpreted error oriented, Very high & byte- messages, memory level compiled exception manager handling

9 Andreas Jakl, 2009 Fast

● Rapid Application Prototyping

● Easy to learn – can you read this code?

import inbox, audio

box = inbox.Inbox() msg_id = box.sms_messages()[0] msg_txt = u"Message: " + box.content(msg_id)

audio.say(msg_txt)

● What would this be like in C++ or Java ME? ...

10 Andreas Jakl, 2009 Symbian OS + Runtimes

Java ME Python .net Basic Perl

Widgets Apache / Silverlight (Web Flash Lite Ruby PHP / MySQL (soon) Runtime)

S60 (C++)

Symbian OS

11 Andreas Jakl, 2009 UI Platforms: S60

● Unified UI platform based on S60 Official UI platform of Former name: Series 60 th Nokia N97 ● support with S60 5 Edition

12 Andreas Jakl, 2009 UI Platforms: S60 www.s60.com

Business High-End Multimedia Mass Market

Nokia N96

Nokia E66 Samsung Omnia HD Nokia 6121 Classic Nokia 5800 XPressMusic

Nokia E71

Nokia N85 SE Idou Nokia 5500 Sport Nokia E90 Samsung INNOV8

13 Andreas Jakl, 2009 Nokia 6210 Navigator PyS60

● Python port to S60

● Web Allows easy access to: Flash Python Managed code Camera Java

Text-to-speech P.I.P.S. Location Ease of development of Ease Symbian Web services Native code C++

Messaging Functionality and performance UI

14 Andreas Jakl, 2009 Setup – Phone PyS60 1.4.x: based on Python 2.3 PyS60 1.9.2+: based on Python 2.5.1, supports new sensor framework ● Install the latest Nokia PC Suite: of S60 3rd Ed., FP2+ http://europe.nokia.com/A4144903

● Download and install PyS60: 1.4.x: http://sourceforge.net/projects/pys60/ 1.9.x+: https://garage.maemo.org/projects/pys60/

● Phone: Phone : PythonForS60_1_x_x_3rdEd.SIS Phone script shell: PythonScriptShell_1_x_x_3rdEd.SIS

15 Andreas Jakl, 2009 Setup – PC

● Extract SDK plug-in to the S60 SDK: PythonForS60_1_x_x_SDK_3rdEd.zip

16 Andreas Jakl, 2009 IDEs

● IDLE – comes with Python SDK C:\Program Files\Python\Lib\idlelib\idle.bat

● PythonWin + Win32 Extensions http://sourceforge.net/projects/pywi n32/

● PyDev Eclipse/Carbide.c++ Plug-in http://pydev.sf.net/

● SPE http://pythonide.stani.be/

17 Andreas Jakl, 2009 Hello World

● Create a file hello.py: hello.py print “Hello World”

● Connect your phone to the PC (“PC Suite” connection mode)

● Transfer the script to E:\Python\ (memory card) using the Nokia PC suite file manager

● Run the script:

18 Andreas Jakl, 2009 Hello World – Emulator

● Copy hello.py to: \winscw\c\python\hello.py

● Start the emulator from: \release\winscw\udeb\epoc. exe

19 Andreas Jakl, 2009 PyDev + Emulator

● Either create the project / workspace directly in the python-dir of the emulator

● Or link the project files to source files in the dir:

20 Andreas Jakl, 2009 Starting with the UI PyS60 – User Interface

21 Andreas Jakl, 2009 Module

● Collection of related functions and data grouped together

● Has to be imported at the beginning: import appuifw

● Addressing a function of the module: appuifw.query(label, name)

● Import multiple modules in a single statement: import appuifw, e32

22 Andreas Jakl, 2009 import appuifw

Query appuifw.query(u"Type a word:", "text", u"Hello") appuifw.query(u"Type a number:", "number", 7) appuifw.query(u"Type a date:", "date") appuifw.query(u"Type a time:", "time") appuifw.query(u"Type a password:", "code") appuifw.query(u"Do you like PyS60?", "query") Syntax: appuifw.query(label, type[, initial value])

number / text float date time code query

23 Andreas Jakl, 2009 import appuifw

Note Dialog appuifw.note(u"Hello") appuifw.note(u"File not found", "error") appuifw.note(u"Upload finished", "conf")

Syntax: appuifw.note(text[, type[, global] ] )

info / default error conf

24 Andreas Jakl, 2009 Variables

● Variables not declared ahead of time

● Implicit typing

● Automated memory management Reference counting, garbage collection

● Variable names can be “recycled”

● del statement allows explicit de-allocation

25 Andreas Jakl, 2009 Variables – Example age = 5 name = u"Andreas" # u in front of the string: unicode name += age # Doesn't work, the type isn’t converted automatically name += str(age) # name == Andreas5 name = age # name now points to the same object as age; name == 5 foo = "xyz" foo “xyz” bar = foo # bar points to the same object as foo bar foo = 123 # a new object is created for foo, bar still points to "xyz“ foo “xyz”

bar 123

26 Andreas Jakl, 2009 Multi-Query Dialog

● Syntax: appuifw.multi_query(label1, label2)

import appuifw

pwd = u"secret"

info = appuifw.multi_query(u"Username:", u"Password:") if info: // returns a tuple with the info login_id, login_pwd = info if login_pwd == pwd: appuifw.note(u"Login successful", "conf") else: appuifw.note(u"Wrong password", "error") else: // returns None – special type appuifw.note(u"Cancelled")

27 Andreas Jakl, 2009 if-statement

● Works like in other languages

● Blocks are defined by indentation – avoids dangling else

● if expression1: expr1_true_suite elif expression2: expr2_true_suite else: none_of_the_above_suite

28 Andreas Jakl, 2009 Lists, Tuples and Dictionaries

● Generic “arrays” for arbitrary number of arbitrary objects

● Ordered and accessed via index offsets

● List – created using [ ] myList = [1, 2, 3, 4] # [1, 2, 3, 4] print myList[0] # 1 # Subsets: sequence[starting_index:ending_index] print myList[1:3] # [2, 3] print myList[2:] # [3, 4] print myList[:3] # [1, 2, 3] myList[1] = 5 # [1, 5, 3, 4] myList[2] = ["bla", (-2.3+4j)] # [1, 5, ["bla", (-2.3+4j)], 4] print myList[2][1] # -2.3+4j print 4 in myList # True 29 Andreas Jakl, 2009 Lists, Tuples and Dictionaries

● Tuple – created using ( ) Immutable – can therefore be used as dictionary keys Also useful when you don’t want a function to be able to change your data

myTuple1 = ('python', 's60', 27) print myTuple1[0] # python myTuple[1] = 'no' # tuples are immutable – exception! myTuple2 = ('symbian', '') myTuple3 = myTuple1 + myTuple2 print myTuple3 # ('python', 's60', 27, 'symbian', 'uiq')

30 Andreas Jakl, 2009 Lists, Tuples and Dictionaries

● Dictionary – created using { } Mapping type – like associative arrays or hashes in Perl Key-value pairs

– Keys: almost any Python type, usually numbers or stings

– Values: arbitrary Python object

myDict = {'planet': 'earth'} myDict['port'] = 80 print myDict # {'planet': 'earth', 'port': 80} print myDict.keys() # ['planet', 'port'] print myDict['planet'] # earth for key in myDict: print "key=%s, value=%s" % (key, myDict[key]) # key=planet, value=earth # key=port, value=80

31 Andreas Jakl, 2009 Popup Menu

● Syntax: appuifw.popup_menu(list[, label ])

import appuifw

items = [u"The Journey", u"RealReplay", u"ShakerRacer"] index = appuifw.popup_menu(items, u"Buy:")

if index == 0: appuifw.note(u"Great choice") elif index == 1: appuifw.note(u"Cool") elif index == 2: appuifw.note(u"I like that") elif index == None: appuifw.note(u"Purchase cancelled")

32 Andreas Jakl, 2009 Selection List Search field appears after pressing a letter key

● Syntax: appuifw.selection_list(choices[, search_field=0])

import appuifw

names = [u"Michael", u"Devon", u"Bonnie", u"April", u"RC3"] index = appuifw.selection_list(names, 1)

if index == 2: print "I love you!" else: print "You're great!"

33 Andreas Jakl, 2009 Multi-Selection List

● Syntax: appuifw.multi_selection_list( choices[, style=„checkbox‟, search_field=0])

import appuifw

names = [u"Michael", u"Devon", u"Bonnie", u"April", u"RC3"] selections = appuifw.multi_selection_list(names, 'checkbox', 1) print selections

Style: checkmark Select multiple items with the pen key Not really important

34 Andreas Jakl, 2009 for loop

● for iter_var in iterable: suite_to_repeat

● With each loop, iter_var set to current element of iterable Similar to foreach in other languages

35 Andreas Jakl, 2009 for Loop – Examples

# Iterating over a string current letter: T for eachLetter in "Text": current letter: e print "current letter:", eachLetter current letter: x current letter: t # Iterating by sequence item Charles nameList = ["Mike", "Sarah", "Charles"] for eachName in sorted(nameList): Mike print eachName Sarah

# Iterating by sequence index Mike for nameIndex in range(len(nameList)): Sarah print nameList[nameIndex] Charles # Iterate over a range value: 0 for eachVal in range(3): print "value: ", eachVal value: 1 value: 2 # Extended syntax: # range(start, end, step = 1) value: 2 for eachVal in range(2, 10, 3): print "value: ", eachVal value: 5 value: 8

36 Andreas Jakl, 2009 while loop

● while expression: suite_to_repeat

● Nice addition: else is executed if loop was not abandoned by break

def showMaxFactor(num): count = num / 2 while count > 1: if num % count == 0: print "Largest factor of %d is %d" % (num, count) break count -= 1 else: print num, "is prime"

for eachNum in range(10, 21): showMaxFactor(eachNum)

37 Andreas Jakl, 2009 Example – System Info SMS import appuifw, messaging, sysinfo

# You could use a dictionary as well, but here this is more straightforward later on infoNames = [u"Profile", u"Battery", u"Signal DBM"] infoCalls = ["sysinfo.active_profile()", "sysinfo.battery()", "sysinfo.signal_dbm()"]

# Let the user choose the information he wants to send choices = appuifw.multi_selection_list(infoNames, "checkbox", 0) infoSms = "" for idx in choices: # Execute the statement(s) stored in the infoCalls-list through the eval-statement, # convert the result to a string and append it to the sms text infoSms += infoNames[idx] + ": " + str(eval(infoCalls[idx])) + "; "

# Query the telephone number smsNum = appuifw.query(u"Number:", "text", u"+15550135") if smsNum: # Send the SMS if the user didn’t cancel messaging.sms_send(smsNum, infoSms) appuifw.note(u"Info sent", "conf")

38 Andreas Jakl, 2009 Procedural and Object Oriented Development Python – Function and Classes

39 Andreas Jakl, 2009 Functions – Basics

● def function_name(arguments): [“function_documentation_string”] function_body_suite

● Supports: def foo(): "foo() -- does't do anything special." Default arguments print "in foo"

Variable length arguments # Execute the function foo() Multiple return values # Print the help text of the function print foo.__doc__ Inner functions # foo() -- does't do anything special. ...

40 Andreas Jakl, 2009 Functions – Default Arguments

● Default arguments: def func(posargs, defarg1=dval1, defarg2=dval2, ...):

def calcTax(amount, rate=0.0275): return amount + (amount * rate)

print calcTax(100) # 102.75

print calcTax(100, 0.05) # 105.0

41 Andreas Jakl, 2009 Functions – Variable Length Arguments

● Variable length arguments (unknown number): Non-keyword variable arguments (tuple): def func([formal_args,] *vargs_tuple): Argument with * will hold all remaining arguments once all formal parameters have been exhausted Keyword variable arguments (dictionary): def func([formal_args,][*vargst,] **vargsd):

def tupleVarArgs(arg1, arg2='defaultB', *theRest): tupleVarArgs('abc') print "formal arg 1: ", arg1 # formal arg 1: abc print "formal arg 2: ", arg2 # formal arg 2: defaultB for eachXtraArg in theRest: print 'another arg: ', eachXtraArg tupleVarArgs('abc', 123, 'xyz', 123.456) # formal arg 1: abc # formal arg 2: 123 # another arg: xyz # another arg: 123.456

42 Andreas Jakl, 2009 Variable Scope global_str = "foo" def foo(): local_str = "bar"

local scope local return global_str + local_str ● Global scope Declared outside a function print foo() Lifespan lasts as long as script is running

● Local scope Live temporarily as long as function they are defined in is active

● Searching for identifiers First local, then global Possible to override global variables by creating a local one

43 Andreas Jakl, 2009 Variable Scope

● Writing to global variables in functions: Creates a new local variable (pushes global variable out of scope) global statement specifically references a named global variable

def foo(): def foo(): localscope bar = 200 global bar print "in foo(), bar is", bar bar = 200

local scope local print "in foo(), bar is", bar bar = 100 print "in __main__, bar is", bar bar = 100 foo() print "in __main__, bar is", bar print "in __main__, bar still is", bar foo() in __main__, bar is 100 print "in __main__, bar is now", bar in foo(), bar is 200 in __main__, bar is 100 in __main__, bar still is 100 in foo(), bar is 200 in __main__, bar is now 200 44 Andreas Jakl, 2009 Classes

● class MyObject(bases): “Documentation text” class_suite

● New style classes should be derived from any other class or from object

● Simplest use: container object for instance attributes Not defined in class definition class MyData(object): pass # code is required syntactically, Only valid for this instance # but no operation is desired mathObj = MyData() mathObj.x = 4 mathObj.y = 5 45 Andreas Jakl, 2009 print mathObj.x * mathObj.y # 20 Classes – Subclasses, Methods

class LibraryEntry(object): ● __init__ def __init__(self, id, title): self.id = id similar to constructor self.title = title def updateId(self, newId): ● self-parameter self.id = newId

Passes reference to class LibraryBook(LibraryEntry): def __init__(self, id, title, author): current instance LibraryEntry.__init__(self, id, title) self.author = author Not needed for static or class methods def updateAuthor(self, newAuthor): self.author = newAuthor Python wants to be libBook = LibraryBook(1, "PyS60", "Andreas Jakl") explicitly clear libBook.updateId(2) libBook.updateAuthor("Nokia")

46 Andreas Jakl, 2009 Classes – Attributes

● Instance attributes are set “on-the-fly” by using them Constructor is the first place to set instance attributes Use default arguments for default instance setup

● Class variables / static data have to be defined class Letter(object): class C(object): def __init__(self, text, author="Andreas Jakl", category=“love"): foo = 100 # Static data self.text = text self.author = author print C.foo # 100 self.category = category C.foo = C.foo + 1 def printLetter(self): print C.foo # 101 print self.text, self.author, self.category

['__class__', '__delattr__', '__dict__', '__doc__', loveLetter = Letter("I love you") '__getattribute__', '__hash__', '__init__', print dir(loveLetter) # Print all methods & attributes of this class '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', 47 Andreas Jakl, 2009 '__str__', '__weakref__', 'author', 'category', 'printLetter', 'text'] How to organize your application PyS60 – Application Structure

48 Andreas Jakl, 2009 Application Structure

Title appuifw.app.title Navigation pane appuifw.app.set_tabs() Body appuifw.app.body

Dialog appuifw. - appuifw.query() - appuifw.note() - ...

Menu Exit appuifw.app. appuifw.app.menu exit_key_handler

49 Andreas Jakl, 2009 UI App – Example

import appuifw, e32

def quit(): print "Exit key pressed" app_lock.signal()

appuifw.app.exit_key_handler = quit appuifw.app.title = u"UI App"

print "App is now running"

app_lock = e32.Ao_lock() app_lock.wait()

print "Application exits"

50 Andreas Jakl, 2009 Callback Function

● No difference to normal functions

● Associating function with event: binding Use function name without () to get the function object Compare to function pointers in C

def quit(): pass # function is empty (instead of {} in C++ / Java)

appuifw.app.exit_key_handler = quit

51 Andreas Jakl, 2009 Wait for the User

● Previously: app. exited after executing all lines

● Event based UI-app: wait for user input

... app_lock = e32.Ao_lock() # Create an instance of an Ao_lock object app_lock.wait() Refer to the Symbian OS course for more information about Waiting for quit() call-back handler Active Objects events app_lock.signal()

52 Andreas Jakl, 2009 Application Body

appuifw.app.screen = “normal" appuifw.app.screen = “large" appuifw.app.screen = "full"

53 Andreas Jakl, 2009 Application Body

● You can assign several objects to the body Canvas: provides drawable screen area + support for handling raw key events Form: complex forms with various input fields Listbox: shows a list of items (single- or double-line-item) Text: free-form text input

54 Andreas Jakl, 2009 Application Menu

import appuifw, e32 ● Defined using tuples: def play(): Text print "Play file" def volume_up(): Call-back function print "Volume up" def volume_down(): – Specify another print "Volume down"

tuple instead to def quit(): print "Exit key pressed" define a submenu app_lock.signal()

appuifw.app.exit_key_handler = quit appuifw.app.title = u"mp3 Player" appuifw.app.menu = [(u"Play", play), (u"Volume", ( (u"Up", volume_up), (u"Down", volume_down) ) )] submenu print "App is now running" app_lock = e32.Ao_lock() app_lock.wait() Example based on [1] 55 Andreas Jakl, 2009 String Manipulation txt = "I like Python" url = " http://www.mopius.com " print txt[2:6] url = url.strip() print txt.find("like") if url.startswith("http://"): if txt.find("love") == -1: print url, "is a valid URL" print "What's wrong with you?" txt.replace("like", "love") webServiceInput = " 1, 2, 3, 4" print webServiceInput.replace(" ", "") print txt.upper() print "Length", len(txt) txt = "one;two;three" print txt.split(";") txt2 = "" http://www.mopius.com is a valid URL if txt2: 1,2,3,4 print "txt2 contains characters" ['one', 'two', 'three'] else: print "txt2 doesn't contain characters" like 2 What's wrong with you? I LIKE PYTHON Length 13 txt2 doesn't contain characters

56 Andreas Jakl, 2009 String Formatting

● String slicing like for lists: [start:end]

● Assemble string based on other variables:

print "Host: %s\tPort: %d" % ("Earth", 80) print "DD.MM.YYYY = %02d.%02d.%d" % (12, 3, 82) list = [3, 2, 1, "go"] # A list has to be converted to a tuple first print "Counting: %d, %d, %d, %s" % tuple(list) Symbol Conversion Host: EarthPort: 80 %s String conversion via str() prior DD.MM.YYYY = 12.03.82 to formatting Counting: 3, 2, 1, go %d Signed decimal integer %f Floating point real number ......

57 Andreas Jakl, 2009 Example – Inbox Search

import inbox, appuifw

# Create an instance of the Inbox object box = inbox.Inbox() # Query search phrase query = appuifw.query(u"Search for:", "text").lower() hits = [] ids = [] # sms_messages() returns message IDs for all messages in the SMS inbox for sms_id in box.sms_messages(): # Retrieve the full message text and convert it to lowercase msg_text = box.content(sms_id).lower() if msg_text.find(query) != -1: # If the text was found, store a preview hits.append(msg_text[:25]) ids.append(sms_id)

# Display all results in a list index = appuifw.selection_list(hits, 1) if index >= 0: # Show the full text of the selected message appuifw.note(box.content(ids[index]))

58 Andreas Jakl, 2009 Event Loop

● e32.Ao_lock waits for events, but stops execution

● Game: App. has to be active all the time But still needs respond to events (keys, ...)

Initialize event call-backs Sleep (+ execute waiting active objects): while :  e32.ao_sleep(interval) Update game state Yield (just execute ready active objects with Redraw screen higher priority) Pause / yield  e32.ao_yield()

59 Andreas Jakl, 2009 The Python way of Exception Handling

60 Andreas Jakl, 2009 Exceptions

● Example: Exception “NameError” raised by the interpreter:

>>> print foo Traceback (most recent call last): File “", line 1, in print foo NameError: name 'foo' is not defined

61 Andreas Jakl, 2009 Exceptions

● try: try_suite # watch for exceptions here except Exception[, reason]: except_suite # exception-handling code

● Different objects derived from Exception

● Exception arguments / reasons: May be passed along Not just a string, but contains more information

62 Andreas Jakl, 2009 Exceptions – Information

try: f = file(u"c:\\python\\test.txt", "w+") print >> f, "Welcome to Python" f.seek(0) print "File contents:", f.read() f.close() except IOError, reason: [Errno 2] No such file or print reason directory: u'c:\\python\\test.txt'

print reason.filename c:\python\test.txt print reason.errno 2 print reason.strerror No such file or directory

63 Andreas Jakl, 2009 Multiple Exceptions

● Catching multiple exceptions: Multiple except-statements Multiple exceptions in one except statement Catch the base class Exception (no good coding style – you might be silently dropping errors)

64 Andreas Jakl, 2009 Code Examples

● Code snippets for: Camera Text to Speech Sound recording / playing Bluetooth Networking http://www.mobilenin.com/pys60/menu.htm

Graphics, UI Additional modules: 3D (Open GL ES) http://cyke64.googlepages.com/ Camera Sensor ...

65 Andreas Jakl, 2009 Bonus – Acceleration Sensor (3rd Ed (FP1))

import appuifw,e32,sensor def get_sensor_data(status): "Callback function for regular accelerometer status" print "x: %d, y: %d, z: %d" % (status['data_1'], status['data_2'], status['data_3']) def exit_key_handler(): # Disconnect from the sensor and exit acc_sensor.disconnect() app_lock.signal() appuifw.app.exit_key_handler = exit_key_handler

# Retrieve the acceleration sensor sensor_type = sensor.sensors()['AccSensor'] # Create an acceleration sensor object acc_sensor = sensor.Sensor(sensor_type['id'],sensor_type['category']) # Connect to the sensor acc_sensor.connect(get_sensor_data)

# Wait for sensor data and the exit event app_lock = e32.Ao_lock() app_lock.wait()

66 Andreas Jakl, 2009 Bonus – Acceleration Sensor (3rd Ed FP2+) from sensor import * import e32, time, appuifw class DemoApp(): def __init__(self): self.accelerometer = AccelerometerXYZAxisData(data_filter=LowPassFilter()) self.accelerometer.set_callback(data_callback=self.my_callback) self.counter = 0

def my_callback(self): # For stream sensor data the callback is hit 35 times per sec (On 5800). # The device cannot handle resource hungry operations like print in the callback function # for such high frequencies. A workaround is to sample the data as demonstrated below. if self.counter % 5 == 0: print "X:%s, Y:%s, Z:%s" % (self.accelerometer.x, self.accelerometer.y, self.accelerometer.z) self.counter = self.counter + 1

def run(self): self.accelerometer.start_listening() def exit_key_handler(): # Disconnect from the sensor and exit global d d.accelerometer.stop_listening() print "Exiting Accelorometer" app_lock.signal() if __name__ == '__main__': appuifw.app.exit_key_handler = exit_key_handler d = DemoApp() d.run() app_lock67 = e32.Ao_lock() Andreas Jakl, 2009 app_lock.wait() Literature – Recommended

Mobile Python Jürgen Scheible, Ville Tuulos Complete overview of Python development for PyS60, many small code samples. Status: Symbian OS 9, PyS60 1.4, 2007 Free code samples: http://www.mobilenin.com/pys60/menu.htm

Core Python Programming (Second Edition) Wesley J. Chun Python for developers who already know other languages. Comprehensive short overview, in later chapters a detailed overview of the individual components. Status: 2007

68 Andreas Jakl, 2009 That’s it! Thanks for your attention

69 Andreas Jakl, 2009