Geschickte Lösung: Dictionaries/ if-else-Kaskaden

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
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

Hi Leute,

Ich bin dabei einen Wrapper zu schreiben für die PS Tools von Windows, nun bin ich noch lange nicht fertig, doch ich
wollte mal wissen, wie man sowas normalerweise macht, denn wie ihr an meinem Codesnippet sehen
könnt, treten bei mir viele Dictionaries und if-else-Kaskaden auf, siehe psexec(). Also wie macht man sowas eleganter?
Danke im voraus.

Liebe Grüße,
Jonas :wink:

Edit: @Codesnippet:
TESTPATH in der main() ist durch "__file__" zur ersetzen,
ich hatte nur einen lokalen Testpfad für Tests in IDLE.
BlackJack

@jonas: Das sieht mir nach ein bisschen viel "Magie" und Typtesterei aus. Ich würde da wohl eher mit objektorientierter Programmierung herangehen, oder zumindest die Aktionen nicht vom Typ der Argumente abhängig machen, sondern die Information was zu tun ist, bei den Werten in `EXEC_PARAMS` hinterlegen. Denn eigentlich sollte das doch für die einzelnen Argumente dort eindeutig sein!? Bei 'password' dürfte doch wohl eine Zeichenkette gefragt sein und weder ein Wahrheitswert noch eine Priorität!?

Ansonsten könntest Du ein paar "list comprehensions" durch Generatorausdrücke ersetzen. Also einfach die eckigen Klammern weglassen.

Es gibt `os.path.join()`.

Bei `psexec()` könnte man auch bei diesem Ansatz einiges verbessern. Da steht sehr oft der Zugriff ``kwargs[key]``, was man vermeiden könnte wenn man gleich über Schlüssel *und* Wert iteriert. Ist das erste ``if`` wirklich notwendig? Dadurch werden nicht existierende Argumentnamen einfach ignoriert. Man könnte ``EXEC_PARAMS[key]`` doch auch einfach einen `KeyError` auslösen lassen. Dann weiss der Aufrufer dass er was falsch gemacht hat. Auf jeden Fall sollte man den Aufruf von `has_key()` durch den ``in``-Operator ersetzen. Das ist besser lesbar und zukunftssicherer -- die Methode ist nämlich "deprecated".

Warum ist das `PRIORITY`-Dictionary im `EXEC_PARAMS`-Dictionary? Das ist eine unnötige Indirektion. Da der Fall sowieso in einem ganz eigenen ``if``-Zweig behandelt wird, hätte man das verwirrende ``EXEC_PARAMS[key][kwargs[key]]`` auch wesentlich einfacher schreiben können. Einfach als ``PRIORITY[value]`` wenn in der Schleife nicht nur über den Schlüssel iteriert würde. Das ist wesentlich übersichtlicher finde ich.

Nimmt eine der Optionen von den PS_TOOL eigentlich eine Zahl als Argument entgegen? Falls ja, ist Dir das hier klar!?

Code: Alles auswählen

In [112]: 1 == True
Out[112]: True

In [113]: 0 == False
Out[113]: True
Also ``psexec(run_on_cpu=1)`` hat deshalb eventuell nicht den gewünschten Effekt.

Im `False`-Fall eine leere Zeichenkette zur Liste hinzuzufügen ist vielleicht auch keine gute Idee. Das ist bei `subprocess` trotzdem ein Argument und eines was die Programme da vielleicht nicht erwarten.

Warum Du die ``if``\s so tief verschachtelt hast, ist mir auch nicht so ganz klar!? Wonach hast Du denn entschieden in einem ``else``-Zweig mit einem ``if`` zu beginnen statt da ein ``elif`` draus zu machen?

Den Typtest solltest Du mit `isinstance()` machen. Da kann man als zweites Argument auch ein Tupel mit mehreren Typen angeben. Allerdings würde ich nicht auf `list` und `tuple` testen sondern auf `basestring` und die Zweige umkehren. Es ist nämlich viel wahrscheinlicher das jemand dort ein anderes "iterable" als Liste oder Tupel übergibt als dass jemand einen eigenen Untertyp von Zeichenketten verwendet.
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

WoW Blackjack ich staune immer wieder wie schnell man von dir einen hochinformativen Beitrag vor die Nase bekommt :wink:

Ich habe noch nicht so ganz verstanden, was ich mit "os.path.join()" anfangen soll? Suchen der PS Tools funktioniert doch super?

@psexec:

Jo, dachte ich mir schon, dass es hier einiges zu verbessern gibt.
Habe länger nix mehr gemacht mit Python, bin gerade dabei mich
wieder mehr reinzufuchsen :D

@Codeschnippsel:

Nee, das war mir so nicht klar, aber danke für den Hinweis ;)

@leere Zeichenkette:

Code: Alles auswählen

>>> "".join(["","hello","","you",""])
'helloyou'
Also kein Problem.

@if/Verschachtelung:

Ich weiß nicht genau, warum ich das so gemacht habe,
hat sich irgendwie so ergeben..

@isinstance():

Wird eingebaut.

@BlackJack:
Vielen Dank nochmal ;)

Liebe Grüße,
Jonas :wink:

@Code:
Wird morgen nochmal verbessert geuppt,
muss heute noch bisschen komplexe Zahlen,
vollständige Induktion, Restklassen und so
ein Kram für Mathe lernen... (-.-)
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

Hi,
ich bin es nochmal, ich habe nun ein anderes Problem und
zwar habe ich in diesem Codestück ein seltsames Verhalten, von dem ich nicht weiß,
wieso?

Eigentlich sollte die Ausgabe sein:

Code: Alles auswählen

-keyH 1 2 -keyF example
stattdessen erhalte ich:

Code: Alles auswählen

-keyE 1 2 -keyE example
Ich weiß leider nicht, wieso und hoffe, ihr könnt mir helfen.
Lg, Jonas :wink:
BlackJack

@jonas: Freie Variablen in Funktionen werden nachgeschlagen wenn die Funktion aufgerufen wird und nicht wenn sie erzeugt werden. Das `parameter` in den ``lambda``\s hat damit in allen den gleichen Wert nämlich was in der `make_function_dictionary()` als Letztes an diesen Namen gebunden wurde.
BlackJack

@jonas: Noch ein paar Anmerkungen:

Statt eine Liste mit den Elementen von `dictionary` in der `create_command()` zu erzeugen und darüber zu iterieren ist `iteritems()` besser.

Warum prüfst Du ob `key` im Dictionary ist um gegebenenfalls einen `KeyError` auszulösen, wo das doch das normale Verhalten ist, wenn man einfach darauf zugreift? Und auch hier wird wieder unnötigerweise eine Liste mit den Schlüsseln erzeugt um dann in linearer Zeit zu schauen ob der Schlüssel da drin enthalten ist. Schlüssel kann man auch ganz einfach mit ``key in dictionary`` überprüfen und das geht viel schneller.

`inputtype` und `func` könnte man auch mittels "sequence unpacking" zuweisen, dann spart man sich eine Zeile und unnötige Indexzugriffe.

Der Formatcode '%s' sorgt dafür dass das Objekt in eine Zeichenkette umgewandelt wird, da muss man nicht selber vorher `str()` verwenden.

Warum hängst Du den `SEPARATOR` überall an statt ihn zum "joinen" zu verwenden?

`make_function_dictionary()` ist insgesamt ein wenig komisch. Das `typedictionary` wird bei jedem Schleifendurchlauf neu erstellt und auch die ganzen ``lambda``-Funktionen darum werden immer wieder neu erstellt. Und `inputtype` in dem Dictionary entspricht ja genau dem Key mit dem Du dann darauf zugreifst. Ich würde das Dictionary da rausziehen und nur die Typen auf die Funktionen abbilden. Und zwar auf Funktionen mit *zwei* Argumenten: Wert und `parameter`. Den kann man dann mit `functools.partial` in der Schleife an den aktuellen Parameter binden.

Auf Wahrheitswerte sollte man übrigens nicht mit ``is`` Testen. Das kann funktionieren, muss es aber nicht.

Code: Alles auswählen

from functools import partial

SEPARATOR = ' '

def make_function_dictionary(dictionary):
    
    converters = {
        bool: lambda p, v: str(p) if v else '',
        int: lambda p, v: str(p) + SEPARATOR + str(v),
        str: lambda p, v: str(p) + SEPARATOR + str(v),
        tuple: lambda p, v: str(p) + SEPARATOR + SEPARATOR.join(map(str, v))
    }

    return dict((k, [t, partial(converters[t], p)])
                for k, (t, p) in dictionary.iteritems())


def create_command(dictionary, **keywords):
    
    command = list()
    for key, value in keywords.iteritems():
        inputtype, func = dictionary[key]
        if isinstance(value, inputtype):
            command.append(func(value))
        else:
            raise ValueError('%s is not type of %s' % (value, inputtype))
    return SEPARATOR.join(command)


def main():
    notready = {
        'keyE': [bool, '-keyE'],
        'keyF': [str, '-keyF'],
        'keyG': [int, '-keyG'],
        'keyH': [tuple, '-keyH']
    }
    notready = make_function_dictionary(notready)
    print create_command(notready, keyE=False, keyF='example', keyH=(1, 2))


if __name__ == "__main__":
    main()
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

Das ist toll, super!
Ich danke dir wirklich sehr, ohne dich hätte ich das nicht
geschafft..:oops:
Ich muss noch viel Lernen über Programmieren mit Python und
allgemein schätze ich, aber ich denke das wird :wink:
Also vielen Dank noch mal!

Lg, Jonas ;)
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

Interessanter fände ich übrigens noch einen Ersatz der Pstools durch Python und wmi. Dann muss man nicht darauf achten, dass das richtige .NET installiert ist.
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

Gute Idee mkesper, aber ich bleibe erstmal bei der Wrapper Idee.
Die "direkte" Pythonlösung kommt dann evtl. noch mal irgendwann
als Erweiterung.

Lg, Jonas :wink:
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@jonas:
Oder Du sprichst mit ctypes die WinAPI direkt an. Je nach Deiner Zielsetzung ist das vllt. sogar der einfachere Weg.

In Sachen Prozessmanagement wird das allerdings sehr schnell Windows-versionspezifisch, gerade wenn Du Funktionen aus psapi.dll und ntdll.dll benötigst. Die WinAPI ist relativ gut dokumentiert (auch ohne MSDN-Zugang), wobei ntdll.dll nicht mehr zur offiziellen "öffentlichen" API gehört, sondern Microsoft als interne Userland->Kernel-API dient. Allerdings lassen sich bestimmte Dinge nur hierüber einsehen, daher verwundert es auch nicht, das die sysinternals-Leute (von denen stammen die pstools) hiervon ordentlich Gebrauch machen.

Damit fallen dann auch Abhängigkeiten wie DDK oder .NET-Installation weg.
Benutzeravatar
jonas
User
Beiträge: 156
Registriert: Dienstag 9. September 2008, 21:03

@jerch:

Danke für den Tip, mal sehen was sich ergibt :wink:

@BlackJack:

Darf ich deine Version von dem Quelltest so übernehmen, oder hast du
damit ein Problem?

Lg, Jonas :wink:
Antworten