Das Finden von Ordnern und Dateien optimieren

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
any
User
Beiträge: 9
Registriert: Mittwoch 1. Juni 2011, 09:02

Hallo liebe Python-Experten!
Mein nächster Lernversuch als blutige Anfängerin im Programmieren ist folgender: Ich möchte ein Programm zum Finden aller Dateien mit einem bestimmten Suchkriterium (par) in einem bestimmten Pfad (pfaad) bzw. im aktuell befindlichen Verzeichnis schreiben. Hier ist die Funktion, die dafür in ein Skript gesetzt werden soll und meine Frage ist, inwieweit man diese noch optimieren könnte. Oder anders, ob die Funktion "soft" genug ist, um in jegliche andere Programme integriert werden zu können. Bitte gebt mir ein paar Tipps!

Code: Alles auswählen

import os
import glob

def find2(par, pfaad = os.getcwd()):
  final=[]
  for i in os.walk(pfaad):
      k = str(i[0]+"/"+par)
      alle = glob.glob(k)
      for liste in alle:
        final.append(liste)
  return final
Benutzeravatar
darktrym
User
Beiträge: 784
Registriert: Freitag 24. April 2009, 09:26

2 Sachen fallen auf neben der kreativen Namensgebung:
  • os.walk gibt doch schon eine Liste von Dateien zurück wieso nutzt du dann noch glob.glob
  • Stringkonkation gibts bessere(schnellere, lesbare) Möglichkeiten
Edit: liste?
„gcc finds bugs in Linux, NetBSD finds bugs in gcc.“[Michael Dexter, Systems 2008]
Bitbucket, Github
any
User
Beiträge: 9
Registriert: Mittwoch 1. Juni 2011, 09:02

Die Namensgebung und die Stringkonkatenation erscheinen mir für meine Zwecke sinnvoll und übersichtlich genug. Es geht mir mehr um Tipps zur funktionalen Verbesserung.

Ich weiß jetzt zwar, an welchen Stellen ich verbessern soll, aber leider immer noch nicht wie...
  • os.walk geht den Baum nach unten durch
  • glob.glob gibt die Liste aus, die aufgebaut wird (oder nicht?)
Widerspricht sich das irgendwie? Es funktioniert ja so..
Zuletzt geändert von any am Freitag 8. Juli 2011, 09:04, insgesamt 1-mal geändert.
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hallo,

du erwähnst ja, dass du blutiger Anfänger bist. Vielleicht möchtest du dir darum auch die Tipps, die dir erstmal nicht zu wichtig erscheinen, zu Herzen nehmen. Dazu gehören besonders auch Tipps bezüglich Namensgebung und Lesbarkeit des Codes. Für dich mag der Code "übersichtlich genug" sein, da wir uns aber damit beschäftigen sollen, sollte dein Anspruch auch sein, dass er für Dritte übersichtlich genug ist.

Statt glob möchtest du dir vielleicht das Modul "fnmatch" ansehen und die os.walk()-Funktion so einsetzen, wie sie in der Dokumentation (s. Codebeispiel) beschrieben ist.

Schönen Gruß,

brb
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Außerdem macht das `pfaad = os.getcwd()` nicht das, was du wahrscheinlich erwartest. Das aktuelle Arbeitsverzeichnis wird nämlich nur einmal abgefragt und nicht bei jedem Funktionsaufruf. Die übliche Lösung dieses Problems ist den Standardwert auf `None` zu setzen und dies beim Ausführen der Funktion zu testen. Hinzu kommt, dass die Auswahl des aktuellen Arbeitsverzeichnisses überflüssig ist, da dies das default-Verhalten ist.

Pfade solltest du nicht mit `+` oder String-Formatting zusammensetzen sondern dazu die `os.path.join`-Funktion verwenden. Dabei kannst du weniger Fehler machen und bist sogar noch unabhängig vom Betriebssystem.
Das Leben ist wie ein Tennisball.
any
User
Beiträge: 9
Registriert: Mittwoch 1. Juni 2011, 09:02

Das `pfaad = os.getcwd()` soll bewirken, dass die Suche entweder ab einem Pfad/Verzeichnis beginnen soll, der/das extra mitgegeben wird oder, wenn das nicht erfolgt, im aktuellen startet.

Stünde da nur 'pfaad' und ich gebe keinen Pfad vor, kommt die Fehlermeldung, dass ihm ein Argument fehlt. (TypeError: find2() takes exactly 2 arguments (1 given))
Setze ich den Wert auf 'None' findet das Programm nichts, gibt aber keinen Fehler aus.
BlackJack

@any: Uns ist schon klar was das bewirken soll. Nur wird das nur *einmal* ausgewertet und zwar wenn das ``def`` ausgeführt wird. Und nicht jedes mal wenn die Funktion aufgerufen wird. Das kann zu subtilen Fehlern führen.

Wenn Du als Default-Wert `None` nimmst, musst Du darauf innerhalb der Funktion prüfen und *dann* natürlich `os.getpwd()` aufrufen. Alternativ könntest Du als Default-Wert auch einfach `os.curdir` verwenden.
any
User
Beiträge: 9
Registriert: Mittwoch 1. Juni 2011, 09:02

Ich danke Euch erstmal. Bin allerdings noch etwas überfordert und tüftele noch weiter daran herum.
Ist "def ausführen" und "Funktion aufrufen" nicht das selbe? :oops: Bzw. warum wird das nur einmal ausgewertet und nicht immer wieder?
BlackJack

@any: Nein das ist nicht dasselbe. Wenn ``def`` ausgeführt wird, dann wird ja nur die Funktion definiert, sie wird dabei aber nicht aufgerufen. Die Ausdrücke für Default-Werte werden halt nur bei beim ``def``\inieren der Funktion ausgewertet. Vielleicht aus Geschwindigkeitsgründen — denn die Auswertung bei jedem Aufruf, würde ja auch bei jedem Aufruf Zeit kosten.

Hätte man es anders gelöst, würden sich Leute beschweren das sie nicht damit gerechnet hätten, das der Ausdruck mehrfach ausgewertet wird. Irgendwen ”überrascht” man an der Stelle also immer.
lunar

@any: Nein, es ist nicht dasselbe, nicht einmal das gleiche ;)

„def ausführen“ bedeutet, dass die Deklaration der Funktion ausgewertet wird. Immer, wenn Python auf ein "def" stößt, erzeugt es ein neues Funktionsobjekt mit dem entsprechenden Namen und den Argument, und speichert den Körper der Funktion in diesem Objekt. Zu diesem Zeitpunkt werden auch die „Default-Werte“ ausgewertet (und dann ebenfalls im Funktionsobjekt gespeichert). Ein solches Funktionsobjekt kann man dann auch entsprechend abfragen:

Code: Alles auswählen

>>> def say_hello(names=[]):
...     """Say hello to the given people"""
...     return 'Hello {0}'.format(', '.join(names))
... 
>>> say_hello
<function say_hello at 0x1dc6578>
>>> say_hello.__name__
'say_hello'
>>> say_hello.__doc__
u'Say hello to the given people'
>>> import inspect
>>> inspect.getargspec(say_hello)
ArgSpec(args=['names'], varargs=None, keywords=None, defaults=([],))
>>> >>> say_hello.__code__
<code object say_hello at 0x1c9ae30, file "<input>", line 2>
In der letzten, etwas seltsamen Zeile verbirgt sich ein sogenanntes "Code"-Objekt. Es enthält den gesamten Funktionskörper (also in diesem Fall das "return …").

Dieser Funktionskörper wurde aber bisher nicht ausgeführt. Das geschieht erst, wenn man die Funktion aufruft:

Code: Alles auswählen

>>> say_hello()
u'Hello '
>>> say_hello(names=['Ford', 'Zaphod'])
u'Hello Ford, Zaphod'
Dabei werden die Argumente an das Code-Objekt übergeben, welches dann ausgeführt wird. Darin besteht der Unterschied zwischen einer Funktionsdeklaration und einem Funktionsaufruf

Beachte die erste Zeile in obigen Beispiel. Beim Aufruf der Funktion ohne einen Namen wird niemand bestimmtes gegrüßt. Die Gefahr von Standard-Argumenten ist nun, dass sich ihre Veränderung auf alle Funktionsaufrufe auswirkt, eben weil sie in der Funktion selbst gespeichert sind:

Code: Alles auswählen

>>> say_hello.__defaults__[0].append('Ford')
>>> inspect.getargspec(say_hello)
ArgSpec(args=['names'], varargs=None, keywords=None, defaults=([u'Ford'],))
>>> say_hello()
u'Hello Ford'
Die erste Zeile hängt an den Standardwert für das "names"-Argument einen Namen an, und auf einmal wird "Ford" gegrüßt, obwohl die Funktion erneut ohne Argumente aufgerufen wurde.
any
User
Beiträge: 9
Registriert: Mittwoch 1. Juni 2011, 09:02

Danke Euch! Besonders Lunars Antwort war eine tolle Erklärung! Immerhin ist jetzt etwas Licht ins Dunkel geraten..
Ich hab auch versucht, andere Tipps umzusetzen, allerdings weiß ich nicht, wie gut mir das gelungen ist. Ich bin schon mal froh, dass (scheinbar) die richtigen Suchergebnisse kommen, wenn ich mein Programm benutze. Die def sieht jetzt wie folgt aus:

Code: Alles auswählen

import os
import glob

def find2(par, pfaad=None):	 
  if pfaad:
    print pfaad
  else:
    pfaad = os.getcwd()             # curdir gibt nur Dateinamen wider, nicht wie getcwd() abs. Pfade
  final=[]	
  for i in os.walk(pfaad):
      k = os.path.join(i[0], par) 
      alle = glob.glob(k)           # wie man stattd. fnmatch verwendet, habe ich noch nicht verstanden
      for liste in alle:
	final.append(liste)
  return final	
Kann mir bitte nochmal jmd ein fnmatch-Beispiel geben, das Manual ist mir nicht gerade eine große Hilfe, etwas zuu kompliziert geschrieben.. (Ich hab sonst nicht viel mit "Computerkram" zu tun..) Danke :-*
Barabbas
User
Beiträge: 349
Registriert: Dienstag 4. März 2008, 14:47

Hallo, eigentlich gibt es in der Dokumentation doch ein wunderbares Beispiel für dich:

Code: Alles auswählen

import fnmatch
import os

for file in os.listdir('.'):
    if fnmatch.fnmatch(file, '*.txt'):
        print file
Nur musst du halt nicht os.listdir() verwenden, da os.walk() dir schon eine Liste von Dateinamen liefert (wie schon erwähnt wurde). Das Ganze könnte dann so aussehen:

Code: Alles auswählen

import os
import fnmatch

for root, dirs, files in os.walk("/home/daniel/Dokumente"):
    for curfile in files:
        path = os.path.join(root, curfile)
        if fnmatch.fnmatch(path, "*Arbeit*"):
            print path
Analog kann man natürlich noch mit den Verzeichnissen in "dirs" verfahren, was besonders dann von Bedeutung ist, wenn man leere Verzeichnisse hat - die werden vom obigen Beispiel noch nicht erfasst.
Antworten