Private 속성 및 Property

Intermediate

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 private 속성을 사용하여 객체 내부를 캡슐화하고 속성 접근을 제어하기 위해 property 데코레이터를 구현하는 방법을 배우게 됩니다. 이러한 기술은 객체의 무결성을 유지하고 적절한 데이터 처리를 보장하는 데 필수적입니다.

또한 __slots__를 사용하여 속성 생성을 제한하는 방법도 이해하게 됩니다. 이 랩 전체에서 이러한 개념을 적용하기 위해 stock.py 파일을 수정할 것입니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 초급 레벨의 실험이며 완료율은 81%입니다.학습자들로부터 96%의 긍정적인 리뷰율을 받았습니다.

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 로 표시하겠습니다.

지침:

  1. 에디터에서 stock.py 파일을 엽니다.

  2. 선행 밑줄을 추가하여 types 클래스 변수를 수정하여 _types로 변경합니다.

    class Stock:
        ## Class variable for type conversions
        _types = (str, int, float)
    
        ## Rest of the class...
  3. 이름이 변경된 변수 _types를 사용하도록 from_row 메서드를 업데이트합니다.

    @classmethod
    def from_row(cls, row):
        values = [func(val) for func, val in zip(cls._types, row)]
        return cls(*values)
  4. stock.py 파일을 저장합니다.

  5. 변경 사항을 테스트하기 위해 test_stock.py라는 Python 스크립트를 만듭니다. 다음 명령을 사용하여 에디터에서 파일을 만들 수 있습니다.

    touch /home/labex/project/test_stock.py
  6. 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()}")
  7. 터미널에서 다음 명령을 사용하여 테스트 스크립트를 실행합니다.

    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

지침:

  1. 에디터에서 stock.py 파일을 엽니다.

  2. @property 데코레이터를 사용하여 cost() 메서드를 property 로 바꿉니다.

    @property
    def cost(self):
        return self.shares * self.price
  3. stock.py 파일을 저장합니다.

  4. 에디터에서 test_property.py라는 새 파일을 만듭니다.

    touch /home/labex/project/test_property.py
  5. 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
  6. 테스트 스크립트를 실행합니다.

    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 데코레이터를 사용합니다.

지침:

  1. 에디터에서 stock.py 파일을 엽니다.

  2. private 속성 _shares_priceStock 클래스에 추가하고 생성자를 수정하여 사용합니다.

    def __init__(self, name, shares, price):
        self.name = name
        self._shares = shares  ## Using private attribute
        self._price = price    ## Using private attribute
  3. 유효성 검사가 있는 sharesprice에 대한 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
  4. 유효성 검사를 위해 property setter 를 사용하도록 생성자를 업데이트합니다.

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares  ## Using property setter
        self.price = price    ## Using property setter
  5. stock.py 파일을 저장합니다.

  6. test_validation.py라는 테스트 스크립트를 만듭니다.

    touch /home/labex/project/test_validation.py
  7. 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}")
  8. 테스트 스크립트를 실행합니다.

    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__를 사용하여 다음을 수행합니다.

  1. 속성 생성을 정의된 속성으로만 제한합니다.
  2. 특히 많은 인스턴스를 생성할 때 메모리 효율성을 향상시킵니다.

지침:

  1. 에디터에서 stock.py 파일을 엽니다.

  2. 클래스에서 사용되는 모든 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...
  3. 파일을 저장합니다.

  4. test_slots.py라는 테스트 스크립트를 만듭니다.

    touch /home/labex/project/test_slots.py
  5. 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}")
  6. 테스트 스크립트를 실행합니다.

    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 를 모두 사용합니다. 일관성과 유지 관리성을 향상시키기 위해 이러한 메커니즘을 일치시켜 동일한 타입 정보를 사용하도록 합니다.

지침:

  1. 에디터에서 stock.py 파일을 엽니다.

  2. _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
  3. stock.py 파일을 저장합니다.

  4. test_subclass.py라는 테스트 스크립트를 만듭니다.

    touch /home/labex/project/test_subclass.py
  5. 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}")
  6. 테스트 스크립트를 실행합니다.

    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__를 사용하는 방법, 그리고 클래스 변수를 사용한 타입 유효성 검사를 일치시키는 방법을 배웠습니다. 이러한 기술은 캡슐화를 적용하고 명확한 인터페이스를 제공함으로써 클래스의 견고성, 효율성 및 유지 관리성을 향상시킵니다.