statische Klassenvariable

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
MoonKid
User
Beiträge: 106
Registriert: Mittwoch 10. Dezember 2014, 16:24

Beziehe mich hier auf Python3.

Code: Alles auswählen

class Foo:
   PUBSUB_New = 'Foo.new'

Foo().PUBSUB_New
Ich versteh sehr wohl, warum die letzte Zeile tatsächlich funktioniert. Auch verstehe ich die Konsequenzen, wenn ich der Variablen in verschiedenen Foo-Instanzen Werte zuweisen würde.

Aus C++ kommend, wünsche ich mir eigentlich, dass diese letzte Zeile gar nicht funktioniert. Die Variable soll zu der Klasse gehören und nicht zu deren Instanzen. Und als kleines Sahnehäubchen, sollte es eine Konstante (also nicht veränderbar) sein.

In der Doku konnte ich dazu nur @staticmethod finden, was genau das mit Klassenmethoden macht. Für Variablen scheint es das aber nicht zu geben?

Hintergrund: Ich nutze pubsub zur Kommunikation zwischen Daten- und Präsentationsschicht. Die pubsub-Nachrichten-strings möchte ich als "Konstanten" (im Sinne von C++ gemeint) im jeweiligen Kontext (der dazugehörigen Klasse) hinterlegen. Aber auch unabhängig von diesem konkreten Anwendungsfall, würde mich eine Lösung bzw. der Python-Weg interessieren.
MoonKid
User
Beiträge: 106
Registriert: Mittwoch 10. Dezember 2014, 16:24

Das mit der Unveränderbarkeit liese sich so lösen?

Code: Alles auswählen

class Foo:
   @property
   def PUBSUB_New():
       return 'Foo.new'

Foo().PUBSUB_New
Problem ist hierbei jedoch, dass es nur auf Instanzebene funktioniert. In der Klasse ist das gar nicht greifbar.
Benutzeravatar
pillmuncher
User
Beiträge: 1532
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@MoonKid: Warum willst du immer C++ in Python programmieren? Programmier halt einfach C++, wenn dir das besser gefällt. Es muss ja nicht jedem alles gleich gut gefallen. Falls du Python weiter verwenden möchtest, solltest du jedoch anfangen, das Wort dynamisch in dem Satz Python ist eine dynamische Programmiersprache ernst zu nehmen. Ich hab es schon oft geschrieben: Python ist kein C++ mit weniger Klammern, sondern eher eine Art Lisp mit weniger Klammern. Wobei ich leider zugeben muss, dass ich am Anfang auch etwas gebraucht habe, bis ich die Konsequenzen davon verstanden hatte. Ich kam auch von statisch typisierten, undynamischen Sprachen wie C++, C# und Java (das war damals noch sehr undynamisch) und brauchte einige Zeit, bis ich mich von den dort geltenden Konventionen gelöst hatte. Seit ich es verstanden habe, habe ich allerdings nie mehr zurückgeblickt. Wenn ich jetzt daran denke, dass ich mal wieder was mit statischer Typisierung programmieren möchte, dann denke ich zB. an Haskell oder OCaml oder sowas.

Sehr geholfen hat mir damals das Python Cookbook, 2nd Ed. Inzwischen gibt es die 3rd Ed., aber ich habe noch nicht reingeschaut. Diese wurde von Dave Beazley besorgt, deswegen vermute ich, dass sie exzellent ist. Auch die Vorträge von Beazley auf Youtube sind alle hervorragend und zudem auch sehr lustig. Er ist mein Python-Held. Wenn du mal drei Stunden Zeit hast, schau dir das hier an: Python 3 Metaprogramming.

Zu Deiner Frage - schau dir mal das hier an:

Code: Alles auswählen

>>> class Foo:
...     x = 1
...
>>> Foo.x
1
>>> Foo.x = 2
>>> Foo.x
2
>>> f = Foo()
>>> f.x
2
>>> f.x = 3
>>> f.x
3
>>> g = Foo()
>>> g.x
2
>>> Foo.x
2
>>> Foo.x = 4
>>> g.x
4
>>> f.x
3
>>> del f.x
>>> f.x
4
Auf Klassenattribute kann man lesend nicht nur über die Klasse, sondern auch über deren Instanzen zugreifen. Schreibend kann man auf Klassenattribute nur über die Klasse zugreifen, aber nicht über die Instanzen. Manchmal verwendet man ein Klassenattribut als Defaultwert, das in einzelnen Instanzen mit einem Instanzattribut überschrieben (d.h.: überdeckt) werden kann, so wie ich das am Ende des Beispiels oben zeige.

Das alles ist ein Artefakt dessen, wie der Zugriff auf Methoden (die ja auch nur - zufällig aufrufbare - Klassenattribute sind) funktioniert. Guckstu:

Code: Alles auswählen

>>> class Bar:
...    def blubb(self, v):
...        print('-->', v)
...
>>> b = Bar()
>>> b.blubb(123)
--> 123
>>> b.blubb = lambda v: print('-->', 0)
>>> b.blubb(123)
--> 0
>>> c = Bar()
>>> c.blubb(123)
--> 123
Ein Attribut wird zuerst in der Instanz gesucht, und falls es dort nicht gefunden wird, in der Klasse, und falls es dort auch nicht gefunden wird, aufwärts entlang der mro-Liste, bis es entweder gefunden wird, oder nicht, woraufhin ein AttributeError fliegt:

Code: Alles auswählen

>>> class Base:
...     zig = 1
...
>>> class Derived(Base):
...     zag = 2
...
>>> b = Base()
>>> b.zig
1
>>> b.zag
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Base' object has no attribute 'zag'
>>> d = Derived()
>>> d.zig
1
>>> d.zag
2
>>> d.test
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Derived' object has no attribute 'test'
>>> Base.mro()
[<class '__main__.Base'>, <class 'object'>]
>>> Derived.mro()
[<class '__main__.Derived'>, <class '__main__.Base'>, <class 'object'>]
Interessanterweise ist mro() Ein Attribut der Metaklasse, deswegen kann man über die Instanz nicht darauf zugreifen:

Code: Alles auswählen

>>> b.mro()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Base' object has no attribute 'mro'
>>> d.mro()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Derived' object has no attribute 'mro'
Es ist völlig logisch und konsistent, dass das so sein muss, denn eine Instanz einer Klasse ist keine Instanz der Klasse einer Klasse - AKA Metaklasse:

Code: Alles auswählen

>>> class MyMeta(type):
...     jause = 1
...
>>> class MyClass(metaclass=MyMeta):
...     sause = 2
...
>>> mc = MyClass()
>>> mc.sause
2
>>> mc.jause
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute 'jause'
>>> MyClass.sause
2
>>> MyClass.jause
1
>>> type(Bar)
<class 'type'>
>>> type(MyClass)
<class '__main__.MyMeta'>
>>> type(mc)
<class '__main__.MyClass'>
Zuletzt geändert von pillmuncher am Freitag 20. Februar 2015, 05:19, insgesamt 2-mal geändert.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
pillmuncher
User
Beiträge: 1532
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Zur zweiten Frage: Properties leben in der Klasse, nicht in der Instanz. Wenn man ein Klassen-Property möchte, dann muss man das folglich in der Klasse der Klasse definieren:

Code: Alles auswählen

>>> class MyMeta(type):
...     def __init__(cls, cname, cbases, cdict):
...         cls._x = 456
...     @property
...     def x(cls):
...         return cls._x
...
>>> class MyClass(metaclass=MyMeta):
...     pass
...
>>> MyClass.x
456
>>> MyClass.x = 789
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Pythonischer wäre es allerdings, einfach ein Klassenattribut zu definieren und mit einem führenden Unterstrich zu benennen. Das zeigt dem Beutzer der Klasse an: Vorsicht, Implementierungsdetail, Benutzung auf eigene Gefahr. Oder man verwendet für den Namen durchgehend GROSSBUCHSTABEN, das bedeutet per Konvention: Bitte als Konstante betrachten und nicht überschreiben. Wenn der Benutzer man selbst ist, sollte man genügend Disziplin aufbringen können, sich an die selbst gegebenen Regeln zu halten - Special cases aren't special enough to break the rules. Wenn der Benutzer jemand anders ist, und sich dieser jemand gerne selber in den Fuß schießen möchte, dann muss er halt mit den Konsequenzen leben. In Python sind wir alle consenting adults, wie Guido es ausdrückt.
In specifications, Murphy's Law supersedes Ohm's.
Antworten