소개
이 랩에서는 private 속성을 사용하여 객체 내부를 캡슐화하고 속성 접근을 제어하기 위해 property 데코레이터를 구현하는 방법을 배우게 됩니다. 이러한 기술은 객체의 무결성을 유지하고 적절한 데이터 처리를 보장하는 데 필수적입니다.
또한 __slots__를 사용하여 속성 생성을 제한하는 방법도 이해하게 됩니다. 이 랩 전체에서 이러한 개념을 적용하기 위해 stock.py 파일을 수정할 것입니다.
This tutorial is from open-source community. Access the source code
이 랩에서는 private 속성을 사용하여 객체 내부를 캡슐화하고 속성 접근을 제어하기 위해 property 데코레이터를 구현하는 방법을 배우게 됩니다. 이러한 기술은 객체의 무결성을 유지하고 적절한 데이터 처리를 보장하는 데 필수적입니다.
또한 __slots__를 사용하여 속성 생성을 제한하는 방법도 이해하게 됩니다. 이 랩 전체에서 이러한 개념을 적용하기 위해 stock.py 파일을 수정할 것입니다.
Python 에서는 속성이 클래스 내에서 내부적으로 사용될 것임을 나타내기 위해 명명 규칙을 사용합니다. 이러한 속성 앞에 밑줄 (_) 을 붙입니다. 이는 다른 개발자에게 이러한 속성이 public 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...
이름이 변경된 변수 _types를 사용하도록 from_row 메서드를 업데이트합니다.
@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 의 Property 를 사용하면 속성처럼 계산된 값에 접근할 수 있습니다. 이렇게 하면 메서드를 호출할 때 괄호가 필요 없어 코드가 더 깔끔하고 일관성이 있습니다.
현재 Stock 클래스에는 주식의 총 비용을 계산하는 cost() 메서드가 있습니다.
def cost(self):
return self.shares * self.price
비용 값을 얻으려면 괄호와 함께 호출해야 합니다.
s = Stock('GOOG', 100, 490.10)
print(s.cost()) ## Calls the method
cost() 메서드를 property 로 변환하여 괄호 없이 비용 값에 접근할 수 있도록 개선할 수 있습니다.
s = Stock('GOOG', 100, 490.10)
print(s.cost) ## Accesses the property
지침:
에디터에서 stock.py 파일을 엽니다.
@property 데코레이터를 사용하여 cost() 메서드를 property 로 바꿉니다.
@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 property 에 접근합니다.
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
Property 를 사용하면 속성 값을 가져오고, 설정하고, 삭제하는 방식을 제어할 수도 있습니다. 이는 속성에 유효성 검사를 추가하여 값이 특정 기준을 충족하는지 확인하는 데 유용합니다.
Stock 클래스에서 shares가 음수가 아닌 정수이고 price가 음수가 아닌 float 인지 확인하려고 합니다. 이를 위해 getter 와 setter 와 함께 property 데코레이터를 사용합니다.
지침:
에디터에서 stock.py 파일을 엽니다.
private 속성 _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 를 정의합니다.
@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
유효성 검사를 위해 property 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 파일을 엽니다.
클래스에서 사용되는 모든 private 속성 이름을 나열하는 __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 클래스 변수와 property setter 를 모두 사용합니다. 일관성과 유지 관리성을 향상시키기 위해 이러한 메커니즘을 일치시켜 동일한 타입 정보를 사용하도록 합니다.
지침:
에디터에서 stock.py 파일을 엽니다.
_types 클래스 변수에 정의된 타입을 사용하도록 property setter 를 수정합니다.
@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 값을 허용하고, 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
이 랩에서는 private 속성 사용법, 메서드를 property 로 변환하는 방법, property 유효성 검사 구현, 메모리 최적화를 위해 __slots__를 사용하는 방법, 그리고 클래스 변수를 사용한 타입 유효성 검사를 일치시키는 방법을 배웠습니다. 이러한 기술은 캡슐화를 적용하고 명확한 인터페이스를 제공함으로써 클래스의 견고성, 효율성 및 유지 관리성을 향상시킵니다.