Python in Objective-C

Du hast eine Idee für ein Projekt?
Antworten
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Verrückte Idee: Wie wäre es, einen kleinen in Objective-C geschriebenen Python-Interpreter zu haben, der z.B. unter iOS funktioniert?

Hat jemand Lust, so etwas zu entwickeln?

Um eine einfache Interaktion mit Objective-C und den Apple-Bibliotheken hinzubekommen, wäre ich zu folgenden Zugeständnissen bereit:

1) Damit Methoden wie `addObject:forKey:` nicht als `self.addObject_forKey_(value, key)` geschrieben werden müssen, wäre ich bereit die Python-Syntax zu ändern und z.B. `self.(addObject:value forKey:key)` zuzulassen.

Die Grammatik muss dafür nur minimal geändert werden:

Code: Alles auswählen

    trailer: '(' [testlist] ')' | '[' subscript ']' | '.' (NAME | message_send)
    message_send: '(' keyword_expr {keyword_expr} ')'
    keyword_expr: NAME ':' test [',']
2) Datentypen sollten die Foundation-Klassen sein, also z.B. `NSMutableDictionary` für `dict` oder `NSArray` für `tuple`. Für Zahlen ist das ineffizient, aber ich glaube, man spart sich Konvertierungen, wenn man Objective-C-Methoden aufrufen will. Außerdem kann man so wahrscheinlich auch Zugriff auf alle existierenden Methoden gewähren. Möglicherweise ist es sinnvoll, wie MacRuby vorzugehen, und jeweils von den Foundation-Klassen zu erben.

3) Objekte müssen explizit freigegeben werden, d.h. es gibt eine `release`-Methode und in jede eigenen Klasse muss man `dealloc` implementieren.

Mein Python-Parser lässt sich relativ direkt in Objective-C überführen. Es müssen etwa 60 AST-Klassen definiert und etwa 100 Parser-Methoden portiert werden. Das ist Fleißarbeit.

Mein Parser ist leider sehr schlecht, was Fehlermeldungen angeht. Er sagt einfach, es ist etwas falsch. Minimal notwendig wäre, die Zeilennummer zu kennen. Ich habe eine `Token`-Klasse eingeführt, die die Position des Tokens im Quelltext kennt und die man so nach einer Zeilennummer fragen könnte. Vielleicht reicht das.

Hier ist ein Beispiel, wie Parser-Methoden aussehen könnten:

Code: Alles auswählen

    @implementation Parser
    
    - (Expr *)parse_or_test	{
    	Expr *expr = [self parse_and_test];
    	while ([self at:@"or"]) {
    		expr = [OrExpr withLeftExpr:expr rightExpr:[self parse_and_test]];
    	}
    	return expr;
    }
    
    - (Expr *)parse_testlist_opt_as_tuple {
        if ([self has_test]) {
            Expr *expr = [self parse_test];
            if (![self at:@","]) {
                return expr;
            }
            NSMutableArray *exprs = [NSMutableArray arrayWithObject:expr];
            if (![self has_test]) {
                return [TupleExpr withExprs:exprs];
            }
            [exprs addObjectsFromArray:[self parse_testlist_opt]];
            return [TupleExpr withExprs:exprs]
        }
    }
    
    @end

Ich bin bei den Klassenmethoden von dem Pattern, "Class classWith..." abgewichen und nutze einfach nur "Class with...", das möge man mir verzeihen. Auch wäre es wahrscheinlich üblicher, `parseOrTest` und `parseTestlistOptAsTuple` zu benutzen.

Die meiste Arbeit wäre, das Laufzeitsystem zu entwickeln. Insbesondere ist mir hier noch nicht klar, wie man die Speicherverwaltung in den Griff bekommt. Und wie man mit z.B. Klassen aus dem UIKit interagieren kann.

Bei der Semantik würde ich mich munter aus Python 1.x bis 3.x bedienen. So reicht es IMHO, einfache Vererbung zu haben und keine Unterklassen von vordefinierten Typen bilden zu können. Strings sollten aber wie `NSString` Unicode-Zeichen enthalten können und ein `bytes`-Typ würde prima auf `NSData` abbildbar sein. Standardfunktionen wie `locals` oder `globals` würde ich vorschlagen wegzulassen und auch von Generatoren würde ich mich zunächst verabschieden wollen.

Ach ja, da Objective-C 2.0 Blöcke kennt und diese vermehrt Einzug in die Klassenbibliothek halten, wäre es überlegenswert, dafür auch eine Syntax zu haben. Vielleicht so:

Code: Alles auswählen

    regexp.(enumerateMatchesInString:s usingBlock:[|match, flags, stop|
        ...
    ])
Solange immer ein `|` hinter `[` folgt, kann man diesen Ausdruck von einer Liste unterscheiden. Das würde ebenso mit `{}` funktionieren. Oder man macht es wie Objective-C, wo ein `^` den Block einleitet. Das Zeichen wird in Python nicht verwendet:

Code: Alles auswählen

    regexp.(enumerateMatchesInString:s usingBlock:^(match, flags, stop):
        ...
    )
Grammatik wäre dann:

Code: Alles auswählen

    test: ... | block
    block: '^' [params] ':' suite

Gibt es weitere Anforderungen? Wünsche? Kommentare?

Stefan
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich habe jetzt einfach mal angefangen. Ich nenne das Projekt "Pyphon".

Parser und AST sind weitestgehend portiert (etwa 2000 Zeilen Code). Ich kann "print(3+4)" ausführen. Ansonsten kracht es aber sofort, weil zu viel noch fehlt. Der Code ist gesprenkelt mit TODOs.

Jetzt würde ich gerne ausprobieren, ob ich so etwas wie `self.(addSubview:ui.Button.(alloc).(initWithFrame:cg.Rect(0, 0, 100, 100)))` ausführen kann. Nachrichten kann ich komplett dynamisch verschicken. In der Variablen `ui` müsste ich ein Proxy-Modul ablegen, welches bei `__getattr__` nach `Button` nach einer Objective-C-Klasse `UIButton` sucht. Dito `cg`, wobei sich dort keine Klasse, sondern eine Funktion versteckt.

Bild

Stefan

Edit: Oops, die Grafik ist aber groß...
Zuletzt geändert von sma am Montag 17. Januar 2011, 00:06, insgesamt 1-mal geändert.
BlackJack

@sma: Warum erfindest Du eine neue Syntax? Kann man statt ``self.addObject_forKey_(value, key)`` das nicht auch so umsetzen, dass man ``self.addObject(value, forKey=key)`` schreiben muss? Oder gäbe es da Konflikte die sich nicht auf diese weise erschlagen lassen?
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Soll dann auch NSArray.arrayWithObject(a, withObject=b, withObject=c) funktionieren? Es wäre eigentlich bei Python ungewöhnlich, das selbe Schlüsselwort mehrfach zu benutzen. Zudem ist die Reihenfolge der Schlüsselwörtern bei Python beliebig. Bei Objective-C ist das ein Selektor, der foo:bar: und nicht bar:foo: bzw. arrayWithObject:withObject:withObject: heißt. Außerdem gefällt mir bei dem Beispiel da oben nicht, dass ein Teil des Selektors irgendwie besonders aussieht, weil er vor der Klammer steht. Da der Objective-C-Teil sowieso nicht in einem normalen Python-Interpreter funktionieren würde, kann man sich da IMHO schon eine besondere Syntax gönnen.

Stefan
BlackJack

@sma: Okay, dann weiss ich jetzt was ich übersehen hatte. Bin ich wahrscheinlich nicht drauf gekommen, weil die selben "Namensteile" mehrfach zu verwenden oder das verwenden der gleichen Signatur nur mit anderer Reihenfolge irgendwie gruselig auf mich wirkt.
OverNord
User
Beiträge: 72
Registriert: Donnerstag 24. Januar 2008, 11:59
Kontaktdaten:

@sma: wie wäre es, wenn man den Punkt vor der Klammer weglassen würde, also z.B. `self(addSubview:ui.Button(alloc)(initWithFrame:cg.Rect(0, 0, 100, 100)))`
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Bei `ui.Button(alloc)` würde man jetzt vermuten, dass `alloc` eine Variable ist und `ui.Button` einfach eine Funktion. Das lässt sich so nicht eindeutig parsen. Statt `ui.Button.(alloc)` präferiere ich zur Zeit das zu Objective-C nähere `UIButton.[alloc]`. Perfekt ist das aber zugegeben alles nicht, denn den "." übersieht man schnell und glaubt dann, es wäre ein Index-Operator.

Was aber letztlich da herauskommen soll, ist ein AST-Knoten `Send` mit drei Argumenten: Einem Empfänger, einem Selektor und einer Argumentliste. Dann kann ich das umsetzen (was gar nicht so einfach ist, weil da auch C-Strukturen oder primitive Typen statt Objekte zurückkommen können).

Ich glaube, ich versuche erst mal, mehr als zwei Konstanten addieren können. Metaprogrammierung in Objective-C ist noch neu für mich.

Stefan
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich habe meinen Code jetzt auf Github veröffentlicht: https://github.com/sma/Pyphon

Stefan
NiklasRosenstein
User
Beiträge: 17
Registriert: Donnerstag 16. Juni 2011, 21:38

Und was ist mit PyObjC von Saurik auf iOD portiert ? ;)
Habe dafür bereits eine App (in Python) geschrieben.
Allerdings wird ein Jailbreak benötigt.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Die Jailbreak-Bedingung ist IMHO keine Option. PyObjC ist IMHO ebenfalls kein so guter Ansatz, weil die Umsetzung der Objective-C-Selektoren in Methodennamen zu sehr hässlichem Code führt. Besser wäre, man baut man per Hand eine spezielle auf Python-Idiome zugeschnittene Bibliothek. Ist natürlich mehr Arbeit, als einfach automatisch jede CocoaTouch-Klasse und Methode zuzulassen. Für alles, was auch CoreFoundation kommt, muss man das aber so oder so machen, da es keine Scripting-Bridge gibt.

Was den Aufruf von Objective-C-Code zur Laufzeit (ohne Jailbreak) angeht, da kann man bei wax gucken, die können das auch. Man muss sich mit NSInvocation quälen und diverse Sonderfälle für Nicht-Objekt-Typen berücksichtigen.

Stefan
deets

Als jemand der OSX/Cocoa/Objective-C/PyObjc programmiert kann ich deinen aesthetischen Argumenten nicht folgen. Die in PyObjc & den NSWebView/Webkit-basierten JS-bindings fuer objective c eingefuehrte Konvention ist zwar sicher nicht ideal, aber sie hat bestechende Vorteile:

- sie ist akzeptiert
- sie leitet sich sehr direkt aus der Ursprungsdefinition ab
- sie erlaubt direktes verwenden beliebigen ObjcC-codes
- der Uebersetzungsprozess ist klar und simpel: Methodennamen kopieren, parameternamen entfernen, Doppelpunkte durch unterstriche ersetzen. Das dauert bei mir ein paar Sekunden. Ich sollte mal nen emacs-macro schreiben..

Das einfuehren von entweder einer speziellen Notation (die in Haesslichkeit dem "original" in nichts nachsteht in meinen Augen) oder gar eine Abstraktions-Schicht mit extra Typen die "pythonischer" ist macht das ganze Problem einfach nur komplizierter, ohne etwas zu gewinnen. Denn wenn ich erst die Apple-Doku waelze, dann die eigene API konsultieren und am Ende doch feststellen muss, dass es noch kein Wrapping gibt und ich jetzt entweder auf dem Schlauch stehe & warten muss, bis du was nachlieferst, oder selbst wrappen muss - dann ist das unbenutzbar.

Ein Kommentar auch noch zu der Verwendbarkeit & Jailbreaks: du wirst ihn brauchen. Denn AFAIK ist Apple da sehr strikt, Dinge, die potentiell Code nachladen & ausfuehren koennen fliegen raus. Das ist schon dem C64-emulator zum Verhaengnis geworden, und das wird auch einen Python-Interpreter (egal welcher Provenienz) das Genick brechen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Statt `list.objectAtIndex_(3)` oder `list.replaceObjectAtIndex_withObject_(3, newObj)` oder `NSDictionary.dictionaryWithObjectsAndValues_(value1, key1, value2, key2, None)` möchte ich lieber `list[3]` oder `list[3] = newObj` oder `{key1:value1, key2:value2}` schreiben können. Und selbst wenn es keine passenden Operatoren bei Python gibt: `list.insert(0, newObj)` passt besser als `list.insertObject_atIndex_(newObj, 0)`. Oder wie wäre es mit

def tableView_targetIndexPathForMoveFromRowAtIndexPath_toProposedIndexPath_(tableView, sourceIndexPath, proposedDestinationIndexPath):
...
Das einfuehren von entweder einer speziellen Notation (die in Haesslichkeit dem "original" in nichts nachsteht in meinen Augen) oder gar eine Abstraktions-Schicht mit extra Typen die "pythonischer" ist macht das ganze Problem einfach nur komplizierter, ohne etwas zu gewinnen. Denn wenn ich erst die Apple-Doku waelze, dann die eigene API konsultieren und am Ende doch feststellen muss, dass es noch kein Wrapping gibt und ich jetzt entweder auf dem Schlauch stehe & warten muss, bis du was nachlieferst, oder selbst wrappen muss - dann ist das unbenutzbar.
Den letzten Punkt sehe ich anders: Was nützt mir eine zweite Sprache (wie Python), wenn ich dann doch Objective-C beherrschen muss, die Objective-C-Dokumentation brauche und eigentlich gleich in dieser Sprache arbeiten könnte? Ich finde, die Abstraktion darf eben nicht leckend sein, sondern wenn schon, dann muss ich auch in meiner Sprache bleiben können, zu der dann auch eine passende Klassen- bzw. Funktionsbibliothek gehört.
Ein Kommentar auch noch zu der Verwendbarkeit & Jailbreaks: du wirst ihn brauchen. Denn AFAIK ist Apple da sehr strikt, Dinge, die potentiell Code nachladen & ausfuehren koennen fliegen raus. Das ist schon dem C64-emulator zum Verhaengnis geworden, und das wird auch einen Python-Interpreter (egal welcher Provenienz) das Genick brechen.
Das stimmt schon länger nicht mehr. Ich habe einen C64-Basic-Interpreter (der natürlich total sinnlos ist für alles, was über `10 PRINT "Hallo": GOTO 10` hinaus geht) auf meinem iPhone. Interpreter an sich sind kein Problem. Was Apple nicht zulässt, ist das Nachladen von Programmen, die dann auf einem Interpreter (oder Emulator) ausgeführt werden. Tatsächlich hatte Apple meinen unfertigen Python-Interpreter bereits für den Store zugelassen. Ein Chinese hatte meinen Quelltext genommen und unter seinem Namen als App gegen Geld in den Store eingestellt - und natürlich schlechte Kritik geerntet, weil da nicht mehr geht als `for i in range(10): print(i)`. Das sind die beiden einzigen Systemfunktionen, die ich implementiert hatte. Hat mich trotzdem ziemlich geärgert und daher habe ich jetzt die Lizenz geändert. Aber ich weiß jetzt, der Interpreter würde (als Lernsystem) akzeptiert werden :)

Stefan
deets

Wie willst du denn die kompletten APIs aller objectiv-c-libs da draussen (sind ja nicht nur die von Apple) wrappen, dann noch eine ausfuehrliche Dokumentation dazu schreiben, und das alles um einen vermeintlich aesthetischen Gewinn zu realisieren? Halte ich fuer etwas sehr ambitioniert ;)

Ich benutze zB regelmaessig fremde frameworks, oder schreibe mir kleine selbst, wo ich zB andere C/C++-libs benutze, aber dank der guten Bridge von pyobjc schnell wieder im Python-Land bin, statt mich mit C++-wrappings rumzuaergern. Oder weil ich die C-Performance punktuell benoetige.

Und deine Argumentation bezueglich der Nuetzlichkeit ist nicht richtig: es gibt eine Menge guter Gruende fuer Python statt Objective-C, genauso wie fuer PyQt/PySide statt purem Qt + C++.

ZB Garbage-collection, automatisches boxing/unboxing, Nutzung von leistungsstarken anderen Python-libs, einfacheres Implementieren von allem anderen *ausser* dem eigentlichen GUI/API-nutzenden Code usw.

Natuerlich gibt es Dinge, die zu vereinfachen sich lohnt - und pyobjc macht ja auch genau das. Man kann ja meistenteils einfach dictionaries und listen/tupel benutzen, wo sonst NS*-collections benutzt werden, und da funktionieren ja auch die in Python ueblichen Idiome wie Index-Zugriff usw.

Es ist natuerlich schoen zu hoeren, dass Apple sich da etwas relaxiert hat bezueglich Interpretern.
Antworten