Anfängerfrage: Modularisieren und Namensräume

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
angwedh
User
Beiträge: 20
Registriert: Dienstag 16. März 2010, 08:04

Hallo Zusammen
Ich habe mal wieder eine Frage, ich denke ich begreiffe da etwas noch nicht. Wie der Titel schon ankündigt, geht es um das Modularisieren eines Programm und die Namensräume.

Da mein "Testprogramm" so langsam aber sicher unübersichtlich wurde, habe ich alles in eigene Module ausgelagert.
Etwas vereinfacht sieht es so aus:

Code: Alles auswählen

-beispiel.py
-control.py
-classes (ordner)
	- __init__.py
	- myclass1
Dafür habe ich ein Modul mit meinen Kontrollfunktionen und in einem Packet "classes" eines mit meiner Klasse. beispiel.py ist meine Hauptfunktion. Inerhalb sieht es etwa so aus:

Code: Alles auswählen

#Beispielsprogramm für mein Problem:

from classes.myclass1 import Myclass1

from control import funktion1, funktion2
#Main
liste = []

funktion1(liste, 'Erstes  Element')
funktion2(liste, 'Bla')

print(liste)

control.py:

Code: Alles auswählen

#Inhalt von Modul control:

def funktion1(liste, element):
    liste.append(element)

def funktion2(liste, bla):
    liste.append(Myclass1(bla,bla))

myclass1.py

#Klasse Myclass1

Code: Alles auswählen

class Myclass1(object):
    def __init__(self, name, bla):
        self.name = name
        self.bla = bla
Mein Problem ist nun, dass meine Klasse nicht von der funktion2 des Moduls controls gefunden wird. Dies ist ja eigentlich auch logisch, das Problem ist nun, dass ich keine Ahnung habe, wie man dies professionel löst.

So viel mir ist, sollte man ja nicht alles in den globalen Namensraum einbinden, aber wie kann man dann einem lokalen Namensraum eine Funktion/Klasse eines anderen Moduls übergeben?

Das zweite, was mir in diesem Fall aufgefallen ist, ist dass wenn ich nun nur das Modul control starte, dass es automatisch irgendwie beispiel.py auch noch ausführt, da die "liste" im globalen Namensraum auftaucht. Ist dies normal?

Und noch eine Nebenfrage: Wie löscht man in der laufenden Pythonshell die Namensräume und alle Variablen (=alles)? Wenn ich Änderungen in einem Modul mache und ich das Hauptprogramm neu starte, hat er immer noch die alten Angaben...

grüsse
angwedh

Edit (BlackJack): Verzeichnisstruktur in Code-Tags gesetzt, damit man die Struktur auch erkennen kann.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Zusammenpacken, was zusammengehoert. Wir sind hier nicht bei Java, wo jede Klasse eine eigene Datei braucht. Modularisierung der Modularisierung willen, waehrend es eindeutige und noetige Abhaengigkeiten gibt, schadet nur.
Ein Paket `classes` hoert sich fuer mich jedenfalls nicht nach etwas sinnvollem an.
Du koenntest aber `funktion2` das Klassenobjekt uebergeben (→ Dependency Injection), auf dem es arbeiten soll und damit die Abhaengigkeit aufloesen.

In `beispiel.py` machst du den globalen Namensraum btw reichlich dreckig ;) Es gibt btw keinen globalen, sondern nur einen modul-globalen Namensraum.
angwedh hat geschrieben:Das zweite, was mir in diesem Fall aufgefallen ist, ist dass wenn ich nun nur das Modul control starte, dass es automatisch irgendwie beispiel.py auch noch ausführt, da die "liste" im globalen Namensraum auftaucht. Ist dies normal?
Das kann so nicht sein: In `control.py` gibt es keine `liste` und die lokalen namen in den Funktionen wuerden den Namen sowieso ueberdecken.
BlackJack

@angwedh: Ich habe mir mal erlaubt Deine Varzeichnisstruktur in Code-Tags zu setzen, damit man die Einrückung auch sieht.

Für Namen von Modulen/Namensräumen gilt normalerweise das gleiche was bei allen anderen Namen auch gilt: Sie sollten den *Sinn* beschreiben, nicht den *Typ*. `classes` ist also IMHO ein schlechter Name weil er nichts darüber sagt was die Klassen machen. Ausserdem impliziert der Name dass da nur Klassen drin sind. Was ist mit Funktionen die thematisch zu den jeweiligen Klassen gehören? Was thematisch zusammengehört sollte auch im Quelltext beziehungsweise in Namensräumen zusammengefasst werden.

Wie wird denn in `beispiel` Deine `Myclass1` gefunden? *Genau so* kann man die auch in `control` finden. Man muss sie importieren. ``from classes.myclass1 import Myclass1`` und sie ist auch in dem Modul/Namensraum bekannt.

Wenn Du im Zuge dieser Importe übrigens auf zirkuläre Importe stösst, also zum Beispiel Modul mit Funktionen braucht Modul mit Klasse und Modul mit dieser Klasse braucht das Modul mit den Funktionen, dann hast Du einen guten Hinweis darauf, dass Deine Aufteilung auf Module nicht richtig ist. Wenn sich zwei Module gegenseitig benötigen, gehören sie in ein Modul, oder der Inhalt der Beiden ist nicht gut aufgeteilt.

Man sollte nicht alles in den globalen Namensraum einbinden, das ist richtig -- das machst Du auch nirgends! Das machen aber auch sehr selten Leute, das ist auch nicht ganz so einfach wie der Fehler den viele Leute machen, nämlich der Sternchenimport. Da ist der Rat aber nicht auf den globalen Namensraum bezogen, sondern man sollte halt auch nicht einfach alles in den Namensraum eines Moduls übernehmen. Dann könnte man sich nämlich Namensräume auch komplett sparen.

Wenn Du nur `control` als Programm startest, wird zumindest bei dem was Du hier zeigst, nur der Code in `control` ausgeführt. Der Namen `liste` ist ein Argument von den Funktionen und hat mit gleichen Namen in anderen Modulen nichts zu tun. `beispiel` wird nur ausgeführt, wenn Du es explizit ausführt, oder importierst. Damit man ein Modul sowohl als Programm ausführen, als auch als normales Modul importieren kann, zum Beispiel um Funktionen oder Klassen daraus zu verwenden, es im in einer Python-Shell zu laden, Tests durchzuführen oder Ähnliches, sollte man die Programmlogik von der Modulebene in eine Funktion verbannen und diese dann am Ende mit folgendem Idiom ausführen:

Code: Alles auswählen

if __name__ == '__main__':
    main()
Hier wird die Funktion `main()` nur ausgeführt wenn das Modul als Programm gestartet wurde, aber nicht wenn es nur importiert wurde.

Zu den Tests müsstest Du mehr darüber sagen wie Du testest und in welcher Python-Shell. Die Shell(s) eigne(t|n) sich eher für das herumspielen und testen von einzelnen Funktionen. Sobald Tests mehr vorbereiteten Zustand benötigen, den man jedesmal neu vorbereiten müsste, sollte man Unit-Tests schreiben. Und/Oder sich überlegen ob das ganze nicht zu verwoben und von zuviel "globalen" Zustand abhängig ist.
angwedh
User
Beiträge: 20
Registriert: Dienstag 16. März 2010, 08:04

Hallo Zusammen
Vielen Dank für die Antworten, ich habe da wirklich etwas falsch verstanden und nun mein Programm so abgeändert, dass thematisches zusammengehörendes zusammen ist. Jetzt besteht mein Programm zwar wieder nur aus zwei Dateien (main.py und der Rest in einer zweiten, da fast alle Funktionen die beiden bis jetzt bestehenden Klassen benötigen) aber im weiteren Verlauf werden aber neue Module kommen.

(edit, 07.05., 08.27): Wo in der Dokumentation findet man die genaue Funktionsweise in Python? Ich finde irgendwie nichts... Möchte mich da noch etwas vertieft damit auseinandersetzen...

Grüsse
angwedh
BlackJack

@angwedh: Was meinst Du mit "genaue Funktionsweise"? Syntax und grundlegende Semantik werden in der Sprachreferenz beschrieben.

Die wirklich *genaue* Funktionsweise von CPython findest Du formal in C und Python definiert im Quelltext: http://svn.python.org/view/python/tags/r265/ ;-)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

angwedh
User
Beiträge: 20
Registriert: Dienstag 16. März 2010, 08:04

Es geht mir vor allem darum, dass ich vertehe wie Python Namensräume verwaltet und wie die Hirarchy aufgebaut ist... Also mehr allgemein als nur für den Import eines Modules.
grüsse
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Na, die Hierarchie ist halt `attribut.attribut.attribut[...]`. Du musst hier schon genau werden und sagen, was dir unklar ist. Eine Suchmaschine spuckt für die Begriffe "python+namespace" oder "python+scope" ja auch schon einiges aus...
BlackJack

@angwedh: So ganz allgemein ist die Antwort: Objekte und im Hintergrund dann Dictionaries. Namensräume werden über Attributzugriffe auf Objekten gelöst. Module sind ja auch nur Objekte wie jedes andere auch.

Nehmen wir mal `os.path.isfile('spam')`: Das sucht im akuellen Modul das Attribut `os` was -- ``import os`` vorrausgesetzt`` -- auch ein Modul-Objekt ist. Da wird dann das Attribut `path` abgefragt, was ebenfalls wieder ein Objekt vom Typ `module` ist, und da wird das Attribut `isfile()` abgefragt, was vom Typ `function` ist. Und das Objekt wird dann aufgerufen.

In "Langform" ist das ``os.__getattr__('path').__getattr__('isfile').__call__('spam')``. Modul-Objekte implementieren halt die `__getattr__`-Methode entsprechend. Im Hintergrund stehen da Dictionaries. Bei den meisten Objekten kann man auf dieses Attribut-Dictionary auch über `obj.__dict__` zugreifen. Bei Modulobjekten geht das zum Beispiel.

Namensräume sind also nichts besonderes. Einfach nur Objekte und Attribute. Bei verschachtelten Namensräumen sind die Attribute halt wieder Modul-Objekte.

Bei einem Import der Form ``import mod`` wird das Modulobjekt `mod` zum Attribut des importierenden Moduls und bei ``from mod import obj`` bekommt das importierende Modul ein Attribut mit dem Namen `obj` das auf das gleiche Objekt wie `mod.obj` verweist. So ein Import ist also Äquivalent zu:

Code: Alles auswählen

import mod as unique_temporary_name
obj = unique_temporary_name.obj
del unique_temporary_name
Antworten