Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Objektorientierung und Klassen

Leverage the power of Python by writing new classes. For interactive reading and executing code blocks Binder and find b09-classes.ipynb, or install Python and JupyterLab locally.

Die Klasse der Klassen

Python ist eine eigens objektorientierte Sprache und macht den Einsatz von Klassen und Objekten äußerst einfach. Dieses Kapitel führt das Konzept der Python-Klassen ein und beginnt mit wesentlichen Definitionen.

Was ist Objektorientierte Programmierung (OOP)?

Object-Oriented Programming (OOP) ist ein Programmierparadigma, das die Architektur der Software mit der Realität ausrichtet. Die Objektorientierung beginnt mit dem Design der Software, in der ein strukturiertes Modell erstellt wird. Das strukturierte Modell enthält Informationen über Objekte und deren Abstraktionen. Die Entwicklung und Implementierung von objektorientierter Software erfordert eine strukturierte Denkweise und ein konzeptionelles Verständnis von Klassen, Erbschaft, Polymorphismus und Verkapselung.

Objekte und Klassen

In der Computersprache ist ein Objekt eine Instanz, die Daten in Form von Feldern (attributes oder properties) und Code in Form von Merkmalen (Funktionen oder Methoden) enthält. Die Merkmale eines Objekts ermöglichen Zugriff (lesen) und Manipulation seiner Datenfelder. Objekte haben ein Konzept von self in Bezug auf ihre Attribute, Methoden und Datenfelder. self verweist intern auf Attribute, Eigenschaften oder Methoden, die einem Objekt gehören.

In Python ist ein Objekt eine Instanz einer Klasse. So stellt eine class für viele ähnliche Objekte mit den gleichen Attributen und Methoden einen Blaudruck dar. Eine Klasse verwendet nicht den Systemspeicher und nur dessen Instanz (d.h. Objekte) wird Speicher verwenden.

Die einfachste Form einer Klasse in Python enthält nur einige grundlegende Aussagen, und es ist sehr empfehlenswert, eine __init__-Anweisung hinzuzufügen, in der Klassenvariablen definiert sind. Wir kommen später in der Rubrik magic-Methoden an die __init__-Anweisung zurück. Das folgende Beispiel zeigt eine der einfachsten möglichen Klassenstrukturen mit einer __init__-Methode. Beachten Sie die Nutzung von self in der Klasse, die object_name.attribute zum Beispiel der IceCream-Klasse wird.

class IceCream:
    def __init__(self, *args, **kwargs):
        self.flavors=["vanilla", "chocolate", "bread"]
    
    def add_flavor(self, flavor):
        self.flavors.append(flavor)
        
    def print_flavors(self):
        print(", ".join(self.flavors))

# create an instance of IceCream and use the print_flavors method
some_scoops = IceCream()
some_scoops.add_flavor("lemon")

# the following statements have similar effects
some_scoops.print_flavors()
print(some_scoops.flavors)
vanilla, chocolate, bread, lemon
['vanilla', 'chocolate', 'bread', 'lemon']

Erbschaft

Das Cambridge Dictionary definiert Erbschaft (Biologie) als “besondere Merkmale von Eltern durch Gene”. Ebenso beschreibt die Erbschaft in OOP die hierarchische Beziehung zwischen Klassen mit is-a-type-of Beziehungen. So kann beispielsweise eine Klasse namens Salmon von einer Klasse namens Fish anerben. In diesem Fall ist Fish die Elternklasse (oder Super-Klasse) und Salmon die Kinderklasse (oder Sub-Klasse), in der Fish Attribute wie preferred_flow_depth oder preferred_flow_velocity mit Fuzzification Methoden definieren könnte, die andere Lebensraumpräferenzen beschreiben. Solche Klassenerben könnten so aussehen:

# define the parent class Fish
class Fish:
    def __init__(self, *args, **kwargs):
        self.preferred_flow_depth = float()
        self.preferred_flow_velocity = float()
        self.species = ""
        self.xy_position = tuple()
        
    def print_habitat(self):
        print("The species {0} prefers {1}m deep and {2}m/s fast flowing waters.".format(self.species, str(self.preferred_flow_depth), str(self.preferred_flow_velocity)))
        
    def swim_to_position(self, new_position=()):
        self.xy_position = new_position


# define the child class Salmon, which inherits (is-a-type-of) from Fish
class Salmon(Fish):
    def __init__(self, species, *args, **kwargs):
        Fish.__init__(self)
        self.family = "salmonidae"
        self.species = species
        
    def habitat_function(self, depth, velocity):
        self.preferred_flow_depth = depth
        self.preferred_flow_velocity = velocity


atlantic_salmon = Salmon("Salmo salar")
atlantic_salmon.habitat_function(depth=0.4, velocity=0.5)
atlantic_salmon.print_habitat()

pacific_salmon = Salmon("Oncorhynchus tshawytscha")
pacific_salmon.habitat_function(depth=0.6, velocity=0.8)
pacific_salmon.print_habitat()
The species Salmo salar prefers 0.4m deep and 0.5m/s fast flowing waters.
The species Oncorhynchus tshawytscha prefers 0.6m deep and 0.8m/s fast flowing waters.

Polymorph

In der Informatik bezieht sich der Polymorphismus auf die Fähigkeit, die gleiche Programmierschnittstelle für verschiedene Grundstrukturen zu präsentieren. Zwar kann eine Definition nicht viel abstrakter sein. Es genügt, sich hier nur auf die Bedeutung des für Python relevanten Polymorphismus zu konzentrieren, und das ist, wenn Kinderklassen Methoden gleichen Namens haben wie die Elternklasse. Zum Beispiel ist der Polymorphismus in Python, wenn wir die swim_to_position-Funktion der oben gezeigten Fish Elternklasse in der Kinderklasse Salmon neu definieren.

Verkapselung (Public und Non-public Attributes)

Das Konzept der Verkapselung kombiniert Daten und Funktionen, um Daten zu manipulieren, wobei beide (Daten und Funktionen) vor externen Störungen und Manipulationen geschützt sind. Die Encapsulation ist auch die Basis von data hide in der Informatik, die die Designentscheidungen in der Software über Objekte trennt, die sich wahrscheinlich ändern.

One of the most important aspects of encapsulation is the differentiation between private and public class variables. A private attribute cannot be modified from outside (i.e., it is protected and cannot be changed for an instance of a class). In Python, there are no inherently private variables and this is why the Python documentation talks about non-public attributes (i.e., _single_leading_underscore defs in a class) rather than private attributes. While using a single underscore variable name is rather a good practice without technical support, we can use __double_leading_underscore attributes to emulate private behavior with a mechanism called name mangling. Read more about variable definition styles in the Python style guide. public attributes can be modified externally (i.e., different values can be assigned to public attributes of different instances of a class).

Im obigen Beispiel der Salmon-Klasse verwenden wir eine öffentliche Variable namens self.family. Das Familienattribut der Salmon-Klasse ist jedoch ein Attribut, das nicht änderbar sein sollte. Ein ähnliches Verhalten wäre für ein Attribut namens self.aggregate_state = "frozen" der IceCream-Klasse wünschenswert. Um das Konzept vertraut zu machen, definiert der folgende Codeblock ein weiteres Kind der Fish-Klasse mit einem nicht-öffentlichen __family-Attribut. Das Attribut __family ist nicht direkt zugänglich, z.B. der neuen Kinderklasse namens Carp. Dennoch möchten wir, dass die Carp-Klasse ein familyattribut hat und dass wir ihren Wert ausdrucken können. Deshalb benötigen wir eine spezielle Methode def family(self), die den gleichen Namen wie das geschützte Attribut und einen @property decorator hat. Das folgende Beispiel enthält eine zusätzliche spezielle Methode namens def family(self, value), die mit einem @property.setter-Dekorator umfasst wird und die es ermöglicht, die nicht-öffentliche __family-Eigenschaft neu zu definieren (auch wenn dies logisch unsinnig ist, weil wir die Umbenennung der __family-Eigenschaft nicht ermöglichen wollen).

class Carp(Fish):
    def __init__(self, species, *args, **kwargs):
        Fish.__init__(self)
        self.__family = "cyprinidae"
        self.species = species
        
    @property
    def family(self):
        return self.__family
    
    @family.setter
    def family(self, value):
        self.__family = value
        print("family set to \'%s\'" % self.__family)
        
        
european_carp = Carp("Cyprinus carpio carpio")
print(european_carp.family)

try:
    print(european_carp.__family)
except AttributeError:
    print("__family is not directly accessible.")

# re-definition of __family through @family.setter method
european_carp.family="lamnidae"
cyprinidae
__family is not directly accessible.
family set to 'lamnidae'

Dekorationen

Im letzten Beispiel haben wir die Implementierung des @propertyDekorators gesehen, der eine Methode in ein nicht klappbares Attribut (Eigentum) und den @attribute.setterDekorator einleitet, um eine nicht-öffentliche Variable neu zu definieren.

Bis hier kennen wir nur Dekorateure als eine effiziente Möglichkeit, Funktionen zu vereinfachen. Dekoratoren sind jedoch ein noch leistungsstärkeres Werkzeug in der objektorientierten Programmierung von Klassen, in denen Dekoratoren verwendet werden können, um Klassenmethoden ähnlich wie Funktionen zu wickeln. Lassen Sie uns ein weiteres Kind der Fish-Klasse definieren, um den @property-Dekorator mit seinen deleter,getter und setter-Methoden zu erkunden.

class Bullhead(Fish):
    def __init__(self, species, *args, **kwargs):
        Fish.__init__(self)
        self.__family = "cottidae"
        self.species = species
        self.__length = 7.0
        
    @property
    def length(self):
        return self.__length
    
    @length.setter
    def length(self, value):
        try:
            self.__length = float(value)
        except ValueError:
            print("Error: Value is not a real number.")
            
    @length.deleter
    def length(self):
        del self.__length
        
european_bullhead = Bullhead("Cottus gobio")

# make use of @property.getter, which directly results from the @property-embraced def length method
print(european_bullhead.length)

# make use of @property.setter method
european_bullhead.length = 6.5
print(european_bullhead.length)

# make use of @property.delete method
del european_bullhead.length
try:
    print(european_bullhead.length)
except AttributeError:
    print("Error: You cannot print a nonexistent property.")
7.0
6.5
Error: You cannot print a nonexistent property.

Überladen und magische Methoden

Die oben genannten Beispiele haben eine spezielle oder *magic Methode mit dem Namen __init__ eingeführt. Wir haben bereits gesehen, dass __init__ nichts Magisches selbst ist und in Python viele weitere vordefinierte Methoden existieren. Bevor wir zu magischen Methoden kommen, ist es wichtig, das Konzept der Überlastung in Python zu verstehen. Haben Sie sich schon gefragt, warum derselbe Bediener je nach Datentyp unterschiedliche Effekte haben kann? Der +-Operator schließt z.B. strings ein, fasst aber numerische Datentypen zusammen:

a_string = "vanilla"
b_string = "cream"
print("+ operator applied to strings: " + str(a_string + b_string))

a_number = 50
b_number = 30
print("+ operator applied to integers: " + str(a_number + b_number))
+ operator applied to strings: vanillacream
+ operator applied to integers: 80

Dieses Verhalten wird als *Operator (oder Funktion) Überlastung in Python bezeichnet und Überlastung ist aufgrund vordefinierter Namen von magischen Methoden in Python möglich. Jetzt sind wir bereit, in den Pool magic Methoden einzutauchen.

Magische Methoden sind eines der Schlüsselelemente, die Python einfach und klar zu bedienen. Wegen ihrer Erklärung mit Doppelunterstrichen (__this_is_magic__) werden auch magische Methoden dunder (**double underscore) genannt. Magische Methoden sind spezielle Methoden mit festen Namen und ihr magic Name ist, weil sie nicht direkt aufgerufen werden müssen. Hinter den Kulissen benutzt Python ständig magische Methoden, beispielsweise wenn eine neue Instanz einer Klasse zugewiesen wird: Wenn Sie var = MyClass() schreiben, ruft Python automatisch MyClass’es __init__() und __new__() magische Methoden an. Magische Methoden gelten auch für alle Operatoren oder (augmented) Aufgaben. So sucht der + binäre Operator Python beispielsweise nach der magischen Methode __add__. Wenn wir also a + b eingeben, und beide Variablen sind Instanzen von MyClass, wird Python die __add__-Methode von MyClass um a.__add__(b) zu bewerben suchen. Wenn Python die __add__-Methode nicht in MyClass finden kann, erhöht sie eine TypeError: unsupported operand.

In den folgenden Abschnitten finden Sie einige dokumentierte magische Methoden zur Verwendung in Klassen und Paketen. Dies sind nur einige der häufigsten magischen Methoden und es existieren mehr dokumentierte magische Objekte oder Attribute.

Operator (binär) und Zuweisungsmethoden

Für jede neue Klasse, die wir in der Lage sein wollen, mit einem Bediener umzugehen (z.B. um Objekte mit result = object1 + object2 zusammenzufassen), müssen wir folgende Methoden implementieren (Überladung).

| Operator | Methode | | Zuordnung | Methode |

| + | object.__add__(self, *args, **kwargs) | | += | object.__iadd__(self, *args, **kwargs) | | - | object.__sub__(self, *args, **kwargs) | | -= | object.__isub__(self, *args, **kwargs) | | * | object.__mul__(self, *args, **kwargs) | | *= | object.__imul__(self, *args, **kwargs) | | // | object.__floordiv__(self, *args, **kwargs) | | /= |object.__itruediv__(self, *args, **kwargs) | | / | object.__truediv__(self, *args, **kwargs) | | //= | object.__ifloordiv__(self, *args, **kwargs) | | % | object.__mod__(self, *args, **kwargs) | |%= |@@@ |object.__imod__(self, *args, **kwargs) | | ** | object.__pow__(self, *args, **kwargs) | | **= | object.__ipow__(self, *args, **kwargs) | | << | object.__lshift__(self, *args, **kwargs) | | <<= |object.__ilshift__(self, *args, **kwargs) | | >> | object.__rshift__(self, *args, **kwargs) | | >>= |object.__irshift__(self, *args, **kwargs) | | & | object.__and__(self, *args, **kwargs) | | &= |object.__iand__(self, *args, **kwargs) | | ^ | object.__xor__(self, *args, **kwargs) | | ^= | object.__ixor__(self, *args, **kwargs) | | \| | object.__or__(self, *args, **kwargs) | | \|= |object.__ior__(self, *args, **kwargs) |

Betreiber (ungültig) und Vergleichsmethoden

Auch nicht- oder vergleichende Operatoren können definiert oder überlastet werden. Unary Operatoren beschäftigen sich mit nur einem Eingang im Gegensatz zu den oben aufgeführten binären Operatoren. Ein unparteiischer Betreiber handelt auf einen einzigen Operand, wie z.B. negation-x, unary plus+x oder bitweise inversion~x (Anmerkung, dass Python keine ++ oder -- Inkrement/Dekrement Operatoren hat). Darüber hinaus beinhalten vergleichende Operatoren (Vergleicher) magische Methoden wie __ne__, als Synonym für **n*ot equal.

| Operator | Methode | Vergleicher | Methode |

| - | object.__neg__(self) | | < | object.__lt__(self, *args, **kwargs) | | + | object.__pos__(self) | | <= | object.__le__(self, *args, **kwargs) | | abs() | object.__abs__(self) | | == | object.__eq__(self, *args, **kwargs) | | ~ | object.__invert__(self) | | != |object.__ne__(self, *args, **kwargs) | | complex() | object.__complex__(self) | | >= | object.__ge__(self, *args, **kwargs) | | int() | object.__int__(self) | |> |@@@ |object.__gt__(self, *args, **kwargs) | | float() | object.__float__(self)

Eine umfassende und inklusive Zusammenfassung der magischen Methoden ist in der Python docs.

Wie sieht eine Klasse in der Praxis so aus, dass sie beispielsweise den + Operator mit einer __add__-Methode verwenden kann? Zu diesem Zweck definieren wir ein weiteres Kind der Fish-Klasse, um einen Schwarm zu bauen:

class Mackerel(Fish):
    def __init__(self, species, *args, **kwargs):
        Fish.__init__(self)
        self.__family = "scombridae"
        self.species = species
        self.count = 1
        
    def __add__(self, value):
        self.count += value
        return self.count
    
    def __mul__(self, multiplier):
        self.count *= multiplier
        return self.count
        
atlantic_mackerel = Mackerel("Scomber scombrus")
print(atlantic_mackerel + 1)
print(atlantic_mackerel * 10)
2
20

Benutzerdefinierte Python Klasse Vorlage

Dieser Abschnitt enthält eine Vorlage für eine benutzerdefinierte Python3 Klasse. Die Vorlage kann mit öffentlichen und nichtöffentlichen Eigenschaften und Anpassungen von magischen Methoden erweitert werden, um die Nutzung von Operatoren wie + oder <= zu ermöglichen. Letztlich gibt es viele Optionen zum Schreiben einer benutzerdefinierten Klasse, aber alle benutzerdefinierten Klassen sollten mindestens die folgenden Methoden enthalten:

  • __init__(self, [...) is the (magic) class initializer, which is called when an instance of the class is created. More precisely, it is called along with the __new__(cls, [...) method, which, in contrast, is rarely used (read more at python.org). The initializer gets the arguments passed with which the object was called. For example, when var = MyClass(1, 'vanilla' ), the __init__(self, [...) method receives 1 and 'vanilla'.

  • __call__(self, [...) kann eine Klasseninstanz direkt anrufen. Beispielsweise kann var('cherry') (entspricht var.__call__('cherry')) verwendet werden, um von 'vanilla' an 'cherry' zu ändern.

So sieht ein robustes Klassenschablone-Skelett so aus:

class NewClass:
    def __init__(self, *args, **kwargs):
        # initialize any class variable here (all self.attributes should be here)
        pass
    
    def methods1_n(self, *args, **kwargs):
        # place class methods between the __init__ and the __call__ methods
        pass
    
    def __call__(self, *args, **kwargs):
        # example prints class structure information to console
        print("Class Info: <type> = NewClass (%s)" % os.path.dirname(__file__))
        print(dir(self))

Das Verständnis der Leistung und Struktur von Klassen und Objektorientierung nimmt Zeit und erfordert Praxis. Zu diesem Zweck geben die Kapitel unter Graphical User Interfaces und Geospatial Python weitere Beispiele von Klassen, um die Konzepte vertraut zu machen.

Erfolgsprüfung lernen

Nehmen Sie den Lernerfolgstest für dieses Jupyter Notebook.