PyCopy Erstlingswerk

Code-Stücke können hier veröffentlicht werden.
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hallo ich habe mich vor einigen Jahren als blutiger Python-Anfänger (der ich immer noch bin) mal an folgendem Datei-Kopierprogramm versucht.
Ich sags mal gleich vorweg - die Struktur wird manchem merkwürdig vorkommen, das ganze Programm ist zuerst im IBM-Mainframe Umfeld (zOS) entstanden und ursprünglich nur als Backup-Tool.
Es hat mir aber gefallen und ich habe es angepaßt an Windows, Linux sollte auch gehen habe es aber lange nicht getestet .

Code: Alles auswählen

import os
import argparse
import shutil
from datetime import datetime

_VSC_ = 0

now = datetime.now()
# _dyyyymmdd_thhmmss
dt_string = now.strftime("_d%Y%m%d_t%H%M%S")

if _VSC_ == 1:
    root = "c:/temp/pycopy/"
else:
    root = os.getcwd().lower().replace("\\","/") + "/"

parser = argparse.ArgumentParser()
parser.add_argument('--file', help='Filename for PDS')
parser.add_argument('--prefix', help='Prefix added before target')
parser.add_argument('--verbose', help='Report every single copy [N]/Y')
parser.add_argument('--wait', help='Ask before copy [N]/Y')
parser.add_argument('--source', help='Single Source folder, may be .')
parser.add_argument('--target', help='Single Target folder')
parser.add_argument('--extfolder', help='Extern folder')
parser.add_argument('--recursive', help='Also copy subdirectories [Y]/N')
parser.add_argument('--homedouble', help='Allow double copy of folders in home [N]/Y')
parser.add_argument('--backupdouble', help='Allow also backup of _home [N]/Y')
parser.add_argument('--foldertimestamp', help='Add Timestamp to folder (strongly advised!) [Y]/N')
parser.add_argument('--excludetar', help='Do not store (old) .tar files in new backup [Y]/N')
args = parser.parse_args()

print("\npycopy start\n")

if args.file == None:
    datei = "copylist.txt"
else:
    datei = str(args.file)
    # knock out --source flag
    args.source = None
# print("Datei:", datei)

if args.prefix == None:
    prefix = ""
else:
    prefix = str(args.prefix) + "/"
# print("Prefix:", prefix)

if args.verbose == None:
    verbose = False
else:
    if str(args.verbose).upper()[0] == "Y":
        verbose = True
    else:
        verbose = False
# print("Verbose:", verbose)

if args.wait == None:
    wait = False
else:
    if str(args.wait).upper()[0] == "Y":
        wait = True
    else:
        wait = False
# print("Wait:", wait)

if args.source == None:
    single_source = ""
else:
    single_source = str(args.source) + ""
# print("Source:", single_source)

if args.target == None:
    single_target = ""
else:
    single_target = str(args.target) + ""
# print("Target:", single_target)
if args.extfolder == None:
    extfolder = ""
else:
    extfolder = str(args.extfolder).lower().replace("\\","/") + "/"
#print("Extfolder:", extfolder)


if args.recursive == None:
    recursive = True
else:
    if str(args.recursive).upper()[0] == "N":
        recursive = False
    else:
        recursive = True
# print("Recursive:", recursive)

if args.homedouble == None:
    allow_double_home = False
else:
    if str(args.homedouble).upper()[0] == "Y":
        allow_double_home = True
    else:
        allow_double_home = False
# print("Allow double home folders:", allow_double_home)

if args.backupdouble == None:
    allow_double_backup = False
else:
    if str(args.backupdouble).upper()[0] == "Y":
        allow_double_backup = True
    else:
        allow_double_backup = False
# print("Allow double backup folders:", allow_double_backup)

if args.excludetar == None:
    exclude_tar = True
else:
    if str(args.excludetar).upper()[0] == "N":
        exclude_tar = False
    else:
        exclude_tar = True
# print("exclude (old) .tar files from new backup:", exclude_tar)

if args.foldertimestamp == None:
    folder_timestamp = True
else:
    if str(args.foldertimestamp).upper()[0] == "N":
        folder_timestamp = False
        # To prevent overwritten folders
        if len(prefix) == 0:
            folder_timestamp = True
    else:
        folder_timestamp = True
# print("Use timestamp for folders:", folder_timestamp)

if not folder_timestamp:
    dt_string = ""
    print("root:", root, "NO dt_string")
else:
    print("root:", root, "dt_string:", dt_string)

def source_liste(multiples):
    new_list = []
    for eintrag in multiples:
        eintrag0 = eintrag.strip()
        if eintrag0 == ".":
            source = root
            target = root + prefix + "_home" + dt_string + "/"
        else:
            source = root + eintrag0 + "/"
            target = root + prefix + eintrag0 + dt_string + "/"
        new_list.append({'source' : source, 'target' :target})
    return new_list

def source_single(source, target):
    if len(extfolder) == 0:
        folder = root
    else:
        folder = extfolder

    new_list = []
    if source == ".":
        source1 = root
        target1 = folder + prefix + "_home" + dt_string + "/"
        # target1 = root + prefix + "_home" + dt_string + "/"
    else:
        source1 = root + source + "/"
        target1 = folder + prefix + target + dt_string + "/"
        # target1 = root + prefix + target + dt_string + "/"
    new_list.append({'source' : source1, 'target' :target1})
    return new_list

liste = []

if args.source == None:
    try:
        liste = source_liste(open(root + datei, "r").read().splitlines())
    except FileNotFoundError:
        liste = []
        print("File Open Error:", datei)
elif len(single_source) > 0:
    if len(single_target) == 0:
        single_target = single_source
    liste = source_single(single_source, single_target)

# print("Liste:", liste)

def copy_windows(source, target):
    global done_liste
    members = []
    source_show = source.replace(root, "")
    if source_show == "":
        source_show = "."
    else:
        source_show = source_show[:-1]
    target_show = target.replace(root, "")
    target_show = target_show[:-1]
    # print("source", source)
    if not os.path.exists(source):
        print(source, "not found")
        return

    members = os.listdir(source)
    if len(members) == 0:
        print("No members found in", source)
        return

    if verbose:
        print("\nSource:", source)
        print("Target:", target)
        print("Copy following members:")
        print(', '.join(members))
    else:
        print("\nProcessing:", source)

    if wait:
        yn = input("Proceed Y/(N) : ")
    else:
        yn = "Y"

    if yn.upper() == "Y":
        rc = 0
        # print("target", target)
        if not os.path.exists(target):
            os.makedirs(target)
            print(target, "created")

        for member in members:
            # do not trap into recursion
            if member == prefix[:prefix.find("/")]:
                continue
            # do not save old tar to new backup
            if exclude_tar:
                if member.find(".tar") > 0:
                    continue
            if not os.path.isfile(source + member):
                recurse_dir = True
                for eintrag in liste + done_liste:
                    if eintrag['source'] == source + member + "/":
                        recurse_dir = False
                if not allow_double_backup and source == root and member[:5] == "_home":
                    recurse_dir = False
                if recurse_dir:
                    if recursive:
                        copy_windows(source + member + "/", target + member + "/")

                # This was subdir and/or has already done. Northing else to copy
                continue

            if shutil.copyfile(source + member, target + member) != None:
                if verbose:
                    print(member, "copied from", source_show, "to", target_show)
                rc += 1

        if rc > 0:
            print(rc, "members copied")

    if not allow_double_home:
        done_liste.append({'source' : source, 'target' : None})
    done_liste.append({'source' : target, 'target' : None})

done_liste = []
for einzel in liste:
    copy_windows(einzel['source'], einzel['target'])

print("\npycopy done\n")
Ich möchte mich mal der ggf. vernichtenden Kritik stellen und fragen, was kann ich besser machen, was vereinfachen und welche Ideen habt Ihr.

Was mir heute auf Anhieb auffällt - das mit den Parametern geht nicht so. Man müßte es zur Zeit so aufrufen

Code: Alles auswählen

python pycopy.py
dann wird die copylist.txt ausgewertet

oder so

Code: Alles auswählen

python pycopy.py --soruce test --target backup2
Kann man die Schlüsselwörter nicht abkürzen auf 1 Buchstaben und/oder mit Stellungsparametern arbeiten?

Code: Alles auswählen


python pycopy.py test 
Kann man verhindern, daß die Datum/Zeit immer neu geschrieben wird (bei den Dateien zumindestens, vielleicht auch die Verzeichnisse).

Das Programm sollte für einzelne Dateien auch finktionieren nicht nur für Verzeichnisse.

Ich habe eine Test-Umgebung hier eingestellt. Ich habe es auch mit größeren Dateien versucht, aber ist eben Python/Script, etwas langsam.

https://github.com/bernd22/pycopy
Benutzeravatar
Dennis89
User
Beiträge: 1503
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,
Kann man die Schlüsselwörter nicht abkürzen auf 1 Buchstaben und/oder mit Stellungsparametern arbeiten?
Kann man:

Code: Alles auswählen

#!/usr/bin/env python
from argparse import ArgumentParser


def main():
    parser = ArgumentParser(description='Write here your description')
    parser.add_argument('first', type=str, help='Enter first argument as string')
    parser.add_argument('-s', type=int, help='Enter 2nd argument as integer')
    parser.add_argument('-something_else', help='Enter what you want')
    arguments = parser.parse_args()
    print("See your arguments in order you wrote them:")
    print(arguments.first)
    print(arguments.s)
    print(arguments.something_else)


if __name__ == "__main__":
    main()
Sieht dann ausgeführt so aus:

Code: Alles auswählen

(venv) [dennis@dennis Forum]$ ./main.py Hallo -s 3 -something_else 12AB
See your arguments in order you wrote them:
Hallo
3
12AB
Argumente ohne führenden Strich sind zwingend anzugeben, die anderen sind optional.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei ich `s` dann einen weiteren, verständlicheren Namen geben würde.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Okay danke, habe mich mit dem parser noch mal belesen und das so denke ich etwas vereinfacht auch mit Y/N Flags.

Code: Alles auswählen

import os
import argparse
import shutil
from datetime import datetime

_VSC_ = 0
_TEST_ = False
now = datetime.now()
# _dyyyymmdd_thhmmss
dt_string = now.strftime("_d%Y%m%d_t%H%M%S")

if _VSC_ == 1:
    root = "c:/temp/pycopy/"
else:
    root = os.getcwd().lower().replace("\\","/") + "/"

parser = argparse.ArgumentParser()
parser.add_argument('--file' , '-f', help='Filename for PDS')
parser.add_argument('--prefix', '-p', help='Prefix added before target')
parser.add_argument('--verbose', '-v', action='store_true', help='Report every single copy')
parser.add_argument('--wait', '-w', action='store_true', help='Ask before copy')
parser.add_argument('--source', '-i', help='Single Source folder, may be .')
parser.add_argument('--target', '-o', help='Single Target folder')
parser.add_argument('--extfolder', '-e', help='Extern folder')
parser.add_argument('--recursive', '-r', action='store_false', help='Do not copy subdirectories')
parser.add_argument('--homedouble', '-d', action='store_true', help='Allow double copy of folders in home')
parser.add_argument('--backupdouble', '-b', action='store_true', help='Allow also backup of _home')
parser.add_argument('--foldertimestamp', '-t', action='store_false', help='Do not Add Timestamp to folder (not advised!)')
parser.add_argument('--excludetar', '-x', action='store_false', help='Do not store (old) .tar files in new backup')
args = parser.parse_args()

print("\npycopy start\n")

datei = "copylist.txt"
if args.file:
    datei = str(args.file)
    # knock out --source flag
    args.source = None
if _TEST_:
    print("Datei:", datei)

prefix = ""
if args.prefix:
    prefix = str(args.prefix) + "/"
else:
    # To prevent overwritten folders
    args.foldertimestamp = True
if _TEST_:
    print("Prefix:", prefix)

verbose = args.verbose
if _TEST_:
    print("Verbose:", verbose)

wait = args.wait
if _TEST_:
    print("Wait:", wait)

single_source = str(args.source) + ""
if _TEST_:
    print("Source:", single_source)

single_target = str(args.target) + ""
if _TEST_:
    print("Target:", single_target)

extfolder = ""
if args.extfolder:
    extfolder = str(args.extfolder).lower().replace("\\","/") + "/"
if _TEST_:
    print("Extfolder:", extfolder)

recursive = args.recursive
if _TEST_:
    print("Recursive:", recursive)

allow_double_home = args.homedouble
if _TEST_:
    print("Allow double home folders:", allow_double_home)

allow_double_backup = args.backupdouble
if _TEST_:
    print("Allow double backup folders:", allow_double_backup)

exclude_tar = args.excludetar
if _TEST_:
    print("exclude (old) .tar files from new backup:", exclude_tar)

folder_timestamp = args.foldertimestamp
if _TEST_:
    print("Use timestamp for folders:", folder_timestamp)

if not folder_timestamp:
    dt_string = ""
    print("root:", root, "NO dt_string")
else:
    print("root:", root, "dt_string:", dt_string)

def source_liste(multiples):
    new_list = []
    for eintrag in multiples:
        eintrag0 = eintrag.strip()
        if eintrag0 == ".":
            source = root
            target = root + prefix + "_home" + dt_string + "/"
        else:
            source = root + eintrag0 + "/"
            target = root + prefix + eintrag0 + dt_string + "/"
        new_list.append({'source' : source, 'target' :target})
    return new_list

def source_single(source, target):
    if len(extfolder) == 0:
        folder = root
    else:
        folder = extfolder

    new_list = []
    if source == ".":
        source1 = root
        target1 = folder + prefix + "_home" + dt_string + "/"
        # target1 = root + prefix + "_home" + dt_string + "/"
    else:
        source1 = root + source + "/"
        target1 = folder + prefix + target + dt_string + "/"
        # target1 = root + prefix + target + dt_string + "/"
    new_list.append({'source' : source1, 'target' :target1})
    return new_list

liste = []

if args.source == None:
    try:
        liste = source_liste(open(root + datei, "r").read().splitlines())
    except FileNotFoundError:
        liste = []
        print("File Open Error:", datei)
elif len(single_source) > 0:
    if len(single_target) == 0:
        single_target = single_source
    liste = source_single(single_source, single_target)

if _TEST_:
    print("Liste:", liste)

def copy_windows(source, target):
    global done_liste
    members = []
    source_show = source.replace(root, "")
    if source_show == "":
        source_show = "."
    else:
        source_show = source_show[:-1]
    target_show = target.replace(root, "")
    target_show = target_show[:-1]
    # print("source", source)
    if not os.path.exists(source):
        print(source, "not found")
        return

    members = os.listdir(source)
    if len(members) == 0:
        print("No members found in", source)
        return

    if verbose:
        print("\nSource:", source)
        print("Target:", target)
        print("Copy following members:")
        print(', '.join(members))
    else:
        print("\nProcessing:", source)

    if wait:
        yn = input("Proceed Y/(N) : ")
    else:
        yn = "Y"

    if yn.upper() == "Y":
        rc = 0
        # print("target", target)
        if not os.path.exists(target):
            os.makedirs(target)
            print(target, "created")

        for member in members:
            # do not trap into recursion
            if member == prefix[:prefix.find("/")]:
                continue
            # do not save old tar to new backup
            if exclude_tar:
                if member.find(".tar") > 0:
                    continue
            if not os.path.isfile(source + member):
                recurse_dir = True
                for eintrag in liste + done_liste:
                    if eintrag['source'] == source + member + "/":
                        recurse_dir = False
                if not allow_double_backup and source == root and member[:5] == "_home":
                    recurse_dir = False
                if recurse_dir:
                    if recursive:
                        copy_windows(source + member + "/", target + member + "/")

                # This was subdir and/or has already done. Northing else to copy
                continue

            if shutil.copyfile(source + member, target + member) != None:
                if verbose:
                    print(member, "copied from", source_show, "to", target_show)
                rc += 1

        if rc > 0:
            print(rc, "members copied")

    if not allow_double_home:
        done_liste.append({'source' : source, 'target' : None})
    done_liste.append({'source' : target, 'target' : None})

done_liste = []
for einzel in liste:
    copy_windows(einzel['source'], einzel['target'])

print("\npycopy done\n")
Was mir jetzt nicht gefällt - und ich habe auch gerade schon gesehen mit dem main() - mein Script hat eigentlich kein maiin() und das steht nur alles so nacheinander mit ein paar def()
Ich habe das zwar versucht umzustellen, scheitere aber weil ich viel zu viele global Variablen bekommen würde, was ich in anderen Sprachen auch so machen würde. Hier in Python scheitert man aber schon an dem Umstand, daß in den def() alles lokal ist und damit für draußen "verloren" was mir VSC dankenswerterweise auch erinnert.

Wie macht man es richtig? In Lazarus zB hätte ich meinen Daten-Deklarationsblock und ein paar lokale Variablen.
Benutzeravatar
Dennis89
User
Beiträge: 1503
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

@__blackjack__ okay:

Code: Alles auswählen

#!/usr/bin/env python
from argparse import ArgumentParser


def main():
    parser = ArgumentParser(description="Write here your description")
    parser.add_argument("first", type=str, help="Enter first argument as string")
    parser.add_argument(
        "-s", "--super_name", type=int, help="Enter 2nd argument as integer"
    )
    parser.add_argument("--something_else", help="Enter what you want")
    arguments = parser.parse_args()
    print("See your arguments in order you wrote them:")
    print(arguments.first)
    print(arguments.super_name)
    print(arguments.something_else)


if __name__ == "__main__":
    main()
@alfware17 Das ist eine Eigenheit von Python? Ich kenne jetzt nicht wirklich viele andere Programmiersprachen, aber soweit mir das bekannt ist, ist das üblich und auch gewollt. Man übergibt einer Funktion alles was sie benötigt beim Aufruf und die Funktion gibt bei Bedarf etwas zurück. Ein einfaches Beispiel:

Code: Alles auswählen

#!/usr/bin/env python


def join_text_and_number(text, number):
    return f"{text}{number}"


def main():
    text_number = join_text_and_number("Hallo", 12)
    print(text_number)


if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Naja, wie du schon schreibst - ein einfaches Beispiel... Aber wenn es etwas komplexer wird?
Nehmen wir das obige Beispiel Programm.
Da werden Parameter ausgwertet (zB. args.verbose) und in dem Fall Scriptvariablen (verbose) gesetzt. Die Funktion copy_windows() kann verbose lesen aber nicht schreiben, was noch ok ist. Manchmal würde aber gerne doch noch eine (für sie nicht lokale) Variable ändern.
Ich könnte jetzt die Parameter-Auswertung in eine main() setzen, auch die Variablen wie verbose. Die Funktion copy_windows() muß dann aber schon fast verschachtelt zu main() da mit rein - wogegen sich mein Gefühl irgendwie sträubt. Mache ich copy_windows() gleichberechtigt zu main(), müßten die Variablen zB verbose und liste global also außerhalb der main() stehen. Macht man das so? Hast du mal ein komplexeres Beispiel?

Und naja, generell ist es in anderen Sprachen ähnlich, das stimmt schon. Nur eben speziell diese "Falle" daß Methoden nicht ohne weiteres Variablen schreiben dürfen, die außerhalb ihrer sind, ist in Python doch schon sehr speziell.

Code: Alles auswählen

x=6
def func1():
   print(x)
   x=4

func1()
print(x)
macht in anderen Sprachen nicht so viele Schwierigkeiten (obwohl man sie lösen kann)
Benutzeravatar
Dennis89
User
Beiträge: 1503
Registriert: Freitag 11. Dezember 2020, 15:13

müßten die Variablen zB verbose und liste global also außerhalb der main() stehen. Macht man das so?
Ne, auf Modulebene, der Code ohne Einrückungen, werden nur Klassen, Funktionen und Konstanten definiert. Eine Ausnahme ist die `if`-Abfrage, die über den Einstieg in die `main`-Funktion entscheidet.

An sich braucht es meiner Meinung nach kein komplexes Beispiel, weil wenn es so komplex ist, ist es vermutlich nicht ganz richtig und kann einfacher ausgedrückt werden. Wichtig in diesem Zusammenhang, eine Funktion macht nur eine Tätigkeit und der Name der Funktion wird nach ihrer Tätigkeit benannt. Wenn du das beachtest und deine Funktion plötzlich `lese_daten_addiere_bunte_smilies_frage_den_user_nach_der_lieblingsfarbe_und_spiele_musik_ab` heißt, dann bist du 100% auf dem falschen Weg. Was ich sagen will, wenn die Funktionen eine Tätigkeit machen, dann ist das meist auch mit den Argumenten und den Rückgabewerten nicht komplex. Ehrlich gesagt, fällt mir jetzt auch kein komplexes Beispiel ein.


Du veränderst in einer Funktion nichts, das irgendwo anders einen Einfluss hat, das wäre wieder global und das vermeiden wir ja mit Absicht. Wenn du jetzt `x` mit dem Wert 6 hast und eine Funktion brauchst die daraus einen anderen Wert macht, dann übergibst du `x` an die Funktion und die Funktion gibt den neuen Wert zurück und den bindest du wieder an einen Namen. Das kann, wenn es Sinn macht, wieder `x` sein oder einen anderen:

Code: Alles auswählen

#!/usr/bin/env python


def change_x(x):
    print(f"x out of `change_x`: {x}")
    return 4


def main():
    x = 6
    x = change_x(x)
    print(x)

if __name__ == "__main__":
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hab inzwischen gerade die Beispiele gefunden, die ich suchte - wo die Variablen hin sollen und wie man darauf zugreift, von verschachtelten und nicht verschachtelten Methoden aus.

Ungünstig ist es, wenn Python im Prinzip auch so funktioniert, daß alles was im File steht, gnadenlos nacheinander abgearbeitet wird und man das (nur) so gelernt hat. Und dann, wenn es zu umfangreich wird oder man es besonders gut machen will, man nachträglich strukturieren muß. Aber ich will ja an dem Programm lernen bzw üben.

Das zweite Beispiel mit Klassen (siehe den anderen Thread) finde ich besser, mal schauen wie weit man das noch treiben kann
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

@alfware17: das Problem ist nicht, dass Du die globalen Variablen nicht in den Funktionen benutzen kannst, sondern dass Du so viele globalen Variablen hast. Du mußt Deine Funktionen so schreiben, dass sie möglichst wenig Argumente brauche, weil sie genau eine Aufgabe erfüllen, die konkret definiert ist.
Konstanten schreibt man ohne führende und endende Unterstriche. Statt dessen sollten sie aussagekäftig sein, was bedeutet VSC?
Für Wahrheitswerte gibt es False und True, da muß man nicht 0 und 1 verwenden.
Pfade sind nicht einfache Strings, vor allem sollte man sie nicht mit normalen Stringfunktionen bearbeiten. Für Pfade gibt es pathlib. Allein die Stellen, wann wo ein '/' eingefügt wird, bietet viel Raum für Fehler.

Die Benennung der Argumente ist verwirrend. --recursive schaltet Rekusivität ab, --foldertimestamp bedeutet KEIN Timestamp.
Zeile 59: hier wandelst Du einen String in einen String um und hängst noch einen leeren String an.
Zeile 94: hier setzt Du eine Variable, die 90 Zeilen davor mit irgend einem anderen Wert belegt wurde. Zusammengehörende Dinge sollten immer eng beieinander stehen.
source_list und source_single sind im Prinzip identisch und es soltle nur eine Funktion geben.
Zeile 134: Dateien, die man öffnet, sollte man auch wieder schließen, am besten mit Hilfe des with-Statements. Statt read und splitline würde man die geöffnete Datei einfach als Iterator über die Zeilen benutzen.
Wenn es einen Fehler gab, sollte das Programm beendet werden, und nicht implizit dadurch, dass liste leer ist.
copy_windows kopiert soweit ich sehe keine Fenster sondern Datein.
Zeile 148: members wird mit einer leeren Liste initialisiert, aber nie benutzt.
Benutzeravatar
grubenfox
User
Beiträge: 593
Registriert: Freitag 2. Dezember 2022, 15:49

alfware17 hat geschrieben: Dienstag 24. September 2024, 12:00 Und dann, wenn es zu umfangreich wird oder man es besonders gut machen will, man nachträglich strukturieren muß. Aber ich will ja an dem Programm lernen bzw üben.
Darum sollte man auch schon "vorträglich" strukturieren, bevor es umfangreich wird. ;) Wie schon hier irgendwo geschrieben nutze ich ja nur einen Editor um meine Quelltexte zu bearbeiten. Daher kann ich in Sachen VSC und PYCharm nur das Internet zitieren:
Using Visual Studio Code for Refactoring
Visual Studio Code is an Integrated Development Environment (IDE) that offers several refactoring options, such as renaming symbols, extracting methods, and changing function signatures. In addition, it has an integrated terminal and debugger.

Using PyCharm for Refactoring
Finally, PyCharm is an IDE specifically designed for Python that offers an extensive array of refactoring tools. It supports all the basic refactoring techniques we've discussed and provides several advanced features, like code inspections and automated refactorings. It also integrates with popular version control systems, making it a great tool for collaborative projects.
Wäre das hier nicht eine gute Gelegenheit zu lernen bzw. üben wie man mit VSC bzw. PyCharm den Python-Code umbaut und verbessert?
Benutzeravatar
grubenfox
User
Beiträge: 593
Registriert: Freitag 2. Dezember 2022, 15:49

alfware17 hat geschrieben: Montag 23. September 2024, 19:13 Ich sags mal gleich vorweg - die Struktur wird manchem merkwürdig vorkommen, das ganze Programm ist zuerst im IBM-Mainframe Umfeld (zOS) entstanden und ursprünglich nur als Backup-Tool.
Es hat mir aber gefallen und ich habe es angepaßt an Windows, Linux sollte auch gehen habe es aber lange nicht getestet .

Ich möchte mich mal der ggf. vernichtenden Kritik stellen und fragen, was kann ich besser machen, was vereinfachen und welche Ideen habt Ihr.
Also wenn man da an die merkwürdige Struktur ran möchte, dann ist mir da noch was aufgefallen:
  • prefix ist überflüssig: steht ja schon in args.prefix
  • verbose ist überflüssig: steht ja schon in args.verbose
  • wait ist überflüssig, steht ja schon in args.wait
  • single_source ist überflüssig, steht ja schon in args.source
... Muss ich weiter machen?

Ungetestet(!) müsste man damit von

Code: Alles auswählen

parser = argparse.ArgumentParser()
parser.add_argument('--file' , '-f', help='Filename for PDS')
parser.add_argument('--prefix', '-p', help='Prefix added before target')
parser.add_argument('--verbose', '-v', action='store_true', help='Report every single copy')
parser.add_argument('--wait', '-w', action='store_true', help='Ask before copy')
parser.add_argument('--source', '-i', help='Single Source folder, may be .')
parser.add_argument('--target', '-o', help='Single Target folder')
parser.add_argument('--extfolder', '-e', help='Extern folder')
parser.add_argument('--recursive', '-r', action='store_false', help='Do not copy subdirectories')
parser.add_argument('--homedouble', '-d', action='store_true', help='Allow double copy of folders in home')
parser.add_argument('--backupdouble', '-b', action='store_true', help='Allow also backup of _home')
parser.add_argument('--foldertimestamp', '-t', action='store_false', help='Do not Add Timestamp to folder (not advised!)')
parser.add_argument('--excludetar', '-x', action='store_false', help='Do not store (old) .tar files in new backup')
args = parser.parse_args()

print("\npycopy start\n")

datei = "copylist.txt"
if args.file:
    datei = str(args.file)
    # knock out --source flag
    args.source = None
if _TEST_:
    print("Datei:", datei)

prefix = ""
if args.prefix:
    prefix = str(args.prefix) + "/"
else:
    # To prevent overwritten folders
    args.foldertimestamp = True
if _TEST_:
    print("Prefix:", prefix)

verbose = args.verbose
if _TEST_:
    print("Verbose:", verbose)

wait = args.wait
if _TEST_:
    print("Wait:", wait)

single_source = str(args.source) + ""
if _TEST_:
    print("Source:", single_source)

single_target = str(args.target) + ""
if _TEST_:
    print("Target:", single_target)

extfolder = ""
if args.extfolder:
    extfolder = str(args.extfolder).lower().replace("\\","/") + "/"
if _TEST_:
    print("Extfolder:", extfolder)

recursive = args.recursive
if _TEST_:
    print("Recursive:", recursive)

allow_double_home = args.homedouble
if _TEST_:
    print("Allow double home folders:", allow_double_home)

allow_double_backup = args.backupdouble
if _TEST_:
    print("Allow double backup folders:", allow_double_backup)

exclude_tar = args.excludetar
if _TEST_:
    print("exclude (old) .tar files from new backup:", exclude_tar)

folder_timestamp = args.foldertimestamp
if _TEST_:
    print("Use timestamp for folders:", folder_timestamp)

if not folder_timestamp:
    dt_string = ""
    print("root:", root, "NO dt_string")
else:
    print("root:", root, "dt_string:", dt_string)
zu dem hier kommen:

Code: Alles auswählen

parser = argparse.ArgumentParser()
parser.add_argument('--file' , '-f', help='Filename for PDS')
parser.add_argument('--prefix', '-p', help='Prefix added before target')
parser.add_argument('--verbose', '-v', action='store_true', help='Report every single copy')
parser.add_argument('--wait', '-w', action='store_true', help='Ask before copy')
parser.add_argument('--source', '-i', help='Single Source folder, may be .')
parser.add_argument('--target', '-o', help='Single Target folder')
parser.add_argument('--extfolder', '-e', help='Extern folder')
parser.add_argument('--recursive', '-r', action='store_false', help='Do not copy subdirectories')
parser.add_argument('--homedouble', '-d', action='store_true', help='Allow double copy of folders in home')
parser.add_argument('--backupdouble', '-b', action='store_true', help='Allow also backup of _home')
parser.add_argument('--foldertimestamp', '-t', action='store_false', help='Do not Add Timestamp to folder (not advised!)')
parser.add_argument('--excludetar', '-x', action='store_false', help='Do not store (old) .tar files in new backup')
args = parser.parse_args()

print("\npycopy start\n")

if args.file:
    # knock out --source flag
    args.source = None
else:
    args.file = "copylist.txt"

if args.prefix:
    args.prefix = str(args.prefix) + "/"
else:
    # To prevent overwritten folders
    args.foldertimestamp = True

if args.extfolder:
    args.extfolder = str(args.extfolder).lower().replace("\\","/") + "/"

if _TEST_:
    print("Datei:", args.file)
    print("Prefix:", args.prefix)
    print("Verbose:", args.verbose)
    print("Wait:", args.wait)
    print("Source:", args.single_source)
    print("Target:", args.single_target)
    print("Extfolder:", args.extfolder)
    print("Recursive:", args.recursive)
    print("Allow double home folders:", args.allow_double_home)
    print("Allow double backup folders:", args.allow_double_backup)
    print("exclude (old) .tar files from new backup:", args.exclude_tar)
    print("Use timestamp for folders:", args.folder_timestamp)

if not args.folder_timestamp:
    dt_string = ""
    print("root:", root, "NO dt_string")
else:
    print("root:", root, "dt_string:", dt_string)
Wobei für die Geschichten mit Dateipfaden (hier bei '.prefix' und '.extfolders') das gilt, was Sirius3 schon weiter oben schrieb: "Für Pfade gibt es pathlib"
Mir ging es hier ja nur um die überflüssigen Scriptvariablen, die schon alle in args enthalten sind und die man damit alle einfach an weitere Funktionen durchreichen/übergeben kann.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei bei ``if _TEST_:`` irgendwie eher logging das Mittel der Wahl wäre, statt so etwas noch mal selbst zu erfinden.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hallo und danke euch erstmal für die ersten Tips.

Was die Parameter und eigenen Variablen angeht - ja da habe ich auch schon drüber nachgedacht, weiter unten in der Auswertung die args.verbose usw. direkt auszuwerten - ich hatte meine Logikl nur bisher erstmal so, erst alles auswerten (wie man sieht knipsen sich da ja durchaus noch 1-2 Flags gegenseitig um) und dann später aus eigenen Variablen auswerten.

Da ich die letzteren aber jetzt eh in eine Klasse schicke, sieht das hinterher ganz anders aus

Und ja - es sollte besser notrecursive und notimestamp heißen, es sind Ausknipser.

Die einzelnen Fehler in Zeile 94 usw schaue ich mir an.

Mit dem pathlib das muß ich erstmal lesen - wenn ich es mir jetzt mit Übersicht besser anschaue, ging es mir damals wohl nur darum, daß ich wenn zB in source oder target
test1\verz2
c:\test\
oder sowas drin steht, das einheitlich auf ein Format gebracht wird und hinten ein /

Mein root sollte eigentlich das Verzeichnis sein wo pycopy selbst gerade steht - ich kann aber nochmal nachdenken und es ist das Wurzelverzeichnis der Platte möglich. Visual Studio Code findet das nicht so, daher habe ich zum Testen den _VSC_ Schalter erfunden
Soll keine Entschuldigung sein, aber das wurde wie gesagt im MainFrame-Umfeld unter zOS entwickelt und erst später an Windows angepaßt

Zur Struktur, ich denke, ich brauche neben der main() nur die copy_windows (ähm weil die Frage aufkam, ich hatte am Großrechner auch eine copy_UNIX und eine copy_MVS - ich kann die copy_Windows aber umbenennen) und eventuell was ich für die Bestückung der liste brauche (hier träume ich aber gerade davon, das eventuell alles in die Klasse auszulagern). Die Schleife zum Abarbeiten der einzelnen Listeneinträge - nun wir werden sehen. Und die Member sind soweit ich mich erinnere, alle die Dateien/Verzeichnisse in einem Listeneintrag, mal sehen ob ich da noch eine def() erzeuge.

Ich komme aber voraussichtlich erst über den Feiertag/Brückentag dazu daran was zu machen
nezzcarth
User
Beiträge: 1733
Registriert: Samstag 16. April 2011, 12:47

Ein paar unsortierte Anmerkungen:
* Generelle stilistische Hinweise gibt ein Linter wie pylint (teilweise etwas übertrieben, aber lehrreich) oder (weniger streng) flake8.
* Quelltexte formatiert man idealerweise mit einem Formatter wie z.B. black
* Auf None wird nicht mit "==" sondern mit "is" geprüft
* Gefühlt die Hälfte deines Codes geht für CLI-Handling drauf. In einem ersten Zwischenschritt würde ich das alles mal in eine Funktion stopfen, die nur das geparste Resultate zurück gibt. Weiterhin ist mein Eindruck, ohne es jetzt alles im Detail durchgegangen zu sein, dass du Auswertungslogik implementierst, die argparse mindestens teilweise schon eingebaut mitbringt (z.B. die default, type oder choices Argumente); nimm dir doch mal die Zeit, die Doku zu argparse in Ruhe durchzugehen.
Benutzeravatar
grubenfox
User
Beiträge: 593
Registriert: Freitag 2. Dezember 2022, 15:49

__blackjack__ hat geschrieben: Dienstag 24. September 2024, 16:09 Wobei bei ``if _TEST_:`` irgendwie eher logging das Mittel der Wahl wäre, statt so etwas noch mal selbst zu erfinden.
Ach, so ein paar Debug-Prints während der Entwicklungsphase (den Eindruck macht das auf mich) schaden doch nicht. Das kann einem glatt die Nutzung einer IDE (wie PyCharm oder VSC) ersparen. Für regelmäßige Ausgaben nach der Entwicklung im produktiven Einsatz ist es was anderes: da wäre ich dann auch für ordentliches Logging.

Dann noch, wie hier drüber von nezzcarth vorgeschlagen, das ganze CLI-Handling in eine eigene Funktion packen, damit man das unabhängig vom ganzen Rest schön testen kann. Oder vielleicht noch besser: in ein eigenes kleines Modul verschieben. Dann kann man auch auf das ``if _TEST_:`` verzichten und die Unterscheidung, ob die Debug-Ausgaben von dem CLI-Modul erzeugt werden sollen oder nicht, den üblichen Python-Mechanismen überlassen [__name__]
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Dass Debug-Prints nichts schaden, steht außer Frage, aber dann bitte per logger.debug und nicht per print.
Mehrere Module braucht man erst, wenn es wirklich zu umfangreich wird, dann würde man die Fachlichkeit, nämlich das Kopieren in ein Modul auslagern.
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hallo, ich habe es mal etwas geteilt und die Parameterauswertung umgeschrieben. Was noch fehlt, ist mein Objekt denn da sollen die Variablen rein bzw auch einige Funktionen

Und das eigentliche Kopieren die Funktion copy_windows() habe ich noch weggelassen, die müßte ich noch überarbeiten. Aber an sich sieht die entstandene copy_list schon mal nicht schlecht aus,
habe etwas mit -i und -o getestet, einfache oder längere Datei mit den sources und/oder . (aktuelles Verzeichnis), alle meine Fälle von damals stimmten.

Code: Alles auswählen

from pathlib import Path
import argparse
from datetime import datetime

def convert_path(p):
    p0 = Path(p)
    posix_p0 = p0.as_posix()
    if not posix_p0.endswith("/"):
        posix_p0 += "/"
    return posix_p0.lower()

def create_copy_list(list):
    c_list = []
    for item in list:
        i_source = convert_path(item[0].strip())
        i_target = convert_path(item[1].strip())
        if i_source == ".":
            c_source = root
            c_target = root + prefix + "_home" + dt_string
        else:
            c_source = root + i_source + "/"
            c_target = root + prefix + i_target + dt_string + "/"
        c_source = convert_path(c_source)
        c_target = convert_path(c_target)
        c_list.append({"source" : c_source, "target" : c_target})
    return c_list

def read_source_file(path):
    s_list = []
    try:
        with open(path, "r") as s_file:
            s_list = s_file.read().splitlines()
            s_list = [[line, line] for line in s_list]
        return s_list
    except FileNotFoundError:
        print("File Open Error: ", path, " not found.")
        return []

def parm_info():
    print("Datei:", file)
    print("Prefix:", prefix)
    print("Verbose:", verbose)
    print("Wait:", wait)
    print("Source:", source)
    print("Target:", target)
    print("Extfolder:", extfolder)
    print("Recursive:", recursive)
    print("Allow double home folders:", allow_double_home)
    print("Allow double backup folders:", allow_double_backup)
    print("Exclude (old) .tar files from new backup:", exclude_tar)
    print("Use timestamp for folders:", folder_timestamp)
    print("Root:", root)
    print("dt_string:", dt_string)
    print("Source Liste:", source_list)
    print("Copy Liste:", copy_list)

now = datetime.now()
# _dyyyymmdd_thhmmss
root = convert_path(Path.cwd())

parser = argparse.ArgumentParser()
parser.add_argument("-f", "--file",  help="Filename for PDS")
parser.add_argument("-p", "--prefix", help="Prefix added before target")
parser.add_argument("-v", "--verbose", action="store_true", help="Report every single copy")
parser.add_argument("-w", "--wait", action="store_true", help="Ask before copy")
parser.add_argument("-i", "--source", help="Single Source folder, may be .")
parser.add_argument("-o", "--target", help="Single Target folder")
parser.add_argument("-e", "--extfolder", help="Extern folder")
parser.add_argument("-r", "--notrecursive", action="store_false", help="Do not copy subdirectories")
parser.add_argument("-d", "--homedouble", action="store_true", help="Allow double copy of folders in home")
parser.add_argument("-b", "--backupdouble", action="store_true", help="Allow also backup of _home")
parser.add_argument("-t", "--notimestamp", action="store_false", help="Do not Add Timestamp to folder (not advised!)")
parser.add_argument("-x", "--excludetar", action="store_false", help="Do not store (old) .tar files in new backup")
args = parser.parse_args()

file = "copylist.txt"
if args.file:
    file = str(args.file)
    # knock out --source flag
    args.source = None

prefix = ""
if args.prefix:
    prefix = convert_path(str(args.prefix))
else:
    # To prevent overwritten folders
    args.notimestamp = True

verbose = args.verbose
wait = args.wait

source = ""
if args.source:
    source = str(args.source)

target = ""
if args.target:
    target = str(args.target)
else:
    if args.source:
        target = source

extfolder = ""
if args.extfolder:
    extfolder = convert_path(str(args.extfolder))

recursive = args.notrecursive
allow_double_home = args.homedouble
allow_double_backup = args.backupdouble
exclude_tar = args.excludetar

dt_string = ""
folder_timestamp = args.notimestamp
if folder_timestamp:
    dt_string = now.strftime("_d%Y%m%d_t%H%M%S")

source_list = []
if not args.source:
    source_list = read_source_file(root + file)
else:
    source_list = [[source, target]]

copy_list = []
copy_list = create_copy_list(source_list)

parm_info()
Sirius3
User
Beiträge: 18216
Registriert: Sonntag 21. Oktober 2012, 17:20

Es darf keine globalen Variablen geben. Variablennamen sollten sprechend sein.
Warum p0? Was soll die 0 bedeuten? p soll wohl path bedeuten, sollte also auch so geschrieben werden.
Das mit pathlib hast Du noch nicht ganz umgesetzt. Man darf keine Pfade per Stringoperationen verarbeiten, nirgends.
Pfade sind im Normalfall Case-sensitiv.
`list` ist der Name der list-Klasse, das sollte man nicht mit einer Variable überschdecken.
c_list sollte besser copy_items genannt werden. Wass soll den i bei i_source oder i_target bedeuten?
Variablen sollten nicht mit Werten belegt werden, die gar nicht gebraucht werden, z.B Zeile 117 source_list, und ganz extrem Zeile 123 mit copy_list.
Warum ist der Input von create_copy_list eine Liste mit Tuplen, der Rückgabewert aber eine Liste mit Wörterbüchern? Das sollte konsistent sein.
Der Spezialfall von source == "." ist komisch. Warum wird in diesem Fall target nicht verwendet? Warum wird an den Zeitstempel in diesem Fall "_home" vorangestellt?
Und warum fängt der Zeitstempel mit einem Unterstrich an?

Die Verarbeitung der Argumente ist noch etwas kompliziert. Vor allem werden Argumente ohne Warnung überschrieben.
Jetzt hast Du zwar notimestamp, der logische Wert im Programm ist nun aber falsch, denn für notimestamp = True wird ein timestamp erzeugt.

Das ganze könnte also so aussehen:

Code: Alles auswählen

import argparse
from datetime import datetime
from pathlib import Path

def create_copy_items(prefix, source_items, timestamp):
    copy_items = []
    for source_path, target_path:
        if source_path == Path("."):
            target_path = prefix / f"_home{timestamp}"
        else:
            target_path = prefix / target_path
            if timestamp:
                 target_path /= timestamp
        copy_items.append((source_path, target_path))
    return copy_items

def read_source_file(path):
    source_items = []
    try:
        with open(path, "r") as file:
            for line in file:
                source_path = Path(line.rstrip())
                source_items.append((source_path, source_path))
    except FileNotFoundError:
        print(f"File Open Error: {path} not found.")
    return source_items


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--file",  help="Filename for PDS")
    parser.add_argument("-p", "--prefix", help="Prefix added before target")
    parser.add_argument("-v", "--verbose", action="store_true", help="Report every single copy")
    parser.add_argument("-w", "--wait", action="store_true", help="Ask before copy")
    parser.add_argument("-i", "--source", help="Single Source folder, may be .")
    parser.add_argument("-o", "--target", help="Single Target folder")
    parser.add_argument("-e", "--extfolder", help="Extern folder")
    parser.add_argument("-r", "--notrecursive", action="store_false", help="Do not copy subdirectories")
    parser.add_argument("-d", "--homedouble", action="store_true", help="Allow double copy of folders in home")
    parser.add_argument("-b", "--backupdouble", action="store_true", help="Allow also backup of _home")
    parser.add_argument("-t", "--notimestamp", action="store_true", help="Do not Add Timestamp to folder (not advised!)")
    parser.add_argument("-x", "--excludetar", action="store_true", help="Do not store (old) .tar files in new backup")
    args = parser.parse_args()

    if args.file and args.source:
        print("You cannot use --file and --source at the same time")
        return

    if args.prefix:
        prefix = args.prefix
    else:
        prefix = "."
        if args.notimestamp:
            print("--notimestamp without --prefix not allowed.")
            return

    if args.source:
        target = args.target if args.target else args.source
        source_items = [
            (Path(args.source), Path(target))
        ]
    else:
        if args.target:
            print("--target ignored")
        input_file = Path(args.file if args.file else "copylist.txt")
        source_items = read_source_file(input_file)

    if args.extfolder:
        extfolder = Path(args.extfolder)
    else:
        extfolder = None

    if args.notimestamp:
        folder_timestamp = ""
    else:
        folder_timestamp = f"_d{datetime.now():%Y%m%d_t%H%M%S}"

    copy_items = create_copy_items(prefix, source_items, folder_timestamp)

    ...


if __name__ == "__main__":
    main()
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

Hallo Sirius3, vielen Dank für deine große Hilfe - da bin ich heute einen guten Schritt vorangekommen. Meine copy_windows kann ich natürlich auch nicht so lassen - das hatte ich aber erwartet. Ich habe mir erstmal alle Infos gesammelt, die mir die pathlib.Path() so gibt und werde das nächste Woche einbauen. Da ist im Detail noch ziemlich der Teufel oder das Eichhörnchen drin, ich habe nämlich eine Rekursion und ich muß erst die Member bestimmen und ich habe damals eine Protokoll-Funktion ohne die Pfade also relativ gemacht (source_show usw.) Dafür brauche ich doch mein "root" bzw sogar den "extfolder" der so gedacht war, daß die Pfade ja auch absolut starten können nicht im aktuellen Verzeichnis. Ich habe es mit eingebaut, das extfolder meine ich.
Was ich ehrlicherweise noch nicht getestet habe, was passiert wenn ich bei -i und -o auch absolute Pfade eingebe, wahrscheinlich muß ich es erkennen und dann extfolder verbieten.

Hier mein Stand:

Code: Alles auswählen

import argparse
from datetime import datetime
from pathlib import Path

def create_copy_items(folder, prefix, source_items, timestamp):
    copy_items = []
    for source_path, target_path in source_items:
        if source_path == Path("."):
            target_path = prefix / f"_home{timestamp}"
        else:
            target_path = prefix / target_path
            if timestamp:
                 target_path /= timestamp
        copy_items.append((folder / source_path, folder / target_path))
    return copy_items

def read_source_file(path):
    source_items = []
    try:
        with open(path, "r") as file:
            for line in file:
                source_path = Path(line.rstrip())
                source_items.append((source_path, source_path))
    except FileNotFoundError:
        print(f"File Open Error: {path} not found.")
    return source_items


def copy_list(source, target, args, copy_items, done_list, extfolder, prefix):
   source_path = Path(source)
   target_path = Path(target)
   print(source, '->', target)
   print(source_path, '->', target_path)
   source_rel = source_path.relative_to(extfolder)
   target_rel = target_path.relative_to(extfolder)
   print(source_rel, '->', target_rel)
   source_member = source_path.name
   target_member = target_path.name
   print(source_member, '->', target_member)
   print(source, source_path, source.exists(), source_path.exists())
   print(target, target_path, target.exists(), target_path.exists())
   print(source_path, 'ist ein Verzeichnis:', source_path.is_dir(), ' oder eine Datei:', source_path.is_file())
   print(target_path, 'ist ein Verzeichnis:', target_path.is_dir(), ' oder eine Datei:', target_path.is_file())
   print('copy_items, vorher')
   print(copy_items)
   print('done_list, vorher')
   print(done_list)
   done_list.append([source, target])
   print('copy_items, nachher')
   print(copy_items)
   print('done_list, nachher')
   print(done_list)

def main():
    print("\npycopy start\n")

    root = Path(Path.cwd())
    print("root=", root)

    dt_timestamp = f"_d{datetime.now():%Y%m%d_t%H%M%S}"
    print("timestamp=", dt_timestamp)

    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--file",  help="Filename for PDS")
    parser.add_argument("-p", "--prefix", help="Prefix added before target")
    parser.add_argument("-v", "--verbose", action="store_true", help="Report every single copy")
    parser.add_argument("-w", "--wait", action="store_true", help="Ask before copy")
    parser.add_argument("-i", "--source", help="Single Source folder, may be .")
    parser.add_argument("-o", "--target", help="Single Target folder")
    parser.add_argument("-e", "--extfolder", help="Extern (start) folder set if not root by default")
    parser.add_argument("-r", "--notrecursive", action="store_false", help="Do not copy subdirectories")
    parser.add_argument("-d", "--homedouble", action="store_true", help="Allow double copy of folders in home")
    parser.add_argument("-b", "--backupdouble", action="store_true", help="Allow also backup of _home")
    parser.add_argument("-t", "--notimestamp", action="store_true", help="Do not Add Timestamp to folder (not advised!)")
    parser.add_argument("-x", "--excludetar", action="store_true", help="Do not store (old) .tar files in new backup")
    args = parser.parse_args()

    if args.file and args.source:
        print("You cannot use --file and --source at the same time")
        return

    if args.prefix:
        prefix = args.prefix
    else:
        prefix = "."
        if args.notimestamp:
            print("--notimestamp without --prefix not allowed.")
            return

    if args.source:
        copy_target = args.target if args.target else args.source
        source_items = [ (Path(args.source), Path(copy_target)) ]
    else:
        if args.target:
            print("--target ignored")
        input_file = Path(args.file if args.file else "copylist.txt")
        source_items = read_source_file(input_file)
    
    extfolder = Path(args.extfolder) if args.extfolder else ""
    folder_timestamp = "" if args.notimestamp else dt_timestamp
    
    copy_items = create_copy_items(extfolder, prefix, source_items, folder_timestamp)

    print(copy_items)

    done_list = []

    for source,target in copy_items:
        copy_list(source, target, args, copy_items, done_list, extfolder, prefix)

    print("\npycopy done\n")

if __name__ == "__main__":
    main()
Eine ganz gemeine Sache wirst du mir wahrscheinlich gleich um die Ohren hauen - ich übergebe mir in der Parameterleiste zur Kopierfunktion noch die args, die Liste copy_items und sogar die done_list.
Letztere erweitere ich mit append und ja ich weiß es ist ein Seiteneffekt, aber ChatGPT sagte, es ist "sauber". Alternativ müßte ich mit return arbeiten. Nur die done_list wird erweitert - ich brauche das, um ggf Doppel-Kopie und wieder die Rekursion zu erkennen. Am Algorithmus habe ich damals schon gesucht - nur ist/hat MVS ein meistens recht flaches Dateisystem am Großrechner, kein Vergleich zu Windows/Linux.
alfware17
User
Beiträge: 51
Registriert: Montag 9. September 2024, 17:53

So ich würde mal sagen "fertig". Es läuft (wieder), hab so weit ich mich an damals erinnere, gestestet und liefert gleiche Ergebnisse.
Habe eine ganze Menge dabei gelernt und bedanke mich für eure Tips, natürlich vor allem an Sirius3.
Ich habe ziemlich lange an einem Fehler gesucht, der durch ein Mißverständnis von string1 + string2 bzw path1 / path2 entsteht und ich mit dem "" und dem Path(".") auf Kriegsfuß stand. Aber nun hab ichs und kann mir das mal merken.
Ich habe alle Hinweise zu den Parametern umgesetzt - natürlich hatte ich dann noch meinen eigenen Dickkopf und wollte unbedingt was mit Objekten drin haben - ich denke das ist mir mit dem parms ganz gut gelungen, wo nun sowohl die args als auch ein paar eigene Variablen drin sind.

Das mit der done_list lasse ich mal offen - ich habe beide Varianten getestet: in-place Update und mit return, auf Anhieb sah ich keinen Unterschied. Wenn mir noch was nicht gefällt, ist es das relativ lange if bei der Weiter-Abfrage - ok, return ging nicht (da unten noch zwei Updates sind) und noch eine neue Methode wollte ich nicht erzeugen, muß man aber wahrscheinlich wenn es noch länger wird.

Code: Alles auswählen

import argparse
from datetime import datetime
from pathlib import Path
import sys
import os
import shutil

# Definition einer Klasse zur Strukturierung der Rückgabewerte
class ParameterBundle:
    def __init__(self, args, source, target, root, folder_timestamp, extfolder, prefix, recursive, input_file):
        self.args = args
        self.source = source
        self.target = target
        self.root = root
        self.folder_timestamp = folder_timestamp
        self.extfolder = extfolder
        self.prefix = prefix
        self.recursive = recursive
        self.input_file = input_file

def parameter():
    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--file",  help="Filename for PDS")
    parser.add_argument("-p", "--prefix", help="Prefix added before target")
    parser.add_argument("-v", "--verbose", action="store_true", help="Report every single copy")
    parser.add_argument("-w", "--wait", action="store_true", help="Ask before copy")
    parser.add_argument("-i", "--source", help="Single Source folder, may be .")
    parser.add_argument("-o", "--target", help="Single Target folder")
    parser.add_argument("-e", "--extfolder", help="Extern (start) folder set if not root by default")
    parser.add_argument("-r", "--notrecursive", action="store_false", help="Do not copy subdirectories")
    parser.add_argument("-d", "--homedouble", action="store_true", help="Allow double copy of folders in home")
    parser.add_argument("-b", "--backupdouble", action="store_true", help="Allow also backup of _home")
    parser.add_argument("-t", "--notimestamp", action="store_true", help="Do not Add Timestamp to folder (not advised!)")
    parser.add_argument("-x", "--excludetar", action="store_true", help="Do not store (old) .tar files in new backup")
    args = parser.parse_args()

    recursive = args.notrecursive
    
    if args.file and args.source:
        print("You cannot use --file and --source at the same time")
        sys.exit()

    if args.prefix:
        prefix = args.prefix
    else:
        prefix = ""
        if args.notimestamp:
            print("--notimestamp without --prefix not allowed.")
            sys.exit()
          
    source = args.source
    if args.source:
        target = args.target if args.target else args.source
    else:
        target = None
        if args.target:
            print("--target ignored")
        
    input_file = Path(args.file if args.file else "copylist.txt")
    extfolder = Path(args.extfolder) if args.extfolder else ""

    root = Path(Path.cwd())
    dt_timestamp = f"_d{datetime.now():%Y%m%d_t%H%M%S}"
    folder_timestamp = "" if args.notimestamp else dt_timestamp

    return ParameterBundle(args, source, target, root, folder_timestamp, extfolder, prefix, recursive, input_file)

def extend_path_by_timestamp(path, timestamp, home):
    if path.name:  
        new_path = path.parent / (path.name + home + timestamp)
    else: 
        path_str = "" if Path(path) == Path(".") else str(path)
        new_path = Path(path_str + home + timestamp)
    return new_path

def create_copy_items(folder, prefix, source_items, timestamp):
    copy_items = []
    for source_path, target_path in source_items:
        if source_path == Path("."):
            target_new = Path(prefix)  
            home = "_home"
        else:
            target_new =  prefix / target_path
            home = ""
        target_new = extend_path_by_timestamp(target_new, timestamp, home)
        copy_items.append((folder / source_path, folder / target_new))
    return copy_items

def read_source_file(path):
    source_items = []
    try:
        with open(path, "r") as file:
            for line in file:
                source_path = Path(line.rstrip())
                source_items.append((source_path, source_path))
    except FileNotFoundError:
        print(f"File Open Error: {path} not found.")
    return source_items

def copy_list(source, target, copy_items, done_list, parms):
    done_list_update = done_list
    if not source.exists():
         print(source, "not found")
         return

    members = os.listdir(source)
    if len(members) == 0:
        print("No members found in", source)
        return

    if parms.args.verbose:
        print("\nSource:", source)
        print("Target:", target)
        print("Copy following members:")
        print(', '.join(members))
    else:
        print("\nProcessing:", source)
    
    yn = input("Proceed Y/(N) : ").upper if parms.args.wait else "Y"
    if yn.upper() == "Y":
        rc = 0
        if not target.exists():
            os.makedirs(target)
            print(target, "created")

        for member in members:
            # do not trap into recursion
            if member == parms.prefix[:parms.prefix.find("/")]:
                continue
            # do not save old tar to new backup
            if parms.args.excludetar and member.find(".tar") > 0:
                continue
            source_member = Path(source / member)
            target_member = Path(target / member)

            if not source_member.is_file():
                recurse_dir = True
                for item in copy_items + done_list_update:
                    if item[0] == source_member:
                        recurse_dir = False
                if not parms.args.backupdouble and source == parms.extfolder and member[:5] == "_home":
                    recurse_dir = False
                if recurse_dir and parms.recursive:
                    done_list_update = copy_list(source_member, target_member, copy_items, done_list_update, parms)

                # This was subdir and/or has already done. Northing else to copy
                continue

            if shutil.copyfile(source_member, target_member) != None:
                if parms.args.verbose:
                    source_rel = source.relative_to(parms.extfolder)
                    target_rel = target.relative_to(parms.extfolder)
                    print(member, "copied from", source_rel, "to", target_rel)
                rc += 1

        if rc > 0:
            print(rc, "members copied")

    
    if not parms.args.homedouble:
        done_list_update.append([source, None]) 
    done_list_update.append([target, None])

    return done_list_update


def main():
    print("\npycopy start\n")

    parms = parameter()
    
    if parms.source:
        source_items = [ (Path(parms.source), Path(parms.target)) ]
    else:
        source_items = read_source_file(parms.input_file)
    
    copy_items = create_copy_items(parms.extfolder, parms.prefix, source_items, parms.folder_timestamp)
    done_list = []

    for source,target in copy_items:
        copy_list(source, target, copy_items, done_list, parms)

    print("\npycopy done\n")

if __name__ == "__main__":
    main()

Als ich fertig war (nein ehrlich schon vorher, da ich während des Programmierens recht eifrig mit chatGPT in Kontakt war und was soll ich sagen, alle Tips zur Syntax und zu Ideen wie man was macht, waren richtig),
habe ich noch ein vergleichbares Programm generieren lassen. Das sieht auf den ersten Blick besser und kürzer aus - man muß allerdings bedenken, daß mein Programm eigentlich nicht nur oder gar nicht kopiert
(jedenfalls in der MVS Großrechner Variante sondern Backups machen soll). Daher auch mein relativ umständliches Handling beim home - das Problem ist halt daß die Backups auch mit ins Home geschrieben werden und daher ist vielleicht 50% "overhead" den das chatGPT Programm gar nicht hat

Code: Alles auswählen

import shutil
from pathlib import Path
import argparse
import datetime

def generiere_zeitstempel():
    return datetime.datetime.now().strftime("_d%y%m%d_t%H%M%S")

def kopiere_datei_oder_ordner(src, dest, rekursiv):
    src_path = Path(src)
    dest_path = Path(dest)

    if src_path.is_file():
        shutil.copy2(src_path, dest_path)
        print(f"Kopiert: {src_path} -> {dest_path}")
    elif src_path.is_dir():
        if rekursiv:
            shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
            print(f"Rekursiv kopiert: {src_path} -> {dest_path}")
        else:
            if not dest_path.exists():
                dest_path.mkdir(parents=True, exist_ok=True)
            for item in src_path.iterdir():
                if item.is_file():
                    shutil.copy2(item, dest_path / item.name)
                    print(f"Kopiert: {item} -> {dest_path / item.name}")
            print(f"Ordner ohne Rekursion kopiert: {src_path} -> {dest_path}")

def parse_args():
    parser = argparse.ArgumentParser(description="Kopiere Dateien und Ordner mit optionaler Rekursion.")
    parser.add_argument("src", help="Quellpfad der Datei oder des Ordners")
    parser.add_argument("dest", nargs='?', help="Zielpfad f�r die Kopie (optional, wird automatisch generiert, wenn nicht angegeben)")
    parser.add_argument("--rekursiv", action="store_true", help="Schalter, um Rekursion in Unterverzeichnisse zu aktivieren")
    return parser.parse_args()

def main():
    args = parse_args()

    if args.dest is None:
        zeitstempel = generiere_zeitstempel()
        dest_path = Path(args.src).parent / (Path(args.src).name + zeitstempel)
    else:
        dest_path = args.dest

    kopiere_datei_oder_ordner(args.src, dest_path, args.rekursiv)

if __name__ == "__main__":
    main()

Die eigentliche Leistung von chatGPT finde ich nicht so sehr, daß es besser Python kann als ich (was keine große Kunst ist) sondern daß es meine zugegeben deutsche Anforderungsbeschreibung vollkommen richtig verstanden und umgesetzt hat und das in einem Affentempo
Antworten