Einige Fragen zu Klassen

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
Kefflin
User
Beiträge: 2
Registriert: Dienstag 20. Februar 2007, 18:54

Dienstag 20. Februar 2007, 19:22

Hi Leute,

erstmal hallo :D

Ich komme aus der PHP5/Java Ecke und möchte gerne Python lernen. Einige Sachen erscheinen mir jedoch fremdartig:

1. Kann es sein, dass es keine geschützten Attribute in Klassen gibt (Private, Protected)?

2. Irgendwo im Internet habe ich gelesen, dass dies mit Unterstrichen gemacht wird. Kann es sein das dies nur ein Styleguide ist?

3. In Pythoncode, den ich studiere, sehe ich öfters solche Konstrukte:

Code: Alles auswählen

class Foo:
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Warum wird das nicht so gemacht, dass name im Kopf der Klasse steht.

Code: Alles auswählen

class Foo:
    name = ""
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name

4. Wenn ich eine Klasse Foo habe, die ich in einem anderen Script verwenden will, mache ich das so:

Code: Alles auswählen

import Foo

foo = Foo.Foo()

Das ist aber unschön, geht das anders?



MFG

Kefflin
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Dienstag 20. Februar 2007, 19:42

Hi und willkommen im Forum.
Kefflin hat geschrieben:[...]
1. Kann es sein, dass es keine geschützten Attribute in Klassen gibt (Private, Protected)?
Korrekt. Sowas wie Private und Protected gibt es nicht in python wie in C/C++/PHP/Java...Wir gehen alle davon aus das wir schon Wissen was wir tun und müssen uns nicht bevormunden Lassen. Schließlich sind wir alle Erwachsen. Soviel zu Grundhaltung von OOP in Python.
Kefflin hat geschrieben:[...]
2. Irgendwo im Internet habe ich gelesen, dass dies mit Unterstrichen gemacht wird. Kann es sein das dies nur ein Styleguide ist?
Jepp. Das ist nur ein Styleguide. Man kann auf Methoden und Attribute mit einem _ oder __ weiterhin von außen zugreifen. Wie du schon erkant hast ist es nur ein Styleguide den viele Pythonisten nutzen um anderen zu signalisieren "Halt, Das ist nur für intern gedacht. Nutze das nur wenn Du genau weißt was du da machst."
Kefflin hat geschrieben:[...]
4. Wenn ich eine Klasse Foo habe, die ich in einem anderen Script verwenden will, mache ich das so:

Code: Alles auswählen

import Foo

foo = Foo.Foo()

Das ist aber unschön, geht das anders?
``from Foo import Foo``


Weiterhin ist es sehr unpythonisch getter und setter für Attribute zu nutzen. Man greift für gewöhnlich direkt darauf zu von außen. Falls man möchte das bei einem zugriff auf ein Atribut noch irgendwas spezielles im internen der Klasse passiert, macht realisiert man sowas mit propertys.

lg
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Dienstag 20. Februar 2007, 19:43

Benutzeravatar
mq
User
Beiträge: 124
Registriert: Samstag 1. Januar 2005, 19:14

Dienstag 20. Februar 2007, 19:49

Moin und willkommen im Forum :)

Ich versuche mal, deine Fragen der Reihe nach durchzugehen.
Kefflin hat geschrieben:1. Kann es sein, dass es keine geschützten Attribute in Klassen gibt (Private, Protected)?

2. Irgendwo im Internet habe ich gelesen, dass dies mit Unterstrichen gemacht wird. Kann es sein das dies nur ein Styleguide ist?
Exakt. In Python gibt es sowas wie private nicht, man vertraut darauf, dass der Programmierer weiss, was er tut. Die Konvention ist, wie du schon richtig gelesen hast, Sachen, die man in anderen Sprachen als "private" deklarieren wuerde, mit einem Unterstrich am Anfang zu benennen, z.B. _methode. Das hindert zwar niemandem am Zugriff darauf, aber die Leute wissen, dass sie es besser nicht anfassen.

3. In Pythoncode, den ich studiere, sehe ich öfters solche Konstrukte:

Code: Alles auswählen

class Foo:
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Warum wird das nicht so gemacht, dass name im Kopf der Klasse steht.

Code: Alles auswählen

class Foo:
    name = ""
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Das hat eine etwas andere semantische Bedeutung. Variablen initialisiert man fuer gewoehnlich halt im Konstruktor (wodurch man das z.B. auch dynamisch machen kann). Dadurch, dass es keine Deklarationen gibt, entfaellt eine Uebersicht aller Instanzvariablen, wie du sie aus Java gewohnt bist (was du vermutlich versuchst, damit zu erreichen, die variablen in den Kopf zu schreiben). Meistens stehen die aber alle im Konstruktor drin.

4. Wenn ich eine Klasse Foo habe, die ich in einem anderen Script verwenden will, mache ich das so:

Code: Alles auswählen

import Foo

foo = Foo.Foo()

Das ist aber unschön, geht das anders?
Ja, du kannst auch

Code: Alles auswählen

from Foo import Foo
foo = Foo()
machen. In Python ist es allerdings nicht so wie in Java, dass man eine Datei fuer eine Klasse hat, die dazu noch den gleichen Namen wie die Klasse hat. Stell dir ein Modul mehr wie ein Java-Package vor, das eine ganze Sammlung von Klassen enthaelt.


Eine wichtige Erkenntnis ist, dass sie die Objektmodelle von Python und Java stark unterscheiden, Python ist da (wir eigentlich ueberall) deutlich dynamischer.

Ich hoffe, ich konnte helfen.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Dienstag 20. Februar 2007, 19:51

Kefflin hat geschrieben:[...]
Warum wird das nicht so gemacht, dass name im Kopf der Klasse steht.

Code: Alles auswählen

class Foo:
    name = ""
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Weil

Code: Alles auswählen

class Foo:
    name = ""
!=

Code: Alles auswählen

class Foo:
    def __init__(self, name):
        self.name = name
Ersteres ist ein Klassenattribut das sich *alle* Instanzen teilen. Das heißt wenn du ``name`` in irgendeiner Instanz änderst betrifft diese Änderung *alle* Instanzen. Das zweite ist ein Instanzattribut. Jede Instanz bekommt eine eigene Version von ``name`` bei der Instantiierung.

lg
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Dienstag 20. Februar 2007, 20:50

lumax hat geschrieben:
3. In Pythoncode, den ich studiere, sehe ich öfters solche Konstrukte:

Code: Alles auswählen

class Foo:
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Warum wird das nicht so gemacht, dass name im Kopf der Klasse steht.

Code: Alles auswählen

class Foo:
    name = ""
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Das hat eine etwas andere semantische Bedeutung. Variablen initialisiert man fuer gewoehnlich halt im Konstruktor (wodurch man das z.B. auch dynamisch machen kann). Dadurch, dass es keine Deklarationen gibt, entfaellt eine Uebersicht aller Instanzvariablen, wie du sie aus Java gewohnt bist (was du vermutlich versuchst, damit zu erreichen, die variablen in den Kopf zu schreiben). Meistens stehen die aber alle im Konstruktor drin.
Nein das ist so nicht ganz richtig und erklärt nicht warum viele Instanzattribute nutzen. Die Entscheidung ob man eine Klassenattribut nutzt oder ein Intanzattribut, wird danach entschieden ob jede Instanz eine eigene Version von ``bar`` bekommen soll oder nicht.

Eine Zuweisung eines Wertes an eine Attribut, *bei der Erzeugung einer Instanz*, wird immer im Konstruktor gemacht unabhängig von der Sprache ;) -- Der Unterscheid bei C++ und Python ist das man instanzattribute in Header schreibt und nicht im Konstruktor. *Aber* wenn man bei der instanzbildung ``bar`` mit einen Wert belegen will, so muss man auch in C++ das im Konstruktor erledigen.

C++ - Klassenattribute:
Beispiel: In C++ werden Klassenattribuete (dort Statische Elemenvariabeln genant) so definiert:

Code: Alles auswählen

class Foo {
    pubilc:
    static int instanz_counter;
}
Es wird also ein ``static`` davorgehängt. All Instanzen von ``Foo`` teilen sich nun ``instanz_counter``. Die Weiteren Zuweisung findet aber auch im Konstruktor statt:

Code: Alles auswählen

class Foo {
    pubilc:
    static int instanz_counter;
    Foo() {instanz_counter++}
    Foo() {instanz_counter--}
}
// Defaultwert! -- Weil man in der Klassendefinition nicht ``static int instanz_counter = 0`` schreiben kann (im Gegensatz zu Python). 
// Das heißt Nur Deklaration sind erlaubt, da man die Initialisierung im Konstruktor tätigen muss :rool:
int Demo::instanz_counter = 0 
Man könnte also auch sowas machen:

Code: Alles auswählen

class Foo {
    pubilc:
    static int bar;
    Foo(int bar_value) {bar = bar_value}
    Foo() {bar = 0}
}
Wie man sieht wird hier ``bar`` auch im Konstruktor Initialisiert.
Python Klassenattribute:
In Python werden Klassenattribuete so definiert:

Code: Alles auswählen

class Foo(object):
    instanz_counter = 0
All Instanzen von ``Foo`` teilen sich nun ``instanz_counter``. Die Weiteren Zuweisung findet im Konstruktor statt:

Code: Alles auswählen

class Foo(object):
    instanz_counter = 0
    def __init__(self):
        instanz_counter += 1
    def __del__(self):
        instanz_counter -= 1
Man könnte also auch sowas machen:

Code: Alles auswählen

class Foo(object):
    bar = 0
    def __init__(self, bar_value):
        Foo.bar = bar_value
    def __del__(self):
        Foo.bar = 0

C++ Instanzattribute:
In C++ werden instanzattribute (also jene von den jede Instanz ihre eigene Version bekommt) **auch** im Header definiert. Das ist der große unterschied zu python (siehe unten).

Code: Alles auswählen

class Foo {
    pubilc:
    int bar;
}
Was hier auffällt ist dass das ``static`` nicht davor ist und somit `` bar`` zu einem Instanzattribut wird und nicht zu einem Klassenattribut.

Auch hier wird die eigentliche Initialisierungen im Konstruktor getätigt:

Code: Alles auswählen

class Foo {
    pubilc:
    int bar;
    Foo(int bar_value): {bar = bar_value} // EDIT: Hatte ich vertauscht gehabt!
}
Python Instanzattribute:
So in Python ist ein großer unterschied. Instanzattribute werden nicht im Header definiert sondern direkt im Konstruktor mit einem ``self.`` davor.:

Code: Alles auswählen

class Foo(object):
    def __init(self)
        self.bar = 0

Initialisierungen mit einem Übergebenen Parameter an dem Konstruktor:

Code: Alles auswählen

class Foo(object):
    def __init(self, bar_value)
        self.bar = bar_value

Zusammengefasst:
C++:
  • Klassenattribute (Elemenvariabeln): Werden im Header mit einem ``static`` davor deklariert (-> static int bar;)
  • Instanzattribute: Werden ebenfalls im Header, aber ohne ``static`` davor, deklariert.
Python:
  • Klassenattribute: Werden im Header definiert.
  • Instanzattribute: Werden nicht im Header definiert, sondern direkt im Konstruktor mit einem self gefolgt von einem Punkt. (-> self.bar = 0)
Ich hoffe der unterschied ist nun klar geworden. Falls ich irgendwo ein Fehler in der Verwendung der Begriffe gemacht habe, oder sonstiges, dann bitte klar stellen.

lg
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Dienstag 20. Februar 2007, 21:37

sape hat geschrieben:
lumax hat geschrieben:
3. In Pythoncode, den ich studiere, sehe ich öfters solche Konstrukte:

Code: Alles auswählen

class Foo:
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Warum wird das nicht so gemacht, dass name im Kopf der Klasse steht.

Code: Alles auswählen

class Foo:
    name = ""
    def __init__(self, name):
        self.name = name
    def info(self):
        print "Ich bin: " + self.name
Das hat eine etwas andere semantische Bedeutung. Variablen initialisiert man fuer gewoehnlich halt im Konstruktor (wodurch man das z.B. auch dynamisch machen kann). Dadurch, dass es keine Deklarationen gibt, entfaellt eine Uebersicht aller Instanzvariablen, wie du sie aus Java gewohnt bist (was du vermutlich versuchst, damit zu erreichen, die variablen in den Kopf zu schreiben). Meistens stehen die aber alle im Konstruktor drin.
Nein das ist so nicht ganz richtig und erklärt nicht warum viele Instanzattribute nutzen.
Ich denke, hier liegt ein Missverständnis vor. Grundsätzlich ist es möglich, "name" sowohl als Klassen- als auch als Instanzattribut zu verwenden. In diesem Fall fungiert das Klassenattribut, falls das Instanzattribut nicht explizit gesetzt wird, als "Instanzattribut-Default".

Ein Beispiel:

Code: Alles auswählen

>>> class Foo:
...     bar = 5

>>> a = Foo()
>>> b = Foo()
>>> c = Foo()

>>> a.bar, b.bar, c.bar
5, 5, 5

>>> b.bar = 10
>>> a.bar, b.bar, c.bar
5, 10, 5
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Dienstag 20. Februar 2007, 21:54

birkenfeld hat geschrieben: Ein Beispiel:

Code: Alles auswählen

>>> class Foo:
...     bar = 5

>>> a = Foo()
>>> b = Foo()
>>> c = Foo()

>>> a.bar, b.bar, c.bar
5, 5, 5

>>> b.bar = 10
>>> a.bar, b.bar, c.bar
5, 10, 5
Stimmt, das ist ein wenig strange. Aber das ist nur wenn man ``bar`` über eine Instaz von ``Foo`` einen "neun" Wert zuweisen will. Da wird dann nicht die Klassevariable verändert sonder eine Instanzvariabel erzeugt (Falls nicht vorhanden) -- IMHO inkonsistente.

Wenn man aber direkt ``bar`` mit ``Foo`` ändert wird die Klassenvariale geändert:

Code: Alles auswählen

class Foo:
    bar = 5
Foo.bar = 10

a = Foo()
b = Foo()
c = Foo()
print a.bar, b.bar, c.bar
Naja, wenn man das über die Instanz macht kommt sowas wie in deinem Beispiel raus. Ziemlich verwirrend. Weist du ob das vielleicht in Py3K überarbeitet wird?


birkenfeld hat geschrieben:In diesem Fall fungiert das Klassenattribut, falls das Instanzattribut nicht explizit gesetzt wird, als "Instanzattribut-Default".
Eigentlich wird das Kalssenattribut durch das Instazattribut überdeckt. -- Ok, das ist natürlich pingelig betrachtet aber der genaue interne Ablauf.

Code: Alles auswählen

class Foo(object):
    bar = "Klassenattribut"
        
    @property
    def bars(self):
        return "%s - %s" % (Foo.bar, self.bar)

b1 = Foo()
b2 = Foo()
b1.bar = 1
b2.bar = 2
print b1.bar
print b2.bar
print
print b1.bars
print b2.bars

Code: Alles auswählen

1
2

Klassenattribut - 1
Klassenattribut - 2
Hier sieht man das dass Klassenattribut ``bar`` weiterhin existiert aber nach außen hin von dem Instanzattribut ``bar`` überdeckt wird.

EDIT: Korrektur.
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Dienstag 20. Februar 2007, 22:02

sape hat geschrieben: Stimmt, das ist ein wenig strange. Aber das ist nur wenn man ``bar`` über eine Instaz von ``Foo`` einen "neun" Wert zuweisen will. Da wird dann nicht die Klassevariable verändert sonder eine Instanzvariabel erzeugt (Falls nicht vorhanden) -- IMHO inkonsistente.
Nein, alles ist konsistent. Du musst Attributzugriff und Attributzuweisung unterschieden. Bei Zugriff wird zuerst im Namespace der Instanz gesucht, dann im Namespace der Klasse, der Oberklassen etc. Erst wenn da nichts gefunden wurden, gibt es den AttributeError.

Zuweisung dagegen erfolgt immer im Namespace der Instanz. D.h.

Code: Alles auswählen

class Foo:
    bar = 5
a = Foo()
a.bar ist 5. a hat im Instanznamespace keinen Eintrag zu "bar".

Code: Alles auswählen

a.bar = 10
a.bar ist nun 10, und im Instanznamespace steht "bar" -> 10. Alle Zugriffe auf "a.bar" liefern ab jetzt das Objekt aus dem Instanznamespace zurück.
birkenfeld hat geschrieben:In diesem Fall fungiert das Klassenattribut, falls das Instanzattribut nicht explizit gesetzt wird, als "Instanzattribut-Default".
Eigentlich wird das Kalssenattribut durch das Instazattribut überdeckt. -- Ok, das ist natürlich pingelig betrachtet aber der genaue interne ablauft.
Ganz richtig, das wollte ich mit dem verunglückten "Default" aussagen.
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Benutzeravatar
Luzandro
User
Beiträge: 87
Registriert: Freitag 21. April 2006, 17:03

Mittwoch 21. Februar 2007, 09:12

sape hat geschrieben:Jepp. Das ist nur ein Styleguide. Man kann auf Methoden und Attribute mit einem _ oder __ weiterhin von außen zugreifen.
Wobei bei 2 Unterstrichen der Name des Attributes geändert wird und man nicht direkt mit dem eigentlichen Namen darauf zugreifen kann, aber was der Sinn davon ist weiß ich eigentl. auch nicht

Code: Alles auswählen

>>> class Foo:
...     __bar = "narf"
...
>>> Foo.__bar
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: class Foo has no attribute '__bar'
>>> Foo._Foo__bar
'narf'
[url=http://www.leckse.net/artikel/meta/profilieren]Profilieren im Netz leicht gemacht[/url]
EyDu
User
Beiträge: 4871
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Mittwoch 21. Februar 2007, 09:25

Luzandro hat geschrieben:Wobei bei 2 Unterstrichen der Name des Attributes geändert wird und man nicht direkt mit dem eigentlichen Namen darauf zugreifen kann, aber was der Sinn davon ist weiß ich eigentl. auch nicht
Das nennt sich "name-mangling" und wird benötigt um Namenskonflikte zu beseitigen. Als Stichwort nenne ich mal Mehrfachvererbung. In der Doku sollten sich genug Informationen finden lassen.
lunar

Mittwoch 21. Februar 2007, 09:25

Luzandro hat geschrieben:
sape hat geschrieben:Jepp. Das ist nur ein Styleguide. Man kann auf Methoden und Attribute mit einem _ oder __ weiterhin von außen zugreifen.
Wobei bei 2 Unterstrichen der Name des Attributes geändert wird und man nicht direkt mit dem eigentlichen Namen darauf zugreifen kann, aber was der Sinn davon ist weiß ich eigentl. auch nicht
Die Veränderung des Namens soll verhindern, dass man das Attribut in einer abgeleiteten Klasse überschreibt:

Code: Alles auswählen

class A:
    def __init__(self):
        self.__foo = 'Hallo'

    def show(self):
        print self.__foo

class B(A):
    def __init__(self):
        A.__init__(self)
        self.__foo = 'Blubb'
Die Ausgabe ist:
A().show() --> 'Hallo'
B().show() --> 'Hallo'
"__foo" wird also in Klasse B nicht überschrieben, weil es hier nicht mehr "__foo" heißt, sondern eben "__A_foo". Zwei Unterstriche entsprechen also dem private anderer Sprachen.

Probiert man das Ganze mit einem Unterstrich für foo ("_foo"), so sieht das Ergebnis anders aus:

Code: Alles auswählen

class A:
    def __init__(self):
        self._foo = 'Hallo'

    def show(self):
        print self._foo

class B(A):
    def __init__(self):
        A.__init__(self)
        self._foo = 'Blubb'
A().show() --> 'Hallo'
B().show() --> 'Blubb'
In Klasse B wird das Attribut _foo also überschrieben. Ein Unterstrich entspricht also eher dem protected anderer Sprachen. Das Attribut ist für Nutzer tabu, kann aber in abgeleiteten Klassen überschrieben werden.
Benutzeravatar
Luzandro
User
Beiträge: 87
Registriert: Freitag 21. April 2006, 17:03

Mittwoch 21. Februar 2007, 09:41

Ah! Klar, danke - sonst müsste ich mir ja beim Ableiten Gedanken über interne Details der Klasse machen
[url=http://www.leckse.net/artikel/meta/profilieren]Profilieren im Netz leicht gemacht[/url]
Kefflin
User
Beiträge: 2
Registriert: Dienstag 20. Februar 2007, 18:54

Mittwoch 21. Februar 2007, 18:16

Hi Leute,

vielen Dank an euch alle für die zahlreichen Informationen. Meine Fragen sind beantwortet. :D
Antworten