介绍
在这个实验中,你将学习如何使用私有属性封装对象内部结构,并实现属性装饰器来控制属性访问。这些技术对于维护对象的完整性和确保正确的数据处理至关重要。
你还将了解如何使用 __slots__ 限制属性的创建。我们将在整个实验过程中修改 stock.py 文件,以应用这些概念。
实现私有属性
在 Python 中,我们使用命名约定来表明一个属性旨在供类内部使用。我们在这些属性前加上下划线 (_)。这向其他开发者发出信号,表明这些属性不是公共 API 的一部分,不应从类外部直接访问。
让我们看看 stock.py 文件中当前的 Stock 类。它有一个名为 types 的类变量。
class Stock:
## Class variable for type conversions
types = (str, int, float)
## Rest of the class...
types 类变量在内部用于转换行数据。为了表明这是一个实现细节,我们将它标记为私有。
说明:
在编辑器中打开
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 中的属性(Properties)允许你像访问属性一样访问计算值。这消除了调用方法时使用括号的需要,使你的代码更简洁和一致。
目前,我们的 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.price保存
stock.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文件。将私有属性
_shares和_price添加到Stock类,并修改构造函数以使用它们: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更新构造函数以使用属性 setter 进行验证:
def __init__(self, name, shares, price): self.name = name self.shares = shares ## Using property setter self.price = price ## Using property setter保存
stock.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 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 类变量和属性 setter 来处理类型。为了提高一致性和可维护性,我们将协调这些机制,以便它们使用相同的类型信息。
说明:
在编辑器中打开
stock.py文件。修改属性 setter 以使用
_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保存
stock.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接受价格的浮点数值(float values),而子类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)中,你已经学习了如何使用私有属性、将方法转换为属性(properties)、实现属性验证、使用 __slots__ 进行内存优化,以及协调类型验证与类变量。这些技术通过强制封装和提供清晰的接口,增强了类的健壮性、效率和可维护性。