# Die unten folgende Tour wurde durch das Kapitel
# 
#   1. A Tutorial Introduction
# 
# aus dem Buch
# 
#   David M. Beazley
#   Python essential reference
#   4. Auflage, 2009 
# 
# inspiriert. Teilweise sind es Code-Fragmente.

# Python ausführen

# interaktiv

$ python3
Python 3.11.2 (main, Nov 30 2024, 21:22:50) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('Hello World')
# Hello World

>>> 6000 + 4523.50 + 134.12
10657.62
>>> _ + 8192.32
18849.940000000002

>>> from decimal import Decimal
>>> print(Decimal(6000) + Decimal('4523.50') + Decimal('134.12') + Decimal('8192.32'))
18849.94

# nicht interaktiv

# Datei helloworld.py
print('Hello World')

$ python3 helloworld.py
Hello World

#!/usr/bin/env python
print('Hello World')

$ chmod +x helloworld.py
$ ./helloworld.py

# ---------------------

# Variablen und arithmetische Ausdrücke

# Zinseszins

principal = 1000 # Startkapital
rate = 0.05      # Zinssatz
numyears = 5     # Anzahl Jahre

year = 1
while year <= numyears:
    principal = principal * (1 + rate)
    # oder:
    # principal *= (1 + rate)

    # verschiedene Varianten der formatierten Ausgabe
    print('%3d %.2f' % (year, principal))
    print(format(year, '3d') , format(principal, '.2f'))
    print('{0:3d} {1:.2f}'.format(year, principal))
    print(f'{year:3} {principal:.2f}')
    print()
    year += 1

# ---------------------

# Verzweigungen

a = b = 0

if a < b:
    print('Yes')
else:
    print('No')

if a < b:
    pass # nichts tun
else:
    print('No')


product = 'game' ; typ = 'pirate memory' ; age = 5

if product == 'game' and typ == 'pirate memory' \
                     and not (age < 4 or age > 8):
    print("I'll take it")

if product == 'game' and typ == 'pirate memory' and 4 <= age <= 8:
    print("I'll take it")


suffix = '.htm'

if suffix == '.htm' or suffix == '.html':
    content = 'text/html'
elif suffix == '.jpg':
    content = 'image/jpeg'
elif suffix == '.png':
    content = 'image/png'
else:
    raise RuntimeError('Unknown content type')


# ab Python 3.10 existiert match
match suffix:
    case '.htm' | '.html':
        content = 'text/html'
    case '.jpg':
        content = 'image/jpeg'
    case '.png':
        content = 'image/png'
    case _:
        raise RuntimeError('Unknown content type')


s = 'Sie haben 2 Millionen gewonnen'
spam = 'Millionen gew'

if spam in s:
    has_spam = True
else:
    has_spam = False

has_spam = spam in s

# ---------------------

# Datei-Eingabe/-Ausgabe

# alle Zeilen einer Datei lesen und ausgeben
f = open('/etc/hosts')
line = f.readline()
while line:
    print(line, end='')
    line = f.readline()
f.close()


# walrus operator := existiert seit Python 3.8
f = open('/etc/hosts')
while (line := f.readline()):
    print(line, end='')
f.close()


for line in open('/etc/os-release')
    print(line, end='')


f = open('zinsen.txt', 'w')
while year <= numyears:
    principal *= (1 + rate)
    print(f'{year:3} {principal:.2f}', file=f)
    year += 1
f.close()


with open('zinsen.txt', 'w') as f:
    for year in range(numyears):
        principal *= (1 + rate)
        print(f'{year+1:3} {principal:.2f}', file=f)


import sys
sys.stdout.write('Enter your name :')
sys.stdout.flush()
name = sys.stdin.readline()
print(name)

print('Enter your name :', end='')
# wegen input() ist kein explizites flush() nötig
name = input()
print(name)

# ---------------------

# Strings

a = "Hello World"
b = 'Python is groovy'
c = """Computer says 'No'"""

print('''Content-type: text/html

<h1>Hello World</h1>
Click <a href="https://www.python.org">here</a>.
''')


a = 'Hello World'
b = a[4]
c = a[:5]
d = a[6:]
e = a[3:8]
f = a[::-1] # umgekehrte Zeichenreihenfolge

palindrom = 'anna'
palindrom == palindrom[::-1]


g = a + 'This is a test'

x = '37'
y = '42'
z = x + y

print('#' * 5)

z = int(x) + int(y)
z = float(x) + int(y)

x = 37
s = 'The value of x is ' + str(x)
s = 'The value of x is ' + repr(x)
s = 'The value of x is ' + format(x, '4d')
s = 'The value of x is ' + format(x, '.5f')
s = f'The value of x is {x:4d} / {x:.5f}'


s = 'abc'
print(s)       # abc
print(str(s))  # abc
print(repr(s)) # 'abc'

eval(repr(s)) == s # True

# ---------------------

# Listen

names = ['Anton', 'Berta', 'Cäsar', 'Dora']

n = names     # Alias-Name für dieselbe Liste!
n is names    # True
n = names[::] # Kopie der Liste
n is names    # False

a = names[2]
names[0] = 'Emil'

names.append('Friedrich')
names.insert(2, 'Thomas')

b = names[0:2]
c = names[2:]
names[1] = 'Lola'
names[0:2] = ['Hugo', 'Emma', 'Willi']


a = [1, 2, 3] + [4, 5]
a.extend([6, 7, 8])

names = []
names = list()

a = [1, True, None, 7.9, ['Mark', 5, [100, 101]], 100]
a[1]
a[4][0]
a[4][2][1]


# Dateiauswertung

import sys
if len(sys.argv) != 2:
    print('Bitte einen Dateinamen angeben')
    exit(1)

with open(sys.argv[1]) as f:
    lines = f.readlines()

fvalues = [float(line) for line in lines]
print(fvalues)

# Minimum und Maximum
print('Minimum =', min(fvalues))
print('Maximum =', max(fvalues))

# ---------------------

# Tupel

stock = 'GOOG', 100, 409.10
address = ('www.python.org', 80)
firstname = 'fn' ; last_name = 'ln' ; phone = '1234-5678'
person = (firstname, last_name, phone)

a = ()
a = tuple()
b = (item,)
c = item,

names, shares, price = stock
host, port = address
firstname, last_name, phone = person
last_name, phone = person[1:]


filename = 'portfolio.csv'

csv_data = '''
AA,100,10.81
BB,15,22.18
CC,5,191.22
DD,19,388.45
'''

with open(filename, 'w') as f:
    print(csv_data.strip(), file=f)

portfolio = []
for line in open(filename):
    fields = line.split(',') # Zeile in Liste aufspalten und Felder extrahieren
    name = fields[0]
    shares = int(fields[1])
    price = float(fields[2])
    stock = name, shares, price
    portfolio.append(stock)  # an die Liste anfügen

from pprint import pprint 
pprint(portfolio)

print(portfolio[0])
print(portfolio[1][1])

total = 0
for i in range(len(portfolio)):
    total += portfolio[i][1] * portfolio[i][2]
print(total)

total = 0
for name, shares, price in portfolio:
    total += shares * price
print(total)

print(sum(shares * price for name, shares, price in portfolio))

# ---------------------

# Sets (Mengen)

s = {9, 10, 3, 5}
s == set([3, 5, 9, 10]) == set((3, 5, 9, 10))
t = set('Hello')
u = {3, 5, 7, 9, 11}

print(u | s) # Vereinigung
print(u & s) # Durchschnitt
print(u - s) # Differenz
print(u ^ s) # symmetrische Differenz (Vereinigung - Durchschnitt)

t.add('x')
s.update([10, 37, 42])
t.remove('H')          # wirft Ausnahme, wenn H nicht existiert
t.discard('H')

# ---------------------

# Dictionaries

stock = {
    'name': 'GOOG',
    'shares' : 100,
    'price' : 490.10,
}

name = stock['name']
stock['shares'] * stock['price']

stock['shares'] = 75
stock['date'] = '2024-12-19'

prices = {}
prices = dict()

prices = {
    'GOOG' : 490.10,
    'AAPL' : 123.50,
    'IBM'  : 91.5,
    'MSFT' : 52.13,
}

if 'SCOX' in prices:
    p = prices['SCOX']
else:
    p = 0.0

p = prices.get('SCOX', 0.0)

syms = list(prices)

del prices['MSFT']


for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    print('2 hoch %d = %d' % (n, 2 ** n))

for n in range(1, 10):
    print(f'2 hoch {n} = {2 ** n}')

a = range(5)
b = range(1, 8)
c = range(0, 14, 3)
d = range(8, 1, -1)

for x in a, b, c, d:
    print(list(x))


a = 'Hello World'
for c in a:
    print(c)

b = ['Dave', 'Mark', 'Ann', 'Phil']
for name in b:
    print(name)

c = {'G00' : 490.10, 'TEM' : 91.50, 'AAPL' : 123.15}
for key in c:
    print(key, c[key])

for key, val in c.items():
    print(key, val)

# ---------------------

# Funktionen

def remainder(a, b):
    q = a // b
    r = a - q * b
    return r, a % b

print(remainder(10, 3))


def divide(a, b):
    q = a // b
    r = a % b
    return q, r

print(divide(10, 3))
print(divide(10, 4))
print(divide(b=4, a=10))


def divide(a, b=3):
    q = a // b
    r = a % b
    return q, r

print(divide(10))


counter = 0
def count():
    global counter
    counter += 1

for i in range(3):
    count()
print(counter)

# ---------------------

# Generatoren

def countdown(n):
    print('Counting down!')
    while n > 0:
        yield n # yield statt return
        n -= 1

c = countdown(5)
print(next(c))
print(next(c))

for i in countdown(5):
    print(i)

# wie "tail -f mylog | grep python"
import time
import sys

def tail(f):
    f.seek(0, 2) # gehe zu EOF
    while True:
        line = f.readline()
        if not line:
            time.sleep(0.1)
            continue
        print(f'tail yield {line!r}', file=sys.stderr)
        yield line

def grep(lines, searchtext):
    for line in lines:
        if searchtext in line:
            print(f'grep yield {line!r}', file=sys.stderr)
            yield line

# touch mylog
# echo 'python 3.11' >> mylog 
# echo 'pytho 3.11' >> mylog 
mylog = tail(open('mylog'))
pylines = grep(mylog, 'python')
for line in pylines:
    print(line, end='')


# Koroutinen

def print_matches(matchtext):
    print('Looking for', matchtext)
    while True:
        line = (yield) # warte auf Textzeile
        if matchtext in line:
            print(line)

matcher = print_matches('python')
next(matcher)  # bis zum ersten (yield) gehen
matcher.send('Hello World')
matcher.send('python is cool')
matcher.send('yow!')
matcher.close()


# Zusammenwirken von Generatoren und Koroutinen

# Menge von Matcher-Koroutinen
matchers = [
    print_matches('python'),
    print_matches('guido'),
    print_matches('Jython'),
]

# alle Matcher durch next() bis zum ersten yield laufen lassen
for m in matchers:
    next(m)

# mylog überwachen
mylog = tail(open('mylog'))
for line in mylog:
    for m in matchers:
        m.send(line.rstrip()) # Daten an jeden Matcher senden

# ---------------------

# Objekte und Klassen

items = [37, 42] # Listenobjekt kreieren
items.append(73) # Element anhängen

items = items.__add__([73, 101]) # items += [73, 101]

print(items)
print(dir(items))


class stack:
    def __init__(self):
        self.stack = []
    def push(self, obj):
        self.stack.append(obj)
    def pop(self):
        return self.stack.pop()
    def length(self):
        return len(self.stack)

s = stack()
s.push('Dave')
s.push(42)
s.push([3, 4, 5])

print(s.stack)

x = s.pop()
y = s.pop()

print(x)
print(y)
print(s.stack)

del s


class stack(list):
    # Methode push() ergänzen
    # Hinweis: Listen haben eine Methode pop()
    def push(self, obj):
        self.append(obj)

    @staticmethod
    def info():
        return 'von einer Liste abgeleitete Stack-Klasse'

s = stack()
print(issubclass(stack, list))
print(isinstance(s, list))
print(isinstance(s, stack))

print(stack.info())
print(s.info())

s.push(1)
s.push(2)
print(s)

# ---------------------

# Ausnahmen (Exceptions)

try:
    f = open('/etc/shadow')
except IOError as e:
    print(e)

try:
    with open('/etc/shadow') as f:
        print(f.read())
except IOError as e:
    print(e)

try:
    with open('/etc/passwd') as f:
        for line in f:
            print(line, end='')
            line.add('X') # bewirkt eine Ausnahme
except Exception as e:
   print(e)
   print(f.closed) # True

try:
    for line in (f := open('/etc/passwd')):
        print(line, end='')
        line.add('X')
except Exception as e:
   print(e)
   print(f.closed) # False

# ---------------------

# Module

# Datei div.py
def divide(a, b):
    q = a // b
    r = a % b
    return q, r

import div
a, b = div.divide(7, 4)

import div as foo
a, b = foo.divide(7, 4)

from div import divide
a, b = divide(7, 4)

from div import *  # den gesamten Modul-Inhalt importieren; meist keine gute Idee

import string
print(dir(string))

# ---------------------

# Hilfe

help()

help('keywords')
help('modules')
help('symbols')
help('topics')

help('')
help(1)

print(issubclass.__doc__)
