Seite 1 von 1

Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 20:13
von ltownatze
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 20:38
von 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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 20:58
von pillmuncher
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}

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 21:03
von ltownatze
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 21:45
von ltownatze
@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'

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 22:08
von pillmuncher
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 22:14
von 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)

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 22:24
von ltownatze
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 22:25
von pillmuncher
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 22:35
von pillmuncher
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 22:39
von ltownatze
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:

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 14. April 2012, 23:16
von pillmuncher
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)

Re: Lists, Dicts und Stringformatierung

Verfasst: Freitag 20. April 2012, 23:54
von ltownatze
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 21. April 2012, 00:01
von Hyperion
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.ä.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 21. April 2012, 05:59
von 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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 26. Mai 2012, 21:14
von ltownatze
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

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 26. Mai 2012, 21:56
von Hyperion
Du solltest mal Deinen Code syntaktisch korrekt formatieren (DocStrings!)! Das kann doch niemals Code sein, den Du ausgetestet hast...

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 26. Mai 2012, 22:00
von ltownatze
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.

Re: Lists, Dicts und Stringformatierung

Verfasst: Samstag 26. Mai 2012, 22:03
von Hyperion
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 ;-)