logische Struktur anlegen

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

Ich habe da auch noch ein etwas größeres Problem:
Ich schreibe sowohl in C als auch in Python.
Das resultierende Programm soll dazu dienen Geräte zu steuern.
Die Ansteuerung der Geräte erfolgt in C über die Extensions.

Ich will nun meine Projektstruktur so anlegen, daß ich eine Hirarchische Gliederung erhalte:
"... Gerätename.Block.Funktion",
wobei "Funktion" die Extension ist und über der Ebene Gerätename nur noch Python Module verwendet werden.
Das ganze soll dann eine Baumstruktur werden in der sich Verzeichnis- und Packagestruktur entsprechen.
Es soll halt möglichst übersichtlich und erweiterbar sein.

Gibts dafür irgendein Beispiel?
Ich nutze zB. im Moment ein Script "GenExt.py" pro Zweig/Ebene, das mir je ein Extension Modul aus einem C-File erstellt.
Das hat den zB. Nachteil, daß ich auf die Funktion einer Extension nur so zugreifen kann:
"... Gerätename.Block.ExtensionModul.Funktion",
ich will aber die "Funktionen" schon in getrennten Files verwalten.

Unter http://docs.python.org/distutils/setupscript.html ist ein bisschen was beschrieben zum Thema Packages und Extensions.
Das hat mir aber noch nicht wirklich weitergeholfen
Habe es schon auch was versucht, aber scheinbar kann ich den PYTHONPATH nicht richtig ergänzen.
(Hab´s in Eclipse unter "Windows->Preferences-> ... versucht - dauert immer ewig bis es durch ist,
und auch über "Settings->ControlPanel->System->Advanced->EnviromentVariables-> ..."
mein Script "GenExt.py" enthält außerdem mehrere gleichgestaltete Blöcke

Code: Alles auswählen

modul = Extension("Gerät.Block.Funktion", sources = ["C/Funktion.c"],
                  include_dirs = ... ,   
                  library_dirs = ...,
                  libraries = [ ..., ... ]) 

setup( 
     name = "...", 
     version = "1.0", 
     description = "Module for  ...", 
     ext_modules = [modul] 
     )
(nebenbei: Die Zeilen mit den Optionen include_dirs, library_dirs und libraries sind immer gleich und ich frage mich, ob das nicht auch kürzer bzw. übersichtlicher geht indem man das nur einmal hinschreiben muss)
Die module versuche ich dann mit

Code: Alles auswählen

import Gerät.Block1
from Gerät.Block2 import (Funktion1,
                         Funktion2,
                         Funktion3)
import Gerät.Block3.Funktion1
einzubinden. Das geht wohl nicht weil dann noch der Name des "ExtensionModul" dazwischen müsste.
irgendeine Idee ?
BlackJack

@hypnoticum: Was ist denn `BlockX` jeweils? Kannst Du da nicht Deine Extension einfach so benennen?

Musst Du zwingend in C Programmieren? Das wäre für mich nur die aller-aller-letzte Zuflucht. Wenn aus irgend welchen Gründen `ctypes` oder `cython` (in der Reihenfolge) nicht in Frage kommen.
deets

@hynoticum

Nein, sowas geht nicht. Es gibt zwar fuer pure Python-Module die Moeglichkeit sogenannter Namespace-Pakete. Aber das klappt AFAIK nicht fuer Extensions.

Und ich denke auch nicht wirklich, dass deine Kriterien von Uebersichtlichkeit & Erweiterbarkeit durch eine Unzahl von Modulen erreicht werden.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

@BlackJack:
Mit ctypes habe ich es vorher gemacht. Der Weg über Extensions ist meiner Meinung für das "Projekt" der bessere weil schon Code und Dokumentation in C existiert.
Ich hatte zu Anfang auch überlegt für jeden Block eine Extension anzulegen. Aber ich habe nicht den Weg gefunden mehrere Funktionen in ein Modul zu kompilieren bzw. diese dann aufzurufen. Außerdem wärs halt nach meinem Empfinden nicht so schön eine immer weiter wachsende Datei zu haben.

@deets:
Schade, wenn es nicht geht. Unter dem von mir angegebenen link im Abschnitt "2.3.1. Extension names and packages" hatte es sich für mich allerdings so angehört, als wenn es möglich sein sollte.
BlackJack

@hypnoticum: Was meinst Du damit dass Du nicht mehrere Funktionen in ein Modul kompilieren kannst!?

Was spricht denn gegen Cython? Da brauchst Du Dich wenigstens nicht um das ganze Verwaltungsgeraffel zu kümmern wie Referenzzähler und vor allem auch Ausnahmen, oder die Strukturen um den Modulinhalt dem Python-Interpreter bekannt zu machen. Um Ausnahmen hast Du Dich in dem gezeigten Quelltext ja gar nicht gekümmert. Das ist ziemlich unsauber und wird früher oder später vielleicht auch zu einer Quelle für Abstürze.

Vorhandener C-Code spricht IMHO auch nur gegen `ctypes` wenn das schon eine Python-Erweiterung ist und selbst dann würde ich versuchen das über die Zeit in Richtung Cython oder `ctypes` zu verlagern. Spätestens wenn ich aus einer C-Erweiterung heraus `Tkinter` ansteuern müsste, würde ich das ganz dringend machen wollen.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

@BlackJack:
>>Was meinst Du damit dass Du nicht mehrere Funktionen in ein Modul kompilieren kannst!?
Ich meine das so wie ich es geschrieben habe: ich weiß nicht wie's geht - ganz einfach.

Cython habe ich mir noch nicht angesehen. Ich bin Einsteiger und habe mir meinen Weg gesucht. Den bin ich mit Extensions gegangen und auch recht zufrieden bis auf das hier genannte Problem, das ja eigentlich auch mehr von "kosmetischer"-Natur ist. Ich werde mir Cython mal ansehen.
Wegen dem Embedding von Python in den Extensions mach dir mal keine Sorgen: das ist nicht so umfangreich und eher eine Randerscheinung/Hilfe die nur in außergewöhnlichen Situationen genutzt werden soll (Fehlermeldung, Unterbrechung des Programms mit Intervention, Ausfall des Reaktorkühlsystems ;)) muss also nicht so zuverlässig sein.
BlackJack

@hypnoticum: Also ich kann echt nicht nachvollziehen wieso Du nicht mehr als eine Funktion in eine C-Erweiterung bekommst. Das ist ja nun echt nichts exotisches. Mir fällt im Gegenteil auf Anhieb keine C-Erweiterung ein, die nur eine Funktion enthält, also müsste so ziemlich jede Vorhandene als Beispiel dienen können, wie das geht.
deets

@BlackJack

Ich verstehe das so, dass er genau das *Gegenteil* will. Lauter einzelne Funktionen, die aber im Grunde in einem Python-Modul stecken. Aus seinem Beispiel:

Gerätename.Block.Funktion

Block ist Python, Funktion dann aber schon eine C-Funktion.

Das geht natuerlich nicht, ausser, er wuerde endlich einsehen, ctypes zu verwenden ;)
BlackJack

@deets: Das verstehe ich nicht? Lauter einzelne Funktionen die in einem Modul stecken -- ob das nun ein Python-Modul oder eine C-Erweiterung ist -- geht doch. Oder was ist für Dich eine "einzelne Funktion"? Ich kann mir echt nicht im entferntesten vorstellen was hier eigentlich das Problem ist!? `Block` müsste halt einfach die C-Erweiterung sein und schon hat man was gewünscht ist.
deets

@BlackJack:

Dann lies nochmal genau:
Ich nutze zB. im Moment ein Script "GenExt.py" pro Zweig/Ebene, das mir je ein Extension Modul aus einem C-File erstellt.
Das hat den zB. Nachteil, daß ich auf die Funktion einer Extension nur so zugreifen kann:
"... Gerätename.Block.ExtensionModul.Funktion",
ich will aber die "Funktionen" schon in getrennten Files verwalten.
Das lese ich so, dass eben eine Funktion ein modul sein soll, was dann gleichzeitig das callable "Funktion" ist.

Ausserdem schreibt er:
Ich will nun meine Projektstruktur so anlegen, daß ich eine Hirarchische Gliederung erhalte:
"... Gerätename.Block.Funktion",
wobei "Funktion" die Extension ist und über der Ebene Gerätename nur noch Python Module verwendet werden.
Jetzt kommt's ein bisschen darauf an, was "ueber der Ebene Geraetename" gemeint ist - die Ebene Block, oder die Ebene "...".

Alles in allem ein bisschen schwurbelig, so ganz 100%ig blicke ich da auch nicht durch.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

Vielen Dank Jungs.
Eigentlich ist es schon so das die Extensions der unteren Ebene nur von Python aus der nächst höheren Ebene aufgerufen werden.
Bevor es zu kompliziert wird werde ich dann mal sehen, ob ich es auch anders machen kann.
BlackJack

@hypnoticum: Bei Python-Modulen die sich auf C-Erweiterungen abstützen, welche vom Benutzer nicht direkt verwendet werden sollen, ist es ein durchaus übliches Muster beide auf der gleichen Verzeichnisebene zu haben und dem Erweiterungsmodul einen '_' voran zu stellen um es als nicht-öffentliche API zu kennzeichnen. Daraus ergeben sich ja letztendlich zwei sauber getrennte Namensräume für die öffentliche und die private API. Also als Verzeichnisbaum sähe dass dann so aus:

Code: Alles auswählen

geraet/
    __init__.py
    block1.py
    _block1.so
    block2.py
    _block2.so
    …
Diese sehr flache Verzeichnisstruktur bietet, soweit ich das verstanden habe, die Namensraumstruktur, die Du dem Benutzer der Bibliothek bieten möchtest: `geraet.block1.function1()`. Und im `block1`-Modul kannst Du auf die C-Erweiterung über `geraet._block1.internal_function1()` zugreifen, bzw. es relativ mit ``import _block1`` oder ``import ._block1`` importieren.

Um mal das Python-Zen zu zitieren (``import this``): `Flat is better than nested.`

Beim Planen muss man übrigens auch nicht zwingend berücksichtigen, dass so ein `block.py` mal so umfangreich wird, dass man es in mehrere Module aufteilen möchte und die der Übersicht halber in ein eigenes Package stecken möchte. Das kann man dann wenn es akut wird nämlich immer noch tun -- ohne dass sich nach aussen hin die API ändern muss! Beispiel:

Code: Alles auswählen

mypackage/
    __init__.py
    spam.py
    …
Und nun wird die Implementierung von `spam` immer grösser und man möchte es gerne in zwei oder drei Module aufteilen:

Code: Alles auswählen

mypackage/
    __init__.py
    spam/
        __init__.py <- Hier die gleiche API wie beim alten spam.py anbieten!
        part1.py
        part2.py
        util.py
Aus Benutzersicht hat sich hier nichts wirklich verändert. Wenn man auch die API für den Benutzer ändern möchte, kann man weiterhin parallel die alte API in `spam/__init__.py` anbieten und die Aufrufe dort "wrappen", dass sie über das `warnings`-Modul jeweils eine `DeprecationWarning` absetzen, und den Benutzern ein paar Releases lang Zeit lassen ihren Quelltext anzupassen.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

Vielen Dank für die Antworten.
Ich lege jetzt eine C-Datei "Block" an, die als Modul kompiliert wird. In dieser C-Datei inkludiere ich die Funktionen, welche in anderen Dateien abgelegt sind. Von diesen Funktionen wiederum gemeinsame genutzte Routinen sind auch in der "Block"-Datei. Das ist etwa so wie ich es haben wollte.

Code: Alles auswählen

#include <Python.h>
#include <string.h>
#include <stdio.h>

PyObject * Routine(double Handle);

#include "cExtenFun1.c"
#include "cExtenFun2.c"

static PyObject *cExtenFun1(PyObject *self, PyObject *args);
static PyObject *cExtenFun2(PyObject *self, PyObject *args);

static PyMethodDef pyExten_methods[] = {
	{"pyExtenFun1", cExtenFun1, METH_VARARGS, "Performs some Function"},
	{"pyExtenFun2", cExtenFun2, METH_VARARGS, "Performs some other Function"},
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC initExten(void){
    Py_InitModule("Exten", pyExten_methods);
};

PyObject * Routine(double C_Handle){
        PyObject *C_Module = NULL, *C_Class = NULL, *C_Instance = NULL;
        Py_Handle = Py_BuildValue("(l)", C_Handle);
	C_Module = PyImport_ImportModule("Py_Module");
	if(C_Module){
		C_Class = PyObject_GetAttrString(C_Module, "Py_Method");
		if (PyCallable_Check(C_Class)){
			C_Instance = PyObject_CallObject(C_Class, Py_Handle);
		}
		else
			printf("Error: Py_Method not found\n");
	}
	else
		printf("Error: Py_Module not found\n");

	return C_Instance;
	Py_XDECREF(C_Module);
}
Das Modul generiere ich mit diesem Script:

Code: Alles auswählen

"""
It might be useful to tell distutils to use mingw. To achieve this,
create a file named distutils.cfg (if you already don't have it) in
\PythonXY\Lib\distutils and add to it the following lines:

[build]
compiler = mingw32
"""
#[url]http://docs.python.org/distutils/setupscript.html[/url] 

import sys
from distutils.core import setup, Extension

sys.argv.append('install')  

modul = Extension("Block", sources = ["C/Block.c"]) 
setup( 
     name = "PyExten", 
     version = "1.0", 
     description = "Test C-Extension", 
     ext_package='Gerät',
     ext_modules = [modul] 
     )
Ein Problem habe ich allerdings noch: Wenn ich etwas an der obigen Datei ändere zB anstelle von "Gerät" "Device" schreibe und nach der Erzeugung des Moduls diese Änderung wieder rückgängig mache, werden von nun an zwei Ordner in ".../Pythonxy/Lib/site-packages" angelegt. Die Erzeugung der Module wird also immer wieder unabhängig von dem Inhalt der Datei vorgenommen ... ?
BlackJack

@hypnoticum: Komplette C-Dateien per ``#include`` in andere zu importieren ist äusserst unschön. Normalerweise würde man nur Headerdateien mit den Deklarationen in der `C/Block.c` inkludieren und nicht den ganzen Quelltext.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

das soll jetzt bitte nicht patzig klingen - aber was bitte heisst "unschön"?
Ich bin eigentlich gerade froh eine Lösung gefunden zu haben, wie soll ich es sonst machen, wenn der Quelltext nicht an einem Stück in einer Datei vorhanden sein soll ...
(mal ganz nebenbei: ich bin kein Informatiker. ich lasse mir gerne helfen, aber letztendlich bin ich froh wenn's läuft)

Nachtrag:
Unschön ist zB. wie ich gerade feststellen musste, dass von mingw nicht neu kompiliert wird, wenn sich eine inkludierte *.c-Datei geändert hat.
Das kann im setup-script mit der option
depends = ['Datei1.c', 'Datei2.c', ...]
zum Glück noch behoben werden.
Zuletzt geändert von hypnoticum am Mittwoch 23. März 2011, 11:55, insgesamt 3-mal geändert.
BlackJack

@hypnoticum: "Unschön" heisst, dass man das nicht macht, weil das früher oder später Probleme bereitet.

Der Quelltext muss nicht an einem Stück vorhanden sein. Das `source`-Argument nimmt doch eine Liste entgegen und nicht nur einen Dateinamen.

Nach den üblichen C-Konventionen sähe dass so aus:

Code: Alles auswählen

C/
    Block.c
    cExtenFun1.h
    cExtenFun1.c
    cExtenFun2.h
    cExtenFun2.c
    common.h
    common.c
In `common.*` wären dann die gemeinsam genutzten Funktionen. Wenn man das nicht so auslagert, bekäme man eine zirkuläre Abhängigkeit bei den ``#include``\s.
deets

@hypnoticum

Der Grund ist der, dass jedes C-File eine 'compilation unit' ist. Und das zusammenfassen via linken geschieht. Wenn du das anders machst, musst du aufpassen, dass der Compilationsprozess niemals dasselbe source-file zweimal einbindet- sonst gibt's doppelte Symbole.

Darum BlackJack's sehr richtiger Ratschlag.
hypnoticum
User
Beiträge: 132
Registriert: Dienstag 15. März 2011, 15:43

Ist vielleicht unüblich, aber funktioniert erstmal.
Ich habe auch nicht den Überblick was beim Compilieren in welcher Reihenfolge abläuft, wie oft jedes File abgesucht wird, Symbole ersetzt werden ...
Sucht der Linker automatisch nach einem gleichnamigen Objekt-File wenn ich Funktionen einer inkludierten Header Datei verwende? Und wenn der zugehörige Quellcode noch gar nicht komnpiliert wurde?
was mich wundert ist, das ich keinen Fehler trotz mehrfach definiertem Label bekomme

Abschließend wärs nett wenn einer wüsste warum das so ist:

>>Wenn ich etwas an der obigen Datei ändere zB anstelle von "Gerät" "Device" schreibe und nach der Erzeugung des Moduls diese Änderung wieder rückgängig mache, werden von nun an zwei Ordner in ".../Pythonxy/Lib/site-packages" angelegt. Die Erzeugung der Module wird also immer wieder unabhängig von dem Inhalt der Datei vorgenommen ... ?
BlackJack

@hypnoticum: Die `distutils` "wissen" wie sie aus den angegebenen Dateien jeweils eine Object-Datei (*.o) machen und rufen am Ende den Linker mit allen Object-Dateien auf. Dann der Linker die Symbole alle auflösen, weil er die exportierten Symbole von allen Übersetzungseinheiten kennt.

Man kann in C ein und die selbe Sache beliebig oft deklarieren, solange die Deklaration immer gleich ist.
Antworten