소개
이 랩에서는 private 속성을 사용하여 객체 내부를 캡슐화하고 속성 접근을 제어하기 위해 property 데코레이터를 구현하는 방법을 배우게 됩니다. 이러한 기술은 객체의 무결성을 유지하고 적절한 데이터 처리를 보장하는 데 필수적입니다.
또한 __slots__를 사용하여 속성 생성을 제한하는 방법도 이해하게 됩니다. 이 랩 전체에서 이러한 개념을 적용하기 위해 stock.py 파일을 수정할 것입니다.
Private 속성 구현하기
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.pytest_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
메서드를 Property 로 변환하기
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.pricestock.py파일을 저장합니다.에디터에서
test_property.py라는 새 파일을 만듭니다.touch /home/labex/project/test_property.pytest_property.py파일에 다음 코드를 추가하여Stock인스턴스를 생성하고costproperty 에 접근합니다.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 유효성 검사 구현하기
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 setterstock.py파일을 저장합니다.test_validation.py라는 테스트 스크립트를 만듭니다.touch /home/labex/project/test_validation.pytest_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.pytest_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 = valuestock.py파일을 저장합니다.test_subclass.py라는 테스트 스크립트를 만듭니다.touch /home/labex/project/test_subclass.pytest_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__를 사용하는 방법, 그리고 클래스 변수를 사용한 타입 유효성 검사를 일치시키는 방법을 배웠습니다. 이러한 기술은 캡슐화를 적용하고 명확한 인터페이스를 제공함으로써 클래스의 견고성, 효율성 및 유지 관리성을 향상시킵니다.