Einführung
Wenn Sie Klassen schreiben, ist es üblich, versuchen, interne Details zu kapseln. In diesem Abschnitt werden einige Python-Programmier-Idiome dazu vorgestellt, darunter private Variablen und Eigenschaften.
This tutorial is from open-source community. Access the source code
💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken
Wenn Sie Klassen schreiben, ist es üblich, versuchen, interne Details zu kapseln. In diesem Abschnitt werden einige Python-Programmier-Idiome dazu vorgestellt, darunter private Variablen und Eigenschaften.
Eine der primären Rollen einer Klasse besteht darin, Daten und interne Implementierungsdetails eines Objekts zu kapseln. Allerdings definiert eine Klasse auch eine öffentliche Schnittstelle, die die Außenwelt verwenden soll, um das Objekt zu manipulieren. Diese Unterscheidung zwischen Implementierungsdetails und der öffentlichen Schnittstelle ist wichtig.
In Python ist fast alles über Klassen und Objekte öffentlich.
Dies ist ein Problem, wenn Sie versuchen, die Details der internen Implementierung zu isolieren.
Python stützt sich auf Programmierkonventionen, um die beabsichtigte Verwendung von etwas anzuzeigen. Diese Konventionen basieren auf der Namensgebung. Es besteht eine allgemeine Einstellung, dass es dem Programmierer überlassen ist, die Regeln zu befolgen, im Gegensatz dazu, dass die Sprache sie durchsetzt.
Jeder Attributname mit führendem _
wird als privat betrachtet.
class Person(object):
def __init__(self, name):
self._name = 0
Wie bereits erwähnt, ist dies nur ein Programmierstil. Sie können es immer noch zugreifen und ändern.
>>> p = Person('Guido')
>>> p._name
'Guido'
>>> p._name = 'Dave'
>>>
Als allgemeine Regel gilt, dass jeder Name mit führendem _
als interne Implementierung betrachtet wird, ob es sich um eine Variable, eine Funktion oder einen Modulnamen handelt. Wenn Sie sich finden, dass Sie solche Namen direkt verwenden, tun Sie wahrscheinlich etwas falsch. Suchen Sie nach höherebenen Funktionalität.
Betrachten Sie die folgende Klasse.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
Ein überraschendes Merkmal ist, dass Sie die Attribute beliebig auf einen beliebigen Wert setzen können:
>>> s = Stock('IBM', 50, 91.1)
>>> s.shares = 100
>>> s.shares = "hundred"
>>> s.shares = [1, 0, 0]
>>>
Sie könnten sich das ansehen und denken, dass Sie zusätzliche Überprüfungen benötigen.
s.shares = '50' #引发一个 TypeError,这是一个字符串
Wie würden Sie das tun?
Eine Möglichkeit: Einführung von Zugangsmethoden.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.set_shares(shares)
self.price = price
## Funktion, die die "get"-Operation abdeckt
def get_shares(self):
return self._shares
## Funktion, die die "set"-Operation abdeckt
def set_shares(self, value):
if not isinstance(value, int):
raise TypeError('Erwartet einen int')
self._shares = value
Schade, dass damit all unseren bestehenden Code kaputt geht. s.shares = 50
wird zu s.set_shares(50)
Es gibt einen alternativen Ansatz zum vorherigen Muster.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def shares(self):
return self._shares
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Erwartet int')
self._shares = value
Der normale Attribut-Zugriff löst jetzt die Getter- und Setter-Methoden unter @property
und @shares.setter
aus.
>>> s = Stock('IBM', 50, 91.1)
>>> s.shares ## Triggers @property
50
>>> s.shares = 75 ## Triggers @shares.setter
>>>
Mit diesem Muster sind keine Änderungen am Quellcode erforderlich. Der neue Setter wird auch aufgerufen, wenn innerhalb der Klasse eine Zuweisung erfolgt, einschließlich innerhalb der __init__()
-Methode.
class Stock:
def __init__(self, name, shares, price):
...
## Diese Zuweisung ruft den unten stehenden Setter auf
self.shares = shares
...
...
@shares.setter
def shares(self, value):
if not isinstance(value, int):
raise TypeError('Erwartet int')
self._shares = value
Es besteht oft Verwirrung zwischen einer Eigenschaft und der Verwendung privater Namen. Obwohl eine Eigenschaft intern einen privaten Namen wie _shares
verwendet, kann der Rest der Klasse (nicht die Eigenschaft) weiterhin einen Namen wie shares
verwenden.
Eigenschaften sind auch nützlich für berechnete Datenattribute.
class Stock:
def __init__(self, name, shares, price):
self.name = name
self.shares = shares
self.price = price
@property
def cost(self):
return self.shares * self.price
...
Dies ermöglicht es Ihnen, die zusätzlichen Klammern zu weglassen und die Tatsache zu verbergen, dass es tatsächlich eine Methode ist:
>>> s = Stock('GOOG', 100, 490.1)
>>> s.shares ## Instanzvariable
100
>>> s.cost ## Berechneter Wert
49010.0
>>>
Das letzte Beispiel zeigt, wie man eine einheitlichere Schnittstelle für ein Objekt erstellt. Wenn man das nicht tut, kann das Verwenden eines Objekts verwirrend sein:
>>> s = Stock('GOOG', 100, 490.1)
>>> a = s.cost() ## Methode
49010.0
>>> b = s.shares ## Datenattribut
100
>>>
Warum ist die ()
für den Wert von cost
erforderlich, aber nicht für shares
? Eine Eigenschaft kann das beheben.
Die @
-Syntax wird als "Dekoration" bezeichnet. Sie gibt einen Modifizierer an, der auf die unmittelbar folgende Funktionsdefinition angewendet wird.
...
@property
def cost(self):
return self.shares * self.price
Weitere Details werden im Abschnitt 7 gegeben.
__slots__
-AttributSie können die Menge der Attributnamen einschränken.
class Stock:
__slots__ = ('name','_shares','price')
def __init__(self, name, shares, price):
self.name = name
...
Es wird ein Fehler für andere Attribute auslösen.
>>> s.price = 385.15
>>> s.prices = 410.2
Traceback (most recent call last):
File "<stdin>", line 1, in?
AttributeError: 'Stock' Objekt hat kein Attribut 'prices'
Obwohl dies Fehler verhindert und die Verwendung von Objekten einschränkt, wird es tatsächlich für die Leistung verwendet und lässt Python den Arbeitsspeicher effizienter nutzen.
Gehen Sie nicht über die Maßen bei privaten Attributen, Eigenschaften, Slots usw. Sie dienen einem bestimmten Zweck und Sie werden sie möglicherweise bei der Lektüre anderer Python-Code sehen. Sie sind jedoch für den meisten alltäglichen Code nicht erforderlich.
Eigenschaften sind eine nützliche Möglichkeit, "berechnete Attribute" einem Objekt hinzuzufügen. In stock.py
haben Sie ein Objekt Stock
erstellt. Beachten Sie, dass bei Ihrem Objekt eine leichte Inkonsistenz bei der Extraktion unterschiedlicher Arten von Daten besteht:
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.shares
100
>>> s.price
490.1
>>> s.cost()
49010.0
>>>
Insbesondere beobachten Sie, wie Sie die zusätzlichen Klammern ()
zu cost
hinzufügen müssen, da es sich um eine Methode handelt.
Sie können die zusätzlichen Klammern ()
bei cost()
entfernen, indem Sie es zu einer Eigenschaft machen. Nehmen Sie Ihre Stock
-Klasse und ändern Sie sie so, dass die Kostenberechnung wie folgt funktioniert:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.1)
>>> s.cost
49010.0
>>>
Versuchen Sie, s.cost()
als Funktion aufzurufen, und beobachten Sie, dass es jetzt nicht funktioniert, da cost
als Eigenschaft definiert wurde.
>>> s.cost()
... funktioniert nicht...
>>>
Dieser Änderung wird wahrscheinlich Ihr früheres pcost.py
-Programm brechen. Sie müssen möglicherweise zurückgehen und die Klammern ()
bei der cost()
-Methode entfernen.
Ändern Sie das shares
-Attribut so, dass der Wert in einem privaten Attribut gespeichert wird und dass eine Paar von Eigenschaftsfunktionen verwendet wird, um sicherzustellen, dass er immer als ganzzahliger Wert festgelegt wird. Hier ist ein Beispiel für das erwartete Verhalten:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG',100,490.10)
>>> s.shares = 50
>>> s.shares = 'a lot'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: erwartet eine Ganzzahl
>>>
Ändern Sie die Stock
-Klasse so, dass sie ein __slots__
-Attribut hat. Anschließend überprüfen Sie, dass neue Attribute nicht hinzugefügt werden können:
>>> ================================ RESTART ================================
>>> from stock import Stock
>>> s = Stock('GOOG', 100, 490.10)
>>> s.name
'GOOG'
>>> s.blah = 42
... sehen Sie, was passiert...
>>>
Wenn Sie __slots__
verwenden, verwendet Python eine effizientere interne Darstellung von Objekten. Was passiert, wenn Sie versuchen, das zugrunde liegende Wörterbuch von s
wie oben zu untersuchen?
>>> s.__dict__
... sehen Sie, was passiert...
>>>
Es sollte bemerkt werden, dass __slots__
am häufigsten als Optimierung für Klassen verwendet wird, die als Datenstrukturen dienen. Das Verwenden von Slots wird solche Programme dazu bringen, viel weniger Speicher zu verwenden und etwas schneller zu laufen. Auf den meisten anderen Klassen sollten Sie jedoch möglicherweise __slots__
vermeiden.
Herzlichen Glückwunsch! Sie haben das Lab zu Klassen und Kapselung abgeschlossen. Sie können in LabEx weitere Labs ausprobieren, um Ihre Fähigkeiten zu verbessern.