instanzmethode kann nicht auf liste zugreifen

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.
Benutzeravatar
pillmuncher
User
Beiträge: 1531
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

BlackJack hat geschrieben:...BCPL...
Das ist übrigens ein Akronym für Before C Programming Language...
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pillmuncher
User
Beiträge: 1531
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Das IMO stäkste Argument gegen die Ungarsiche Notation und ihre Verwandten ist ein ökononisches. Wenn ich etwas programmiere, sollte ich möglichst viele Optionen offenlassen, damit ich in der Zukunft möglichst wenige von meinen Festlegungen revidieren muss. The Zen of Python fasst das zusammen:

Now is better than never.
Although never is often better than *right* now


Wenn ich etwas nicht programmieren muss, habe ich Zeit und damit Geld gespart, von den Nerven ganz zu schweigen. Und wenn ich etwas, dass ich gebaut habe, nicht ändern muss, habe ich ebenfalls Geld & Zeit & Nerven gespart.

Ein Beispiel: Angenommen, ich muss in meinem Programm Daten in Bäumen verwalten, dann könnte ich in jedem Eltern-Knoten eine Liste mit Kindern verwenden:

Code: Alles auswählen

class ParentNode(object):
    def __init__(self, ...):
        self.nodeList = []
    ...
Ich gehe davon aus, dass nodeList an vielen Stellen im Programm verwendet wird. Irgendwann kommt als Anforderung an mein Programm hinzu, dass die Kinder eines Eltern-Knotens eindeutig sein müssen, dass also ein Kind-Knoten nicht zweimal in derselben nodeList enthalten sein darf. Dann könnte ich das zB. so machen:

Code: Alles auswählen

class ParentNode(object):
    def __init__(self, ...):
        self.nodeList = set()
    ...
Jetzt habe ich das Problem, dass nodeList eigentlich nodeSet heißen müsste. Ich muss also den gesamten Code durcharbeiten um den Namen zu ändern. Natürlich muss ich dann auch alle Aufrufe von nodeList.append(...) in nodeSet.add(...) ändern, alle Stellen, wo nodeSet sortiert wird rausschmeißen (weil man sets bekanntermaßen nicht sortieren kann), und viele weitere Umstellungen vornehmen. Klar kann man jetzt sagen, dass das eben in der Natur der Sache liegt, wenn man derlei Änderungen in den Datenstrukturen vornimmt. Man hätte eben von Anfang an nodeList privat machen müssen und alle Operationen darauf einkapseln sollen, also zB.

Code: Alles auswählen

class ParentNode(object):
    def __init__(self, ...):
        self._nodeList = []
    def append(self, childObj):
        self._nodeList.append(childObj)
    ...
Wenn man hier ein set einbauen würde, bekäme man etwa:

Code: Alles auswählen

class ParentNode(object):
    def __init__(self, ...):
        self._nodeSet = set()
    def append(self, childObj):
        self._nodeSet.add(childObj)
    ...
und das öffentliche Interface (und damit der Client-Code) könnte zu großen Teilen unverändert bleiben. Leider ist das sehr java'esque und nicht eben pythonisch. Das Problem dabei ist, dass ich den Code mit Gettern, Settern und anderen Kapselungs-Methoden zukleistern muss, und die eigentliche Programm-Logik verschwindet unter diesem ganzen Lärm. Willkommen in der Java-Welt :twisted:.

Natürlich könnte man mich jetzt darauf hinweisen, dass ich gar nicht mehr von Namensgebung, sondern von etwas ganz anderem rede, dass also meine Argumentation den in Frage stehenden Punkt gar nicht mehr berührt. Das hielte ich allerdings für voreilig. Denn hier offenbart sich IMO die versteckte Überzeugung, dass Programme aus Operationen bestehen, die mehr oder weniger basale Datenstrukturen erzeugen und sie modifizieren. Dann erscheint es auch sinnvoll, sich in der Namensgebung auf diese Strukturen (bzw. deren konkrete Typen) zu beziehen. Objektorientierung wird dabei aufgefasst als Bündelungsmechanismus, der solche Datenstrukturen mit den zu ihnen passenden Operationen assoziiert. Oft wird man dann Code finden, der in etwa so aussieht:

Code: Alles auswählen

class ParentNode(object):
    ...
    def foo(self):
        ... # foo'ing parent
    def bar(self):
        ... # bar'ing parent

class ChildNode(object):
    ...
    def foo(self):
        ... # foo'ing child
    def bar(self):
        ... # bar'ing child

...
tree = ...
stack = [tree]
while stack:
    node = stack.pop()
    node.foo()
    stack.extend(node.getChildren())
...
stack = [tree]
while stack:
    ...
    node.bar()
    ...
Abgesehen davon, dass man jedesmal den Traversierungs-Mechanismus neu programmieren muss (was regelmäßig die beliebte copy & paste Programmiererei nach sich ziehen wird), muss jede neue Art von Operation in alle Knoten-Klassen eingebaut werden. Alles muss immer wieder angefasst werden und man muss immer wissen, welche Datenstrukturen allem zugrunde liegen. Man programmiert also gegen eine konkrete Implementation, und nicht, wie von der OO gefordert, gegen ein Interface. Dann scheint es wirklich nützlich sein, wenn man möglichst viele Informationen in den Namen codiert hat, damit man nicht bei jeder Variablen nachschauen muss, welchen Typ das Objekt hat, das sie referenziert. Hoffentlich ist das dann auch der tatsächliche Typ, denn Python-Variablen können ja beliebige Objekte referenzieren, und es gibt keine Garantien, dass nicht jemand irgendwas anderes zugewiesen hat:

Code: Alles auswählen

myStr = 7
myList = datetime.time()
Das nur nebenbei.

Für mich besteht OO nicht in erster Linie darin, Daten und Operationen zu bündeln, sondern starre Kopplungen zu vermeiden. Dazu könnte man im vorliegenden Fall einen double-dispatch-Mechanismus verwenden:

Code: Alles auswählen

class ParentNode(object):
    ...
    def accept(self, other):
        other.preorder_parent(self)
        for each in self._children: # kann ein set, eine list, oder irgendwas anderes sein,
            each.accept(other)      # der Witz ist, dass niemand außer ParentNode das wissen muss
        other.postorder_parent(self)

class ChildNode(object):
     def accept(self, other):
        other.preorder_child(self)
        other.postorder_child(self)

class PrettyPrinter(object):
    def __init__(self):
        self.indent = 0
    def preorder_parent(self, node)
        self.indent += 1
        # print parent node pretty
    def postorder_parent(self, node):
        self.indent -= 1
    def preorder_child(self, node):
        # print child node pretty
    def postorder_child(self, node):
        pass

class SomeOtherTreeOperation(object):
    def preorder_parent(self, node):
        ...
    ...

...

root = ...
root.accept(PrettyPrinter())
Dadurch wird der Client-Code weitgehend unabhängig von irgendwelchen Datenstrukturen im Baum. Auch die Traversierung fällt nicht mehr in den Aufgabenbereich des Clients. Natürlich müssen Kind-Knoten immer noch irgendwie erzeugt und an den richtigen Stellen in den Baum eingefügt werden, aber dafür wäre eine "invertierte" Lösung besser:

Code: Alles auswählen

class ParentNode(object):
    ...
    def create_child(self, *params):
        self._my_weird_data_structure.insert(self.create_node(*params))
    def create_node(*params)
        ... # node erzeugen mit *params
...

parent = ... # get parent from somewhere
parent.create_child(...)
Der Client-Code muss sich so nicht mehr um die Interna der Klassen kümmern, auf denen er operiert.

Letztlich laufen diese länglichen Ausführungen darauf hinaus, nicht, wie in Java üblich, Datenstrukturen einzukapseln, sondern statt dessen Funktionalität. Dadurch macht man sich unabhängig von konkreten Datenstrukturen, und damit verschwindet auch die vermeintliche Notwendigkeit, sich in der Benennung auf konkrete Datentypen zu beziehen. Ich habe einige Freiheitsgrade dazugewonnen, denn ich kann soz. ceteris paribus programmieren, d.h., ich kann etwas ändern oder etwas neues einbauen, und alles andere bleibt gleich. Und ich habe den Geltungsbereich konkreter Namen soweit eingeschränkt, dass es auch dann noch übersichtlich bleibt, wenn ich self._children schreibe statt self._childAr/List/Set/... .
In specifications, Murphy's Law supersedes Ohm's.
Antworten