Seite 1 von 1

fileNameCorrector

Verfasst: Samstag 15. April 2006, 13:55
von icepacker
Hi
Nach einiger Zeit mit der ich mich nun mit Python beschäftige, habe ich jetzt
mal die Gelegenheit gefunden mein erstes sinvolles (wenn man von Spielen
absieht :wink: ) Anwendungsprogramm zu schreiben.

Ich musste in einem ganzen batzen von Dateien die Groß-/Kleinschreibung
der Dateinamen ändern. Mein Script, dass diese Aufgabe löst wil ich euch
natürlich nicht vorenthalten :D

Ich poste es natürlich auch hier, damit ihr das Script verbessern könnt...
Zum Beispiel weiß ich nicht, ob hier OOP Sinn machen würde.
(Von Python mit OOP bin ich sowieso abgeschreckt; da blick ich echt nicht
mehr durch, ob jetzt New-Style Klassen oder doch nicht? oder was ganz
anderes? :? )

Naja hier mal das Script :wink:

Code: Alles auswählen

import os

def getFileNames(dir):
	"""Hohlt alle Dateinamen aus dem Verzeichnis"""
	getFiles = []

	for file in os.listdir(dir):
		getFiles.append(file)

	return getFiles
	

def correctFileNames(fileNames):
	""" Korrigiert die Dateinamen.

	zB: aus 0596000855_python2-chp-2-sect-12.html
	wird: 0596000855_python2-CHP-2-SECT-12.html
	"""
	newFileNames = []

	for i in range(len(fileNames)):
		s = fileNames[i].split('-', 1)  # ['0596000855_python2', 'chp-2-sect-12.html']
		str = s[1].split('.')    # ['chp-2-sect-12', 'html']
		str[0] = str[0].upper() # ['CHP-2-SECT-12', 'html']
	
		# ['0596000855_python2', '-', 'CHP-2-SECT-12', '.', 'html']
		newFileNames.append(s[0] + '-' + str[0] + '.' + str[1]) 

	return newFileNames


def renamesTheFiles(oldFileNames, newFileNames, dir):
	# Ins betreffende Verzeichnis wechseln
	os.chdir(dir)
	
	# Die Dateinamen aendern
	for i in range(len(oldFileNames)):
		os.rename(oldFileNames[i], newFileNames[i])


def main():
	oldFileNames = []
	newFileNames = []
	dir = 'testordner'

	oldFileNames = getFileNames(dir)
	newFileNames = correctFileNames(oldFileNames)
	renamesTheFiles(oldFileNames, newFileNames, dir)


if __name__ == '__main__':
	main()
lg icepacker

Verfasst: Samstag 15. April 2006, 14:42
von modelnine
Ich liebe jegliche Form von Generator-/List-Comprehensions. Deswegen mal das ganze, nur als eine List-Comprehension, mit genügend Fehlerprüfungen (die das Ursprungsprogramm nicht hat), und wenn man die ganzen NLs die zur besseren Lesbarkeit drin sind rausnehmen würde fast ein Einzeiler :-)

Code: Alles auswählen

from os import listdir, rename
from os.path import isfile, join

curdir = "."
[rename(join(curdir,src),join(curdir,dst))
 for src, dst in
 [(x[0],"%s-%s.%s" % (x[1],x[2].upper(),x[3]))
  for x in
  [(oldname,)+
   (lambda a,b,c=(lambda d:tuple(d.split("."))): (a,)+c(b))
   (*oldname.split("-",1))
   for oldname in listdir(curdir)
   if isfile(join(curdir,oldname)) and "-" in oldname and "." in oldname]
  if len(x) == 4]
 if src <> dst]
Wer kann den noch weiter optimieren? Mir gefällt die ganz innere Schleife eher nicht; was besseres ist mir aber im Moment nicht eingefallen...

Verfasst: Samstag 15. April 2006, 15:05
von gerold
modelnine hat geschrieben:...fast ein Einzeiler :-)
Hi modelnine!

Ich möchte dich nur mal zwischendurch an import this erinnern. :mrgreen:

lg
Gerold
:-)

Verfasst: Samstag 15. April 2006, 15:07
von modelnine
Ich möchte dich nur mal zwischendurch an import this erinnern.
LOL :-) Ich weiß, ich weiß... Ich bin aber nun mal ein Fan der funktionalen Programmierung... ;-)

Verfasst: Samstag 15. April 2006, 16:11
von icepacker
gerold hat geschrieben:an import this erinnern.
hehe sehr treffend :lol:

Deswegen überlege ich auch gerade, ob ich überhaupt versuchen soll den Code
von modelnine zu verstehen :shock: Aber auf jedenfall beeindruckend.

Nee ich denke ich warte erstmal... :wink: vielleicht gibts ja noch weitere Vorschläge,
Verbesserungen.

lg icepacker

Verfasst: Samstag 15. April 2006, 17:57
von modelnine
Deswegen überlege ich auch gerade, ob ich überhaupt versuchen soll den Code
von modelnine zu verstehen
Auf jeden Fall! Im Endeffekt ist der Code zwar "unpythonisch," (in gewisser Weise, ich stehe dazu dass ich ihn nicht komplett "unpythonisch" finde!) weil import this zum Beispiel auch davon redet dass explizit besser als implizit ist (und der Code ziemlich auf Seiteneffekte der Auswertung einer List-Comprehension abziehlt), aber in anderen Programmiersprachen, gerade in funktionalen, wird so programmiert (in Methoden aufgeteilt, aber trotzdem siehts im Endeffekt so aus), deswegen, gerade wenn Du auch mal ein Fünkchen funktionale Programmierung sehen willst ist es sinnvoll wenn Du zumindest probierst den groben Ablauf der Evaluierung zu verstehen.

Der Code ist (entgegen dem was gerold suggeriert ;-)) wirklich nicht schwer zu verstehen, gerade wenn man einfach mal guckt wie der von innen nach außen ausgewertet wird. Vielleicht macht das ganze (oldname,)+lambda()()-blubb ein bisschen Mühe beim verstehen, aber im Endeffekt macht das nichts anderes als aus einem String "old-name.html" ein Tupel ("old-name.html","old","name","html") zu machen. Der Rest sollte wirklich kein Problem sein, wenn man halbwegs versteht was eine List-Comprehension macht, und dass es im Endeffekt nur eine Kurzschreibweise für eine for-Schleife ist.

Verfasst: Samstag 15. April 2006, 22:01
von Marten2k
Hallo icepacker,

ich habe mir gerade mal den Code so grob angeschaut. Ein paar Verbesserungsvorschläge hätte ich.
1. Wieso kuerzt du nicht:

Code: Alles auswählen

def getFilenames(dir):
   return os.listdir(dir)
oder nocht besser:

Code: Alles auswählen

def getFilenames(dir):
    os.chdir(dir)
    return glob.glob("*.html")
2. Zum zusammenfuehren und splitten von Pfaden wuerde ich dir
os.path.join(...) und os.path.split..(...) empfehlen. Die Funktionen achten auch auf die '\' am Ende usw.
Also erst die Erweiterung abspalten mit os.path.splitext(...) und dann die Dateienamen splitten, wenn du es brauchst.

3. unter der Annahme, dass du immer nur chp in CHP und sec in SEC aendern willst, wuerde ich den dateinamen garnicht splitten und nur string.replace("chp","CHP")... verwenden.

4. Wenn du auch die Unterverzeichnisse durchlaufen willst wuerde
ich os.walk empfehlen.

Hier mal meine Version (nur im Kopf entstanden):

Code: Alles auswählen

def Change(aFilename):
   # Hier deine Aenderrungen an den Dateinamen.
   # Wenn immer nur 'chp' in 'CHP usw. dann wuerde
   # ich string.replace nutzen
   aNewFilename = aHtmlFilename.replace("chp","CHP")
   try:
      os.rename(aFilename,aNewFilename)
   except:
      return False
   else:
      return True

def main():
   for Root, SubDirs, Files in os.walk(<Pfad>):
         Renamed = [Change(File) for File in Files]
         # Vielleicht noch besser
         # NotRenamed = [File for File in Files if not Change(File)]
         # print "ERRORS: %s"%NotRenamed
Der Code oben soll nur ein grob Vorlage sein. Es wird nicht geprueft ob die Dateien ueberhaupt die Endung .html haben.

Gruss
Marten

Re: fileNameCorrector

Verfasst: Sonntag 16. April 2006, 08:20
von BlackJack
icepacker hat geschrieben:Ich poste es natürlich auch hier, damit ihr das Script verbessern könnt...
Na dann mal los... :-)
Zum Beispiel weiß ich nicht, ob hier OOP Sinn machen würde.
(Von Python mit OOP bin ich sowieso abgeschreckt; da blick ich echt nicht
mehr durch, ob jetzt New-Style Klassen oder doch nicht? oder was ganz
anderes? :? )
OOP würde hier nicht wirklich Sinn machen, das ist also schon mal okay so. Aber OOP in Python ist eigentlich recht einfach. Die Frage nach New-Style-Klassen oder nicht ist bis zu einem gewissen Punkt wohl Geschmackssache. Man braucht sie auf jeden Fall, wenn man Properties verwenden will.

Code: Alles auswählen

import os

def getFileNames(dir):
	"""Hohlt alle Dateinamen aus dem Verzeichnis"""
	getFiles = []

	for file in os.listdir(dir):
		getFiles.append(file)

	return getFiles
Das man diese Funktion einfach durch den direkten Aufruf von `os.listdir()` ersetzen kann, wurde ja schon gesagt. Das liefert schon eine List mit den Namen zurück.

Zwei Anmerkungen zu den Namen: 1. Laut PEP 8 (Styleguide) sollte man ausser für Klassennamen Kleinschreibung und Unterstriche verwenden. Also `get_filenames` statt `getFileNames` und 2. ist es keine gute Idee die Namen von eingebauten Objekten an andere Objekte zu binden. In der Funktion betrifft das `file` und `dir` wie man am Syntax-Highlighting erkennen kann.

Code: Alles auswählen

def correctFileNames(fileNames):
	""" Korrigiert die Dateinamen.

	zB: aus 0596000855_python2-chp-2-sect-12.html
	wird: 0596000855_python2-CHP-2-SECT-12.html
	"""
	newFileNames = []

	for i in range(len(fileNames)):
		s = fileNames[i].split('-', 1)  # ['0596000855_python2', 'chp-2-sect-12.html']
		str = s[1].split('.')    # ['chp-2-sect-12', 'html']
		str[0] = str[0].upper() # ['CHP-2-SECT-12', 'html']
	
		# ['0596000855_python2', '-', 'CHP-2-SECT-12', '.', 'html']
		newFileNames.append(s[0] + '-' + str[0] + '.' + str[1]) 

	return newFileNames
Hier hast Du das eingebaute `str` neu gebunden.

Und Du benutzt für die Schleife ein Idiom das man meistens bei Leuten findet, die von anderen Programmiersprachen kommen. Immer wenn Du in einer Schleife `range(len(irgendwas))` siehst, dann solltest Du überlegen ob's nicht einfacher geht. Wenn Du den Index nur benutzt um dann sequentiell über das `irgendwas` zu iterieren, dann kannst Du Dir den Index sparen und gleich direkt über die Elemente des `irgendwas` iterieren. Das liesse sich zum Beispiel auch so schreiben:

Code: Alles auswählen

    for filename in filenames:
        prefix, tail = filename.split('-', 1)
        tail, extension = os.path.splitext(tail)
        new_filenames.append('%s-%s.%s' % (prefix, tail.upper(), extension))
Ganz ohne Indexe. Ist oft viel übersichtlicher.

Code: Alles auswählen

def renamesTheFiles(oldFileNames, newFileNames, dir):
	# Ins betreffende Verzeichnis wechseln
	os.chdir(dir)
	
	# Die Dateinamen aendern
	for i in range(len(oldFileNames)):
		os.rename(oldFileNames[i], newFileNames[i])
Auch hier kann man den Index loswerden in dem man entweder das eingebaute `zip()` oder `itertools.izip()` verwendet:

Code: Alles auswählen

    for old_name, new_name in zip(old_filenames, new_filenames):
        os.rename(old_name, new_name)
Es geht beim loswerden des Index übrigens nicht nur um den "ästhetischen" Aspekt oder das man dann eine lokale Variable weniger hat, um die man sich Gedanken machen muss. Mit Index funktioniert der Code nur mit Sequenzen, wenn man direkt über die Objekte iteriert, dann funktioniert er auch mit Iteratoren und Generatoren, also Objekten die erst bei Bedarf den nächsten Wert berechnen könnten.

In die Richtung könnte man den Code auch umschreiben. Man muss natürlich nicht ganz so extrem werden wie modelnine, aber wenn man das Program so gestaltet, das in jedem Schritt immer nur ein Name verarbeitet wird, dann spart man Speicherplatz für die Listen mit den Zwischenergebnissen.

Verfasst: Sonntag 16. April 2006, 11:03
von mawe
Hi!

Blackjack's Vorschlag, gepaart mit einem

Code: Alles auswählen

for filename in glob.glob(path):
oder

Code: Alles auswählen

for filename in glob.glob(path, "*.html"):
ist meiner Meinung nach eine recht elegante Lösung.
modeline hat geschrieben: gerade wenn Du auch mal ein Fünkchen funktionale Programmierung sehen willst ist es sinnvoll wenn Du zumindest probierst den groben Ablauf der Evaluierung zu verstehen.
Davon würde ich eher abraten :) So eine lange "Wurst" schreibt niemand (und will auch normalerweise niemand schreiben, vom Lesen ganz zu schweigen), auch nicht in "funktionalen Programmersprachen".

Gruß, mawe

Verfasst: Sonntag 16. April 2006, 11:07
von modelnine
So eine lange "Wurst" schreibt niemand (und will auch normalerweise niemand schreiben, vom Lesen ganz zu schweigen), auch nicht in "funktionalen Programmersprachen".
Huh? Das sind einfach drei ineinander verschachtelte Schleifen. Mehr nicht. Und wie gesagt, mit der inneren bin ich nicht zufrieden (habs aber nicht anders hinbekommen ohne eine Hilfsmethode), aber bis auf den lambda-Hack ist das genau das was man in einer normalen funktionalen Programmiersprache zu sehen bekommt, ich denke da nur an meine SML-Programmierung an der Uni (unter anderem, ich hab 'ne Zeitlang auch an MLDonkey mitentwickelt, und der Code sieht in vielen Teilen genau so aus).

Unabhängig davon, ich bin ModelNine, und keine Mode-Line. ;-)

PS: Ich gehöre nicht zu den Leuten die Perl in Python schreiben wollen. Nur, wenn's mittels ein bisschen funktionaler Programmierung einen angenehmen Einzeiler gibt für eine Methode, dann bin ich sofort dafür. Gerade List-Comprehensions um des Nebeneffekts der Auswertung eines Ausdrucks in einer Schleife Willen sieht man in meinen Programmen relativ oft (ob das jetzt mit LC/GC, reduce/map/was auch immer ist ist dabei relativ wurscht).

Verfasst: Sonntag 16. April 2006, 11:26
von mawe
modelnine hat geschrieben: Unabhängig davon, ich bin ModelNine, und keine Mode-Line.
Hoppla, das n hab ich bis jetzt völlig übersehen :)
modelnine hat geschrieben: Huh? Das sind einfach drei ineinander verschachtelte Schleifen. Mehr nicht.
Na das reicht doch :) Zwei LC ineinander ist schon mühsam zu lesen, aber drei ... das ist grausam :) Für mich jedenfalls. Naja, die Geschmäcker sind verschieden.
modelnine hat geschrieben: unter anderem, ich hab 'ne Zeitlang auch an MLDonkey mitentwickelt, und der Code sieht in vielen Teilen genau so aus
Dann hast Du wahrscheinlich schon Übung im Lesen von solchen Konstrukten.
Ich versuche sie zu vermeiden, auch wenn ich in OCaml programmiere.
modelnine hat geschrieben: Nur, wenn's mittels ein bisschen funktionaler Programmierung einen angenehmen Einzeiler gibt für eine Methode, dann bin ich sofort dafür.
Ich bin der letzte der etwas gegen funktionale Programmierung hat, ich programmiere selbst liebend gerne in OCaml. Aber das was Du hier gepostest hast, ist - das ist jetzt natürlich wieder meine ganz persönliche Meinung - _kein_ "angenehmer Einzeiler" :)

Verfasst: Sonntag 16. April 2006, 11:52
von gerold
modelnine hat geschrieben:Das sind einfach drei ineinander verschachtelte Schleifen. Mehr nicht.
Hi modelnine!

Auch wenn ich mich damit bei dir unbeliebt mache. Das ist, in meinen Augen, kein schöner Code.

Schöner Code zeichnet sich unter anderem dadurch aus, dass ein Dritter den Code ziemlich schnell durchschauen kann und diesen leicht erweitern kann. Und das ohne ihn drei mal durchlesen zu müssen.
Bevor jemand diesen Code weiterverwendet, schreibt er ihn lieber neu. Das klingt doch eher nach Perl als nach Python, oder?

Es ist alles in einer Wurst geschrieben. Keine Kommentare. Keine Blöcke die einzelne Arbeitsschritte optisch trennen. Das ist mehr ein Intelligenztest als schöner Code.

Python ist auch deshalb Python, weil es versucht, lesbaren/wartbaren Code zu unterstützen und teilweise auch zu erzwingen. Leider funktioniert das nicht immer, wie man sieht. Wenn ich öfter solchen Code analysieren müsste, dann würde mir ziemlich bald der Spaß an Python verloren gehen.

Nichts für ungut. Es ist nicht alles falsch, was mir nicht gefällt. :wink:

lg
Gerold
:-)

Verfasst: Sonntag 16. April 2006, 13:04
von modelnine
Auch wenn ich mich damit bei dir unbeliebt mache. Das ist, in meinen Augen, kein schöner Code.
Huh? Wie solltest Du Dich unbeliebt machen, außer wenn Du eine unsachliche Diskussion anfangen würdest? Also, ich bin mehr als genügend kritikfähig (zumindest im Normalfall ;-)), dass ich solche Kritik durchaus verstehe, und sie manchmal sogar befolge, und im besonderen auch auf den Menschen der mich kritisiert hat eigentlich nie sauer bin. ;-)

Aber, unabhängig davon, ich denk dass mawe schon ganz recht hat, dass das eine "Erziehungssache" ist. Ich persönlich finde LCs, auch geschachtelte, nichts was man dokumentieren oder sonstwie anders machen müsste, ganz im Gegenteil, ich find sie im Normalfall sogar einfacher zu verstehen als der ausgeschriebene Schleifen-Code, weil dieser erheblich länger ist, und normalerweise auch mit continue oder ähnlichem auskommen muß aufgrund des if-s, was die Logik dann eben nicht mehr "trivial sequentiell" macht, wie eine List-Comprehension. Ich kenn diese Art des Programmierens (wie ich das probiert hab in dem Beispiel zu machen) zur genüge aus funktionalen Sprachen, und ganz allgemein find ich, wenn man sich einmal darauf eingelassen hat das zu verstehen, dann ist es auch kein Problem mit solchen Konstrukten umzugehen, relativ egal wie tief verschachtelt es ist.

Hier gilt aber sicherlich, wie wie bei vielen Dingen, dass man entweder ein Auge für solche Konstrukte hat und sie übersichtlich findet (wie gesagt, das lambda()-Konstrukt find ich _nicht_ übersichtlich, mir ist aber nix besseres dafür eingefallen, weil Python keine partielle Applikation einer LC auf einen Sequenztyp kann), oder eben nicht. Mir persönlich fällts sehr viel schwerer einen langen, verschachtelten, if-break-continue-reichen Schleifencode zu verstehen.

Und zu guter letzt:
Es ist alles in einer Wurst geschrieben. Keine Kommentare. Keine Blöcke die einzelne Arbeitsschritte optisch trennen. Das ist mehr ein Intelligenztest als schöner Code.
Ohne, dass ich Dich jetzt irgendwie auf die Palme bringen will:

1) es ist keine Wurst, weil ich die LCs eben getrennt habe, und jeden logischen Block der LC auf eine Zeile gesetzt hab. Ohne dieses Formatierungskriterium versteh ich den Block auch nicht sofort.

2) Kommentare sind aus meiner Sicht das unwichtigste an einem Programm, gerade bei "trivialen" Dingen wie soetwas. Entweder das Programm ist selbstverständlich (was ich eben hier finde), oder es ist es nicht, und gerade dann sollte man überlegen, inwiefern man es neu schreibt. Wenn man Kommentare braucht, dann nur, weil man die Programmlogik nicht direkt aus dem Code erkennen kann, oder Hinweise geben will. Für so etwas wie hier sind Kommentare aber eher hinderlich als föderlich. (was will man schreiben? "# Hier wird src -> dst umbenannt" ist der nutzloseste Kommentar des Jahrhunderts)

3) Natürlich trennen auch hier Blöcke die Arbeitsschritte, auch wenn die mit [ beginnen und mit ] aufhören, und deswegen nicht umbedingt Python-typisch sind. ;-)

4) Intelligenztest ist das sicherlich nicht, sonst könnte ich den Code nicht einfach so runterschreiben. ;-)

Aber, wie gesagt, ich denk dass wir uns hier wirklich in einem Feld befinden wo Geschmäcker viel ausmachen, und auch der persönliche Hintergrund. icepacker, lern's so wie mawe und gerold es vorgeschlagen haben, ich bin nicht repräsentativ für einen guten Python-Programmierer. ;-)

Verfasst: Samstag 22. April 2006, 18:26
von icepacker
Hi
Vorweg erstmal: Danke für die rege Beteiligung eurerseits und sorry hat ein bisschen gedauert.

BlackJack hat geschrieben:1. Laut PEP 8 (Styleguide) sollte man ausser für Klassennamen Kleinschreibung und Unterstriche verwenden. Also `get_filenames` statt `getFileNames`
Schade :( Weil mit Kamelhöckern (heißt doch so oder?) lässt es sich imo besser
schreiben, als immer diese Unterstriche zu benutzen. Aber gut dann halte ich
mich eben an den Codingstyle.
BlackJack hat geschrieben:2. ist es keine gute Idee die Namen von eingebauten Objekten an andere Objekte zu binden. In der Funktion betrifft das `file` und `dir` wie man am Syntax-Highlighting erkennen kann.
Ja das ist mir auch schon aufgefallen. Leider bin ich bei Variablennamen benennen
nicht sehr kreativ.
BlackJack hat geschrieben:Und Du benutzt für die Schleife ein Idiom das man meistens bei Leuten findet, die von anderen Programmiersprachen kommen.
Ertappt :o
Aber so langsam finde ich gefallen an dieser Art von For-Schleife.
modelnine hat geschrieben: Aber, wie gesagt, ich denk dass wir uns hier wirklich in einem Feld befinden wo Geschmäcker viel ausmachen, und auch der persönliche Hintergrund. icepacker, lern's so wie mawe und gerold es vorgeschlagen haben, ich bin nicht repräsentativ für einen guten Python-Programmierer. Wink
Ja so werde ich es erstmal machen. Aber andererseits finde dieses LC schon ziemlich
Interessant und werde mir das auf jedenfall mal angucken!
Schließlich kann es denke ich nicht schaden, wenn man sich verschiedene
Programmierparadigma-/techniken aneignet :)


So jetzt zurück zum Code :D
Nach euren Vorschlägen habe ich ein bisschen weiter rumgebastelt und jetzt
ist imo ein recht schönes Ergebnis beirausgekommen.
Der Code ist jetzt nur noch halb so lang, leichter zu lesen und leichter anzupassen!

Code: Alles auswählen

#! /usr/bin/python
import os, glob


def rename_files(path):
	os.chdir(path)
	
	for filename in glob.glob('*[chp]*.html'):
		os.rename(filename, change_filename(filename))


def change_filename(filename):
	prefix, tail = filename.split('-', 1)
	tail, extension = os.path.splitext(tail)
	return '%s-%s%s' % (prefix, tail.upper(), extension) 


def main():
	path = '.'
	rename_files(path)


if __name__ == '__main__':
	main()
lg icepacker

Ps: Natürlich ist weiterhin Feedback erwünscht.