Leverage the power of Python by writing new classes. For interactive reading and executing code blocks and find b09-classes.ipynb, or install Python and JupyterLab locally.
Watch this section as a video
Watch this section as a video on the @Hydro-Morphodynamics channel on YouTube.
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. In Python ist ein Objekt eine Instanz einer Klasse. Eine Klasse definiert Verhalten und kann als Blaupause für ihre Instanzen wirken. Attribute speichern Objektzustand und Methoden sind Funktionen, die auf der Klasse definiert sind, die auf Instanzen arbeiten. Durch die Konvention wird der erste Parameter einer Instanzmethode self genannt; Python liefert die Instanz für dieses Argument, wenn die Methode durch eine Instanz aufgerufen wird.
Klassen selbst sind Objekte und verbrauchen Speicher. Eine Klasse kann __init__ definieren, um eine neue Instanz zu initialisieren, aber eine explizite __init__-Methode ist nicht erforderlich.

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):
super().__init__(*args, **kwargs)
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¶
Der Polymorphismus ermöglicht es unterschiedlichen Objekttypen, die gleiche Schnittstelle zu unterstützen. Eine Unterklasse, die ein geerbtes Verfahren überwiegt, ist ein Beispiel. Python verwendet auch häufig Ente-Typing: Unbezogene Klassen können über die gleiche Operation verwendet werden, wenn sie die erforderlichen Methoden oder Attribute bereitstellen. 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.
Einer der wichtigsten Aspekte der Verkapselung ist die Differenzierung zwischen private und public-Klassenvariablen. Ein private Attribut kann nicht von außen geändert werden (d.h. es ist geschützt und kann nicht zum Beispiel einer Klasse geändert werden). Python erzwingt nicht wirklich private Instanz Attribute. Ein Name wie _family ist nicht öffentlich nach Konvention. Ein Name wie __family ist ein Name, der den Klassennamen einschließt, vor allem um zufällige Konflikte in Unterklassen zu reduzieren; er kann immer noch bewusst zugegriffen werden. Eigenschaften können verwaltete Lese-, Schreib- und Löschverhalten durch Getter-, Setter- und Löschfunktionen bereitstellen.
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¶
Der @property decorator definiert einen Getter, so dass european_carp.family den von der Methode zurückgegebenen Wert liest. Der @family.setter-Dekorator definiert, was passiert, wenn der Clientcode eine Zuordnung wie european_carp.family = "Lamnidae" durchführt.
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 (TypeError, ValueError):
print("Error: Value is not a real number.")
@length.deleter
def length(self):
del self.__length
# Read through the getter, assign through the setter, and delete through the deleter
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.
Magic methods are one of the key elements that make Python easy and clear to use. Because of their declaration using double underscores (__this_is_magic__), magic methods are also called dunder (double underscore) methods. Magic methods are special methods with fixed names and their magic name is because they do not need to be directly invoked. Behind the scenes, Python constantly uses magic methods, for example, when a new instance of a class is assigned: When var = MyClass() is evaluated, Python calls MyClass.__new__ to create an instance. If __new__ returns an instance of MyClass, Python then calls MyClass.__init__ to initialize it. For a + b, Python first tries a.__add__(b); if that returns NotImplemented, Python may try the reflected operation b.__radd__(a) before raising TypeError.
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 (1) | Methode (1) | Operator (2) | Methode (2) |¶
| + | object.__add__(self, other) | | += | object.__iadd__(self, other) |
| - | object.__sub__(self, other) | | -= | object.__isub__(self, other) |
| * | object.__mul__(self, other) | | *= | object.__imul__(self, other) |
| // | object.__floordiv__(self, other) | | //= |object.__ifloordiv__(self, other) |
| / | object.__truediv__(self, other) | | /= | object.__itruediv__(self, other) |
| % | 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, whenvar = MyClass(1, 'vanilla' ), the__init__(self, [...)method receives1and'vanilla'.__call__(self, [...)kann eine Klasseninstanz direkt anrufen. Beispielsweise kannvar('cherry')(entsprichtvar.__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 instance attributes here.
pass
def method_1(self, *args, **kwargs):
pass
def __call__(self, *args, **kwargs):
print(f"Class info: {type(self).__name__}")
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.
