介绍
在这个实验中,你将学习如何使用私有属性封装对象内部结构,并实现属性装饰器来控制属性访问。这些技术对于维护对象的完整性和确保正确的数据处理至关重要。
你还将了解如何使用 __slots__
限制属性的创建。我们将在整个实验过程中修改 stock.py
文件,以应用这些概念。
This tutorial is from open-source community. Access the source code
💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版
在这个实验中,你将学习如何使用私有属性封装对象内部结构,并实现属性装饰器来控制属性访问。这些技术对于维护对象的完整性和确保正确的数据处理至关重要。
你还将了解如何使用 __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__
进行内存优化,以及协调类型验证与类变量。这些技术通过强制封装和提供清晰的接口,增强了类的健壮性、效率和可维护性。