Metaklassen und spezielle Attribute

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
Soxxes
User
Beiträge: 12
Registriert: Mittwoch 28. August 2019, 14:53

Hallo zusammen,

ich wollte die Frage(n) eigentlich nicht stellen, aber ich stoße immer wieder darauf und habe eigentlich keine Ahnung, worum es da geht.
Es geht um Metaklassen und spezielle Attribute.

Sachen wie:

- die "Main-Funktion"

Code: Alles auswählen

if __name__ == '__main__':
    main()
- Konstruktor

Code: Alles auswählen

class Foo():
    def __init__(self):
habe ich einfach immer hingenommen; nach dem Motto: "Das ist halt so".
Spätestens bei solchen Zeilen habe ich dann erst recht keine Ahnung mehr, was eigentlich abgeht:

Code: Alles auswählen

def __str__(self):
        return self.__class__.__name__
Irgendwie bekommt man das mit ein bisschen Hilfe von Google schon hin, aber ich bin so weit entfernt davon wirklich zu verstehen, was da überhaupt passiert.

Auf meiner Expedition bin ich schließlich auf "Metaklassen" (metaclasses) gestoßen, was mich nur noch mehr verwirrt hat. (Bsp.: die type-Metaklasse)
Dieser Artikel ist eigentlich ganz gut:
https://realpython.com/python-metaclasses/
Dennoch sind bei mir noch viele Fragezeichen im Kopf.


1. Frage:
Was sind diese speziellen Attribute:
https://docs.python.org/3/library/stdty ... .__class__
Viele benutzen sie offenbar oft. Was kann ich mit denen anstellen? Ich nutze sie, ohne zu wissen, was sie überhaupt machen.

2. Frage:
Metaklassen ... what?!
Es heißt im o.g. Artikel:
You can also call type() with three arguments—type(<name>, <bases>, <dct>):

<name> specifies the class name. This becomes the __name__ attribute of the class.
<bases> specifies a tuple of the base classes from which the class inherits. This becomes the __bases__ attribute of the class.
<dct> specifies a namespace dictionary containing definitions for the class body. This becomes the __dict__ attribute of the class.
Wie wahrscheinlich ist es, dass ich eine Klasse jemals so erstelle?
Was sind diese Attribute __name__ (ok, das ist relativ einleuchtend), __bases__ und __dict__?
Es heißt in dem Artikel auch, dass viele Programmierer nie wirklich etwas damit zu tun haben. Stimmt das? Ist es Zeitverschwendung sich damit auseinanderzusetzen?


Kennt jemand ein gutes Buch, eine YouTube-Video-Reihe, einen Udemy-Kurs oder sonst irgendetwas zu diesen Themen?
Über hilfreiche Antworten würde ich mich sehr freuen. :)


Weitere (unwichtigere) Frage:
Außerdem habe ich in einem anderen Forenbeitrag gelesen, dass man nicht via

Code: Alles auswählen

for i in range(len(x)):
iteriert. Dies sei ein sog. "anti pattern". Ich habe das gegoogelt und bin auf eine interessante Seite gelangt:
https://docs.quantifiedcode.com/python-anti-patterns/
Super cool, da werde ich noch ein bisschen stöbern.
Aber zur Frage: Wieso macht man das nicht so? Ich weiß, dass ich das auch öfter so gemacht habe. Was wäre denn der richtige Weg?
Zum Iterieren über Dicts, habe ich das gefunden:
https://docs.quantifiedcode.com/python- ... onary.html



Liebe Grüße,
Marc
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die speziellen Attribute und Metaklassen haben erstmal nicht wirklich viel miteinander zu tun. Letztere greifen auf erstere konzeptionell zurueck, ich wuerde dir aber empfehlen, Metaklassen fuer den Moment beiseite zu lassen. Das ist ein Kirsche-auf-der-Torte-Thema fuer Fortgeschrittene. Und die (sprich: auch ich) lernen dann irgendwann, die im Grunde nur sehr selten bis nie zu benutzen. Und darum gehe ich darauf auch jetzt nicht ein.

Was die speziellen Attribute angeht: da ist erstmal ueberhaupt nichts magisches dabei. Der einzige Grund, warum die so "komisch" aussehen ist, dass durch die Verwendung von __irgendwas__ ein Name entsteht, bei dem erstmal kaum eine Chance besteht, das der unabsichtlich mit etwas kollidiert, das auch so heisst. Es ist *SEHR* wahrscheinlich, dass man irgendwo Code hat, in dem der Bezeichner "name" vorkommt. zB in einer Funktion "register_user". Und damit muss man nun eben einen Namen finden fuer den Namen des Moduls, der dieses Problem der Kollision NICHT hat. Also nimmt man __name__. Und so geht das weiter. Da "python main.py" und "import main" unterschieden werden muessen, heisst das erstere im Attribut __name__ eben "__main__", und nicht "main", weil man das sonst nicht unterscheiden kann. "class" ist ein Schluesselwort, und kann nicht genommen werden fuer das Attribut eines Objektes, das dessen Klasse enthaelt. Das braucht man aber, weil der Attribute-Lookup in Python so aussieht, dass fuer einen Ausdruck

Code: Alles auswählen

objekt.attribut
zuerst im Instanz-Woerterbuch nachgeschlagen wird. Und wenn es da nicht ist, dann schaut man auf der Klasse nach. Auf die Art und Weise kann "attribut" eine Methode sein, und man kann die dann aufrufen. Somit wird aus class __class__. Hier kommt dann auch __name__ auf Klassen zum tragen. Eine Methode "user.name()" ist sicher oft vorhanden, und sollte dann nicht dazu fuehren, dass man den Klassennamen nicht mehr erfahren kann. Also.... macht man __name__ draus. Es mag auch noch ein paar spezielle Regeln geben, zB geht das hier fuer __name__ nicht, aber fuer egal:

Code: Alles auswählen

class Foo:
    egal = 1000

f = Foo()
print(f.egal)
print(f.__name__)
Und auch das Instanz-Woerterbuch muss nunmal irgendwie erreichbar sein. Man nennt es also __dict__, auch wieder um die Kollision mit anderen Namen zu vermeiden.

Etc.

Sehr viel mehr ist da nicht dran. Man muss die halt kennen.
Benutzeravatar
__blackjack__
User
Beiträge: 14050
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Soxxes: Ich weiss nicht was man auf die Frage was diese speziellen Attribute anderes antworten soll als das was in der von Dir selbst verlinkten Seite in der Dokumentation steht.

Was konkret verstehst Du denn daran nicht?

Zum Beispiel bei:

Code: Alles auswählen

def __str__(self):
    return self.__class__.__name__
Da in Python jedes Objekt einen Typ hat und Typen durch Klassen beschrieben werden, hat jedes Objekt auch eine dazugehörige Klasse. Und die ist über das Attribut `__class__` erreichbar.

Objekte wie Module, Funktionen, Klassen, Methoden, … die bei ihrer Definition einen festen Namen bekommen haben, besitzen ein `__name__`-Attribut was diesen Namen als Zeichenkette liefert.

Da Klassen in dieser Liste vorkommen, folgt daraus das ``self.__class__.__name__`` der Name der Klasse ist, von dem das Objekt `self` ist.

Was bei ``type(<name>, <bases>, <dct>)`` die Argumente `bases` und `dct` sind, sollte man doch an den Beispielen in dem Artikel erkennen können. Bei `bases` eigentlich schon am Namen wenn man normale Klassen verstanden hat.

Und ja, ich denke es ist Zeitverschwendung. Der Artikel hat ja auch dieses Tim Peters Zitat: “Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).”

Damit kann man sich beschäftigen wenn man alles andere der Sprache verstanden hat.

Statt ``for in in range(len(sequence)):`` schreibt man ``for item in sequence:`` oder `for i, item in enumerate(sequence):`` falls man zusätzlich noch eine laufende Zahl benötigt. Warum sollte eigentlich einleuchtend sein: der Code ist simpler. Und man sieht in der ``for``-Schleife schon was da passiert, oder eben auch nicht passiert. Wenn da kein laufender Index ist, kann man sich schon mal ziemlich sicher sein, dass es nicht nur mit Sequenztypen funktioniert, sondern auch mit beliebigen anderen iterierbaren Objekten. Die müssen dazu weder eine abfragbare Länge haben noch Indexzugriff anbieten.

Das hier zum Beispiel funktioniert mit einer Liste, aber nicht mit einem Dateiobjekt:

Code: Alles auswählen

for i in range(len(lines)):
    print(lines[i], end="")
Das hier funktioniert sowohl mit einer Liste als auch mit einer Datei:

Code: Alles auswählen

for line in lines:
    print(line, end="")
Wobei Datei jedes ”dateiähnliche” Objekt sein kann. Also nicht nur eine Datei auf der Festplatte, sondern beispielsweise auch die Standardausgabe von einem mit `subprocess.Popen` gestarteten Prozess, oder was `urllib.request.urlopen()` liefert.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Soxxes
User
Beiträge: 12
Registriert: Mittwoch 28. August 2019, 14:53

Ich danke euch beiden.
Jetzt ist einiges auf jeden Fall klarer, ich werde wohl noch ein bisschen dazu lesen müssen.
Generell fällt mir bei jedem Beitrag hier im Forum immer mehr auf, wie wenig Ahnung ich eigentlich habe. :D


Liebe Grüße,
Marc
Antworten