Parameter für Python Scripte

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
kiiu
User
Beiträge: 9
Registriert: Donnerstag 24. August 2006, 09:31

Hallo,
(me = BA Student)

mit Python habe ich noch nicht lange zutun, jedoch traf ich bereits zweimal auf die Problematik,
daß ich ein PythonScript versehen mit Parametern aufrufen wollte (der Aufruf dieser Scripte erfolgt im Makefile).
Nachdem ich quasi zweimal genau das gleiche in den Quellcode geschrieben habe (von unterschiedlichen Namen und deren Anzahl abgesehen),
kam mir die Idee, daß es vielleicht geschickter wäre, ein allgemeines Modul zu verwenden, um dann einfach Objekte zu erzeugen,
denen ich als Name den gewünschten Parameter übergebe ..

Öh, gedacht, etwas feiner gedacht, getan :)

Hiermit kann ich eine beliebige Menge von Parametern erzeugen, und verwalten -
Mit dem Verwalten ist das Auslesen von der Konsole und die Rückgabe der zugehörigen Werte gemeint.

Ich muss lediglich ein Objekt der Klasse myParameter, dem ich auch gleich meine Parameternamen mitgebe, erzeugen.
Im Folgenden wende ich auf das Objekt die Methode handleOptions() an, und danach lass ich mir mit getValueOf(<paraName>) den Wert zurück geben

Hm versteht überhaupt jemand was ich erzähle ? ^^
Nuja, soviel sei noch gesagt, ich gebe zu, daß sich kein Nutzen hinsichtlich weniger Code im letztendl. Script einstellt.
Aber das ist mir egal, denn mein erstes Modul steht :P

Nun es gibt vielleicht nette Anmerkungen/Tipps hinsichtl. des Codes, oder vielleicht findet jemand Fehler, oder jemand hat eine Frage ?
Dann keine Scheu - ich freu mich auf eure Antworten ;)

Code: Alles auswählen

# -*- coding: ISO8859-1 -*-
# ------------------------------------------------------
# dieses Modul realisiert die Klasse "myParameter" die vom Benutzer 
# definierte Parameter (beliebig viel) aus der Kommandozeile ausliest
#
# Die Idee ist objektorientiert:
#	Möchte man zb. ein Pythonscript mit Parametern versehen, 
#	so erzeugt man ein Objekt der	hier zur Verfügung gestellten Klasse 
#	"myParameter" und übergibt dabei die gewünschten Parameter 
#	(genauer den Namen der Parameter)
#	Hinweis: Es ist vereinbart, daß entweder direkt Strings übergeben werden, 
#	oder aber eine bzw. mehrere Liste(n) von Strings
#	Sollte dies nicht der Fall sein, 
#	wird ein Error mit dem Namen "typError" verursacht
#
#	Ein Objekt myParameter enthällt dann mehrere Parameter, 
#	deren Werte vorerst leere Strings sind.
#	Ansprechbar von außen sind diese mit der Methode 
#	"getValueOf(*parameter)" (genaueres siehe Methodenbeschreibung)
#	Zum Ermitteln/Einlesen der Werte der definierten Parameter 
#	von der Kommandozeile muss die Methode "handleOptions()" 
#	auf das Objekt angewendet werden (genaueres siehe Methodenbeschreibung)
#
# Die Benutzung ist sehr simpel gehalten,:
#	1. neues Objekt obj erzeugen (dabei alle später verfügbaren Parameter übergeben)
#	2. obj.handleOptions() aufrufen
#	3. von obj.getValueOf(xyz) den Wert von xyz holen
#
##############
# WICHTIG:
##############
# 2 Möglichkeiten für die Syntax auf der Kommandozeile oder Makefile
# --option=value
# --option
##############
# Der erste Fall führt zu einer Wertabspeicherung als String !
# Der zweite Fall führt zu einer Wertabspeicherung 
# als Booleanscher Wert: True !
##############
#

import sys

# Klasse myParameter
class myParameter:
	# ------------
	# init
	def __init__(self, *NamesOrLists):
		# Error der erzeugt wird, wenn andere Typen außer String übergeben werden
		self.typError="typError - str/list erlaubt, verwendet"
		# Error der erzeugt wird, wenn unbekannte Parameter gelesen 
		# (von der Konsole) oder übergeben werden
		self.unknownParameter = "unbekannter Parameter"
		# hier stehen später die Optionen (Objekte von der Klasse _someOption) drin
		self._OptionList = []
		# gegebene Optionsnamen werden als Option in die Liste eingefügt
		# impliziert Prüfung auf Typkonsistenz und evt. Fehlererzeugung 
		# (letzteres in Methode _appendOption(blub))
		for onePart in NamesOrLists:
			if type(onePart) == str:
				self._appendOption(onePart)
			elif type(onePart) == list:
				for entry in onePart:
					if type(entry) == str:
						self._appendOption(entry)
					else:
						raise self.typError, type(entry)
			else:
				raise self.typError, type(onePart)
	# ------------
	# handleOptions
	def handleOptions(self):
		# Variablendeklarationen
		_OptionNames = self._getOptionNames()
		
		# es wird die Kommandozeile eingelesen, 
		# sollte ein unbekannter Parameter gelesen werden, 
		# wird der "unknownParameter" Fehler erzeugt
		for Strings in sys.argv[1:]:
			# an Syntax --option_name=value oder --option_name gebunden !!
			slicedString = Strings[2:].split("=") 
			givenOption = slicedString[0]
			# wenn die Länge von der Liste größer als 1 ist, 
			# dann steht noch der zugehörige Wert drin
			if len(slicedString) > 1:
				relatedValue = slicedString[1]	# String als Optionsinhalt speichern
			else:
				relatedValue = True	# True als Optionsinhalt speichern
			
			# immer dann falsch, 
			# wenn eine eingelesende Option nicht gefunden werde konnte
			_wasFound=False 
			
			# es werden die vorhandenen Optionen nun durchlaufen, 
			# und mit den eingelesenden abgeprüft
			for predefinedOption in self._OptionList:
				# wenn eine angegebene Option gleichnamig einer vordefinierten ist,
				# so überschreibe deren Wert mit dem angegebenen
				if givenOption == predefinedOption.getName():
					predefinedOption.setValue(relatedValue)
					_wasFound=True
					break
				else:
					_wasFound=False
			
			# wenn eine Option nicht gefunden werde konnte, 
			# wird der Fehler "unknownParameter" erzeugt
			if _wasFound==False:
				_OptionTip = ""
				for _option in _OptionNames:
					_OptionTip=_OptionTip+_option+" "
				_ErrorMessage = 
				"ein oder mehrere Parameter von der Kommandozeile wurden nicht gefunden.\n"+\
				"Hinweis: korrekte Syntax lautet: --option_name= \n"+\
				"Hinweis: definierte Parameter: "+_OptionTip
				raise self.unknownParameter, _ErrorMessage
	# ------------
	# getValueOf
	def getValueOf(self, *NamesOrLists):
		_ValueList = []
		# gegebene Optionsnamen werden mit vorhandenen geprüft 
		# und deren Werte zurückgegeben 
		# Rückgabetyp entsprecht Eingabetyp, 
		# so zb. liefert eine Liste mit Optionen eine Liste mit den zugehörigen Werten
		# impliziert Prüfung auf Typkonsistenz und evt. Fehlererzeugung
		for onePart in NamesOrLists:
			if type(onePart) == str:
				_ValueList.append(self._getValueOf(onePart))
			elif type(onePart) == list:
				for entry in onePart:
					_ValueList.append(self._getValueOf(entry))
			else:
				raise self.typError, type(onePart)
		# ist Liste mit mehr als einem Wert gefüllt, so wird die Liste selbst übergeben
		# falls nicht, so wird das Element an sich (dann vom Typ String) übergeben
		if len(_ValueList) > 1:
			return _ValueList
		else:
			return _ValueList[0]
	# ------------
	# _appendOption
	def _appendOption(self, OptionName):
		self._OptionList.append(_someOption(OptionName))
	# ------------
	# _getOptionNames
	def _getOptionNames(self):
		returnNameList = []
		for option in self._OptionList:
			returnNameList.append(option.getName())
		return returnNameList
	# ------------
	# _getValueOf
	def _getValueOf(self, givenOption):
		for predefinedOption in self._OptionList:
			if givenOption == predefinedOption.getName():
				return predefinedOption.getValue()
		_ErrorMessage = "'"+givenOption+"' ist kein definierter Parameter"
		raise self.unknownParameter, _ErrorMessage
# -------------------
# someOption class
class _someOption:
	# init
	def __init__(self, Name, Value=False):
		self.Name = Name
		self.Value = Value
	
	# setValue
	def setValue(self, Value):
		self.Value = Value
	
	# getValue
	def getValue(self):
		return self.Value
	
	# getName
	def getName(self):
		return self.Name
Zuletzt geändert von kiiu am Donnerstag 14. September 2006, 16:31, insgesamt 1-mal geändert.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hi kiiu!

Willkommen im Python-Forum!

Bitte schreibe kürzere Codezeilen. So kann man deinen Quellcode kaum lesen (und ich habe eh schon auf die Auflösung 1152x864 Pixel gestellt).

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
kiiu
User
Beiträge: 9
Registriert: Donnerstag 24. August 2006, 09:31

hm 1280x1024 4tw :P
ich hoffe es ist so besser, bin jetzt mal "off" :wink:
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Wenn ich es richtig verstehe hast du optpasrse nachprogrammiert, oder?
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
BlackJack

kiiu hat geschrieben:Nun es gibt vielleicht nette Anmerkungen/Tipps hinsichtl. des Codes, oder vielleicht findet jemand Fehler, oder jemand hat eine Frage ?
Erste Frage: Kennst Du das `optparse` Modul aus der Standardbibliothek?

Dann hast Du mit TABs eingerückt, üblich sind bei Python vier Leerzeichen pro Einrücktiefe und keine TABs in der Datei. Die nächste Konvention betrifft die Namen. Klassennamen fangen mit einem Grossbuchstaben an und MixedCase wird nur für diese benutzt. Für Attribute und andere Namen sind kleine_worte_mit_unterstrichen vorzuziehen. Ausser KONSTANTEN die man komplett gross schreibt. Um Zuweisungen und Operatoren sollte man Leerzeichen schreiben, damit das nicht alles optisch zusammenklebt.

Die Kommentare zu den einzelnen Methoden hättest man als Docstrings schreiben können, dann lassen sie sich auch im Interpreter "live" lesen und mit entsprechenden Werkzeugen zu HTML Doku umwandeln.

In der `__init__()` Methode sind zwei veraltete Mechanismen. Zum einen das Testen auf einen Typ mit `type(obj) = type_obj`, das man heute mit `isinstance()` löst, weil mittlerweile auch von eingebaute Typen neue Klassen abgeleitet werden können und die Typprüfung mit `type()` unnötig streng ist. Das andere sind Zeichenketten als Ausnahmen. Die lassen sich vom Aufrufenden nicht sauber abfangen und es sind auch keine Ausnahme-Hierarchien durch Vererbung möglich. Für Typfehler gibt's zum Beispiel schon die Klasse `TypeError`.

Letztendlich ist `__init__()` viel zu kompliziert. Warum nicht einfach:

Code: Alles auswählen

        def __init__(self, names):
            for element in names:
                self._append_option(element)
Bzw. würde ein ``self._options = map(_SomeOption, names)`` vollkommen ausreichen.

So kann jedes beliebige iterierbare Objekt übergeben werden, das Optionsnamen enthält. Warum muss der Aufrufer ``[['a', 'b'], 'c', ['d', 'e']]`` übergeben können, wenn er genau so gut ``['a', 'b', 'c', 'd', 'e']`` übergeben könnte. Er kann die Liste auch mit `list.append()`, `list.extend()` und `itertools.chain()` aufbauen.

In `handleOptions()` prüfst Du nicht ob der Optionsname auch mit '--' beginnt, d.h. kürzere Zeichenketten funktionieren nicht und bei längeren wird erst alles ab dem dritten Zeichen untersucht. Ausserdem dürfen im Wert einer Option keine Gleichheitszeichen vorkommen. Die Option ``--formula='e=m*c^2'`` macht Probleme.

Am Ende der Methode wird deutlich das eine Liste mit `_someOption` Objekten nicht die günstigste Datenstruktur ist. Man könnte die `_someOption` Klasse komplett weglassen und die Optionen in einem Dictionary verwalten. Dann lässt sich wesentlich einfacher feststellen ob ein Wert vorhanden ist oder nicht. Und nach aussen könnte die `myParameter` Klasse auch das Dictionary Protokoll anbieten. Eine Umbenennung in `Parameters` macht deutlicher das dieses Objekt nicht ein Parameter ist, sondern mehrere davon enthält. Ein einfaches "Skelett" für so eine Klasse und die Benutzung:

Code: Alles auswählen

class Parameters:
    def __init__(self, names):
        self._options = dict.fromkeys(names, False)
    
    def __getitem__(self, name):
        return self._options[name]
    
    def parse_args(self, args=sys.argv[1:]):
        for argument in args:
            pass    # Kommandozeile parsen kommt hier hin.

parameters = Parameters(('input', 'output', 'verbose'))
parameters.parse_args(daten_aus_einer_konfig_datei)
parameters.parse_args()
in_file = open(parameters['input'], 'rb')
Und diese ganzen "getter" und "setter" sind in Python nicht notwendig. Lass die weg und greif direkt auf Attribute zu. Falls die irgendwann einmal keine einfachen Attribute sind und durch eine Berechnung ersetzt werden sollen, dann kannst Du Properties dafür verwenden.
kiiu
User
Beiträge: 9
Registriert: Donnerstag 24. August 2006, 09:31

..hm - nein, ich kenne `optparse` nicht :P
allerdings kenne ich es jetzt und ich muss zugeben, daß des Teil nit übel zu sein scheint -
einige ideen die ich als evtuelle Erweiterung hatte, sind da sogar au schon drin ^^
..

dennoch:
BlackJack, vielen Dank für dein Feedback -
Ich gebe zu, bisher verwende ich ganz und gar meine eigene "Konvention",
die auch von Script zu Script unterschiedl. ist :/
Daß die Tab Einrückung nicht üblich ist, wundert mich. Ich dachte Leerzeichen und Tabs wären "erlaubt",
und für Tabs hatte ich mich entschieden, weil diese es mir leichter machen die Einrückung (wenn nötig) zu Überprüfen.

Meinst Du mit Docstrings solchen Code den man ungfähr so """ ... """" schreibt ?
Ich hatte mich nämlich schon des öfteren beim Anschauen von Scripten über diese Kommentare gewundert.

Das `isinstance()` Teil werde ich mir anschauen ;)
Allerdings muss bei den "Zeichenketten als Ausnahmen" nochmal nachfragen - wie meinst Du das ?
bzw. was sind Ausnahme-Hierarchien durch Vererbung ? (wenn ich mal so blöd fragen darf ..)

Die `map()` Funktion sehe ich zum Ersten mal - interesant was dazu auf python.org steht
=> damit werde ich mal nen bissle rumspielen :>

Jo daß meine vorrausgesetze Syntax in keinster Weise abgefragt wird, weis ich und ist schlecht.
Aber ich hatte ja schon nen paar Erweiterungen im Kopf und wollte des mit reinhauen ..
Bei e=m*c^2 haste mich drangekriegt - das habe ich natürlich total verpeilt ^^

Zum Thema Dictionary:
Ich hatte, bevor ich das Script schrieb, geforscht welche Alternativen ich zur Liste habe
und bin dabei auf die Dictionary's gestossen, allerdings waren diese mir suspekt und ich habe sie links liegen lassen ..
Nun, so wird es geschehen, daß ich sie wieder aufnehme, und genauer unter die Lupe nehme :>

Ok, an die "getter" und "setter" werde ich zukünftig denken, daß kam wohl aus meinem Java Unterbewusstsein :roll:

---
aso und eine Frage zum Schluss bezügl. Namensgebung:

diese Funktionen in der Art __blablub__ haben mir anfangs Sorge bereitet,
da ich mich unaufhörlich fragte: wtf, 4x Unterstrich ?_?
Dann kam mir der Gedanken Python eigene Funktionen würden so gekennzeichnet, aber das stimmt nicht ..
Du hast auch eine Funktion deklariert: `__getitem__` -
was ist der Gedanke (an der Schreibweise) dahinter ? :>
BlackJack

kiiu hat geschrieben:Daß die Tab Einrückung nicht üblich ist, wundert mich. Ich dachte Leerzeichen und Tabs wären "erlaubt",
und für Tabs hatte ich mich entschieden, weil diese es mir leichter machen die Einrückung (wenn nötig) zu Überprüfen.
Wieso ist das mit TABs leichter? Wenn etwas syntaktisch Probleme macht, dann beschwert sich der Übersetzer mit einem `IndentationError`. Ansonsten sieht man auch 4 Zeichen eingerückte Blöcke sehr gut.
Meinst Du mit Docstrings solchen Code den man ungfähr so """ ... """" schreibt ?
Ich hatte mich nämlich schon des öfteren beim Anschauen von Scripten über diese Kommentare gewundert.
Genau das meinte ich. Zeichenketten die am Modulanfang und gleich nach Klassen- Funktions- und Methodendefinitionen stehen, werden zu einem Attribut (`__doc__`) des jeweiligen Objekts. Die kann man sich dann in der Python-Shell mit `help(obj)` anschauen oder mit `pydoc` (Standardbibliothek) als Text oder HTML ausgeben lassen. Es gibt auch externe Programme wie `epydoc`, die daraus Java-ähnliche HTML Doku machen. Du kannst die Docstrings also mit Javadoc vergleichen.
Allerdings muss bei den "Zeichenketten als Ausnahmen" nochmal nachfragen - wie meinst Du das ?
bzw. was sind Ausnahme-Hierarchien durch Vererbung ? (wenn ich mal so blöd fragen darf ..)
Du löst Ausnahmen mit ``raise 'Typfehler Text'`` aus, anstatt ein von `Exception` abgeleitetet Objekt wie z.B. ``raise TypeError('Text')`` zu verwenden. Die lassen sich wesentlich flexibler mit ``try ... except`` behandeln. Zum Beispiel kann man dort auch eine Basisklasse der eigentlichen Ausnahme angeben.
aso und eine Frage zum Schluss bezügl. Namensgebung:

diese Funktionen in der Art __blablub__ haben mir anfangs Sorge bereitet,
da ich mich unaufhörlich fragte: wtf, 4x Unterstrich ?_?
Dann kam mir der Gedanken Python eigene Funktionen würden so gekennzeichnet, aber das stimmt nicht ..
Du hast auch eine Funktion deklariert: `__getitem__` -
was ist der Gedanke (an der Schreibweise) dahinter ? :>
Das sind Funktionen mit besonderer Bedeutung. Meistens für Operatorüberladung oder eingebaute Funktionen. Die `__getitem__()` Methode wird zum Beispiel aufgerufen wenn man `obj[key]` schreibt. Das wird als `obj.__getitem__(key)` ausgeführt.

Du solltest am besten mal das Tutorial durchgehen. Dictionaries nicht zu benutzen weil sie suspekt sind ist seeehr komisch. Das ist eine ziemlich grundlegende Datenstruktur.
Antworten