Private Attribute und Properties (Eigenschaften)

PythonPythonIntermediate
Jetzt üben

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

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.

Dies ist ein Guided Lab, das schrittweise Anweisungen bietet, um Ihnen beim Lernen und Üben zu helfen. Befolgen Sie die Anweisungen sorgfältig, um jeden Schritt abzuschließen und praktische Erfahrungen zu sammeln. Historische Daten zeigen, dass dies ein Labor der Stufe Anfänger mit einer Abschlussquote von 81% ist. Es hat eine positive Bewertungsrate von 96% von den Lernenden erhalten.

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:

  1. Öffnen Sie die Datei stock.py im Editor.

  2. Ä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...
  3. Aktualisieren Sie die Methode from_row, um die umbenannte Variable _types zu verwenden.

    @classmethod
    def from_row(cls, row):
        values = [func(val) for func, val in zip(cls._types, row)]
        return cls(*values)
  4. Speichern Sie die Datei stock.py.

  5. 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.py
  6. Fügen Sie den folgenden Code zur Datei test_stock.py hinzu. Dieser Code erstellt Instanzen der Stock-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()}")
  7. Führen Sie das Testskript mit dem folgenden Befehl im Terminal aus:

    python /home/labex/project/test_stock.py

    Sie 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:

  1. Öffnen Sie die Datei stock.py im Editor.

  2. Ersetzen Sie die cost()-Methode durch eine Property (Eigenschaft) mithilfe des @property-Dekorators:

    @property
    def cost(self):
        return self.shares * self.price
  3. Speichern Sie die Datei stock.py.

  4. Erstellen Sie eine neue Datei namens test_property.py im Editor:

    touch /home/labex/project/test_property.py
  5. Fügen Sie den folgenden Code zur Datei test_property.py hinzu, um eine Stock-Instanz zu erstellen und auf die cost-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 property
  6. Führen Sie das Testskript aus:

    python /home/labex/project/test_property.py

    Sie sollten eine ähnliche Ausgabe sehen wie:

    Stock: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
✨ Lösung prüfen und üben

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:

  1. Öffnen Sie die Datei stock.py im Editor.

  2. Fügen Sie der Stock-Klasse private Attribute _shares und _price hinzu 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 attribute
  3. Definieren Sie Properties (Eigenschaften) für shares und price mit 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 = value
  4. Aktualisieren 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 setter
  5. Speichern Sie die Datei stock.py.

  6. Erstellen Sie ein Testskript namens test_validation.py:

    touch /home/labex/project/test_validation.py
  7. Fügen Sie den folgenden Code zur Datei test_validation.py hinzu:

    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}")
  8. Führen Sie das Testskript aus:

    python /home/labex/project/test_validation.py

    Sie 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
✨ Lösung prüfen und üben

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:

  1. Die Attributerstellung auf die von uns definierten Attribute zu beschränken.
  2. Die Speichereffizienz zu verbessern, insbesondere beim Erstellen vieler Instanzen.

Anweisungen:

  1. Öffnen Sie die Datei stock.py im Editor.

  2. 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...
  3. Speichern Sie die Datei.

  4. Erstellen Sie ein Testskript namens test_slots.py:

    touch /home/labex/project/test_slots.py
  5. Fügen Sie den folgenden Code zur Datei test_slots.py hinzu:

    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}")
  6. Führen Sie das Testskript aus:

    python /home/labex/project/test_slots.py

    Sie sollten eine Ausgabe sehen, die zeigt, dass Sie auf die definierten Attribute zugreifen können, aber der Versuch, ein neues Attribut hinzuzufügen, einen AttributeError auslöst.

    Name: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
    Error: 'Stock' object has no attribute 'extra'
✨ Lösung prüfen und üben

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:

  1. Öffnen Sie die Datei stock.py im Editor.

  2. Ändern Sie die Property (Eigenschafts)-Setter, um die in der Klassenvariable _types definierten 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 = value
  3. Speichern Sie die Datei stock.py.

  4. Erstellen Sie ein Testskript namens test_subclass.py:

    touch /home/labex/project/test_subclass.py
  5. Fügen Sie den folgenden Code zur Datei test_subclass.py hinzu:

    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}")
  6. Führen Sie das Testskript aus:

    python /home/labex/project/test_subclass.py

    Sie sollten sehen, dass die Basisklasse Stock Float-Werte für den Preis akzeptiert, während die Unterklasse DStock Decimal-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.