Live Hacking

Strings können viel: split()

Beim zeilenweisen Lesen eines Files, die Nutzdaten einer Zeile extrahieren wollend … * Frage: wie spalte ich eine Zeile, die offensichtlich mit Tabs separierte Felder hat? * Antwort: `split() ohne Argumente <https://docs.python.org/3/library/stdtypes.html#str.split>`__ ist am gscheitesten, weil es aufeinanderfolgende Whitespaces als eines nimmt.

[80]:
line = '  \t   abc \t       def   '
line.split()
[80]:
['abc', 'def']

Tuple unpacking ist praktisch, wenn man weiss, dass z.B. genau drei Felder rauskommen werden …

[81]:
line = 'aaa\tbbb\tccc'
feld0, feld1, feld2 = line.split()
print(feld0, feld1, feld2)
aaa bbb ccc

Gibts optionale Felder in der Zeile - aber ganz sicher mindestens drei -, so splittet man drei weg, und bearbeitet den Rest manuell nach …

[82]:
# optional fields
line = 'aaa\tbbb\tccc  ddd  eee'
feld0, feld1, feld2, rest = line.split(maxsplit=3)
print('feld0:', feld0, ', feld1:', feld1, ', feld2:', feld2, ', rest:', rest)

restfields = rest.split()
if len(restfields):
    print('do is no wos dabei')
feld0: aaa , feld1: bbb , feld2: ccc , rest: ddd  eee
do is no wos dabei

Geht auch von rechts her …

[83]:
line = '1.2.3.4.10101010'
rest, switchnumber = line.rsplit('.', maxsplit=1)
[84]:
print(rest, switchnumber)
1.2.3.4 10101010

Mutability, Immutability: kann man nicht oft genug wiederholen

[85]:
l1 = [1,2,3]
l2 = l1
print(l1)
print(l2)
[1, 2, 3]
[1, 2, 3]
[86]:
l1.append('vier')
l1
[86]:
[1, 2, 3, 'vier']

Das ist der Punkt: die Änderung sieht man auch über l2

[87]:
l2
[87]:
[1, 2, 3, 'vier']

l1 und l2 zeigen auf dasselbe Objekt:

[88]:
print(id(l1))
print(id(l2))
140070310441600
140070310441600

Dass die beiden auf das gleiche Objekt zeigen, ist nicht so das Problem. Das Problem ist eigentlich, dass das Objekt verändert werden kann.

  • list ist mutable -> Problem

  • tuple ist immutable -> kein Problem

[89]:
t1 = (1,2,3)
t1
[89]:
(1, 2, 3)
[90]:
t2 = t1
t2
[90]:
(1, 2, 3)
[91]:
print(id(t1))
print(id(t2))
140070371015728
140070371015728
[92]:
try:
    t1.append('vier')
except AttributeError:
    print('gottseidank sind tuples immutable')
gottseidank sind tuples immutable

Dictionaries

[93]:
d = {'eins': 1,
     'zwei': 2
    }
d['eins']
[93]:
1
[94]:
print(type(d))
<class 'dict'>

Alternativer dict Aufruf: Liste von (key,value) Paaren

[95]:
d = dict([('eins', 1), ('zwei', 2)])

“Hartes” Indizieren …

[96]:
d['eins']
[96]:
1

Fehlender Key -> Exception

[97]:
try:
    d['drei']
except KeyError as e:
    print('nix key in dict:', e)
nix key in dict: 'drei'

“Weiches” Nettfragen …

[98]:
d.get('eins')
[98]:
1
[99]:
value = d.get('drei')
print(value)
None

Shortcuts

[100]:
# umstaendlich
value = d.get('hundert')
if value is None:
    value = 100
print(value)
100
[101]:
# einfach
value = d.get('hundert', 100)
print(value)
100
[102]:
d
[102]:
{'eins': 1, 'zwei': 2}
[103]:
# umstaendlich
value = d.get('hundert')
if value is None:
    d['hundert'] = 100
    value = 100
print(value)
100
[104]:
d
[104]:
{'eins': 1, 'zwei': 2, 'hundert': 100}
[105]:
# (clear this up)
del d['hundert']
[106]:
# einfach
value = d.setdefault('hundert', 100)
print(value)
100
[107]:
d
[107]:
{'eins': 1, 'zwei': 2, 'hundert': 100}

Objektorientierte Programmierung

[108]:
class MeinVoelligSinnloserTyp:
    def __init__(self):
        self.eins = 1
        self.zwei = 2
    def sinnlose_addition(self):
        return self.eins + self.zwei
[109]:
sinnlobj = MeinVoelligSinnloserTyp()
[110]:
sinnlobj.eins
[110]:
1
[111]:
sinnlobj.zwei
[111]:
2
[112]:
sinnlobj.drei = 3
[113]:
sinnlobj
[113]:
<__main__.MeinVoelligSinnloserTyp at 0x7f64a8934890>
[114]:
sinnlobj.drei
[114]:
3
[115]:
sinnlobj.sinnlose_addition()
[115]:
3

Inheritance

[116]:
class NochWenigerSinnvoll(MeinVoelligSinnloserTyp):
    def __init__(self, der_absolute_unsinn):
        super().__init__()
        self.unsinn = der_absolute_unsinn
    def uebertreibs_jetzt_bitte(self):
        self.eins += self.unsinn
        self.zwei += self.unsinn
[117]:
bs = NochWenigerSinnvoll(666)
[118]:
bs
[118]:
<__main__.NochWenigerSinnvoll at 0x7f64a894a790>
[119]:
bs.sinnlose_addition()
[119]:
3
[120]:
bs.uebertreibs_jetzt_bitte()
[121]:
bs.sinnlose_addition()
[121]:
1335

Date and Time and Time Deltas - datetime

[122]:
import datetime

now = datetime.datetime.now()
now
[122]:
datetime.datetime(2020, 3, 17, 13, 55, 2, 819508)
[123]:
uptime = datetime.timedelta(seconds=358)
uptime
[123]:
datetime.timedelta(seconds=358)
[124]:
boot = now - uptime
boot
[124]:
datetime.datetime(2020, 3, 17, 13, 49, 4, 819508)

Bissl potschert zum Schreiben is scho … (andere Formen von import)

[125]:
from datetime import datetime, timedelta
irgendwann_einmal_gewesen = datetime.now() - timedelta(days=15)

irgendwann_einmal_gewesen
[125]:
datetime.datetime(2020, 3, 2, 13, 55, 2, 834972)
[126]:
from datetime import datetime as datetime_from_batteries
datetime_from_batteries.now()
[126]:
datetime.datetime(2020, 3, 17, 13, 55, 2, 840837)

String Representations: __str__()

[127]:
# recover from the datetime massacre we created above
del datetime
import datetime
now = datetime.datetime.now() # for example
str(now)
[127]:
'2020-03-17 13:55:02.846889'

Frage: woher weiss str(), datetime als String ausschauen will?

[128]:
class Sinnlos:
    def __init__(self, sinnlos):
        self.sinnlos = sinnlos
    def __str__(self):
        return 'sinnlose str repraesentation von {}'.format(self.sinnlos)

str(Sinnlos(666))
[128]:
'sinnlose str repraesentation von 666'
[129]:
str(Sinnlos([1,2,3,'vier']))
[129]:
"sinnlose str repraesentation von [1, 2, 3, 'vier']"

subprocess

[130]:
import subprocess

subprocess.run(['ls', '-l'])
[130]:
CompletedProcess(args=['ls', '-l'], returncode=0)

Nicht viel Information: returncode = 0 -> ls erfolgreich beendet * Frage: Was ist mit dem Output? * Antwort: schau in dem Terminal nach, das du verwendest, um dieses Notebook anzuzeigen. (Die Cells werden vom Browser an den Notebook-Server gesendet, der sie dann ausführt.) Hmm … * Frage: und wie krieg ich den von dort weg, in mein Programm rein? * Antwort: so ->

[131]:
result = subprocess.run(['ls', '-l'], capture_output=True)
print(result.stdout)
b'total 52\ndrwxrwxr-x. 2 jfasch jfasch  4096 Mar 17 13:48 Exercises\n-rw-r--r--. 1 jfasch jfasch  2607 Mar 17 13:48 index.rst\n-rw-rw-r--. 1 jfasch jfasch 33768 Mar 17 13:54 LiveHacking.ipynb\ndrwxrwxr-x. 2 jfasch jfasch  4096 Mar 17 13:48 Misc\n-rw-r--r--. 1 jfasch jfasch     0 Mar 17 13:48 README.rst.~1~\ndrwxr-xr-x. 4 jfasch jfasch  4096 Mar 17 13:48 SwitchZeug\n'

Hmm … das ist jetzt kein String, sondern Bytes (erkennbar am ‘b’). Keiner weiss, in welchem Encoding ls -l seinen stdout formuliert - also ist das nur logisch so.

[132]:
print(type(result.stdout))
<class 'bytes'>
  • Frage: Wenn ich irgendwas mit dem Output machen will?

  • Antwort: Dann konvertier es in einen String. Dabei musst du dich aber aufs Encoding festlegen.

[133]:
stdout_as_utf8 = str(result.stdout, encoding='utf-8')
print(type(stdout_as_utf8))
<class 'str'>
[134]:
stdout_as_utf8
[134]:
'total 52\ndrwxrwxr-x. 2 jfasch jfasch  4096 Mar 17 13:48 Exercises\n-rw-r--r--. 1 jfasch jfasch  2607 Mar 17 13:48 index.rst\n-rw-rw-r--. 1 jfasch jfasch 33768 Mar 17 13:54 LiveHacking.ipynb\ndrwxrwxr-x. 2 jfasch jfasch  4096 Mar 17 13:48 Misc\n-rw-r--r--. 1 jfasch jfasch     0 Mar 17 13:48 README.rst.~1~\ndrwxr-xr-x. 4 jfasch jfasch  4096 Mar 17 13:48 SwitchZeug\n'
[135]:
stdout_as_utf8.split('\n')
[135]:
['total 52',
 'drwxrwxr-x. 2 jfasch jfasch  4096 Mar 17 13:48 Exercises',
 '-rw-r--r--. 1 jfasch jfasch  2607 Mar 17 13:48 index.rst',
 '-rw-rw-r--. 1 jfasch jfasch 33768 Mar 17 13:54 LiveHacking.ipynb',
 'drwxrwxr-x. 2 jfasch jfasch  4096 Mar 17 13:48 Misc',
 '-rw-r--r--. 1 jfasch jfasch     0 Mar 17 13:48 README.rst.~1~',
 'drwxr-xr-x. 4 jfasch jfasch  4096 Mar 17 13:48 SwitchZeug',
 '']
[136]:
# fuer ls gibts was besseres
import os

for elem in os.listdir('.'):
    print(elem)
.ipynb_checkpoints
Misc
SwitchZeug
index.rst
LiveHacking.ipynb
README.rst.~1~
Exercises
.gitignore
[137]:
# noch besser: rekursives Iterieren!!
for dirpath, dirnames, filenames in os.walk('.'):
    print('dirpath:', dirpath, ', dirnames:', dirnames, ', filenames:', filenames, '\n')
dirpath: . , dirnames: ['.ipynb_checkpoints', 'Misc', 'SwitchZeug', 'Exercises'] , filenames: ['index.rst', 'LiveHacking.ipynb', 'README.rst.~1~', '.gitignore']

dirpath: ./.ipynb_checkpoints , dirnames: [] , filenames: ['LiveHacking-checkpoint.ipynb', 'ITG-Project-checkpoint.ipynb']

dirpath: ./Misc , dirnames: [] , filenames: ['scope.py', 'getter-setter.py', 'eval-argv.py', 'fibo-generator-yield.py', 'listcomprehension-generatorexpression.py', 'operator-overloading.py', 'scope-closure.py']

dirpath: ./SwitchZeug , dirnames: ['SwitchZeug', 'data'] , filenames: ['01-der-anfang.py', 'switchdb-create-schema.py', 'switchdb-report-switch.py', 'tests.py', 'michi-switch-upgrade-von-ini.py', 'michi-switch-upgrade-config.txt', '02-michi-parse.py', 'switchdb-insert-switch.py', 'michi-switch-upgrade-von-pyexec.py', 'tests.py.~1~', 'michi-switch-upgrade-config.ini']

dirpath: ./SwitchZeug/SwitchZeug , dirnames: [] , filenames: ['interface.py', 'switch.py', 'error.py', 'database.py.~1~', 'michi.py', 'database.py', '__init__.py']

dirpath: ./SwitchZeug/data , dirnames: [] , filenames: ['ifAdminStatus.txt', 'ifDescr.txt', 'ifLastChange.txt', 'snmpEngineTime.txt', 'ifOperStatus.txt']

dirpath: ./Exercises , dirnames: [] , filenames: ['20.py', 'prime.py', 'uniq.py', 'digit.py']

enumerate(), zip()

Feine kleine Helferlein … (hochtrabend nennt sich das “Funktionales Programmieren”) * `enumerate() <https://docs.python.org/3/library/functions.html#enumerate>`__ * `zip() <https://docs.python.org/3/library/functions.html#zip>`__ * Mehr davon

[138]:
l = [200, 300, 400]
for num, elem in enumerate(l, start=1):
    print(num, elem)
1 200
2 300
3 400
[139]:
l1 = [1,2,3]
l2 = ['eins', 'zwei', 'drei']
for links, rechts in zip(l1, l2):
    print(links, rechts)
1 eins
2 zwei
3 drei

List Comprehension

[140]:
# umstaendlich

l = [1,2,3,4]
l_quadrat = []
for n in l:
    l_quadrat.append(n**2)
print(l_quadrat)
[1, 4, 9, 16]
[141]:
# einfach

l_quadrat = [n**2 for n in l]
print(l_quadrat)
[1, 4, 9, 16]

exec(), eval()

  • Frage: (Michi) mein Programm hat eine komplizierte Konfiguration (Anm.: assoziiert Firmware-Versionen mit Files, und macht Updates basierend darauf), die im Programm aufgebaut wird. Wie kann ich die Konfiguration aus dem Programm rausnehmen und die Python-Variablen von einem Konfigurationsfile lesen?

  • Antwort: `eval() <https://docs.python.org/3/library/functions.html#eval>`__ und `exec() <https://docs.python.org/3/library/functions.html#exec>`__

  • Siehe Beispiel mit ``exec()` <SwitchZeug/michi-switch-upgrade-von-pyexec.py>`__

  • Siehe Beispiel mit ``configparser`: Windows .ini <SwitchZeug/michi-switch-upgrade-von-ini.py>`__

[142]:
expression = '1 == 2'
eval(expression)
[142]:
False
[143]:
liste_als_string = '[1,2,3]'
[144]:
liste = eval(liste_als_string)
liste
[144]:
[1, 2, 3]

Umgebender Context

[145]:
herbert = 666
[146]:
eval('herbert') # Variable 'herbert' liegt heraussen
[146]:
666
[147]:
exec('joerg = 42') # Variable 'joerg' wird heraussen definiert
[148]:
joerg
[148]:
42

Eigener Context

[149]:
eval('joerg*3', {'joerg': 666})
[149]:
1998
[150]:
del joerg # cleanup what I did above
[151]:
context = {}
exec('joerg = 42', context)
[152]:
context['joerg']
[152]:
42

json

Das ist das Leichteste was es gibt …

[153]:
mein_dict = {
    'joerg': 666,
    'gerhard': {
        'age': 32,
        'size': 175,
    }
}
[154]:
import json

dict_as_json_string = json.dumps(mein_dict)
dict_as_json_string
[154]:
'{"joerg": 666, "gerhard": {"age": 32, "size": 175}}'
[155]:
gelesenes_dict_von_json = json.loads(dict_as_json_string)
gelesenes_dict_von_json
[155]:
{'joerg': 666, 'gerhard': {'age': 32, 'size': 175}}