Private 属性とプロパティ

PythonPythonIntermediate
今すぐ練習

This tutorial is from open-source community. Access the source code

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

この実験(Lab)では、private 属性を使用してオブジェクトの内部をカプセル化し、属性アクセスを制御するために property デコレーターを実装する方法を学びます。これらのテクニックは、オブジェクトの整合性を維持し、適切なデータ処理を保証するために不可欠です。

また、__slots__ を使用して属性の作成を制限する方法も理解します。この実験(Lab)全体を通して stock.py ファイルを修正し、これらの概念を適用します。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("Encapsulation") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("Raising Exceptions") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/conditional_statements -.-> lab-132494{{"Private 属性とプロパティ"}} python/classes_objects -.-> lab-132494{{"Private 属性とプロパティ"}} python/encapsulation -.-> lab-132494{{"Private 属性とプロパティ"}} python/raising_exceptions -.-> lab-132494{{"Private 属性とプロパティ"}} python/decorators -.-> lab-132494{{"Private 属性とプロパティ"}} end

Private 属性の実装

Python では、属性がクラス内で内部的に使用されることを示すために、命名規則を使用します。これらの属性にはアンダースコア (_) をプレフィックスとして付けます。これは、これらの属性がパブリック API の一部ではなく、クラスの外部から直接アクセスすべきではないことを他の開発者に知らせるものです。

stock.py ファイルの現在の Stock クラスを見てみましょう。types という名前のクラス変数があります。

class Stock:
    ## Class variable for type conversions
    types = (str, int, float)

    ## Rest of the class...

types クラス変数は、行データを変換するために内部的に使用されます。これが実装の詳細であることを示すために、private としてマークします。

手順:

  1. エディターで stock.py ファイルを開きます。

  2. types クラス変数を変更して、先頭にアンダースコアを追加し、_types に変更します。

    class Stock:
        ## Class variable for type conversions
        _types = (str, int, float)
    
        ## Rest of the class...
  3. from_row メソッドを更新して、名前を変更した変数 _types を使用します。

    @classmethod
    def from_row(cls, row):
        values = [func(val) for func, val in zip(cls._types, row)]
        return cls(*values)
  4. stock.py ファイルを保存します。

  5. test_stock.py という名前の Python スクリプトを作成して、変更をテストします。次のコマンドを使用して、エディターでファイルを作成できます。

    touch /home/labex/project/test_stock.py
  6. 次のコードを test_stock.py ファイルに追加します。このコードは、Stock クラスのインスタンスを作成し、それらに関する情報を出力します。

    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. ターミナルで次のコマンドを使用して、テストスクリプトを実行します。

    python /home/labex/project/test_stock.py

    次のような出力が表示されるはずです。

    Name: GOOG, Shares: 100, Price: 490.1
    Cost: 49010.0
    Name: AAPL, Shares: 50, Price: 142.5
    Cost: 7125.0

メソッドをプロパティに変換する

Python のプロパティを使用すると、計算された値を属性のようにアクセスできます。これにより、メソッドを呼び出すときに括弧が不要になり、コードがよりクリーンで一貫性のあるものになります。

現在、Stock クラスには、株式の総コストを計算する cost() メソッドがあります。

def cost(self):
    return self.shares * self.price

コスト値を取得するには、括弧を付けて呼び出す必要があります。

s = Stock('GOOG', 100, 490.10)
print(s.cost())  ## Calls the method

cost() メソッドをプロパティに変換することで、これを改善し、括弧なしでコスト値にアクセスできるようにすることができます。

s = Stock('GOOG', 100, 490.10)
print(s.cost)  ## Accesses the property

手順:

  1. エディターで stock.py ファイルを開きます。

  2. @property デコレーターを使用して、cost() メソッドをプロパティに置き換えます。

    @property
    def cost(self):
        return self.shares * self.price
  3. stock.py ファイルを保存します。

  4. エディターで test_property.py という名前の新しいファイルを作成します。

    touch /home/labex/project/test_property.py
  5. 次のコードを test_property.py ファイルに追加して、Stock インスタンスを作成し、cost プロパティにアクセスします。

    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. テストスクリプトを実行します。

    python /home/labex/project/test_property.py

    次のような出力が表示されるはずです。

    Stock: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
✨ 解答を確認して練習

プロパティの検証の実装

プロパティを使用すると、属性値の取得、設定、および削除の方法を制御することもできます。これは、属性に検証を追加し、値が特定の基準を満たしていることを確認するのに役立ちます。

Stock クラスでは、shares が負でない整数であり、price が負でない浮動小数点数であることを確認する必要があります。これを実現するために、getter(ゲッター)と setter(セッター)とともにプロパティデコレーターを使用します。

手順:

  1. エディターで stock.py ファイルを開きます。

  2. private 属性 _shares_priceStock クラスに追加し、コンストラクター(constructor)を変更してそれらを使用します。

    def __init__(self, name, shares, price):
        self.name = name
        self._shares = shares  ## Using private attribute
        self._price = price    ## Using private attribute
  3. 検証を使用して sharesprice のプロパティを定義します。

    @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. コンストラクターを更新して、検証のためにプロパティセッターを使用します。

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares  ## Using property setter
        self.price = price    ## Using property setter
  5. stock.py ファイルを保存します。

  6. test_validation.py という名前のテストスクリプトを作成します。

    touch /home/labex/project/test_validation.py
  7. 次のコードを test_validation.py ファイルに追加します。

    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. テストスクリプトを実行します。

    python /home/labex/project/test_validation.py

    有効な更新が成功し、無効な更新に対して適切なエラーメッセージが表示される出力が表示されるはずです。

    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
✨ 解答を確認して練習

メモリ最適化のための __slots__ の使用

__slots__ 属性は、クラスが持つことができる属性を制限します。インスタンスへの新しい属性の追加を防ぎ、メモリ使用量を削減します。

Stock クラスでは、__slots__ を使用して以下を行います。

  1. 属性の作成を、定義した属性のみに制限します。
  2. 特に多数のインスタンスを作成する場合に、メモリ効率を向上させます。

手順:

  1. エディターで stock.py ファイルを開きます。

  2. __slots__ クラス変数(class variable)を追加し、クラスで使用されるすべての private 属性名をリストします。

    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. ファイルを保存します。

  4. test_slots.py という名前のテストスクリプトを作成します。

    touch /home/labex/project/test_slots.py
  5. 次のコードを test_slots.py ファイルに追加します。

    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. テストスクリプトを実行します。

    python /home/labex/project/test_slots.py

    定義された属性にアクセスできるものの、新しい属性を追加しようとすると AttributeError が発生することが出力に表示されるはずです。

    Name: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
    Error: 'Stock' object has no attribute 'extra'
✨ 解答を確認して練習

クラス変数との型検証の調整

現在、Stock クラスは、型処理のために _types クラス変数とプロパティセッターの両方を使用しています。一貫性と保守性を向上させるために、これらのメカニズムを調整して、同じ型情報を使用するようにします。

手順:

  1. エディターで stock.py ファイルを開きます。

  2. _types クラス変数で定義された型を使用するようにプロパティセッターを変更します。

    @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. stock.py ファイルを保存します。

  4. test_subclass.py という名前のテストスクリプトを作成します。

    touch /home/labex/project/test_subclass.py
  5. 次のコードを test_subclass.py ファイルに追加します。

    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. テストスクリプトを実行します。

    python /home/labex/project/test_subclass.py

    基本の Stock クラスは price(価格)に float(浮動小数点数)値を受け入れますが、DStock サブクラスは Decimal 値を必要とすることがわかります。

    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

まとめ

この実験(Lab)では、private 属性の使用方法、メソッドをプロパティに変換する方法、プロパティの検証の実装方法、メモリ最適化のための __slots__ の使用方法、およびクラス変数との型検証の調整方法を学びました。これらのテクニックは、カプセル化を強制し、明確なインターフェースを提供することにより、クラスの堅牢性、効率、および保守性を向上させます。