Codereview

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
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

Hallo zusammen,

ich beschäftige mich erst seit kurzem mit Python, arbeite beruflich mit einer anderen Sprache und möchte vermeiden, dass sich schlechte Angewohnheiten festsetzen.
Daher würde ich Euch bitten, Euch den folgenden Code anzuschauen, und wenn möglich zu bewerten.
Es geht mir darum, Strukturen, die man in Python anders machen würde als ich es getan habe, zu identifizieren. (Ausgangsthread zu meinem Anliegen).

Ich habe mir eine kleine Basisbibliothek aufgebaut, in der ich verschiedene Funktionen, die ich häufiger benutzen könnte, in separaten Files ausgelagert habe.
Es handelt sich dabei hauptsächlich um Wrapper um bereits vorhandene Funktionen, die einfacher im Aufruf sind, oder noch zusätzliche Aufgaben erledigen.
So gibt es bspw. Units, die Zugriff auf die Registry regeln, oder String-Routinen anbieten usw.

Das folgende Script soll eine Schnittstelle zu diesen 'Utilities' sein.
Ich möchte dieses Script mit verschiedenen Parametern aufrufen können, und dieses Script ruft dann die gewünschten Funktionen aus den anderen Scripten auf.
Mein Ziel war es, das Script möglichst allgemein und das Hinzufügen neuer Funktionen einfach zu halten.
Das Ganze soll bspw. vom Windows Task Scheduler genutzt werden, um bspw. regelmässig irgendwelche Registry-Einträge zu ändern, Services zu starten/beenden usw.

Ich würde mich freuen, wenn Ihr Euch das Ganze mal anseht und mir um die Ohren haut :)

Ich habe mir angewöhnt, möglichst viel zu kommentieren - das hat mir im Beruf oftmals weitergeholfen, da ich sehr vergesslich bin.
Es sollte daher ersichtlich sein, was ich erreichen möchte - auch wenn der Code selber evtl. sch.... ist ;)

Besten Dank im Voraus für Eure Mühen.

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
Script: function_caller
Stellt eine Schnittstelle zu Funktionen anderer Skripte bereit und leitet
deren Aufruf ein
'''

from argparse import ArgumentParser
import fileutils
import inspect
import logfile
import sysutils
import registryutils
import exceptionutils

# ******************************************************************************

def show_help():

    '''
    Funktion:
        Gibt die aufrufbaren Funktionen zurück

    Paramater:

    Rückgabe:
        Alle aufrufbaren Funktionen
    '''

    string = ''
    for function in functions:
        string = string + function + '\n'

    return string

# ******************************************************************************

# Logfile festlegen
log = logfile.Logfile(sysutils.get_windows_path('mydocuments') +
                      '\\logs\\function_caller.log')
log.Loglevel = 3

try:
    # Definierte Funktionen
    functions = {
        'help': show_help,
        'makedirs': fileutils.makedirs,
        'regsetval': registryutils.set_registry_value
    }

    # Argument-Parser initialisieren
    parser = ArgumentParser()

    # Positional Arguments (müssen vorhanden sein)
    parser.add_argument('function', help='function to be called')

    # Optional Arguments (können vorhanden sein)
    parser.add_argument('-nolog', help='log call')
    parser.add_argument('-args', nargs='+', help='optional arguments')

    # Argumente auslesen
    args = parser.parse_args()

    # Wenn die übergebene Funktion in der Liste der festgelegten Funktionen ist
    func = args.function
    if func in functions:

        # Ermitteln, wieviele Argumente die Funktion erwartet
        args_needed = inspect.getfullargspec(functions[func])
        args_needed_count = len(args_needed.args)

        # Default-Argumente müssen abgezogen werden
        if args_needed.defaults != None:
            def_args = args_needed.defaults[0]
        else:
            def_args = 0

        # Wenn nicht die korrekte Anzahl an optionalen Argumenten für diese
        # Funktion übergeben worden ist, Fehlermeldung
        if args.args == None:
            args_count = 0
        else:
            args_count = len(args.args)

        # Wenn die Routine Defaultargumente beinhaltet, dann muss die Anzahl
        # der übergebenen Argumente sich in der Range (min./max. Args) bewegen
        if def_args != 0:
            do_it = args_needed_count - def_args <= args_count <= args_needed_count

        else:
            do_it = args_count = args_needed_count

        if not do_it:
            parser.error('the function "%s" needs %s argument(s)' %
                         (func, args_needed_count - def_args))

        # ansonsten ausführen
        # (*args entpackt die Parameter auf der Gegenseite)
        # Aufzurufende Funktion erwartet Argument
        else:
            if args_needed_count > 0:
                result = functions[func](*args.args)
            # erwartet kein Argument
            else:
                result = functions[func]()

            # Ggf. Rückgabe ausgeben
            print('function_caller result: %s' % (result))
            if not args.nolog:
                log.output(1, '\n function: %s \n arguments: %s \n result: %s \n' %
                        (func, args.args, result))
    else:
        print('invalid function')
except Exception as ex:
    print(exceptionutils.throw_exception(ex))
    log.output(1, 'Error in function "%s" (%s)' %
               (func, exceptionutils.throw_exception(ex)))
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@sfx2k: Kommentare, die nur beschreiben, was sowieso in der nächsten Zeile steht, sind dennoch überflüssig.
Du hast viel zu viel Magie in Deinem Code. Das habe ich auch als Anfänger gerne gemacht. Tatsächlich macht Python die Überprüfung der Parameter beim Funktionsaufruf automatisch, Du mußt es also nicht selbst machen. Ich weiß nicht, was Du alles in Deine utils-Module gepackt hast, aber das Logging-Modul bringt schon alles zum Loggen mit.
Pfade setzt man nicht mit + sondern mit os.path.join zusammen. Alle Befehle auf Modulebene sollten in eine main-Funktion wandern:

Code: Alles auswählen

import os 
import logging
from argparse import ArgumentParser
import fileutils
import sysutils
import registryutils
import exceptionutils

LOGFILE = os.path.join(sysutils.get_windows_path('mydocuments'), 'logs', 'function_caller.log')

def main():
    functions = {
        'help': lambda: '\n'.join(functions) + '\n',
        'makedirs': fileutils.makedirs,
        'regsetval': registryutils.set_registry_value,
    }
    
    parser = ArgumentParser()
    parser.add_argument('function', help='function to be called')
    parser.add_argument('-nolog', help='log call')
    parser.add_argument('-args', nargs='+', help='optional arguments')
    args = parser.parse_args()
    
    logging.basicConfig(
        level=logging.WARNING if args.nolog else logging.INFO,
        filename=LOGFILE
    )
    logging.getLogger().addHandler(logging.StreamHandler())
 
    try:
        func = functions[args.function]
    except KeyError:
        logging.error('invalid function')
    else:
        try:
            result = func(*args.args)
        except Exception:
            logging.exception('Error in function "%s"' % args.function)
        else:
            logging.info('\n function: %s \n arguments: %s \n result: %s \n' %
                        (args.function, args.args, result))

if __name__ == '__main__':
    main()
BlackJack

Man könnte bei `argparse` auch aus den vorhandenen Funktionen ein Argument machen, also `argparse` schon sagen was es für eine Auswahl gibt. Dann kümmert sich das Modul schon um eine Fehlermeldung, und in der Hilfe werden sie auch angezeigt ohne das man selber eine 'help'-Funktion benötigt.

Das `args` ein optionales Argument ist halte ich auch für keine so gute Idee. Und bei Optionen die aus mehr als einem Buchstaben bestehen erwartet man eigentlich *zwei* '-' vor der Option.

Wenn man ”Magie” betreiben möchte, dann macht man beim `argparse` eine Argumentgruppe pro Funktion die sich gegenseitig ausschliesst und benutzt Introspektion um die Anzahl und Namen der Funktionsargumente auch dem `ArgumentParser` bekannt zu machen. Dann wird die automatisch generierte Hilfe noch schöner. Allerdings hätte man dann immer noch keinen Hilfetext pro Argument. Da würde wahrscheinlich nur helfen die Subparser selber zu erstellen, mit weniger Magie.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Ich würde argparse nicht in einer Bibliothek verwenden. Bibliotheken sollen ja von Programmen verwendet werden und diese Programme haben eventuell selbst wiederum Parameter und brauchen ein eigenes argparse und dann hast Du zwei mal argparse und das macht Probleme.

Wenn Du wiederverwendbare Funktionen in Module auslagerst, dann brauchst Du dafür keine extra Infrastruktur zu programmieren. Python ist die Infrastruktur dafür selbst. Einfach die Funktion in ein Modul, das Modul in den Pythonpath und bei Bedarf das Modul mit import importieren. Du brauchst auch nicht jeden Funktionsaufruf in ein try-catch zu klammern, Python hat eine Fehlerbehandlung schon fertig eingebaut, es gibt Dir eine saubere Fehlermeldung mit Zeilennummer und beendet das Programm. Überleg mal ob Dein Ansatz bei einem Programmierfehler genauso hilfreich ist, Du bekommst im Logfile die Meldung 'invalid function' ohne Zeilennummer und das Programm läuft weiter (und macht eventuell sogar Schaden). Ich finde da Fail-Fast besser.
a fool with a tool is still a fool, www.magben.de, YouTube
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

Hallo zusammen,

ich danke Euch erstmal für Eure Beiträge :)
Sirius3 hat geschrieben:@sfx2k: Kommentare, die nur beschreiben, was sowieso in der nächsten Zeile steht, sind dennoch überflüssig.
Dessen bin ich mir bewusst. Allerdings hilft es mir sehr, wenn ich mich in eine neue Sprache einarbeite.
Sirius3 hat geschrieben: Du hast viel zu viel Magie in Deinem Code. Das habe ich auch als Anfänger gerne gemacht. Tatsächlich macht Python die Überprüfung der Parameter beim Funktionsaufruf automatisch, Du mußt es also nicht selbst machen.
Ich mache ja die Prüfung auf die Parameter nicht der Überprüfung willen, sondern um dem Aufrufer mitzuteilen, wieviele Parameter Funktion x benötigt.
Sirius3 hat geschrieben: Ich weiß nicht, was Du alles in Deine utils-Module gepackt hast, aber das Logging-Modul bringt schon alles zum Loggen mit.
Ja, nachdem ich meine Logging-Routine gebaut habe, ist mir das auch aufgefallen :)
Allerdings habe ich dann noch ein paar Sachen eingebaut wie Einrücken auf Basis des Loglevels etc.
Sirius3 hat geschrieben: Pfade setzt man nicht mit + sondern mit os.path.join zusammen.
Hey, super Tipp. Das wusste ich nicht - habe mir dabei immer die Finger gebrochen :)
Sirius3 hat geschrieben: Alle Befehle auf Modulebene sollten in eine main-Funktion wandern:
Okay, habe ich umgesetzt :)
BlackJack hat geschrieben: Man könnte bei `argparse` auch aus den vorhandenen Funktionen ein Argument machen, also `argparse` schon sagen was es für eine Auswahl gibt. Dann kümmert sich das Modul schon um eine Fehlermeldung, und in der Hilfe werden sie auch angezeigt ohne das man selber eine 'help'-Funktion benötigt.
Hört sich gut an - das probiere ich mal aus :)
BlackJack hat geschrieben: Das `args` ein optionales Argument ist halte ich auch für keine so gute Idee.
Stimmt, irgendwie keine so gut Idee ;)
BlackJack hat geschrieben: Und bei Optionen die aus mehr als einem Buchstaben bestehen erwartet man eigentlich *zwei* '-' vor der Option.
Ja, mit argparse habe ich mich ehrlich gesagt sehr schwer getan. Ich glaube, so ganz habe ich diese Routine noch nicht ganz durchblickt.
Ich habe es vorher mit optparse gemacht, und das fiel mir wesentlich leichter.
BlackJack hat geschrieben: Wenn man ”Magie” betreiben möchte, dann macht man beim `argparse` eine Argumentgruppe pro Funktion die sich gegenseitig ausschliesst und benutzt Introspektion um die Anzahl und Namen der Funktionsargumente auch dem `ArgumentParser` bekannt zu machen. Dann wird die automatisch generierte Hilfe noch schöner. Allerdings hätte man dann immer noch keinen Hilfetext pro Argument. Da würde wahrscheinlich nur helfen die Subparser selber zu erstellen, mit weniger Magie.
Das muss ich nochmal googlen ;)
MagBen hat geschrieben: Ich würde argparse nicht in einer Bibliothek verwenden. Bibliotheken sollen ja von Programmen verwendet werden und diese Programme haben eventuell selbst wiederum Parameter und brauchen ein eigenes argparse und dann hast Du zwei mal argparse und das macht Probleme.
Wenn Du wiederverwendbare Funktionen in Module auslagerst, dann brauchst Du dafür keine extra Infrastruktur zu programmieren. Python ist die Infrastruktur dafür selbst. Einfach die Funktion in ein Modul, das Modul in den Pythonpath und bei Bedarf das Modul mit import importieren.
Der gepostete Code ist keine Bibliothek! Er ist der Zugang zu meiner Bibliothek.
Es geht darum, die Funktionen der Bibliothek von außen aufzurufen, also bspw. von der Windows-Kommandozeile aus, oder aus einem anderen Programm (andere Sprache).
Dieses Script ist der globale Ansprechpartner dafür.
MagBen hat geschrieben: Du brauchst auch nicht jeden Funktionsaufruf in ein try-catch zu klammern, Python hat eine Fehlerbehandlung schon fertig eingebaut, es gibt Dir eine saubere Fehlermeldung mit Zeilennummer und beendet das Programm. Überleg mal ob Dein Ansatz bei einem Programmierfehler genauso hilfreich ist, Du bekommst im Logfile die Meldung 'invalid function' ohne Zeilennummer und das Programm läuft weiter (und macht eventuell sogar Schaden). Ich finde da Fail-Fast besser.
Hier bekomme ich schon Zeilennummer etc. in mein Log geschrieben (durch meine Exceptionsutils ;) )
Da ich den Aufruf der Funktion u.U. nicht explizit antriggere (z.B. Task Scheduler), muss ich Fehler loggen - sie würden doch sonst untergehen.
Oder hast Du eine andere Idee?
BlackJack

@sfx2k: Was mir noch aufgefallen ist bei Namensgebung ist das Du allen Deinen Modulen aus irgendwelchen gründen einen `*utils`-Anhang verpasst. Muss das sein? Falls das irgendwie wichtig ist, wäre ein Namensraum `utils` vielleicht besser als das an jeden Namen hinten dran zu schreiben. Also ein Package. Und insgesamt würde ich auch alles in ein Package stecken, damit man nicht irgendwann in Namenskollisionen mit anderen Bibliotheken rennt, die man jetzt oder in der Zukunft verwenden will.

Es scheint ja für Windows zu sein, darum weiss ich nicht ob der folgende Kritikpunkt da anwendbar ist, aber unter Unix fänd ich so eine Kommandozeilenprogramm das ”alles” kann komisch. Da herrscht ja eher „die ein Werkzeug für genau eine Aufgabe”-Philosophie vor und dass man diese Werkzeuge dann auf verschiedene Weisen kombinieren kann. Die Powershell ist unter Windows auch ziemlich mächtig. Ich würde also erst mal schauen ob man damit etwas nicht machen kann, bevor ich dafür etwas eigenes schreibe was man auf der Shell aufruft.
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

BlackJack hat geschrieben: @sfx2k: Was mir noch aufgefallen ist bei Namensgebung ist das Du allen Deinen Modulen aus irgendwelchen gründen einen `*utils`-Anhang verpasst. Muss das sein?
Das ist zum Beispiel soetwas, was ich aus Delphi übernommen habe.
Ich bin ein ordnungsliebender Mensch und da dachte ich, dass alles thematisch in Files untergebracht sinnvoll wäre.
BlackJack hat geschrieben: Falls das irgendwie wichtig ist, wäre ein Namensraum `utils` vielleicht besser als das an jeden Namen hinten dran zu schreiben. Also ein Package. Und insgesamt würde ich auch alles in ein Package stecken, damit man nicht irgendwann in Namenskollisionen mit anderen Bibliotheken rennt, die man jetzt oder in der Zukunft verwenden will.
Damit weiß ich jetzt erstmal nichts anzufangen.
Bzw. ich weiß gerade nicht, wo darin der Vorteil liegt.
Vielleicht verstehe ich es aber auch nicht richtig.

Ich stelle mir das so vor, dass ich alle diese Dateien in einem Verzeichnis namens bspw. base habe.
Importieren würde ich die Files dann nicht mit import filename, sondern mit from base import filename
Korrekt?
Und wenn ich das base dann noch personalisieren würde, also bspw. sfx2kbase, dann ist die Gefahr von Kollisionen geringer.
Habe ich das so richtig verstanden?
BlackJack hat geschrieben: Es scheint ja für Windows zu sein, darum weiss ich nicht ob der folgende Kritikpunkt da anwendbar ist, aber unter Unix fänd ich so eine Kommandozeilenprogramm das ”alles” kann komisch. Da herrscht ja eher „die ein Werkzeug für genau eine Aufgabe”-Philosophie vor und dass man diese Werkzeuge dann auf verschiedene Weisen kombinieren kann. Die Powershell ist unter Windows auch ziemlich mächtig. Ich würde also erst mal schauen ob man damit etwas nicht machen kann, bevor ich dafür etwas eigenes schreibe was man auf der Shell aufruft.
Lassen wir den fachlichen Hintergrund resp. Sinn oder Unsinn mal außen vor - ich wollte sowas einfach mal ausprobieren ;)
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Ja, du hast Packages richtig verstanden. "sfx2kbase" hoert sich allerdings danach an, dass du das nach dem Autor - also dir - richten willst. Das ist eher weniger ueblich.
Ueblicherweise entspricht das Toplevel Paket (und damit das Verzeichnis) dem Programm-/Bibliotheksnamen.

Mehr zu Paketen gibt es im Tutorial: https://docs.python.org/2/tutorial/modu ... l#packages
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

cofi hat geschrieben:Ja, du hast Packages richtig verstanden. "sfx2kbase" hoert sich allerdings danach an, dass du das nach dem Autor - also dir - richten willst. Das ist eher weniger ueblich.
Ueblicherweise entspricht das Toplevel Paket (und damit das Verzeichnis) dem Programm-/Bibliotheksnamen.
Laufe ich dann nicht wieder Gefahr von Kollisionen? Ich meine, wenn ich mein Package 'Utils' nenne - das machen doch bestimmt andere auch so oder?
Ich könnte mir vorstellen, dass es utils.registry bspw. häufiger geben könnte.

Was wäre denn ein sinniger Name für soetwas?
Tut mir leid, dass ich da ein bissel einfallslos bin; aber um soetwas brauchte ich mir bisher nie Gedanken zu machen.
Ich bin weiß Gott nicht abgeneigt, dass zukünftig zu tun - aber irgendwie fehlt mir da der Ansatz.
BlackJack

@sfx2k: Thematisch in Module ordnen ist ja in Ordnung aber warum enden die Namen alle mit ”utils” als wenn das irgendwie einen Mehrwert hätte? Den sehe ich nämlich nicht. Beziehungsweise statt das an alle Namen hinten dran zu schreiben, wäre es IMHO sinnvoller mehr Ordnung da rein zu bringen in dem man nicht an Namen etwas hinten dran hängt sondern diese Module in einem Package zusammenfasst. Wobei ich bei `*utils` nach wie vor den Sinn anzweifle. Das gleiche würde ich so spontan zu dem `*base` sagen was Du für den Paketnamen vorgeschlagen hast.

Welchen Namen hat Dein Werkzeug? *Das* wäre doch dann ein netter Name für das Package wo die Module drin sind, die zu diesem Werkzeug gehören. Eventuell noch um den Zusatz `*lib` erweitert (ja ich weiss ich habe gerade gegen solche Namenszusätze gewettert ;-)) falls man das Werkzeug unter genau dem gleichen Namen als Skript systemweit installieren möchte.

Bezüglich der Namenskollisionen: Der Pfad in dem das ausgeführte Modul liegt, ist normalerweise im Suchpfad für Python-Module. Und zwar vor allen, oder zumindest den meisten anderen. Wenn Du jetzt also ein Modul mit dem Namen `fileutils` hast, und irgendeine Bibliothek von einem Drittanbieter verwendest die ebenfalls ein Modul mit dem Namen hat und diese Bibliothek dann ein ``import fileutils`` ausführt, importiert die *Dein* Modul statt ihr eigenes. Was dann mit ziemlicher Sicherheit nicht das enthält was an der Stelle erwartet wird. Darum sollte man eigentlich jedes Projekt das aus mehr als einem Modul besteht in ein Package stecken, damit „auf oberster Ebene” nur ein Modul-/Paketname steht, und nur der gegebenfalls noch Probleme bereiten kann. Ein-Modul-Projekte muss man deshalb nicht zwangsläufig als Package anfangen, um für den Fall vorzusorgen das mal so gross wird, dass man es auf mehrere Module aufteilen möchte, denn man kann ein Modul problemlos und transparent in ein Package umwandeln, wenn es nötig wird.

Das Package könnte man so organisieren, mit dem Inhalt von dem gezeigten Quelltext in der ``__init__.py``:

Code: Alles auswählen

sfx2k_lib/
    __init__.py
    exceptions.py
    file.py
    logging.py
    registry.py
    system.py
Das Werkzeug sähe dann so aus:

Code: Alles auswählen

#!/usr/bin/env python3
# coding: utf8
from sfx2k_lib import main


if __name__ == '__main__':
    main()
Oder man schreibt sich eine Batch-Datei die das Modul aufruft.

Der Pfad wo das ``sfx2k_lib/``-Verzeichnis drin liegt, muss im Suchpfad für Python-Module liegen. Also zum Beispiel weil man die entsprechende Umgebungsvariable gesetzt hat, oder weil man das Package ”ordentlich” installiert hat.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

sfx2k hat geschrieben:Der gepostete Code ist keine Bibliothek! Er ist der Zugang zu meiner Bibliothek.
Es geht darum, die Funktionen der Bibliothek von außen aufzurufen, also bspw. von der Windows-Kommandozeile aus, oder aus einem anderen Programm (andere Sprache).
Dieses Script ist der globale Ansprechpartner dafür.
Es stellt sich die Frage ob Du soetwas wirklich brauchst. Du kannst nämlich auch Funktionen aus Python-Modulen in einem Einzeiler direkt von der Kommandozeile aufrufen. Das hier ist z.B. mein einfachstes MPI-Testprogramm.

Code: Alles auswählen

python -c "import socket; print(socket.gethostname())"
Ich rufe die Funktion gethostname des Moduls socket auf. Das könnte ich so auch in einem Scheduler machen. (Bei MPI ist es immer ganz spannend herauszufinden, wie die Prozesse auf die verschiedenen Rechner aufgeteilt werden.)
sfx2k hat geschrieben:Hier bekomme ich schon Zeilennummer etc. in mein Log geschrieben (durch meine Exceptionsutils ;) )
Da ich den Aufruf der Funktion u.U. nicht explizit antriggere (z.B. Task Scheduler), muss ich Fehler loggen - sie würden doch sonst untergehen.
Oder hast Du eine andere Idee?
Scheduler haben oftmals ihre eigene Logging Infrastruktur und loggen alles was nach Standard-Error geschrieben wird und das macht ja die Python-Standard-Fehlerbehandlung. Wenn ich z.B.

Code: Alles auswählen

python -c "import socket; print(socket.get_hostname())"
schreibe gibt mir Python eine ganz ähnliche Fehlermeldung wie Dein Logger:

Code: Alles auswählen

Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: 'module' object has no attribute 'get_hostname'
a fool with a tool is still a fool, www.magben.de, YouTube
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

sfx2k hat geschrieben:ch mache ja die Prüfung auf die Parameter nicht der Überprüfung willen, sondern um dem Aufrufer mitzuteilen, wieviele Parameter Funktion x benötigt.
Genau das macht die Standardfehlermeldung bei falscher Anzahl an Parametern auch:

Code: Alles auswählen

>>> def func(a, b, c): pass
... 
>>> func(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 3 arguments (2 given)
>>> func(1, 2, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 3 arguments (4 given)
sfx2k hat geschrieben:Ja, nachdem ich meine Logging-Routine gebaut habe, ist mir das auch aufgefallen
Allerdings habe ich dann noch ein paar Sachen eingebaut wie Einrücken auf Basis des Loglevels etc.
Auch beim logging-Modul läßt sich die Ausgabe ganz individuell einstellen. Lies einfach mal die Dokumentation dazu durch. Wenn es schon was fertiges gibt, sollte man sich ganz genau überlegen, ob man da etwas selbst macht. Zum einen ist die Wahrscheinlichkeit, dass man Fehler einbaut, ziemlich groß und der Aufwand das gründlich zu testen nicht unerheblich, zum anderen zwingst Du damit alle, die Deinen Code verwenden, sich auch noch in Deine eigengestrickten Module einzuarbeiten, obwohl sie wahrscheinlich die aus der Standardbibliothek schon kennen und anwenden können. Also Mehraufwand bei Dir, Mehraufwand bei allen anderen, ohne dass es wirklich einen Vorteil bietet.
sfx2k
User
Beiträge: 54
Registriert: Dienstag 2. September 2014, 13:29

BlackJack hat geschrieben: Thematisch in Module ordnen ist ja in Ordnung aber warum enden die Namen alle mit ”utils” als wenn das irgendwie einen Mehrwert hätte? Den sehe ich nämlich nicht. Beziehungsweise statt das an alle Namen hinten dran zu schreiben, wäre es IMHO sinnvoller mehr Ordnung da rein zu bringen in dem man nicht an Namen etwas hinten dran hängt sondern diese Module in einem Package zusammenfasst. Wobei ich bei `*utils` nach wie vor den Sinn anzweifle. Das gleiche würde ich so spontan zu dem `*base` sagen was Du für den Paketnamen vorgeschlagen hast.
Du hast recht; einen Mehrwert bieter das wirklich nicht :)
Wie gesagt, ich habe das aus meiner Delphi-Bibliothek so übernommen.
BlackJack hat geschrieben: Welchen Namen hat Dein Werkzeug? *Das* wäre doch dann ein netter Name für das Package wo die Module drin sind, die zu diesem Werkzeug gehören. Eventuell noch um den Zusatz `*lib` erweitert (ja ich weiss ich habe gerade gegen solche Namenszusätze gewettert ;-)) falls man das Werkzeug unter genau dem gleichen Namen als Skript systemweit installieren möchte.
Das 'Werkzeug' hat keinen Namen - es ist einfach eine Basisbibliothek mit Routinen, die man immer wieder gebrauchen kann.
BlackJack hat geschrieben: Bezüglich der Namenskollisionen: Der Pfad in dem das ausgeführte Modul liegt, ist normalerweise im Suchpfad für Python-Module. Und zwar vor allen, oder zumindest den meisten anderen. Wenn Du jetzt also ein Modul mit dem Namen `fileutils` hast, und irgendeine Bibliothek von einem Drittanbieter verwendest die ebenfalls ein Modul mit dem Namen hat und diese Bibliothek dann ein ``import fileutils`` ausführt, importiert die *Dein* Modul statt ihr eigenes. Was dann mit ziemlicher Sicherheit nicht das enthält was an der Stelle erwartet wird. Darum sollte man eigentlich jedes Projekt das aus mehr als einem Modul besteht in ein Package stecken, damit „auf oberster Ebene” nur ein Modul-/Paketname steht, und nur der gegebenfalls noch Probleme bereiten kann. Ein-Modul-Projekte muss man deshalb nicht zwangsläufig als Package anfangen, um für den Fall vorzusorgen das mal so gross wird, dass man es auf mehrere Module aufteilen möchte, denn man kann ein Modul problemlos und transparent in ein Package umwandeln, wenn es nötig wird.
Okay, das leuchtet allerdings ein.
BlackJack hat geschrieben: Das Package könnte man so organisieren, mit dem Inhalt von dem gezeigten Quelltext in der ``__init__.py``:

Code: Alles auswählen

sfx2k_lib/
    __init__.py
    exceptions.py
    file.py
    logging.py
    registry.py
    system.py
Das Werkzeug sähe dann so aus:

Code: Alles auswählen

#!/usr/bin/env python3
# coding: utf8
from sfx2k_lib import main


if __name__ == '__main__':
    main()
Oder man schreibt sich eine Batch-Datei die das Modul aufruft.

Der Pfad wo das ``sfx2k_lib/``-Verzeichnis drin liegt, muss im Suchpfad für Python-Module liegen. Also zum Beispiel weil man die entsprechende Umgebungsvariable gesetzt hat, oder weil man das Package ”ordentlich” installiert hat.
Das ist doch mal eine Ansage :) Danke!
MagBen hat geschrieben: Es stellt sich die Frage ob Du soetwas wirklich brauchst. Du kannst nämlich auch Funktionen aus Python-Modulen in einem Einzeiler direkt von der Kommandozeile aufrufen. Das hier ist z.B. mein einfachstes MPI-Testprogramm.

Code: Alles auswählen

python -c "import socket; print(socket.gethostname())"
Ich rufe die Funktion gethostname des Moduls socket auf. Das könnte ich so auch in einem Scheduler machen. (Bei MPI ist es immer ganz spannend herauszufinden, wie die Prozesse auf die verschiedenen Rechner aufgeteilt werden.)
Wie schon weiter oben gesagt, bitte ich, den Sinn oder Unsinn dieses Scriptes außen vor zu lassen; ich wollte das einfach mal ausprobieren :)
Aber dennoch gut zu wissen, dass der generelle direkte Aufruf auch problemlos funktioniert.
MagBen hat geschrieben: Scheduler haben oftmals ihre eigene Logging Infrastruktur und loggen alles was nach Standard-Error geschrieben wird und das macht ja die Python-Standard-Fehlerbehandlung. Wenn ich z.B.

Code: Alles auswählen

python -c "import socket; print(socket.get_hostname())"
schreibe gibt mir Python eine ganz ähnliche Fehlermeldung wie Dein Logger:

Code: Alles auswählen

Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: 'module' object has no attribute 'get_hostname'
Okay, leuchtet ein. Werde mal schauen, ob der Windows-Task-Planer das auch kann :)
Sirius3 hat geschrieben: Genau das macht die Standardfehlermeldung bei falscher Anzahl an Parametern auch:

Code: Alles auswählen

>>> def func(a, b, c): pass
... 
>>> func(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 3 arguments (2 given)
>>> func(1, 2, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() takes exactly 3 arguments (4 given)
Okay, das war mir so nicht bewusst :)
Sirius3 hat geschrieben: Auch beim logging-Modul läßt sich die Ausgabe ganz individuell einstellen. Lies einfach mal die Dokumentation dazu durch. Wenn es schon was fertiges gibt, sollte man sich ganz genau überlegen, ob man da etwas selbst macht. Zum einen ist die Wahrscheinlichkeit, dass man Fehler einbaut, ziemlich groß und der Aufwand das gründlich zu testen nicht unerheblich, zum anderen zwingst Du damit alle, die Deinen Code verwenden, sich auch noch in Deine eigengestrickten Module einzuarbeiten, obwohl sie wahrscheinlich die aus der Standardbibliothek schon kennen und anwenden können. Also Mehraufwand bei Dir, Mehraufwand bei allen anderen, ohne dass es wirklich einen Vorteil bietet.
Ich werde mir das Python-Logging-Modul mal genauer ansehen.
Auch hier ging es eigentlich nur darum, beim Üben etwas 'produktives' zu bauen.
Antworten