Lists, Dicts und Stringformatierung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

Hallo, Python-Freunde.

Ich möchte eine Textdatei (/etc/fstab) so einlesen, dass ich am Ende ein Dictionary in dieser Form habe:

Code: Alles auswählen

{'/dev/scd0': ['/media/cdrom0', 'udf,iso9660', 'user,noauto', '0', '0'], 
'/dev/sda5': ['/home', 'ext3', 'defaults', '0', '2'], 
'/dev/sda6': ['none', 'swap', 'sw', '0', '0'], 
'/dev/sda2': ['/', 'ext3', 'errors=remount-ro', '0', '1'], 
'/dev/sda3': ['/testing', 'ext3', 'defaults', '0', '2']}
Mit unten stehendem Code erreiche ich dieses Ziel zwar, kann mir aber nicht vorstellen, dass das eine besonders elegante Lösung des Problems ist.
Am meisten stört mich, dass ich die Liste 10 mal 'durch laufen' muss um alle überflüssigen Leerzeichen zu entfernen.

Würde mich sehr freuen, wenn mir jemand einen Tipp geben kann wie ich das besser umsetzen kann.

Code: Alles auswählen

 def read_fstab(self):
    lines = []
    with open('fstab') as f:
      for line in f:
        if line.startswith('#') or line.startswith('proc'):
          pass
        else:
          line = re.sub(r'[\s]', ' ', line)
          line = line.split(' ')
          j = 0
          while j < 10:
            i = 0
            for element in line:
              if element == '':
                del line[i]
              i += 1
            j += 1
          if element:
            lines.append(line)
    fstab = {}
    for line in lines:
      fstab[line[0]] = line[1:]
    return fstab
Dank und Gruß
der atze

P.S.: Sorry fürs Topic.. mir fällt beim besten Willen nichts passenderes ein.
BlackJack

@ltownatze: Dein aufteilen an einem Leerzeichen ist IMHO das erste unelegante, denn dadurch entstehen die Leerzeichen erst. Das nächste Problem ist, dass Du Elemente aus einer Liste entfernst über die Du gerade iterierst. Dadurch verändert sich die Liste und es werden in der Schleife nicht mehr alle Elemente erfasst, weil durch die Verschiebung durch das löschen welche „übersprungen” werden. An der Stelle hättest Du ganz einfach eine neue Liste ohne die Leerzeichen erstellen können.

Aber wie gesagt, das Problem fängt schon damit an, dass Du die Leerzeichen-Elemente erst erzeugst.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Was BlackJack gesagt hat.

Ich würde es ungefähr so machen (ungetestet):

Code: Alles auswählen

split = re.compile(pattern=r'\s+').split
with open('fstab', 'r') as fstab:
    lines = ([match.group(0) for match in split(line)]
                for line in fstab
                    if not (line.startswith('#') or line.startswith('proc')))
result = {line[0]:line[1:] for line in lines if line}
In specifications, Murphy's Law supersedes Ohm's.
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

Aber wie gesagt, das Problem fängt schon damit an, dass Du die Leerzeichen-Elemente erst erzeugst.
Die Leerzeichen entstehen durch

Code: Alles auswählen

line = re.sub(r'[\s]', ' ', line)
Hier wird jeder Tabulator durch 4 Leerzeichen ersetzt. Ich konnte bisher keine Möglichkeit finden das zu ändern.
Ich hatte mit dem Gedanken gespielt das mit

Code: Alles auswählen

line.split('\t')
zu lösen, das klappt aber nur, wenn in der Datei wirklich Tabulatoren genutzt werden. Ich kann aber nicht davon ausgehen, dass jeder der mein Programm nutzt seine fstab so schreibt wie ich.

Hoffe das ist einigermaßen verständlich formuliert.
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

@pillmuncher
Ich komme mit deinem Code nicht wirklich zu Recht, habe versucht es in meinen Code einzupflegen:

Code: Alles auswählen

  def read_fstab(self):
    split = re.compile(pattern=r'\s+').split
    with open('fstab') as fstab:
      lines = ([match.group(0) for match in split(line)]
                for line in fstab
                  if not (line.startswith('#') or line.startswith('proc')))
if __name__ == '__main__':
    Fstab().read_fstab()
Bekomme aber nur diesen Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "./backend.py", line 130, in <module>
    Fstab().read_fstab()
  File "./backend.py", line 41, in read_fstab
    for line in lines:
  File "./backend.py", line 40, in <genexpr>
    if not (line.startswith('#') or line.startswith('proc')))
AttributeError: 'str' object has no attribute 'group'
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

ltownatze hat geschrieben:@pillmuncher
Ich komme mit deinem Code nicht wirklich zu Recht, habe versucht es in meinen Code einzupflegen:
Sorry. Es muss wohl heißen (immer noch ungetestet):

Code: Alles auswählen

    def read_fstab(self):
        split = re.compile(pattern=r'\s+').split
        with open('fstab') as fstab:
            lines = (split(line)  # <== so gehört's wohl
                        for line in fstab
                            if not (line.startswith('#') or line.startswith('proc')))
if __name__ == '__main__':
    Fstab().read_fstab()
Wozu du allerdings eine Methode brauchst, erschließt sich mir nicht (Python is not Java). Du erzeugst hier zwar lines, aber gibst sie weder mit return zurück, noch speicherst du sie in einem Attribut von self.

Und bitte pro Ebene +4 Spaces einrücken, wie in PEP8 empfohlen. Dann kann ich es einfach in meinen Editor kopieren und bearbeiten, ohne erst händisch alles so einrücken zu müssen, dass meine PEP8-konformen Editor-Einstellungen damit zurecht kommen.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@pillmuncher: Vielleicht ist es schon ein wenig zu spät, aber was macht Deine `re`-Lösung jetzt anders als ein einfaches `str.split()` ohne Argumente?

Mein Ansatz:

Code: Alles auswählen

def read_fstab(filename='/etc/fstab'):
    with open(filename) as lines:
        rows = (
            s.split()
            for s in lines
            if s.strip() and not s.startswith(('#', 'proc'))
        )
        return dict((r[0], r[1:]) for r in rows)
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

Danke erstmal für eure Hilfe, werde mich Morgen mal mal mit "Generators" beschäftigen, ist für mich noch ziemliches Neuland, möchte aber gerne Verstehen warum dieser Code funktioniert.
Der Vollständigkeit halber:

Code: Alles auswählen

class Fstab:
  '''Read and write fstab'''

  def __init__(self):
    pass

  def read_fstab(self):
    result = {}
    split = re.compile(pattern=r'\s+').split
    with open('fstab') as fstab:
      lines = (split(line)
                for line in fstab
                  if not (line.startswith('#') or line.startswith('proc')))
      for line in lines:
        result[line[0]] = line[1:]
    print result
            ## line = re.sub(r'[\s]', ' ', line)
            ## line = line.split(' ')
            ## j = 0
            ## while j < 10:
              ## i = 0
              ## for element in line:
                ## if element == '':
                  ## del line[i]
                ## i += 1
              ## j += 1
            ## if element:
              ## lines.append(line)
    ## fstab = {}
    ## for line in lines:
      ## fstab[line[0]] = line[1:]
    ## return fstab

  def write_fstab(self):
    pass

Code: Alles auswählen

$ ./backend.py 
{'': [''], '/dev/scd0': ['/media/cdrom0', 'udf,iso9660', 'user,noauto', '0', '0', ''], '/dev/sda5': ['/home', 'ext3', 'defaults', '0', '2', ''], '/dev/sda6': ['none', 'swap', 'sw', '0', '0', ''], '/dev/sda2': ['/', 'ext3', 'errors=remount-ro', '0', '1', ''], '/dev/sda3': ['/testing', 'ext3', 'defaults', '0', '2', '']}
Über den Sinn und Unsinn das ganze in eine Klasse zu packen wäre auch ein interessantes Thema, ich möchte diesen Thread gerne auf die funktionalität der Methode "read_fstab" beschränken. Hoffe ihr habt dafür Verständnis.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

BlackJack hat geschrieben:@pillmuncher: Vielleicht ist es schon ein wenig zu spät, aber was macht Deine `re`-Lösung jetzt anders als ein einfaches `str.split()` ohne Argumente?
Öh, äh... nix? :lol: Ich hab einfach den Code vom OP umgebaut und nich wirklich über diesen Aspekt nachgedacht, weil mein Hauptaugenmerk darauf lag, die scheußlichen i und j loszuwerden.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

ltownatze hat geschrieben:

Code: Alles auswählen

class Fstab:  # <== hier bitte, wenn überhaupt eine Klasse, dann new style: class Fstab(object):
  '''Read and write fstab'''

  def __init__(self):  # <== das weglassen, da es genau denselben Effekt hat, wenn es weggelassen wird.
    pass

  def read_fstab(self):
    result = {}
    split = re.compile(pattern=r'\s+').split
    with open('fstab') as fstab:
      lines = (split(line)
                for line in fstab
                  if not (line.startswith('#') or line.startswith('proc')))
      for line in lines:  # <== du musst testen, ob line kein Leerstring ist
        result[line[0]] = line[1:]  # <== ich hatte dir schon gezeigt, wie man das pythonischer schreiben kann, allerdings für Python 2.7
    print result
BlackJacks Code zeigt den Weg, den du gehen solltest.
In specifications, Murphy's Law supersedes Ohm's.
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

ich hatte dir schon gezeigt, wie man das pythonischer schreiben kann, allerdings für Python 2.7
Um sicherzugehen, dass mein Code auch auf einer standard Debian squeeze Installation läuft, muss der Code mit 2.6.6 kompatibel sein. Hätte ich, rückwirkend betrachtet, mal besser schon im Eröffnungspost erwähnen sollen.. :oops:
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

ltownatze hat geschrieben:Um sicherzugehen, dass mein Code auch auf einer standard Debian squeeze Installation läuft, muss der Code mit 2.6.6 kompatibel sein.

Code: Alles auswählen

result = dict((line[0], line[1:]) for line in lines if line)
In specifications, Murphy's Law supersedes Ohm's.
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

Soo, ich habe eure Hinweise jetzt umgesetzt und denke diese Verstanden zu haben.
Habe versucht das ganze auf eine vergleichbare Situiation zu übertragen, im Gegensatz zur Methode read_fstab(), wird hier aber keine Liste mit Dictionarys erzeugt[?] sondern ein Dictionary mit Dictionarys

Code: Alles auswählen

import re
import subprocess

class Fstab(object):
    '''Read and write fstab'''

    def read_fstab(self, filename='/etc/fstab'):
        '''Returns a dictionary in the form
        <file system> : [<mount point>, <type>, <options>, <dump>, <pass>]'''''

        with open(filename) as lines:
            rows = (
                s.split()
                for s in lines
                if s.strip() and not s.startswith(('#', 'proc'))
            )
            return dict((r[0], r[1:]) for r in rows)

    def write_fstab(self, fstab=None, filename='/tmp/fstabXXXX'):
        ''''fstab is expected to be a dictionary as returnd by self.read_fstab()'''

        fstab = fstab
        s = '''# /etc/fstab - automaticly created by fstabtool
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0\n'''

        with open(filename, 'w') as f:
            for key in fstab:
                s = s + key + re.sub(r'[\[,\],\',"]', ' ', str(fstab[key])) + '\n'
            f.write(s)

class Devices(object):
    '''TODO: Write DocString'''

    def get_devices(self, partitions = '/proc/partitions'):
        with open(partitions) as lines:
            results = (
                re.search(r'sd[a-z][0-9]*', line)
                for line in lines
            )
            device_names = ([match.group(0) for match in results if match])
        return dict((device, self.get_device(device)) for device in device_names)

    def get_device(self, device):
        device = '/dev/' + device

        p = subprocess.check_output(['lsblk',
                                    '-Po',
                                    'NAME,UUID,LABEL,SIZE,TYPE,FSTYPE,MOUNTPOINT', device]).splitlines()
        output = p[0].split()

        return dict(e.split('=') for e in output)
Um die Hintergründe zu erläutern, habe ich einmal den vollständigen Code genommen.
Das ganze soll ein Modul sein. Man sollte damit ein Programm realisieren können, das eine beliebige Datei im Format einer fstab (fstab-Format) liest, manipuliert, und wieder im fstab-Format in eine beliebe Datei schreibt.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Wozu dienen die beiden Klassen?

Code: Alles auswählen

device = '/dev/' + device
Das solltest Du nie machen, sondern `os.path.join` für das Zusammensetzen von Pfaden benutzen.

Strings solltest Du aber auch generell nicht mit `+` zusammensetzen, sondern mittels `"".format()` o.ä.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

ltownatze: Das hier ist äusserst unschön: ``re.sub(r'[\[,\],\',"]', ' ', str(fstab[key]))``. Statt aus der Zeichenkettendarstellung einer Liste die unerwünschten Zeichen zu entfernen wäre es viel einfacher, lesbarer, und auch robuster eine Zeichenkette aus den Elementen zu erzeugen, die den unerwünschten Kram gar nicht erst enthält.
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

Sooo.. lang hat's gedauert, aber ich habe enlidch mal wieder etwas Zeit gefunden.

Bin mir nicht ganz sicher ob ich die Anregung bezüglich str.format() richtig umgesetzt habe.
Könnte mir vorstellen das es eine elegantere lösung gibt:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import re
import subprocess
import os

class Fstab(object):
    '''Read and write fstab'''

    def read_fstab(self, filename='/etc/fstab'):
        '''Returns a dictionary in the form
        <file system> : [<mount point>, <type>, <options>, <dump>, <pass>]'''

        with open(filename) as lines:
            rows = (
                s.split()
                for s in lines
                if s.strip() and not s.startswith(('#', 'proc'))
            )
            return dict((r[0], r[1:]) for r in rows)

    def write_fstab(self, fstab=None, filename='/tmp/fstabXXXX'):
        '''fstab is expected to be a dictionary as returnd by self.read_fstab()'''

        fstab_heading = '''# /etc/fstab - automaticly created by fstabtool
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0'''
        fstab_body = []
        for key in fstab:
            fstab_body.append('{0}\t{1}'.format(key, "\t".join(fstab[key])))
        fstab_body = '\n'.join(fstab_body)
        newfstab = '{0}\n{1}\n'.format(fstab_heading, fstab_body)

        with open(filename, 'w') as f:
            f.write(newfstab)

class Devices(object):
    '''TODO: Write DocString'''

    def get_devices(self, partitions = '/proc/partitions'):
        with open(partitions) as lines:
            results = (
                re.search(r'sd[a-z][0-9]*', line)
                for line in lines
            )
            device_names = ([match.group(0) for match in results if match])
        return dict((device, self.get_device(device)) for device in device_names)

    def get_device(self, device):
        device = os.path.join('/dev/', device)

        p = subprocess.check_output(['lsblk',
                                    '-Po',
                                    'NAME,UUID,LABEL,SIZE,TYPE,FSTYPE,MOUNTPOINT', device]).splitlines()
        output = p[0].split()

        return dict(e.split('=') for e in output)

    def get_UUID(self, device):
        return self.get_device(device)['UUID']
Dank und Gruß
der atze
Zuletzt geändert von ltownatze am Samstag 26. Mai 2012, 22:02, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du solltest mal Deinen Code syntaktisch korrekt formatieren (DocStrings!)! Das kann doch niemals Code sein, den Du ausgetestet hast...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
ltownatze
User
Beiträge: 28
Registriert: Donnerstag 8. April 2010, 16:02

Mir ist das kurz nach dem Post auch durch Zufall aufgefallen.. Der Code läuft hier genau so aber einwandfrei.

Edit: Hab die DocStrings korrigiert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Stimmt. Ich hatte das in einer Shell getestet, mich da aber wohl vertan. Auf jeden Fall solltest Du die Anführungszeichen bei DocStrings auf exakt drei beschränken. Das kannst Du auch nachträglich editieren ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten