はじめに
この実験(Lab)では、private 属性を使用してオブジェクトの内部をカプセル化し、属性アクセスを制御するために property デコレーターを実装する方法を学びます。これらのテクニックは、オブジェクトの整合性を維持し、適切なデータ処理を保証するために不可欠です。
また、__slots__ を使用して属性の作成を制限する方法も理解します。この実験(Lab)全体を通して stock.py ファイルを修正し、これらの概念を適用します。
Private 属性の実装
Python では、属性がクラス内で内部的に使用されることを示すために、命名規則を使用します。これらの属性にはアンダースコア (_) をプレフィックスとして付けます。これは、これらの属性がパブリック API の一部ではなく、クラスの外部から直接アクセスすべきではないことを他の開発者に知らせるものです。
stock.py ファイルの現在の Stock クラスを見てみましょう。types という名前のクラス変数があります。
class Stock:
## Class variable for type conversions
types = (str, int, float)
## Rest of the class...
types クラス変数は、行データを変換するために内部的に使用されます。これが実装の詳細であることを示すために、private としてマークします。
手順:
エディターで
stock.pyファイルを開きます。typesクラス変数を変更して、先頭にアンダースコアを追加し、_typesに変更します。class Stock: ## Class variable for type conversions _types = (str, int, float) ## Rest of the class...from_rowメソッドを更新して、名前を変更した変数_typesを使用します。@classmethod def from_row(cls, row): values = [func(val) for func, val in zip(cls._types, row)] return cls(*values)stock.pyファイルを保存します。test_stock.pyという名前の Python スクリプトを作成して、変更をテストします。次のコマンドを使用して、エディターでファイルを作成できます。touch /home/labex/project/test_stock.py次のコードを
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()}")ターミナルで次のコマンドを使用して、テストスクリプトを実行します。
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
手順:
エディターで
stock.pyファイルを開きます。@propertyデコレーターを使用して、cost()メソッドをプロパティに置き換えます。@property def cost(self): return self.shares * self.pricestock.pyファイルを保存します。エディターで
test_property.pyという名前の新しいファイルを作成します。touch /home/labex/project/test_property.py次のコードを
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テストスクリプトを実行します。
python /home/labex/project/test_property.py次のような出力が表示されるはずです。
Stock: GOOG Shares: 100 Price: 490.1 Cost: 49010.0
プロパティの検証の実装
プロパティを使用すると、属性値の取得、設定、および削除の方法を制御することもできます。これは、属性に検証を追加し、値が特定の基準を満たしていることを確認するのに役立ちます。
Stock クラスでは、shares が負でない整数であり、price が負でない浮動小数点数であることを確認する必要があります。これを実現するために、getter(ゲッター)と setter(セッター)とともにプロパティデコレーターを使用します。
手順:
エディターで
stock.pyファイルを開きます。private 属性
_sharesと_priceをStockクラスに追加し、コンストラクター(constructor)を変更してそれらを使用します。def __init__(self, name, shares, price): self.name = name self._shares = shares ## Using private attribute self._price = price ## Using private attribute検証を使用して
sharesとpriceのプロパティを定義します。@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コンストラクターを更新して、検証のためにプロパティセッターを使用します。
def __init__(self, name, shares, price): self.name = name self.shares = shares ## Using property setter self.price = price ## Using property setterstock.pyファイルを保存します。test_validation.pyという名前のテストスクリプトを作成します。touch /home/labex/project/test_validation.py次のコードを
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}")テストスクリプトを実行します。
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__ を使用して以下を行います。
- 属性の作成を、定義した属性のみに制限します。
- 特に多数のインスタンスを作成する場合に、メモリ効率を向上させます。
手順:
エディターで
stock.pyファイルを開きます。__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...ファイルを保存します。
test_slots.pyという名前のテストスクリプトを作成します。touch /home/labex/project/test_slots.py次のコードを
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}")テストスクリプトを実行します。
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 クラス変数とプロパティセッターの両方を使用しています。一貫性と保守性を向上させるために、これらのメカニズムを調整して、同じ型情報を使用するようにします。
手順:
エディターで
stock.pyファイルを開きます。_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 = valuestock.pyファイルを保存します。test_subclass.pyという名前のテストスクリプトを作成します。touch /home/labex/project/test_subclass.py次のコードを
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}")テストスクリプトを実行します。
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__ の使用方法、およびクラス変数との型検証の調整方法を学びました。これらのテクニックは、カプセル化を強制し、明確なインターフェースを提供することにより、クラスの堅牢性、効率、および保守性を向上させます。