os.rename funktioniert nicht :(

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
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Hallo, ich möchte ein paar Dokumente umbenennen, und bekomme immer folgenden Fehler: "OSError: [WinError 87] Falscher Parameter: Blablabla "

Code: Alles auswählen

import os, time, re
from datetime import datetime

mylist: list = []
i: int = 1
path = input("Verzeichnispfad: ")
for fname in sorted(os.listdir(path), key=lambda f: os.path.getmtime(os.path.join(path, f))):
    print(fname)
    new_fname = fname
    if re.match("^\d{3}_", fname):
        new_fname = new_fname[4:]
    new_fname = (
        str(i).zfill(3)
        +"_"
        +datetime.fromtimestamp(os.path.getmtime(os.path.join(path, fname))).isoformat("_")
        +"_"
        +new_fname
    )
    print(new_fname)
    mylist.append((os.path.join(path, fname), os.path.join(path, new_fname)))
    i += 1
for e in mylist:
    print(e)
    # os.rename(e[0], e[1])
Was mach ich falsch? Python will mich doch veräppeln...
__deets__
User
Beiträge: 14541
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ohne den echten Fehler ist die Antwort auch nur "Das geht aber, Blablabla".
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Die ganzen Typannotationen sind wieder alle überflüssig. Strecke deine Energie lieber in aussagekräftige Variablennamen.
Das os-Modul bietet bitte low-level Funktionen. Benutze pathlib.Path. Strings setzt man nicht mit + zusammen sondern benutzt Formatstrings.
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@greetings1: Hör doch mal bitte mit diesen völlig sinnlosen Typannotationen auf. Und ich meine hier nicht mal generell Typannotationen sondern das die, die Du da machst selbst wenn man Typannotationen ganz toll findet, völlig unnötiger Quatsch sind. Wenn man einem Namen eine Liste oder eine ganze Zahl zuweist, macht es genau überhaupt gar keinen Sinn da zusätzlich noch ``list`` und ``int`` als Annotation dran zu schreiben. Weder der menschliche Leser noch eine statische Typprüfung braucht das. Beide sehen und verstehen welchen Typ der literale Wert hat, der dort zugewiesen wird.

`time` wird importiert aber nirgends verwendet.

`mylist`, `e`, `fname`, und `new_fname` sind keine guten Namen. `my` ist überflüssig und nichtssagend, Grunddatentypen haben in Namen nichts zu suchen, und man sollte keine kryptischen Abkürzungen verwenden. Namen sollen dem Leser verraten was der Wert dahinter bedeutet, nicht zum rätselraten zwingen. Was soll das `f` in `fname` bedeuten? „File“? Dir ist klar das nichts in dem Code verhindert, dass da auch Verzeichnisnamen dran gebunden werden‽

In neuem Code sollte man `pathlib` verwenden.

Für das `i` gibt es `enumerate()`, das zählt man nicht manuell hoch.

Wenn in einer literalen Zeichenkette Backslashes vorkommen, dann sollte man die entweder „escapen“ oder ein ”rohes” Zeichenkettenliteral verwenden, statt darauf zu setzen, dass die Backslashes schon nicht mit Zeichen kombiniert werden die zu einer besonderen Bedeutung führen.

`re.match()` verankert das Muster bereits am Anfang der Zeichenkette, das "^" ist also überflüssig.

Ich würde das so schreiben, dass man für den Slice keine magische 4 in den Quelltext schreiben muss, sondern das von der Match-Länge abhängig machen.

Das zusammenbasteln des neuen Namens mittels ``+`` ist deutlich unübersichtlicher als ein f-Zeichenkettenliteral.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import re
from datetime import datetime as DateTime
from pathlib import Path


def main():
    base_path = Path(input("Verzeichnispfad: "))
    old_and_new_path_pairs = []
    for i, path in enumerate(
        sorted(
            base_path.iterdir(),
            key=lambda path: path.stat().st_mtime,
        ),
        1,
    ):
        print(path)
        new_name = path.name

        match = re.match(r"\d{3}_", new_name)
        if match:
            new_name = new_name[match.end() :]

        timestamp = DateTime.fromtimestamp(path.stat().st_mtime)
        new_name = f"{i:03}_{timestamp:%Y-%m-%d_%H_%M_%S.%f}_{new_name}"
        print(new_name)

        old_and_new_path_pairs.append((path, path.with_name(new_name)))

    for old_path, new_path in old_and_new_path_pairs:
        print(f"{old_path!r} -> {new_path!r}")
        # old_path.rename(new_path)


if __name__ == "__main__":
    main()
Das doppelte Abfragen des Zeitstempels von der Datei könnte man sich noch sparen (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import re
from datetime import datetime as DateTime
from pathlib import Path


def main():
    base_path = Path(input("Verzeichnispfad: "))
    old_and_new_path_pairs = []
    for i, (timestamp, path) in enumerate(
        sorted(
            (DateTime.fromtimestamp(path.stat().st_mtime), path)
            for path in base_path.iterdir()
        ),
        1,
    ):
        print(path)
        new_name = path.name

        match = re.match(r"\d{3}_", new_name)
        if match:
            new_name = new_name[match.end() :]

        new_name = f"{i:03}_{timestamp:%Y-%m-%d_%H_%M_%S.%f}_{new_name}"
        print(new_name)

        old_and_new_path_pairs.append((path, path.with_name(new_name)))

    for old_path, new_path in old_and_new_path_pairs:
        print(f"{old_path!r} -> {new_path!r}")
        # old_path.rename(new_path)


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Die Fehlermeldung war:

Code: Alles auswählen

Traceback (most recent call last):
  File "lable_it.py", line 27, in <module>
    os.rename(e[0], e[1])
OSError: [WinError 87] Falscher Parameter: 'C:\\Users\\xx\\Documents\\xx\\xx\\Vergleich.pdf' -> 'C:\\Users\\xx\\Documents\\xx\\xx\\001_2019-xx-xx_14:xx:54_Vergleich.pdf'
Er kam mit den doppelten \\ nicht zurecht...

Jetzt funktioniert es. Vielen Dank dafür!

Code: Alles auswählen

import re
from datetime import datetime as DateTime
from pathlib import Path

base_path = Path(input("Verzeichnispfad: "))
old_and_new_path_pairs = []
for i, (timestamp, path) in enumerate(
    sorted(
        (DateTime.fromtimestamp(path.stat().st_mtime), path)
        for path in base_path.iterdir()
    ),
    1,
):
    print(path)
    new_name = path.name

    match = re.match(r"\d{3}_", new_name)
    if match:
        new_name = new_name[match.end() :]

    new_name = f"{i:03}_{timestamp:%Y-%m-%d_%H-%M-%S}_{new_name}"
    print(new_name)

    old_and_new_path_pairs.append((path, path.with_name(new_name)))

for old_path, new_path in old_and_new_path_pairs:
    print(f"{old_path!r} -> {new_path!r}")
    old_path.rename(new_path)
Aber eine Kleinigkeit noch... Wenn ich das Script nochmals aufrufen würde, würde er zwar das Präfix "xxx_" entfernen, jedoch wäre das Datum dann doppelt in dem Dateinamen vorhanden.
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@greetings1: Nope, das war ganz sicher nicht der doppelte \, denn da gab es nirgends einen doppelten \. Nur viele einfache:

Code: Alles auswählen

In [195]: len("\\")                                                             
Out[195]: 1

In [196]: print("\\")                                                           
\

In [197]: print(repr("\\"))                                                     
'\\'
Was Windows nicht mag sind Doppelpunkte in Dateinamen, darum habe ich nicht `isoformat()` verwendet, sondern das selbst ohne Doppelpunkte so ähnlich formatiert.

Für die Kleinigkeit musst Du halt den Zeitstempel auch mit in den regulären Ausdruck aufnehmen. Hier zeigt sich dann auch warum es gut ist die 4 nicht hart in den Quelltext zu schreiben, sondern vom `match` abzufragen. Der Wert stimmt dann automatisch wenn man den regulären Ausdruck anpasst.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
greetings1
User
Beiträge: 51
Registriert: Donnerstag 22. Oktober 2020, 18:19

Vielen Dank, ich wusste gar nicht, dass :-Zeichen nicht zulässig sind. ;)

Der RegEx wäre dann:

Code: Alles auswählen

match = re.match(r"\d{3}_\d\d-\d\d-\d\d_\d\d-\d\d-\d\d_", new_name)
ja?

Und wo kann ich eigentlich mehr über dieses f" lernen? Ist das eine format-Funktion?

Edit: Pardon: meinte:

Code: Alles auswählen

    match = re.match(r"^\d{3}_.{10}_.{8}_", new_name)
Funktioniert prima. ;)
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@greetings1: In der Python-Dokumentation ist oben rechts auf so ziemlich jeder Seite ein Link zum Index. Da dann "F" anklicken und der Eintrag für f-Zeichenkettenliterale ist da dann auch gleich als erstes. 🙂
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten