Einführung
In diesem Lab lernen Sie, wie Sie Objektinterna mithilfe von privaten Attributen kapseln und Property-Dekoratoren implementieren, um den Attributzugriff zu steuern. Diese Techniken sind unerlässlich, um die Integrität Ihrer Objekte zu wahren und eine korrekte Datenverarbeitung sicherzustellen.
Sie werden auch verstehen, wie Sie die Attributerstellung mithilfe von __slots__ einschränken können. Wir werden die Datei stock.py während dieses Labs modifizieren, um diese Konzepte anzuwenden.
Implementierung privater Attribute
In Python verwenden wir eine Namenskonvention, um anzugeben, dass ein Attribut für die interne Verwendung innerhalb einer Klasse vorgesehen ist. Wir versehen diese Attribute mit einem Unterstrich (_). Dies signalisiert anderen Entwicklern, dass diese Attribute nicht Teil der öffentlichen API (Application Programming Interface) sind und nicht direkt von außerhalb der Klasse aufgerufen werden sollten.
Betrachten wir die aktuelle Stock-Klasse in der Datei stock.py. Sie hat eine Klassenvariable namens types.
class Stock:
## Class variable for type conversions
types = (str, int, float)
## Rest of the class...
Die Klassenvariable types wird intern verwendet, um Zeilendaten zu konvertieren. Um anzugeben, dass dies ein Implementierungsdetail ist, markieren wir sie als privat.
Anweisungen:
Öffnen Sie die Datei
stock.pyim Editor.Ändern Sie die Klassenvariable
types, indem Sie einen vorangestellten Unterstrich hinzufügen und sie in_typesändern.class Stock: ## Class variable for type conversions _types = (str, int, float) ## Rest of the class...Aktualisieren Sie die Methode
from_row, um die umbenannte Variable_typeszu verwenden.@classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values)Speichern Sie die Datei
stock.py.Erstellen Sie ein Python-Skript namens
test_stock.py, um Ihre Änderungen zu testen. Sie können die Datei im Editor mit dem folgenden Befehl erstellen:touch /home/labex/project/test_stock.pyFügen Sie den folgenden Code zur Datei
test_stock.pyhinzu. Dieser Code erstellt Instanzen derStock-Klasse und gibt Informationen über sie aus.from stock import Stock ## Create a stock instance s = Stock('GOOG', 100, 490.10) print(f"Name: {s.name}, Shares: {s.shares}, Price: {s.price}") print(f"Cost: {s.cost()}") ## Create from row row = ['AAPL', '50', '142.5'] apple = Stock.from_row(row) print(f"Name: {apple.name}, Shares: {apple.shares}, Price: {apple.price}") print(f"Cost: {apple.cost()}")Führen Sie das Testskript mit dem folgenden Befehl im Terminal aus:
python /home/labex/project/test_stock.pySie sollten eine ähnliche Ausgabe sehen wie:
Name: GOOG, Shares: 100, Price: 490.1 Cost: 49010.0 Name: AAPL, Shares: 50, Price: 142.5 Cost: 7125.0
Umwandlung von Methoden in Properties (Eigenschaften)
Properties (Eigenschaften) in Python ermöglichen Ihnen den Zugriff auf berechnete Werte wie Attribute. Dies macht Klammern beim Aufrufen einer Methode überflüssig, wodurch Ihr Code sauberer und konsistenter wird.
Derzeit hat unsere Stock-Klasse eine cost()-Methode, die die Gesamtkosten der Aktien berechnet.
def cost(self):
return self.shares * self.price
Um den Kostenwert zu erhalten, müssen wir ihn mit Klammern aufrufen:
s = Stock('GOOG', 100, 490.10)
print(s.cost()) ## Calls the method
Wir können dies verbessern, indem wir die cost()-Methode in eine Property (Eigenschaft) umwandeln, wodurch wir auf den Kostenwert ohne Klammern zugreifen können:
s = Stock('GOOG', 100, 490.10)
print(s.cost) ## Accesses the property
Anweisungen:
Öffnen Sie die Datei
stock.pyim Editor.Ersetzen Sie die
cost()-Methode durch eine Property (Eigenschaft) mithilfe des@property-Dekorators:@property def cost(self): return self.shares * self.priceSpeichern Sie die Datei
stock.py.Erstellen Sie eine neue Datei namens
test_property.pyim Editor:touch /home/labex/project/test_property.pyFügen Sie den folgenden Code zur Datei
test_property.pyhinzu, um eineStock-Instanz zu erstellen und auf diecost-Property (Eigenschaft) zuzugreifen:from stock import Stock ## Create a stock instance s = Stock('GOOG', 100, 490.10) ## Access cost as a property (no parentheses) print(f"Stock: {s.name}") print(f"Shares: {s.shares}") print(f"Price: {s.price}") print(f"Cost: {s.cost}") ## Using the propertyFühren Sie das Testskript aus:
python /home/labex/project/test_property.pySie sollten eine ähnliche Ausgabe sehen wie:
Stock: GOOG Shares: 100 Price: 490.1 Cost: 49010.0
Implementierung der Property (Eigenschafts)-Validierung
Properties (Eigenschaften) ermöglichen es Ihnen auch, zu steuern, wie Attributwerte abgerufen, gesetzt und gelöscht werden. Dies ist nützlich, um Ihren Attributen eine Validierung hinzuzufügen und sicherzustellen, dass die Werte bestimmte Kriterien erfüllen.
In unserer Stock-Klasse möchten wir sicherstellen, dass shares eine nicht-negative Ganzzahl und price eine nicht-negative Gleitkommazahl ist. Wir verwenden Property (Eigenschafts)-Dekoratoren zusammen mit Gettern und Settern, um dies zu erreichen.
Anweisungen:
Öffnen Sie die Datei
stock.pyim Editor.Fügen Sie der
Stock-Klasse private Attribute_sharesund_pricehinzu und ändern Sie den Konstruktor, um sie zu verwenden:def __init__(self, name, shares, price): self.name = name self._shares = shares ## Using private attribute self._price = price ## Using private attributeDefinieren Sie Properties (Eigenschaften) für
sharesundpricemit Validierung:@property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, int): raise TypeError("Expected integer") if value < 0: raise ValueError("shares must be >= 0") self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, float): raise TypeError("Expected float") if value < 0: raise ValueError("price must be >= 0") self._price = valueAktualisieren Sie den Konstruktor, um die Property (Eigenschafts)-Setter für die Validierung zu verwenden:
def __init__(self, name, shares, price): self.name = name self.shares = shares ## Using property setter self.price = price ## Using property setterSpeichern Sie die Datei
stock.py.Erstellen Sie ein Testskript namens
test_validation.py:touch /home/labex/project/test_validation.pyFügen Sie den folgenden Code zur Datei
test_validation.pyhinzu:from stock import Stock ## Create a valid stock instance s = Stock('GOOG', 100, 490.10) print(f"Initial: Name={s.name}, Shares={s.shares}, Price={s.price}, Cost={s.cost}") ## Test valid updates try: s.shares = 50 ## Valid update print(f"After setting shares=50: Shares={s.shares}, Cost={s.cost}") except Exception as e: print(f"Error setting shares=50: {e}") try: s.price = 123.45 ## Valid update print(f"After setting price=123.45: Price={s.price}, Cost={s.cost}") except Exception as e: print(f"Error setting price=123.45: {e}") ## Test invalid updates try: s.shares = "50" ## Invalid type (string) print("This line should not execute") except Exception as e: print(f"Error setting shares='50': {e}") try: s.shares = -10 ## Invalid value (negative) print("This line should not execute") except Exception as e: print(f"Error setting shares=-10: {e}") try: s.price = "123.45" ## Invalid type (string) print("This line should not execute") except Exception as e: print(f"Error setting price='123.45': {e}") try: s.price = -10.0 ## Invalid value (negative) print("This line should not execute") except Exception as e: print(f"Error setting price=-10.0: {e}")Führen Sie das Testskript aus:
python /home/labex/project/test_validation.pySie sollten eine Ausgabe sehen, die erfolgreiche, gültige Aktualisierungen und entsprechende Fehlermeldungen für ungültige Aktualisierungen anzeigt.
Initial: Name=GOOG, Shares=100, Price=490.1, Cost=49010.0 After setting shares=50: Shares=50, Cost=24505.0 After setting price=123.45: Price=123.45, Cost=6172.5 Error setting shares='50': Expected integer Error setting shares=-10: shares must be >= 0 Error setting price='123.45': Expected float Error setting price=-10.0: price must be >= 0
Verwendung von __slots__ zur Speicheroptimierung
Das Attribut __slots__ beschränkt die Attribute, die eine Klasse haben kann. Es verhindert das Hinzufügen neuer Attribute zu Instanzen und reduziert die Speichernutzung.
In unserer Stock-Klasse verwenden wir __slots__, um:
- Die Attributerstellung auf die von uns definierten Attribute zu beschränken.
- Die Speichereffizienz zu verbessern, insbesondere beim Erstellen vieler Instanzen.
Anweisungen:
Öffnen Sie die Datei
stock.pyim Editor.Fügen Sie eine
__slots__-Klassenvariable hinzu, die alle von der Klasse verwendeten privaten Attributnamen auflistet:class Stock: ## Class variable for type conversions _types = (str, int, float) ## Define slots to restrict attribute creation __slots__ = ('name', '_shares', '_price') ## Rest of the class...Speichern Sie die Datei.
Erstellen Sie ein Testskript namens
test_slots.py:touch /home/labex/project/test_slots.pyFügen Sie den folgenden Code zur Datei
test_slots.pyhinzu:from stock import Stock ## Create a stock instance s = Stock('GOOG', 100, 490.10) ## Access existing attributes print(f"Name: {s.name}") print(f"Shares: {s.shares}") print(f"Price: {s.price}") print(f"Cost: {s.cost}") ## Try to add a new attribute try: s.extra = "This will fail" print(f"Extra: {s.extra}") except AttributeError as e: print(f"Error: {e}")Führen Sie das Testskript aus:
python /home/labex/project/test_slots.pySie sollten eine Ausgabe sehen, die zeigt, dass Sie auf die definierten Attribute zugreifen können, aber der Versuch, ein neues Attribut hinzuzufügen, einen
AttributeErrorauslöst.Name: GOOG Shares: 100 Price: 490.1 Cost: 49010.0 Error: 'Stock' object has no attribute 'extra'
Abgleich der Typvalidierung mit Klassenvariablen
Derzeit verwendet unsere Stock-Klasse sowohl die Klassenvariable _types als auch Property (Eigenschafts)-Setter für die Typbehandlung. Um die Konsistenz und Wartbarkeit zu verbessern, gleichen wir diese Mechanismen ab, sodass sie dieselben Typinformationen verwenden.
Anweisungen:
Öffnen Sie die Datei
stock.pyim Editor.Ändern Sie die Property (Eigenschafts)-Setter, um die in der Klassenvariable
_typesdefinierten Typen zu verwenden:@property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, self._types[1]): raise TypeError(f"Expected {self._types[1].__name__}") if value < 0: raise ValueError("shares must be >= 0") self._shares = value @property def price(self): return self._price @price.setter def price(self, value): if not isinstance(value, self._types[2]): raise TypeError(f"Expected {self._types[2].__name__}") if value < 0: raise ValueError("price must be >= 0") self._price = valueSpeichern Sie die Datei
stock.py.Erstellen Sie ein Testskript namens
test_subclass.py:touch /home/labex/project/test_subclass.pyFügen Sie den folgenden Code zur Datei
test_subclass.pyhinzu:from stock import Stock from decimal import Decimal ## Create a subclass with different types class DStock(Stock): _types = (str, int, Decimal) ## Test the base class s = Stock('GOOG', 100, 490.10) print(f"Stock: {s.name}, Shares: {s.shares}, Price: {s.price}, Cost: {s.cost}") ## Test valid update with float try: s.price = 500.25 print(f"Updated Stock price: {s.price}, Cost: {s.cost}") except Exception as e: print(f"Error updating Stock price: {e}") ## Test the subclass with Decimal ds = DStock('AAPL', 50, Decimal('142.50')) print(f"DStock: {ds.name}, Shares: {ds.shares}, Price: {ds.price}, Cost: {ds.cost}") ## Test invalid update with float (should require Decimal) try: ds.price = 150.75 print(f"Updated DStock price: {ds.price}") except Exception as e: print(f"Error updating DStock price: {e}") ## Test valid update with Decimal try: ds.price = Decimal('155.25') print(f"Updated DStock price: {ds.price}, Cost: {ds.cost}") except Exception as e: print(f"Error updating DStock price: {e}")Führen Sie das Testskript aus:
python /home/labex/project/test_subclass.pySie sollten sehen, dass die Basisklasse
StockFloat-Werte für den Preis akzeptiert, während die UnterklasseDStockDecimal-Werte benötigt.Stock: GOOG, Shares: 100, Price: 490.1, Cost: 49010.0 Updated Stock price: 500.25, Cost: 50025.0 DStock: AAPL, Shares: 50, Price: 142.50, Cost: 7125.00 Error updating DStock price: Expected Decimal Updated DStock price: 155.25, Cost: 7762.50
Zusammenfassung
In diesem Lab haben Sie gelernt, wie man private Attribute verwendet, Methoden in Properties (Eigenschaften) umwandelt, Property (Eigenschafts)-Validierung implementiert, __slots__ zur Speicheroptimierung einsetzt und die Typvalidierung mit Klassenvariablen abgleicht. Diese Techniken verbessern die Robustheit, Effizienz und Wartbarkeit Ihrer Klassen, indem sie Kapselung erzwingen und klare Schnittstellen bereitstellen.