OOP Problem mit Zugriff auf Objekte

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
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

sma hat geschrieben:Eine Metaklasse kann aber genau wie bei Python mehr als ein Exemplar haben.
sicher? ich hab das anders gelernt... habe da aber auch nur verstaubtes halbwissen.

sma hat geschrieben: Bei Smalltalk könnten sich im Gegensatz zu Python übrigens Klassen ihre methodDictionaries teilen, etwas das Ruby braucht, um Module-Includes zu implementieren. Das geht in Python AFAIK nicht.
warum solte man denn sowas jetzt wieder brauchen?
(ausser evtl bei der implementierung der sprache)
sorry aber das lasse ich erst als "macht" gelten wenn du mir ein anwendungsbeispiel gibst :)
sma hat geschrieben: Daraus schließe ich somit, dass Smalltalk mächtiger ist. QED. Andere Manipulationen erlauben es, auf Sprachebene ohne die VM zu ändern, Traits oder Mehrfachvererbung einzubauen.
(trait=mixin?)
in py hab ich ja mehrfachvererbung. und kann damit auch einfach ein methodenbündel zu beliebigen klassen mixin-en. dass das hier weniger formal abläuft ist py nicht vorzuwerfen...
http://www.kinderpornos.info
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Dill hat geschrieben:
sma hat geschrieben:Eine Metaklasse kann aber genau wie bei Python mehr als ein Exemplar haben.
sicher? ich hab das anders gelernt... habe da aber auch nur verstaubtes halbwissen.
Ganz sicher. Ich habe schon mal eine Smalltalk-VM geschrieben.

Module benutzt man bei Ruby auch als Mixins. Und Mixins sind praktisch. So kann man sich in seine Klasse etwa das Modul "Enumerable" hineinmischen und bekommt dadurch 30 oder so praktische Collection-Methoden, wenn man selbst "each" implementiert. Damit ist dieses Modul eigentlich der Theorie nach ein Trait, ein parametrisierbares Set von Methoden. Nur das Ruby das informell per Modul-Kommentar regelt und das System dies nicht überprüft.

Ein import in Python funktioniert anders. Er kopiert Referenzen. Ändere ich zur Laufzeit ein Python-Modul, welches bereits importiert wurde, dann ändert sich an der Importstelle nichts mehr. Bei Ruby hingegen teilen sich alle Klassen, die ein Modul importiert haben, dieses und Änderungen an dem Ruby-Modul wirken sich sofort aus.

Bei Python gibt es auch gar kein Konzept, Methoden nachträglich in Klassen einzufügen, um so das Verhalten der Exemplare zu ändern. Man könnte an __bases__ fummeln, aber das hat einen sehr negativen Beigeschmack.

Mixin-Module braucht man, um Wiederverwendung zu implementieren. Vererbung gibt, das haben viele Jahre Erfahrung mit Smalltalk gezeigt, die leider in vielen Sprachen wie Java noch nicht angekommen sind, eine zu starke Bindung uns sollte möglichst immer vermieden werden.

Da die meisten Sprachen Vererbung und Subtypbildung gleichsetzen, brauchen sie Vererbung zum Aufbau einer Typhierarchie und erkennen nicht, dass dies eigentlich zwei unterschiedliche Konzepte sind. Die "Duck Typing"-Bewegung ist dabei, dass zu erkennen, spricht es aber IMHO nicht deutlich genug aus. Und ja, auch Javas Schnittstellen-Klassen (aka interfaces) sind ein Schritt in diese Richtung, allerdings denkt man erst jetzt darüber nach, dass es ja total toll wäre, wenn man auch strukturgleichen Klassen nachträglich noch Interfaces zuweisen könnte (interface injection als Teil des Davinci-VM-Projekts) - nach 10+ Jahren.

Und ja, man kann grob Traits=Mixins sagen und man kann mit Mehrfachvererbung wiederum beides implementieren. Manchmal ist aber weniger mehr und daher glaube ich, dass traits das überlegere Konzept sind.

Stefan
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Dill hat geschrieben:(trait=mixin?)
Je nachdem wie du schaust:

Aus Java-Sicht ist ein Trait wie ein partiell implementertes Interface, aus Python-Sicht ist ein Trait wie ein Mixin, dass bestimmte nicht-implementierte Methoden mit reinzieht (die dann implementiert werden müssen).
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
Dill
User
Beiträge: 470
Registriert: Mittwoch 10. Januar 2007, 14:52
Wohnort: Köln

ok bin überzeugt :)

Leonidas hat geschrieben:... aus Python-Sicht ist ein Trait wie ein Mixin, dass bestimmte nicht-implementierte Methoden mit reinzieht (die dann implementiert werden müssen).
was macht das in python für einen sinn nicht implementierte methoden in eine klasse zu ziehen? das ist dann nur ein vertrag, dass diese klasse diese methoden zu implementieren hat oder wie?
http://www.kinderpornos.info
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Ja, das ist nur ein Vertrag; den Python einfach nicht prüft.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Bei Smalltalk könnten sich im Gegensatz zu Python übrigens Klassen ihre methodDictionaries teilen, etwas das Ruby braucht, um Module-Includes zu implementieren. Das geht in Python AFAIK nicht.
Man kann __dict__ auf Klassen selbst manipulieren. Will man die Methoden einer Klasse auch schlicht auf eine andere kopieren, kann man sie als Funktionen definieren und dann explizit zuweisen, man kann aber auch alle Funktion über das "im_func" Attribut von Methoden extrahieren und types.MethodType benutzen um sich eine Methode für die eigene Klasse zu bauen.

Code: Alles auswählen

In [1]: from types import MethodType

In [3]: class A(object):
   ...:     def foo(self):
   ...:         print "Hallo!", self
   ...:         
   ...:         

In [4]: class B(object):
   ...:     foo = A.foo.im_func
   ...:     
   ...:     

In [5]: B.bar = A.foo.im_func

In [6]: class C(object):
   ...:     pass
   ...: 

In [7]: c = C()

In [8]: c.foo = MethodType(A.foo.im_func, c, C)

In [9]: A.foo()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)

/home/str1442/<ipython console> in <module>()

TypeError: unbound method foo() must be called with A instance as first argument (got nothing instead)

In [10]: A().foo()
Hallo! <__main__.A object at 0x9b190cc>

In [11]: B().foo()
Hallo! <__main__.B object at 0x9b190cc>

In [12]: B().bar()
Hallo! <__main__.B object at 0x9b190cc>

In [13]: c.foo()
Hallo! <__main__.C object at 0x9b1922c>
Will man irgendwelche Magie veranstalten, kann man __dict__, isinstance auf MethodType und andere Typen aus dem types Module oder ähnliches benutzen. Man könnte aus __dict__ sogar ein property machen und so die vollkommene Macht darüber bekommen, mag aber sein das man da ein paar Dinge bei beachten muss.
Ein import in Python funktioniert anders. Er kopiert Referenzen. Ändere ich zur Laufzeit ein Python-Modul, welches bereits importiert wurde, dann ändert sich an der Importstelle nichts mehr. Bei Ruby hingegen teilen sich alle Klassen, die ein Modul importiert haben, dieses und Änderungen an dem Ruby-Modul wirken sich sofort aus.
Natürlich ändert sich der Inhalt eines Python Modules überall, wo es benutzt wird. Ein Modul wird in sys.modules (ist ein dict) gespeichert und an sich ist ein Modul nur eine Instanz von types.ModuleType. Nur wenn ein richtiges Modul (also eine .py Datei) geändert wird, ändert sie sich nicht sofort, dann muss man erst reload() benutzen oder das Modul selbsständig aus sys.modules löschen und es neu importieren. Aber Python soll schließlich auch nicht die Dateien auf dem Rechner andauernd überwachen.
Bei Python gibt es auch gar kein Konzept, Methoden nachträglich in Klassen einzufügen, um so das Verhalten der Exemplare zu ändern.
Doch, siehe oben, Klassen händeln das automatisch, und zu speziellen Instanzen kann man sich auch Methoden generieren. Zusätzlich steht einem die ganze Magie von Metaklassen, Klassendekoratoren und "Metafunktionen" (Funktionen, die eine Klasse zurückgeben und "name, bases, dict" als Parameter annehmen, und somit als __metaclass__ spezifiert werden können - im Grunde das gleiche wie Klassendekoratoren, aber übernehmen zusätzlich die Konstruktion) zur Verfügung. An __bases__ rumzubasteln geht auch, ja - man kann sogar die Klasse eines Exemplars ändern:

Code: Alles auswählen

In [19]: class A(object):
   ....:     def foo(self):
   ....:         print self
   ....:         
   ....:         

In [20]: class B(object):
   ....:     def bar(self):
   ....:         print self
   ....:         
   ....:         

In [21]: a = A()

In [22]: print a.__class__, a.foo()
<class '__main__.A'> <__main__.A object at 0x9b63eec>
None

In [23]: a.__class__ = B

In [24]: print a.__class__, a.bar()
<class '__main__.B'> <__main__.B object at 0x9b63eec>
None
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

str1442 hat geschrieben:Man kann __dict__ auf Klassen selbst manipulieren.
Also mein Python 2.6.2 sagt da: AttributeError: attribute '__dict__' of 'type' objects is not writable

Ich bleibe daher dabei: Zwei Klassen können sich kein method dictionary teilen. Kopien will man ja gerade nicht, denn ansonsten hat man nicht die richtige Semantik.

Noch mal zu dem Unterschied zwischen Python und Ruby. Ein `include Bar` innerhalb einer Klasse Foo fügt konzeptionell alle Methoden aus Bar zu den existierenden Methoden von Foo hinzu. In Python würde man hier ein `from bar import *` innerhalb des Moduls `foo` machen. Auch dieses fügt alles aus bar konzeptionell zu foo hinzu. Bei Python wird hier kopiert. Ändert man später noch mal bar, ändert sich foo nicht. Bei Ruby eben nicht. Hier wird nämlich das dictionaries mit den Methoden von Bar in einer neuen Schattenklasse in die Vererbungshierarchie von Foo eingeklinkt. Ändert man Bar, ändert sich auch die Menge der Methoden, die Foo-Exemplaren aufrufen können.

Stefan
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

sma hat geschrieben:
str1442 hat geschrieben:Man kann __dict__ auf Klassen selbst manipulieren.
Also mein Python 2.6.2 sagt da: AttributeError: attribute '__dict__' of 'type' objects is not writable
Das was du willst, ginge aber mit Metaklassen(__metaclass__).
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Ein `include Bar` innerhalb einer Klasse Foo fügt konzeptionell alle Methoden aus Bar zu den existierenden Methoden von Foo hinzu. In Python würde man hier ein `from bar import *` innerhalb des Moduls `foo` machen.
Wenn man den Sternchen Import benutzt, wird nichts kopiert - es werden nur neue Referenzen auf Objekte gebildet. "from foobar import spam" ist das gleiche wie "import foobar; spam = foobar.spam; del foobar". Ändert man die Spam Referenz, zeigt sie auf ein anderes Objekt. Ändert man ihren Inhalt, ändert er sich überall. Das ist das normale Verhalten, welches Python überall an den Tag legt. Aber Module samt * Import sind nicht dazu gedacht, "Modulvererbung" irgendwie hinzubekommen. Will man alle Methoden einer anderen Klasse zu einer Klasse hinzufügen, kann man entweder diverse Magie benutzen oder einfach vererben.
Ich bleibe daher dabei: Zwei Klassen können sich kein method dictionary teilen. Kopien will man ja gerade nicht, denn ansonsten hat man nicht die richtige Semantik.
Also um sicher zu gehen: Du willst nur, das zwei Klassen die gleichen Methoden bekommen, dh sie beide die gleiche Funktionen bekommen und daraus eigenständig Methoden bauen? Die obigen Methoden von mir tun genau das. Und da wird auch nichts kopiert; Das im_func Attribut zeigt einfach nur auf eine Funktion und wenn ich es einer anderen Klasse gebe, nutzt die die gleiche Funktion (wrappt aber ein anderes MethodType Objekt darum). Und was spricht gegen soetwas?

Code: Alles auswählen

In [21]: def include(cls):
   ....:     def metafunc(name, bases, d):
   ....:         for attr in dir(cls):
   ....:             if isinstance(getattr(cls, attr), types.MethodType):
   ....:                 d[attr] = getattr(cls, attr).im_func
   ....:         return type(name, bases, d)
   ....:     return metafunc
   ....: 

In [22]: class A(object):
   ....:     def first(self):
   ....:         print 42
   ....:     def second(self):
   ....:         print "xyz"
   ....:         
   ....:         

In [24]: import types

In [25]: class B(object):
    __metaclass__ = include(A)
    def another(self):
        print self
   ....:         
   ....:         

In [29]: b = B()

In [30]: dir(b)
Out[30]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__metaclass__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__str__',
 '__weakref__',
 'another',
 'first',
 'second']

In [31]: b.first()
42

In [32]: b.second()
xyz

In [33]: b.another()
<__main__.B object at 0x876324c>
PS: Mit __dict__ auf Klassen meinte ich eigentlich, zb ein property aus dem __dict__ Attribut im Klassenkörper bei der Klassendefinition zu machen. Bei type gibt es aber sicher auch einen Weg, __dict__ zu beeinflussen.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Mein Punkt war und ist, dass Smalltalk ausdrucksstärker ist als Python, was angezweifelt wurde. Daher musste ich einen (beliebigen) Punkt nennen, der bei Smalltalk (oder Ruby) möglich ist, nicht aber bei Python. Bei Smalltalk können sich zwei Klassen ein Dictionary für Methoden teilen, bei Python habe ich nach wie vor keinen äquivalenten Code gesehen und glaube, es geht nicht, da das dict einer Klasse irgendwie speziell ist (dictproxy) den man nicht selbst setzen kann.

Die include()-Funktion von str1442 kopiert und ist damit kein Beispiel für das gemeinsame Benutzen eines dict.

Hier ist, was gehen muss:

Code: Alles auswählen

class A(object):
    def a(self): return 1
class B(object): pass
class C(object): pass

include(module=A, into=B)
include(module=A, into=C)

print B().a() # 1
print C().a() # 1
A.b = lambda self: 2
print B().b() # 2
del A.a
print C().a() # error
Stefan
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Achso - Bitte um Entschulding, da hatte ich "methodDictionaries" falsch aufgefasst. Nun, die einfachste Möglichkeit dafür bietet __setattr__ und __delattr__, ich nutze eine Closure:

Code: Alles auswählen

In [5]: def include(into=()):
   ...:     class MetaCls(type):
   ...:         def __setattr__(self, name, value):
   ...:             if isinstance(value, types.FunctionType):
   ...:                 for cls in into:
   ...:                     setattr(cls, name, value)
   ...:             super(MetaCls, self).__setattr__(name, value)
   ...:         def __delattr__(self, name):
   ...:             if isinstance(getattr(self, name), types.MethodType):
   ...:                 for cls in into:
   ...:                     delattr(cls, name)
   ...:             super(MetaCls, self).__delattr__(name)
   ...:     return MetaCls
   ...: 

In [7]: import types

In [8]: class A(object):
   ...:     pass
   ...: 

In [9]: class B(object):
   ...:     pass
   ...: 

In [10]: class C(object):
   ....:     __metaclass__ = include((A, B))
   ....:     pass
   ....: 

In [11]: C.a = lambda self: 42

In [12]: print A().a()
42

In [13]: print B().a()
42

In [14]: print C().a()
42

In [15]: del C.a

In [16]: print A().a()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)

/home/str1442/<ipython console> in <module>()

AttributeError: 'A' object has no attribute 'a'

In [17]: print B().a()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)

/home/str1442/<ipython console> in <module>()

AttributeError: 'B' object has no attribute 'a'

In [18]: print C().a()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)

/home/str1442/<ipython console> in <module>()

AttributeError: 'C' object has no attribute 'a'

In [19]: def greet(self):
   ....:     print "Greet from: ", self
   ....:     
   ....:     

In [20]: C.greet = greet

In [21]: A().greet()
Greet from:  <__main__.A object at 0x929ff8c>

In [22]: B().greet()
Greet from:  <__main__.B object at 0x929ff8c>

In [23]: C().greet()
Greet from:  <__main__.C object at 0x95f1c0c>
Wahlweise dann natürlich mit Initialisierung von bereits vorhandenen Methoden. Ob man __dict__ in Metaklassen genauso manipulieren kann, wie __dict__ einer Instanz, weiß ich nicht - naive Versuche scheitern. Aber da man sich Metaklassen selbst zusammenbauen kann wie man will, bin ich zuversichtlich, das es da sicherlich einen Weg gibt (Eventuell über die Metaklasse der Metaklasse ;)). Manipulationen an __dict__ funktionieren mit __get/set/delattr__ aber doch recht gut (die Möglichkeit hätte ich sowieso vorher in Betracht ziehen sollen).
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Eine Klasse in Smalltalk hat ein dictionary - das method dictionary - wo eben die Methoden drin stecken. Das entspricht in etwa dem `__dict__` einer Klasse in Python. Dort stecken allerdings eigentlich beliebige Objekte drin und Funktionen, die im Kontext eines Exemplars von dort ausgelesen werden, verwandeln sich auf "magische" Weise in gebundene Methoden.

Das Beispiel verstehe ich allerdings immer noch nicht. Du kopierst ja immer noch. Wie häufig noch muss ich sagen, dass es gerade darum geht, dass man nicht kopieren will/soll/braucht.

Normalfall in Smalltalk:

Code: Alles auswählen

    +-------+
    | Class |
    |   *--------> +------+
    +-------+      | Dict |
        ^          |  *-------> +--------+
        |          +------+     | Method |
        |                       +--------+
    +-------+
    | Class |
    |   *-------> ...
    +-------+
Jede Klasse (also jedes Exemplar einer Metaklasse) hat eine Exemplarvariable `methodDict`, in dem ein Exemplar von `MethodDictionary` (Unterklasse von `Dictionary`) steckt, das `CompiledMethod`-Exemplare unter Namen (Exemplare von `Symbol`) abgelegt hat. Die Klasse hat eine weitere Exemplarvariable `superclass`, die die Oberklasse enthält.

Da die method dictionaries eigenständige Objekte sind, kann ich jetzt dies machen:

Code: Alles auswählen

                +------+
                |ClassC|
                +------+
       ^            |          ^
       |            v          |
    +------+   +-------+   +------+
    |Vrtual|-->| MDict |<--|Vrtual|
    +------+   +-------+   +------+
       ^                       ^
       |                       |
    +------+               +------+  
    |ClassA|               |ClassB|  
    +------+               +------+
Und so funktionieren Mixin-Module bei Ruby.

Stefan
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Mein Beispiel tut genau das, was du erreichen wolltest - Ich registriere mit include() mehrere Klassen und diese bekommen sofort sämtliche Methoden der "Server Klasse" übertragen bei Änderung.

Ja, es ist kein einzelnes, für alle Klassen geltendes Dict. Das braucht man zur Lösung des Problems aber auch nicht. Da der Namensraum eines beliebigen Objektes in Python aber sowieso in __dict__ gespeichert wird und somit Zuweisungen zu Objekten mittels dem "=" Operator nur "syntactic Sugar" dafür sind, überwacht meine MetaCls ob bestimmte Funktionen gesetzt werden oder Methoden gelöscht werden und passt die anderen Objekte entsprechend an. Da aber, wie du ja auch schon sagst, __dict__ von type() Objekten speziell zu sein scheint, kann man es so direkt (auf type() Exemplaren und nur dort) nicht anpassen. Ich vermute aber stark, daß man dieses Verhalten irgendwie mittels einer Subklasse von type anpassen kann.

Da du ja explizt erwähnst, daß in diesem dict in Smalltalk nur Methoden gespeichert werden, ist es so direkt mit __dict__ sowieso nicht vergleichbar. Will man also unbedingt eine Lösung mit einem Methoden-Wörterbuch haben, könnte man das in einer eigenen Metaklasse selbst definieren (ala methods_dict Attribute) und __setattr__ und __delattr__ nutzen, um eben dann eben dieses Dict zu beeinflussen. Dann könnte man einfach alle Dicts unter den Klassen aufteilen.

Ich sehe da aber nicht besonders viel Sinn darin. Es geht doch gar nicht darum, ob man nun genau wie in Smalltalk solche Method Dictionaries einfach implementieren kann; Ähnliches Verhalten wird doch mit __set und delattr__ auch erreicht. Insofern ist das kein Argument für eine höhere Ausdrucksstärke Smalltalks, im Endeffekt kann man sich mittels Metaklassen und __setattr__ usw oder komplett eigenen Definitionen und Attributen alles zurechtbasteln.

Zu meinen Beispiel: Kopiert wird da nichts. Die Funktionen bleiben überall gleich. Es wird nur explizit überprüft, ob alle zu überwachenden Klassen besitimmte Methoden kennen und diese gegebenenfalls gelöscht bzw Funktionen überall eingetragen. Mein Beispiel sollte nur aufzeigen, wie man das von dir geforderte Verhalten einfach nachbilden kann.

Auf die Gefahr hin mich zu wiederzuholen (ist schon spät): Würde ich nun diesen Mechanismus genauso nachbauen wollen, würde ich eine Subklasse von type erstellen, welche ein method_dict Dictionary bekommt und mittels __setattr__ und __delattr__ alle gefundenen Funktionen in dieses Dict einträgt. Dann könnte man einfach die Dictionaries zu einem verbinden. Im Endeffekt käme aber nichts anderes bei heraus, wie das, was ich in meinen letzten Posting aufzeigte. Vielleicht morgen.
Antworten