Brauche Kritik: Programm aufteilen und ist code ok so?

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
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

Vorneweg: Ich habe mich bereits von der in diesem Posting vorgestellten Struktur verabschiedet. Die aktuelle Fassung ist weiter unten hier: viewtopic.php?p=193409#p193409

Hallo an alle. Ich bin relativ neu in Python und bräuchte mal ein wenig Rat und Hilfe. Ich bin zwar kein vollständiger Anfänger im Bereich der Programmierung, aber auch kein C/C++ Experte. ... so mal nur als Vorwarnung.^^ Es ist im Grunde kein spezielles Problem, bin mir halt nur nicht ganz sicher.

Wie sollte ich mein Programm arrangieren und ist der Code so ok? Ziel ist es hierbei das Programm modularisch aufzubauen.

Es soll ein gar nicht so komplizierter CLI & GUI Launcher für ein bestimmtes Programm werden. Es geht um den Emulator M.A.M.E.. Es wird einen temporären Verzeichnis generieren und Gui zum eingeben des Highscore erzeugen etc... Wie wäre es zum Beispiel mit solch einer Struktur: (mamehi ist der Name des Projektes)
  • Mamehi/
    • mamehi/
      • commandline.py (unabhängige Klasse zum Aufrufen von Programmen, wrapper für subprocess.Popen)
      • filesystem.py (Sammlung von Funktionen die mit Dateisystem zu tun haben und in keine andere Kategorie passen)
      • __main__.py (erzeugt Haupt-Mamehi Instanz und ruft main() auf, anschließend sys.exit())
      • mamehi.py (einzelne Klasse mit main Methode und optionen parsen etc)
      • splitpath.py (einzelne Klasse zum Teilen eines strings in seine Pfad-Bestandteile und zum absolut machen)
    • make.sh
    • setup.py
    • README
Ne GUI und ne CLI Oberfläche, sowie weitere Klassen z.B. wegen Konfigurationsdatei werden noch gemacht. Meine Frage ist, was haltet ihr davon? Das Programm kann gestartet werden, wenn man in der Kommandozeile "python mamehi" (den Ordner unter Mamehi) eingibt. Es startet __main__.py automatisch. Ist diese Vorgehensweise empfehlenswert? Ich versuche die gesamten Bestandteile zu trennen. Allerdings habe ich das mit dem Fehler fangen noch nicht so drauf.

Hier ich poste mal die commandline.py Klasse. Was haltet ihr von den Kommentaren und den Stil? Ist allerdings in Englisch:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: iso-8859-1 -*-


"""
Copyright (c) 2011, Tuncay
All rights reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright 
    notice, this list of conditions and the following disclaimer in the 
    documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
"""


import os
import subprocess
import shlex
import copy


class CommandLine:
    
    """
    Prepares and runs a command line and gets it output.  Command can be a 
    list of arguments or a single string with all arguments.  In that case, 
    it will be converted back to a list, by analizing its parts.
    
    Attributes: (read-only)
        command -- The actual command line as a list.
    
    Methodes:
        add_arg -- Adds arguments to the command.
        remove_arg -- Removes argument from command.
        execute -- Runs the command and gets it output.
    
    Examples:
        >>> cmd = CommandLine('echo')
        >>> print cmd.add_arg('hello world')
        ['echo', 'hello world']
        
        >>> print cmd.execute('!'),
        hello world !
        
    """
    
    def __init__(self, command):        
        """
        Initiates the command from list or string.
        
        Parameters:
            command -- An args list to execute with Popen.  If this is a 
            string, then it will be analyzed and splitted up into a list of
            args with shlex.split() function.
        
        Remarks:
            Saves the command line as an instance attribute to be used later.  
            Accepts lists and single strings only.
        
        """        
        if isinstance(command, list):
            self.command = command
        elif isinstance(command, basestring):
            self.command = shlex.split(command)
        else:
            raise TypeError()
    
    def add_arg(self, value):        
        """
        Appends additional arguments to the current command. 
        
        Parameters:
            value -- Can be a list, which then extends the command args list.
            If this is a string, then string will be appended as a single 
            entry in commands args list.
        
        Return:
            The updated command.
        
        Remarks:
            Changes the self.command attribute directly.
        
        """        
        if isinstance(value, basestring):
            self.command.append(value)
        elif isinstance(value, list):
            self.command.extend(value)
        else:
            raise TypeError()
        
        return self.command
    
    def remove_arg(self, value):        
        """
        Remove an single argument by matching value.
        
        Parameters:
            value -- A string entry to remove from commands list of args 
            entries.  If this is a list, then all matching strings are 
            removed.
        
        Return:
            The updated command.
        
        Remarks:
            Changes the self.command attribute directly.
        
        """
        if isinstance(value, basestring):
            if value in self.command:
                self.command.remove(value)
        elif isinstance(value, list):
            for a in value:
                if a in self.command:
                    self.command.remove(a)
        else:
            raise TypeError()
        return self.command
 
    def execute(self, args=None, wait=True, workingdir=None):        
        """
        Executes the command and get its output.
        
        Parameters:
            args -- Additional arguments (list or string) to use at execute
            time only.
            
            wait -- If set to True, then stdout and stderr are catched and
            returned. If set to False, then script does not wait until 
            programs execution is ended and gets the pid as return value.
            
            workingdir -- Uses the this folder as working dir and changes
            temporary the current working dir of script.
        
        Return:
            Catches the stdout and stderr output of console (without showing
            console) and gets them together as a single string.
        
        """
        # Backup the current working dir, for temporary change.
        if workingdir is not None:
            cwd_backup = os.getcwd()
            os.chdir(workingdir)
        # Make a copy of the command, for making temporary changes on it.
        command = copy.copy(self.command)
        # Temporary add additional arguments to the command, if specified.
        if args is not None:
            if isinstance(args, basestring):
                command.append(args)
            elif isinstance(args, list):
                command.extend(args)
            else:
                raise TypeError()
        # Execute the command. Do not use the shell, as the command is 
        # expected to be a list. The stdout and stderr are piped together into
        # a single string.
        proc = subprocess.Popen(command,
            shell=False,
            stdout=subprocess.PIPE, 
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            cwd=workingdir)
        # Revert to saved previous current working dir.
        if workingdir is not None:
            os.chdir(cwd_backup)
        # Get the stdout and stderr output from console or the process pid.
        if wait:
            retval = proc.communicate()[0]
        else:
            retval = proc.pid
            
        return retval


if __name__ == "__main__":
    import doctest
    doctest.testmod()
Zuletzt geändert von finki am Sonntag 6. März 2011, 15:32, insgesamt 1-mal geändert.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Soweit ich das sehe brauchst du für nichts von alledem Klassen. Definiere das einfach als Funktionen auf unterster Ebene. Außerdem würde ich die ganzen Utilities in ein gemeinsames "utils.py"-Modul zusammenfassen.

Der `Popen`-Wrapper erscheint mir ziemlich sinnlos. Ruf' `subprocess.Popen` einfach direkt auf. Wenn du wirklich irgendwelches Argument-Gewurschtel machen musst, dann subklasse `Popen` und überschreibe `__init__` in der Subklasse.
BlackJack

@finki: Du hast da ziemlich oft "einzelne Klasse" stehen. Erinnert ein wenig an Java und ist ein etwas ungewöhnlich.

Die `Command`-Klasse finde ich auch reichlich komisch. Das sieht verdammt "over engineered" aus. Wozu braucht man so etwas? Hast Du ein Programm geschrieben und festgestellt das Du so eine Klasse brauchen könntest, oder hast Du die Klasse geschrieben, in der Hoffnung Du könntest die irgendwie gebrauchen? Rufst Du die Methoden tatsächlich mal mit Listen und mal mit Zeichenketten auf? Warum? Und was ist der Einsatzzweck von `remove_arg()`?

Warum geben die Methoden fast alle `self.command` zurück. Wer rechnet denn mit so etwas? Und wieder die Frage nach der tatsächlichen Verwendung!?

`add_arg()` sollte `add_args()` heissen. `remove_arg()` sollte nur ein Argument entgegen nehmen -- für eine Liste von Argumenten müsste es dann eine `remove_args()` geben. Also rein von der Namensgebung -- ich bin so gar nicht überzeugt, dass diese Methode nicht viel zu unsinnig ist.

Die vielen `isinstance()` sind unschön. Typtesten läuft sowohl "duck typing" als auch OOP entgegen.

Finger weg von `os.chdir()`. Zumal das hier auch noch völlig unnötig ist!

`copy.copy()` um eine Liste zu kopieren ist mit Kanonen auf Spatzen geschossen. ``list(command)`` tut es auch, oder auch einfach nur ``command = command + args`` wenn `args` eine Liste ist.

Funktionen sollten nur eine Sache tun und einen "duck type" als Ergebnis haben. Wenn eine Funktion je nach Argumenten eine Zeichenkette oder eine Zahl zurück gibt, die etwas völlig unterschiedliches bedeuten, dann hat man in der Regel zwei verschiedene Funktionen in einer vermischt.
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

Erstmal danke für eure Hinweise. Ich glaube ich Fuchtel da noch ein wenig zu viel herum. Habe deswegen so viele Klassen, weil ich mir erhoffe eine einmal erstellte Klasse mit so wenig wie möglichen Anpassungen wieder verwenden zu können; halt OOP Grundgedanke. Ich dachte sauberes Trennen wäre ein Pluspunkt in der Entwicklung (und dann fummel ich mit isinstance() noch rum -.-).
Dauerbaustelle hat geschrieben:Außerdem würde ich die ganzen Utilities in ein gemeinsames "utils.py"-Modul zusammenfassen.
Ich wollte schon gerne alles getrennt haben, da jede Klasse voneinander unabhängig wiederverwendbar sein sollte und so allgemein wie möglich gehalten ist.

Und wie gesagt, das Herzstück des Programmes ist ja dann später mamehi.py, welches alle anderen Klassen und Aufrufe koordiniert. Dadurch erhoffe ich mir eine möglichst Funktionale main() zu haben und so flexibel wie möglich zu behalten. Denn alle zusätzlichen Klassen und Module, welche nicht in mamehi.py zu finden sind, sollen selbstständige Projekte darstellen und unabhängig wie möglich funktionieren können. Damit ich sie halt auch für andere Projekte wieder verwenden kann. Ich hoffe dadurch ist besser verständlich, wieso es so explizit gehalten ist.
BlackJack hat geschrieben:Warum geben die Methoden fast alle `self.command` zurück. Wer rechnet denn mit so etwas?
Das ist eine gute Frage. An dieser Stelle würde ich mal sagen, das es nur ein dummy Rückgabewert ist. Ich hatte keine bessere Idee.
BlackJack hat geschrieben:Und was ist der Einsatzzweck von `remove_arg()
Ich habe da an das Modifizieren gedacht, nachdem sie einmal benutzt wurde und in veränderter Form wieder benötigt wird. Vielleicht hier auch "overheated"... Ich sollte wohl eher nur das nötigste umsetzen.
BlackJack hat geschrieben:`add_arg()` sollte `add_args()` heissen. `remove_arg()` sollte nur ein Argument entgegen nehmen -- für eine Liste von Argumenten müsste es dann eine `remove_args()` geben. Also rein von der Namensgebung -- ich bin so gar nicht überzeugt, dass diese Methode nicht viel zu unsinnig ist.
Ja, vermutlich hätte ich es besser trennen sollen.
BlackJack hat geschrieben:Die vielen `isinstance()` sind unschön.
Es sieht nicht elegant aus, das gebe ich zu. Aber sollte eine Funktion / Methode nicht soweit automatisieren können wie es nur kann? Vielleicht habe ich mich auch nur nicht ganz eindeutig für einen Stil entschieden. Ich komme aus einer einfachen prozeduralen Automationssprache ohne Typen (alles nur Strings -.-). Da habe ich wohl noch nicht so den dreh raus.
BlackJack hat geschrieben:Finger weg von `os.chdir()`.
Wahrscheinlich gehöre ich zu den Leuten, die alles doppelt und dreifach absichern, weil sie nicht genau wissen wie das einzelne Schloß funktioniert. Auch hier wieder, ich war nicht sicher ob es nötig war. Aber so wie du es schreibst, scheint mir das so als ob es eine Sünde wäre os.chdir() zu benutzen. Wieso? (Mal abgesehen davon, das es hier anscheinend nutzlos ist.)
BlackJack hat geschrieben:`copy.copy()` um eine Liste zu kopieren ist mit Kanonen auf Spatzen geschossen. ``list(command)`` tut es auch, oder auch einfach nur ``command = command + args`` wenn `args` eine Liste ist.
Vielleicht habe ich nicht gründlich genug gesucht. Ich war nämlich verzweifelt, weil Python immer als Referenz umgeht. Dann habe ich nämlich nach einer Möglichkeit gesucht um genau das Kopieren zu erzwingen. Aber an eine derart einfache Lösung habe ich noch gar nicht gedacht. Danke. Ich werde mir mal list() dann näher anschauen.

Anscheinend fehlt mir noch die Übung und das Gefühl für Programmieren im "echten" Python Stile. Aber wie ich sehe, gibt es hier doch sehr kritikreiche Leute. :D (Was jetzt selbst keine Kritik war. xD) Allein jetzt habe ich schon einiges gelernt und es hat mir zu denken gegeben. Super Community, vielen danke noch mals.
BlackJack

@finki: Die Wiederberwendbarkeit ist IMHO Blödsinn und auch nicht der Grundgedanke von OOP. Wiederverwendbarkeit ist auch bei prozeduraler oder funktionaler Programmierung möglich und gewünscht und genau wie da gibt es auch bei OOP Sachen die sich dafür anbieten und solche die es nicht tun. Alles irgendwie auf Flexibilität und Wiederverwendbarkeit zu trimmen ist kontraproduktiv.

Wenn Du jede Klasse wiederverwenden willst, dann musst Du die nicht nur in verschiedene Module stecken, sondern in separat voneinander installierbare Projekte. Das wäre noch mehr Overkill als es jetzt schon scheint.

So flexibel wie möglich sollte man gar nicht programmieren. Nur so flexibel wie nötig. Sonst programmiert man Sachen, die nicht nur flexibler sind als man das überhaupt braucht, sondern auch komplexer oder zumindest umfangreicher. Man programmiert Unnötiges, muss das testen und dokumentieren, und bei der Fehlersuche kann es unter Umständen auch im Weg sein. Die beiden Abkürzungen in dem Zusammenhang sind *KISS* und *YAGNI* -- *Keep It Simple and Stupid* und *You Ain't Gonna Need It*. Einfache Lösungen die dass tun was man haben möchte und nichts schreiben was man gar nicht braucht.

Wenn eine Methode nichts sinnvolles zurück geben kann, dann lass das ``return`` einfach weg.

Zu `remove_arg()`: Hast Du Dir da mal konkrete Anwendungsfälle ausgedacht und insbesondere auch bedacht was passiert wenn es mehrere Argumente gibt die gleich sind? Die Reihenfolge spielt bei vielen Programmen ja eine Rolle. Oder mal ein ganz pathalogisches Beispiel (ungetestet):

Code: Alles auswählen

command = Command(['echo', 'hallo', 'echo'])
command.remove_arg('echo')  # Ach ich will gar nicht "echo" ausgeben...
command.execute('welt')     # ...sondern lieber "welt".
Mit `os.chdir()` änderst Du einen globalen Zustand des Prozesses. Das kann man nur an einer Stelle tun. Denn wenn mehrere Funktionen das tun, wird es irgendwann unübersichtlich und man landet früher oder später bei einem falschen Arbeitsverzeichnis. Zum Beispiel ist Dein Code nicht dagegen abgesichert, dass zwischen wechseln und zurück wechseln eine Ausnahme auftreten kann. Wenn die weiter oben in der Aufrufhierarchie behandelt wird, dann läuft das Programm mit einem falschen Arbeitsverzeichnis weiter. Oder heutzutage sind Prozessoren mit mehreren Kernen ja nichts ungewöhnliches mehr, so dass man auf die Idee kommen könnte mehrere `Command`\s in Threads parallel abzusetzen. Das kann auch ein falsches Arbeitsverzeichnis zur Folge haben.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

finki hat geschrieben:, weil ich mir erhoffe eine einmal erstellte Klasse mit so wenig wie möglichen Anpassungen wieder verwenden zu können [...]
Ich wollte schon gerne alles getrennt haben, da jede Klasse voneinander unabhängig wiederverwendbar sein sollte und so allgemein wie möglich gehalten ist.
Das ist aus Design-Sicht bestimmt ein eleganter Ansatz. In Praxis ist es meiner Erfahrung nach aber meistens so, dass man, wenn man alles möglichst übergenerisch haben möchte, extraviel Code dafür benötigt, übergenerischen Code zu schreiben (weil man so viele verschiedene Fälle beachten muss) und dann zusätzlich extraviel Code braucht, um den generischen Code mit konkretem zu "wrappen". Unterm Strich hast du dann viel mehr Arbeit, viel mehr Code voller Bugs und lesbar wird das Ganze auch nicht.

Meiner Meinung nach ist der beste Ansatz, fehlerfreie, wartbare und hochqualitative Software zu schreiben, nur das zu implementieren, was man wirklich unbedingt braucht (Keep It Simple, Stupid), und das dann konkret und nicht generisch.

Sollte dir dann irgendwann während der Entwicklung auffallen, dass der Code Wiederholungen (oder sehr ähnliche Codestellen) hat, kann man einzelne Schnipsel auslagern in generischen Code, der dann aber auch wieder nur das abdeckt, was unbedingt nötig ist.

Mal etwas für die Zukunft zu bauen, weil man es ja theoretisch irgendwann vielleicht gebrauchen könnte, funktioniert nicht. Du musst dann später sowieso wieder Anpassungen vornehmen. Dann kannst du's auch gleich lassen und erst dann implementieren, wenn du's benötigst.

Jetzt wollte ich hier ganz poetisch zitieren, weiß aber nicht mehr, wer's gesagt hat. Drum auf Deutsch und aus dem Kopf: "Ein Programm ist nicht dann perfekt, wenn man nichts mehr hinzufügen kann. Ein Programm ist dann perfekt, wenn man nichts mehr wegnehmen kann."

Edit: BlackJack war schneller, weil ich so ewig nach dem Zitat gegoogelt hab :)
Zuletzt geändert von Dauerbaustelle am Samstag 5. März 2011, 23:44, insgesamt 2-mal geändert.
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

Eure / Deine Argumentationen sind sehr einleuchtend. Ich denke, ich werde das gesamte Konzept wegwerfen und mich auf einfacheres besinnen. Ich glaube, mehr als eine Quelldatei brauche ich nicht. Vielleicht eine oder höchstens zwei zusätzliche thematische Module. Zumal ich glaube einige Klassen weg zu lassen oder einfacher zu gestalten.

Gut das ich mal hier nach gefragt habe. 8) Danke sehr.

@Dauerbaustelle:
An das Überschreiben vorhandener Klasseneigenschaften oder -Methoden traue ich mich noch nicht so ran. Habe da Angst mehr kaputt zu machen als zu korrigieren.
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

Eine Frage hätte ich noch:
Dauerbaustelle hat geschrieben:Definiere das einfach als Funktionen auf unterster Ebene.
Wie sollte ich das verstehen? Was ist hier mit unterster Ebene gemeint?

Ach ja, ... und den KISS-Ansatz hatte ich wohl ganz vergessen. War wohl zu eifrig gewesen. Habe aber so wenigstens in einem Monat einiges von den Python-Konzepten schon gelernt.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Modulebene. Nicht innerhalb einer Klasse. Einfach auf Modulebene. Visuell ausgedrückt: Das `def` ist nicht eingerückt.
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

Ok, meine aktuelle Organisation sieht folgendermaßen aus:
  • Mamehi/
    • COPYING (Lizenz)
    • mamehi.py (Hauptprogramm, enthält alle Funktionen.)
      • split_path()
      • create_zip()
      • md5sum()
      • execute()
      • parse_options()
      • main()
    • setup.py (Momentan leer, eventuell nicht benötigt.)
    • README
Es enthält keine einzige Klasse mehr. Weitere Funktionen sind im Kommen. Ich habe (danke euch) gesehen wie unnötig es war, wie sinnlos kompliziert ich es mir selbst gemacht habe. Und dann noch ohne wirklichen Nutzen. Ich hätte mal eine weitere Frage: Wenn ich gerne eure Meinung zu meinen unabhängig funktionierenden Funktionen hätte, sollte ich für jede Funktion ein neues Thread eröffnen? Wenn man ganze Programme und zu viel auf einmal vorstellt, läuft es doch Gefahr das keiner wirklich durchblicken möchte und die schnelle Hilfe b.z.w. Meinung dann flöten geht.

Da ich schon mal was vorgestellt hatte, hier noch die aktuelle Fassung von execute(), vormals in CommandLine:

Code: Alles auswählen

def execute(command, args=None, wait=True, workingdir=None):       
    """
    Executes the command and get its output.
    
    Parameters:
        command -- An args list to execute with Popen().  If this is a string, 
        then it will be analyzed and splitted up into a list of args with 
        shlex.split() function.
        
        args -- Additional arguments (list or string) to append to command.
        
        wait -- If set to True, then stdout and stderr are catched and 
        returned.  If set to False, then script does not wait until programs 
        execution is ended and gets the pid as return value.
        
        workingdir -- Uses this folder as current working directory for the
        command.
    
    Return:
        Catches the stdout and stderr output of console (without showing
        console) and gets them together as a single string.
    
    Examples:
        >>> basecmd = ['echo', 'hello']
        >>> print execute(basecmd, 'world'),
        hello world
    
    """
    # Convert the commandline and argument strings into an executable list for 
    # Popen() and add any additional args to command.
    if isinstance(command, basestring):
        command = shlex.split(command)
    if args is not None:
        if isinstance(args, basestring):
            args = shlex.split(args)
        command.extend(args)
    # Execute the command. Do not use the shell, as the command is expected to 
    # be a list.  The stdout and stderr are piped together into a single string.
    proc = subprocess.Popen(command,
        shell=False,
        stdout=subprocess.PIPE, 
        stderr=subprocess.STDOUT,
        universal_newlines=True,
        cwd=workingdir)
    # Get the stdout and stderr output from console or the process pid.
    if wait:
        output = proc.communicate()[0]
    else:
        output = proc.pid
            
    return output
Benutzeravatar
snafu
User
Beiträge: 6832
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Warum immer noch die Trennung von `command` und `args`? Und weiterhin gilt das, was von BJ schon vor ein paar Beiträgen gesagt wurde: Funktionen sollten sich darauf beschränken, möglichst nur eine Sache / einen Typen zurückzugeben. Ich finde es zudem nicht gerade intuitiv, dass ein gesetztes `wait`-Flag die Ausgabe zurückgibt. Im Prinzip finde ich, dass deine Funktion nichts weiter als ein subprocess.check_output() mit bedarfsweiser Umwandlung eines Strings in eine Liste darstellt (und selbst die kann unpraktisch bei Dateinamen mit Leerzeichen sein).

Greife doch, soweit es eben geht, auf vorhandenen Code zurück anstatt mit Eigenkreationen daher zu kommen bzw das Rad neu zu erfinden, wie man so schön sagt. Funktionen sollten immer einen Mehrwert bieten und nicht darauf aus sein, vorhandene Dinge zu verkomplizieren. Und vor allem: "Explicit is better than implicit", d.h. zuviel Automatisierung in einer Funktion, bei der du glaubst, dass sie dem Anwender hilft, führt nicht selten zum genauen Gegenteil. ;)
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

snafu hat geschrieben:Warum immer noch die Trennung von `command` und `args`?
command kann ja an sich selbst Argumente enthalten. Ich habe mir gedacht, das zum Beispiel command eine args-Liste darstellt, welche ausgeführt wird. Es stellt sozusagen eine Art Basis dar, bei dessen Ausführung eine weitere "temporäre" args hinzu gegeben werden kann.
snafu hat geschrieben:Funktionen sollten sich darauf beschränken, möglichst nur eine Sache / einen Typen zurückzugeben.
Da würde ich dir ja gerne Recht geben, aber wenn ich diese beiden Typen in der Funktion zusammenfasse, dann spare ich mir zwei fast identische Funktionen. Daher dachte ich, würde es Sinn machen diesen über einen einzigen Parameter steuern zu lassen.
snafu hat geschrieben:Ich finde es zudem nicht gerade intuitiv, dass ein gesetztes `wait`-Flag die Ausgabe zurückgibt.
Oh?! Wieso nicht? Vielleicht passt diese Art wohl nicht ganz in die pythonsche-Welt. Da woher ich her komme, da ist das so typisch.

Wenn man das wait-Flag gesetzt hat, dann kann man doch nur noch die PID auslesen, oder nicht? Oder ist es dennoch möglich die Ausgabe auszulesen. Das habe ich dann wohl falsch verstanden und würde es gerne so haben, das immer die stdout & stderr ausgelesen werden.
snafu hat geschrieben:Im Prinzip finde ich, dass deine Funktion nichts weiter als ein subprocess.check_output()
Diese Funktion/Methode steht mir nicht zur Verfügung, da sie erst seit Python 2.7 dabei ist und ich nur 2.6.6 habe. Ich möchte Python auch nicht updaten, damit meine Programme mit 2.6.6 kompatibel bleiben. Es ist die Standard Version von Ubuntu 10.04.
snafu hat geschrieben:Umwandlung eines Strings in eine Liste darstellt (und selbst die kann unpraktisch bei Dateinamen mit Leerzeichen sein)
Das verstehe ich nicht. Wenn man in der Kommandozeile einen Dateinamen mit Leerzeichen eingibt, dann sollte diese doch selbstverständlich von Anführungsstrichen umgeben werden. Dann wird dieser Abschnitt als eine Einheit gesehen, oder? Genauso geht doch die Shell auch vor. Oder habe ich die Funktionsweise von shlex.split() missverstanden?
BlackJack

@finki: Ich habe den Eindruck das ist immer noch eine Funktion ohne konkrete Anwendungsfälle. Wenn man eine Menge von festen und variablen Argumenten hat, dann dürften die selten in zwei Listen aufzuspalten sein. Viel öfter hat man in einer Liste von Argumenten einzelne Elemente an beliebigen aber festen Positionen die man gerne pro Aufruf austauschen würde.

Zwei fast identische Funktionen sehe ich da nicht, sondern eine Funktion welche die andere Aufruft. Also eine die das `Popen`-Exemplar zurück gibt -- da kann man ja die PID abfragen wenn man möchte, und eine die diese Funktion aufruft und die Ausgabe von dem `Popen`-Exemplar ausliest. Zumindest die wäre aber so trivial, dass man sie sich im Grunde sparen könnte.

Was hat denn die Eingabe eines Dateinamens an der Kommandozeile mit Deiner Funktion zu tun? Die wird ja im Programm aufgerufen und nicht von der Kommandozeile.
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

BlackJack hat geschrieben:Was hat denn die Eingabe eines Dateinamens an der Kommandozeile mit Deiner Funktion zu tun? Die wird ja im Programm aufgerufen und nicht von der Kommandozeile.
Achso na klar. Also mein Programm soll ja mit seinen Optionen dazu dienen ein anderes bequemer zu starten. Und da war ich stark auf die Kommandozeile fixiert. Aber dennoch, wer eine Zeile als quasi Kommandozeile an shlex.split() übergibt, wird doch wohl Anführungssriche für Dateinamen mit Leerzeichen benutzten, ganz so wie man das bei der Kommandozeile auch macht. So war das gemeint.

Und das mit dem .pid und .communicate() hatte ich total falsch verstanden. Nun habe ich immer die stdout und stderr als return output. Ich kam irgendwie wohl zu dem Entschluss, das wenn man die PID ausliest, dass dann nicht gewartet wird (aus Python) und die Ausgabe flöten geht. Aber wie gesagt, das war mein Fehler und ist kein Thema mehr.

Aber wie bringe ich nun dazu Popen() NICHT zu warten? (EDIT: Wort "NICHT" im Satz hinzu ergänzt.)

Und wegen den Listen... Ich habe mir schon mal ein Prototyp geschrieben und da war ich noch ganz am Anfang von Python. Irgendwie sah ich wohl da einen Nutzen, aber je mehr ich darüber nachdenke und ihr darüber schreibt, umso weniger Sinn sehe ich auch darin. Naja ich bin dann mal weiter am Überarbeiten.
Zuletzt geändert von finki am Montag 7. März 2011, 04:03, insgesamt 1-mal geändert.
Benutzeravatar
snafu
User
Beiträge: 6832
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@finki: Du denkst in meinen Augen immer noch zu kompliziert, so wie es vorher auch schon bei `remove_arg` und `add_arg` war: Wenn ich eine Basis haben möchte, mache ich mir eine Liste mit den "Grundargumenten", die ich dann vor dem eigentlichen Aufruf erweitern kann. Dafür muss deine Funktion keine zwischengeschaltete Instanz mehr bieten. Versteh mich bitte nicht falsch: Mein Ziel ist es nicht, möglichst intensiv an dir rumzunörgeln, aber wenn du nach Kritik fragst, musst du dir als Anfänger solche Hinweise, die sicherlich auf mangelnder Erfahrung beruhen (was ja an sich nichts schlimmes ist), schon gefallen lassen.
finki
User
Beiträge: 20
Registriert: Samstag 19. Februar 2011, 11:15

@snafu
Aber ich sehe doch nicht ein "rumnörgel" in eurer Kritik! So bin ich nicht. Ich bin wirklich heilfroh das sich jemand mit Erfahrung meinen Kot (kleiner Witz xD) angeschaut hat. Und eure Meinung und Verbesserungsvorschläge nehme ich mir zu Herzen (in positiver Hinsicht). Denn ich möchte Lernen. Sonst würde ich das hier nicht posten und nach Kritik fragen. Keine Sorge, ich gehöre nicht zu den Trollen. :P

Ich denke, das ich von einer anderen Umgebung vorbelastet bin. Vergesse schnell das Python viel bietet und leicht umzugehen ist. Und das mit dem kompliziert denken... Seit meiner Schulzeit sagen mir die Lehrer das ich immer viel zu kompliziert denke. Ich sehe das ich hier gut aufgehoben bin. Werde ganz sicher meine nächsten Ergebnisse vorzeigen. Dann aber in einem neuen Thread als vollständiges Programm. ... und das kann noch dauern. :oops:
Antworten