Problem argparse positional argument, das mit '-' beginnt

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
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hallo,

das folgende simple Beispiel funktioniert, sofern das positional argument als Zahl interpretiert werden kann:

Code: Alles auswählen

#!/usr/bin/python

import argparse

def action(values):
    return values

class MyAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, action(values))


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', nargs='*', action=MyAction)
    parser.add_argument('-f', '--foo', action='store_true')

    print parser.parse_args()

Code: Alles auswählen

$ test +3
Namespace(positional=['+3'], foo=False)
$ test -1.3
Namespace(positional=['-1.3'], foo=False)
$ test +3.b
Namespace(positional=['+3.b'], foo=False)
$ test -3.b
usage: test [-h] [-f] [positional [positional ...]]
test: error: unrecognized arguments: -3.b
Wenn ich die Doku richtig verstehe, ist das ein Problem mit der Magie...
Python Doku hat geschrieben:The parse_args() method attempts to give errors whenever the user has clearly made a mistake, but some situations are inherently ambiguous. For example, the command-line argument -1 could either be an attempt to specify an option or an attempt to provide a positional argument. The parse_args() method is cautious here: positional arguments may only begin with - if they look like negative numbers and there are no options in the parser that look like negative numbers.
Zur Umgehung des beschriebenen Verhaltens liefert die Doku folgendes:
Python Doku hat geschrieben:If you have positional arguments that must begin with - and don’t look like negative numbers, you can insert the pseudo-argument '--' which tells parse_args() that everything after that is a positional argument.
Wenn ich mein kleines Beispiel allerdings dahingehend ändere, geschieht folgendes:

Code: Alles auswählen

#!/usr/bin/python

import argparse
import sys

...
...

if __name__ == '__main__':
    sys.argv.insert(1, '--')
    ...
    ...

Code: Alles auswählen

$ test -3.b -f
Namespace(foo=False, positional=['-3.b', '-f'])
Alle folgenden Argumente werden also schlichtweg dem positional argument untergeschoben. Auch nicht das, was ich möchte. Zudem behagt mir das Manipulieren an der 'sys.argv' auch nicht wirklich...
Vorläufig habe ich die Sache so gelöst:

Code: Alles auswählen

#!/usr/bin/python

import argparse

def action(values):
    return values

class MyAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, action(values))


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('positional', nargs='*', action=MyAction)
    parser.add_argument('-f', '--foo', action='store_true')
    known, unknown = parser.parse_known_args()
    if unknown:
        known.positional = action(unknown)
    print known

Code: Alles auswählen

$ test -3.b -f
Namespace(foo=True, positional=['-3.b'])
Was mir daran nicht gefällt ist, dass ich quasi außerhalb von argparse die Gültigkeit eines Argumentes prüfen muss, sprich: Wenn es sich bei positional arguments z. B. um Datumsargumente handelt *grins*, dann werden nicht nur die vom Nutzer auch so gemeinten Argumente sondern auch alle ungültigen Argumente an einen Datumsparser weitergereicht. Dort muss also erstmal entschieden werden, ob es sich um ungültige Datumsargumente oder um grundsätzlich ungültige Argumente handelt. IMHO fast nicht möglich. Die Fehlermeldung müsste also lauten:

Code: Alles auswählen

UnknownError: Wenn ich gekonnt hätte, hätte ich ein Datum gebildet.
Konnte ich aber nicht, vielleicht wollten Sie ja etwas ganz anderes?
Aber das konnte ich auch nicht.
:D

Was meint ihr?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
deets

Du machst dir dein Leben immer so zielsicher so kompliziert...

Ich wuerde ja erstmal die Notwendigkeit fuer deine Notation hinterfragen. Danach dann anmerken, dass du nicht sys.argv manipulieren musst, sondern auch einfach kopieren, die Kopie entsprechend prozessieren, und dann parsieren lassen.

Und last but not least: wenn man etwas will, das partout nicht mit dem geht, was man schon hat, dann muss man Code schreiben. Das nennt sich ganz allgemein "programmieren".. ;)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

deets hat geschrieben:Du machst dir dein Leben immer so zielsicher so kompliziert...
Da kenne ich Leute, die neigen dazu, mir das Gegenteil vorzuwerfen... :)

Ok, zur Notation: Beim Aufruf meines Kalender können Argumente übergeben werden, die zu einem Datum gebildet werden.
- Die Reihenfolge dieser Argumente ist per default ymd.
- Werden 2 Argumente übergeben, werden diese als month/day, bei nur 1 Argument als day interpretiert.
- Jedes fehlende Argument erthält per default den Wert des aktuellen Datums.

Code: Alles auswählen

denkdran 2012.9.21    -> datetime(2012, 9, 21)
denkdran 10.20        -> datetime(2012, 10, 20)
denkdran 5            -> datetime(2012, 9, 5)
- Bei der Übergabe können den jeweiligen Argumenten auch Berechnungen mitgegeben werden. Fehlt der Referenzwert, wird dieser wiederum durch den entsprechenden Wert des aktuellen Datums ersetzt.

Code: Alles auswählen

denkdran 2012+1.9.21  -> datetime(2013, 9, 21)
denkdran 10.+3        -> datetime(2012, 10, 24)
denkdran -2           -> datetime(2012, 9, 19)
Soweit verläuft alles nach Plan, das '-2' im letzten Beispiel wird von argparse als negative Zahl interpretiert, deshalb geht das "noch durch".

Code: Alles auswählen

denkdran -1.9.20      -> datetime(2011, 9, 20) (wenn argparse das durchgehen ließe)
Bei '-1.9.20' steigt argparse dann aus, weil sich daraus keine negative Zahl herleiten lässt und '1.9.20' kein Argumentname ist.

Soviel zur Notation.
deets hat geschrieben:... wenn man etwas will, das partout nicht mit dem geht, was man schon hat, ...
Ich habe ja gezeigt, dass es geht. Meine Frage ist vielmehr, ob ich mir mit meiner Lösung Probleme einhandle, die mangels besseren Wissens nicht auf meinem Radar erscheinen.
Oder ob es sogar eine wesentlich simplere Lösung gibt, die ich trotz Doku rauf- und runterlesen und längerem googeln nicht sehen kann.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
lunar

@mutetella Ich finde diese Notation zu kompliziert, und nicht intuitiv. Steht "-1.1" nun für den 20.1.2012, oder den 20.8.2012? Wo ziehst Du die Grenze? Erlaubst Du noch Multiplikation oder Division?

Ich sehe auch keinen Mehrwert darin, Datumsarithmetik in den Argumenten zu erlauben. Deine Beispiele würde ich sogar als Argument dagegen ansehen. Statt "2012+1.9.21" kann ich auch direkt "2013.9.21" eingeben. Das ist nicht nur kürzer, sondern auch einfacher und unkomplizierter, und auf den ersten Blick verständlich. Der einzige Vorteil an Deiner Notation wäre, dass ich 2012+1 nicht im Kopf rechnen muss, doch ich glaube, Du kannst Deinen Nutzern Arithmetik im Zahlenraum 1 bis 31 durchaus zumuten ;) Ich jedenfalls fühle mich davon nicht überfordert :)

Wenn Du Arithmetik beibehalten willst, dann trenne zwischen der direkten Eingabe eines absoluten Datums, und dem Berechnen eines Datums. Stelle arithmetischen Angaben beispielsweise ein "=" voran: "=2012.9.21 - 1". Das gäbe dann auch Raum für eine bessere Syntax bei arithmetischen Ausdrücken, die nicht so mehrdeutig ist. Beispielsweise "=2012.9.21 + 1y + 2m - 10d" als "2013.12.11".
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

lunar hat geschrieben:Ich sehe auch keinen Mehrwert darin, Datumsarithmetik in den Argumenten zu erlauben. Deine Beispiele würde ich sogar als Argument dagegen ansehen. Statt "2012+1.9.21" kann ich auch direkt "2013.9.21" eingeben.
Ich gebe Dir Recht, meine Beispiele könnten durchaus als Gegenargument dienen. Allerdings wollte ich damit auch an mein argparse-Problem "heranführen"... :wink:
lunar hat geschrieben: Ich finde diese Notation zu kompliziert, und nicht intuitiv.
Ich möchte einen Programmaufruf ermöglichen, der eben genau das ist, unkompliziert und intuitiv. Natürlich trau' ich mir und anderen zu, zum Jahr 2012 1, 2 oder vielleicht sogar noch 3 Jahre aufzuaddieren... :wink:
Wenn ich aber z. B. das Datum von vorgestern oder morgen möchte, hakt es bei mir regelmäßig daran, dass ich auf Anhieb das heutige Datum nicht weiß. Und da finde ich einen Aufruf "denkdran -2" für vorgestern oder "denkdran +1" für morgen sehr angenehm. Und ein "denkdran +7" am Ende eines Monats ist herrlich, weiß ich doch nie aus dem Stegreif, ob der aktuelle Monat 30 oder 31 Tage hat.
Der Konsequenz wegen habe ich das eben auch für die y- und m-Werte übernommen. Auch wenn es natürlich hier nicht immer sinnvoll erscheint und demnach wohl auch selten zur Anwendung kommt.
Ok, "-7y" oder "+3m" sind eine Möglichkeit, über die ich noch nachdenken werde... Tage ohne Angabe einer Kennzeichnung, Monate und Jahre mit Angabe von 'm'- bzw. 'y'-tag berechnen.
lunar hat geschrieben:Wo ziehst Du die Grenze? Erlaubst Du noch Multiplikation oder Division?
Genau hier ziehe ich die Grenze! :lol:

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten