특수 메서드 재정의

Intermediate

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

소개

이 랩에서는 특수 메서드를 재정의하여 객체의 동작을 사용자 정의하는 방법을 배우게 됩니다. 또한 사용자가 정의한 객체가 인쇄되는 방식을 변경하고 객체를 비교 가능하게 만들 것입니다.

또한 컨텍스트 관리자 (context manager) 를 만드는 방법을 배우게 됩니다. 이 랩에서 수정할 파일은 stock.py입니다.

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

__repr__을 사용한 객체 표현 개선

Python 에서 객체는 두 가지 방식으로 문자열로 표현될 수 있습니다. 이러한 표현은 서로 다른 목적을 가지며 다양한 시나리오에서 유용합니다.

첫 번째 유형은 **문자열 표현 (string representation)**입니다. 이는 str() 함수에 의해 생성되며, print() 함수를 사용할 때 자동으로 호출됩니다. 문자열 표현은 사람이 읽을 수 있도록 설계되었습니다. 객체를 우리가 이해하고 해석하기 쉬운 형식으로 나타냅니다.

두 번째 유형은 **코드 표현 (code representation)**입니다. 이는 repr() 함수에 의해 생성됩니다. 코드 표현은 객체를 다시 생성하기 위해 작성해야 하는 코드를 보여줍니다. 이는 코드에서 객체를 정확하고 모호하지 않게 표현하는 데 더 중점을 둡니다.

Python 의 내장 date 클래스를 사용하여 구체적인 예를 살펴보겠습니다. 이를 통해 문자열 표현과 코드 표현의 차이점을 실제로 확인할 수 있습니다.

>>> from datetime import date
>>> d = date(2008, 7, 5)
>>> print(d)              ## Uses str()
2008-07-05
>>> d                     ## Uses repr()
datetime.date(2008, 7, 5)

이 예에서 print(d)를 사용하면 Python 은 date 객체 d에 대해 str() 함수를 호출하고, YYYY-MM-DD 형식의 사람이 읽을 수 있는 날짜를 얻습니다. 대화형 셸에서 단순히 d를 입력하면 Python 은 repr() 함수를 호출하고, date 객체를 다시 생성하는 데 필요한 코드를 볼 수 있습니다.

다양한 방법으로 repr() 문자열을 명시적으로 얻을 수 있습니다. 다음은 몇 가지 예입니다.

>>> print('The date is', repr(d))
The date is datetime.date(2008, 7, 5)
>>> print(f'The date is {d!r}')
The date is datetime.date(2008, 7, 5)
>>> print('The date is %r' % d)
The date is datetime.date(2008, 7, 5)

이제 이 개념을 Stock 클래스에 적용해 보겠습니다. __repr__ 메서드를 구현하여 클래스를 개선할 것입니다. 이 특수 메서드는 객체의 코드 표현이 필요할 때 Python 에서 호출됩니다.

이를 위해 편집기에서 stock.py 파일을 엽니다. 그런 다음 __repr__ 메서드를 Stock 클래스에 추가합니다. __repr__ 메서드는 Stock 객체를 다시 생성하는 데 필요한 코드를 보여주는 문자열을 반환해야 합니다.

def __repr__(self):
    return f"Stock('{self.name}', {self.shares}, {self.price})"

__repr__ 메서드를 추가한 후, 완성된 Stock 클래스는 다음과 같이 표시됩니다.

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, shares):
        self.shares -= shares

    def __repr__(self):
        return f"Stock('{self.name}', {self.shares}, {self.price})"

이제 수정된 Stock 클래스를 테스트해 보겠습니다. 터미널에서 다음 명령을 실행하여 Python 대화형 셸을 엽니다.

python3

대화형 셸이 열리면 다음 명령을 시도해 보십시오.

>>> import stock
>>> goog = stock.Stock('GOOG', 100, 490.10)
>>> goog
Stock('GOOG', 100, 490.1)

__repr__ 메서드가 주식 포트폴리오와 어떻게 작동하는지 확인할 수도 있습니다. 다음은 예입니다.

>>> import reader
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> portfolio
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44), Stock('MSFT', 200, 51.23), Stock('GE', 95, 40.37), Stock('MSFT', 50, 65.1), Stock('IBM', 100, 70.44)]

보시다시피, __repr__ 메서드는 대화형 셸 또는 디버거에 표시될 때 Stock 객체를 훨씬 더 유익하게 만들었습니다. 이제 각 객체를 다시 생성하는 데 필요한 코드를 표시하므로 디버깅 및 객체의 상태를 이해하는 데 매우 유용합니다.

테스트를 마쳤으면 다음 명령을 실행하여 Python 인터프리터를 종료할 수 있습니다.

>>> exit()

__eq__를 사용하여 객체 비교 가능하게 만들기

Python 에서 두 객체를 비교하기 위해 == 연산자를 사용하면 Python 은 실제로 __eq__ 특수 메서드를 호출합니다. 기본적으로 이 메서드는 객체의 id 를 비교합니다. 즉, 내용이 아닌 동일한 메모리 주소에 저장되어 있는지 확인합니다.

예를 들어 보겠습니다. Stock 클래스가 있고, 동일한 값을 가진 두 개의 Stock 객체를 생성한다고 가정해 보겠습니다. 그런 다음 == 연산자를 사용하여 비교하려고 합니다. Python 인터프리터에서 다음과 같이 할 수 있습니다.

먼저 터미널에서 다음 명령을 실행하여 Python 인터프리터를 시작합니다.

python3

그런 다음 Python 인터프리터에서 다음 코드를 실행합니다.

>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
False

보시다시피, 두 Stock 객체 ab가 속성 (name, shares, price) 에 대해 동일한 값을 갖더라도 Python 은 서로 다른 메모리 위치에 저장되어 있기 때문에 서로 다른 객체로 간주합니다.

이 문제를 해결하기 위해 Stock 클래스에서 __eq__ 메서드를 구현할 수 있습니다. 이 메서드는 Stock 클래스의 객체에 대해 == 연산자가 사용될 때마다 호출됩니다.

이제 stock.py 파일을 다시 엽니다. Stock 클래스 내부에 다음 __eq__ 메서드를 추가합니다.

def __eq__(self, other):
    return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
                                         (other.name, other.shares, other.price))

이 메서드가 수행하는 작업을 자세히 살펴보겠습니다.

  1. 먼저 isinstance 함수를 사용하여 other 객체가 Stock 클래스의 인스턴스인지 확인합니다. 이는 Stock 객체만 다른 Stock 객체와 비교하려는 경우 중요합니다.
  2. otherStock 객체인 경우, self 객체와 other 객체의 속성 (name, shares, price) 을 비교합니다.
  3. 두 객체 모두 Stock 인스턴스이고 해당 속성이 동일한 경우에만 True를 반환합니다.

__eq__ 메서드를 추가한 후, 완성된 Stock 클래스는 다음과 같이 표시됩니다.

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, shares):
        self.shares -= shares

    def __repr__(self):
        return f"Stock('{self.name}', {self.shares}, {self.price})"

    def __eq__(self, other):
        return isinstance(other, Stock) and ((self.name, self.shares, self.price) ==
                                             (other.name, other.shares, other.price))

이제 개선된 Stock 클래스를 테스트해 보겠습니다. Python 인터프리터를 다시 시작합니다.

python3

그런 다음 Python 인터프리터에서 다음 코드를 실행합니다.

>>> import stock
>>> a = stock.Stock('GOOG', 100, 490.1)
>>> b = stock.Stock('GOOG', 100, 490.1)
>>> a == b
True
>>> c = stock.Stock('GOOG', 200, 490.1)
>>> a == c
False

훌륭합니다! 이제 Stock 객체는 메모리 주소가 아닌 내용에 따라 제대로 비교할 수 있습니다.

__eq__ 메서드의 isinstance 검사는 매우 중요합니다. 이는 Stock 객체만 비교하는지 확인합니다. 이 검사가 없으면 Stock 객체를 Stock 객체가 아닌 다른 것과 비교하면 오류가 발생할 수 있습니다.

테스트를 마쳤으면 다음 명령을 실행하여 Python 인터프리터를 종료할 수 있습니다.

>>> exit()

컨텍스트 관리자 만들기

컨텍스트 관리자는 Python 에서 특별한 유형의 객체입니다. Python 에서 객체는 동작을 정의하는 다양한 메서드를 가질 수 있습니다. 컨텍스트 관리자는 특히 __enter____exit__의 두 가지 중요한 메서드를 정의합니다. 이러한 메서드는 with 문과 함께 작동합니다. with 문은 코드 블록에 대한 특정 컨텍스트를 설정하는 데 사용됩니다. 일련의 일이 발생하는 작은 환경을 생성하는 것으로 생각하고, 코드 블록이 완료되면 컨텍스트 관리자가 정리를 처리합니다.

이 단계에서는 매우 유용한 기능을 가진 컨텍스트 관리자를 만들 것입니다. 표준 출력 (sys.stdout) 을 임시로 리디렉션합니다. 표준 출력은 Python 프로그램의 일반적인 출력이 가는 곳으로, 일반적으로 콘솔입니다. 이를 리디렉션하여 출력을 대신 파일로 보낼 수 있습니다. 이는 콘솔에 표시될 출력을 저장하려는 경우 유용합니다.

먼저 컨텍스트 관리자 코드를 작성할 새 파일을 만들어야 합니다. 이 파일의 이름을 redirect.py로 지정합니다. 터미널에서 다음 명령을 사용하여 만들 수 있습니다.

touch /home/labex/project/redirect.py

이제 파일이 생성되었으므로 편집기에서 엽니다. 열리면 다음 Python 코드를 파일에 추가합니다.

import sys

class redirect_stdout:
    def __init__(self, out_file):
        self.out_file = out_file

    def __enter__(self):
        self.stdout = sys.stdout
        sys.stdout = self.out_file
        return self.out_file

    def __exit__(self, ty, val, tb):
        sys.stdout = self.stdout

이 컨텍스트 관리자가 수행하는 작업을 자세히 살펴보겠습니다.

  1. __init__: 이는 초기화 메서드입니다. redirect_stdout 클래스의 인스턴스를 만들 때 파일 객체를 전달합니다. 이 메서드는 해당 파일 객체를 인스턴스 변수 self.out_file에 저장합니다. 따라서 출력을 리디렉션하려는 위치를 기억합니다.
  2. __enter__:
    • 먼저 현재 sys.stdout을 저장합니다. 나중에 복원해야 하므로 중요합니다.
    • 그런 다음 현재 sys.stdout을 파일 객체로 바꿉니다. 이 시점부터 일반적으로 콘솔로 이동하는 모든 출력은 대신 파일로 이동합니다.
    • 마지막으로 파일 객체를 반환합니다. with 블록 내부에서 파일 객체를 사용하려는 경우 유용합니다.
  3. __exit__:
    • 이 메서드는 원래 sys.stdout을 복원합니다. 따라서 with 블록이 완료되면 출력이 정상적으로 콘솔로 다시 이동합니다.
    • 예외 유형 (ty), 예외 값 (val), 추적 정보 (tb) 의 세 가지 매개변수를 사용합니다. 이러한 매개변수는 컨텍스트 관리자 프로토콜에 필요합니다. with 블록 내에서 발생할 수 있는 예외를 처리하는 데 사용됩니다.

이제 컨텍스트 관리자를 테스트해 보겠습니다. 테이블의 출력을 파일로 리디렉션하는 데 사용합니다. 먼저 Python 인터프리터를 시작합니다.

python3

그런 다음 인터프리터에서 다음 Python 코드를 실행합니다.

>>> import stock, reader, tableformat
>>> from redirect import redirect_stdout
>>> portfolio = reader.read_csv_as_instances('portfolio.csv', stock.Stock)
>>> formatter = tableformat.create_formatter('text')
>>> with redirect_stdout(open('out.txt', 'w')) as file:
...     tableformat.print_table(portfolio, ['name','shares','price'], formatter)
...     file.close()
...
>>> ## Let's check the content of the output file
>>> print(open('out.txt').read())
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44

훌륭합니다! 컨텍스트 관리자가 예상대로 작동했습니다. 테이블 출력을 out.txt 파일로 성공적으로 리디렉션했습니다.

컨텍스트 관리자는 Python 에서 매우 강력한 기능입니다. 리소스를 적절하게 관리하는 데 도움이 됩니다. 다음은 컨텍스트 관리자의 몇 가지 일반적인 사용 사례입니다.

  • 파일 작업: 파일을 열 때 컨텍스트 관리자는 오류가 발생하더라도 파일이 제대로 닫히도록 할 수 있습니다.
  • 데이터베이스 연결: 사용을 마친 후 데이터베이스 연결이 닫히도록 할 수 있습니다.
  • 스레드 프로그램의 잠금: 컨텍스트 관리자는 안전한 방식으로 리소스를 잠그고 잠금 해제할 수 있습니다.
  • 환경 설정 임시 변경: 코드 블록에 대한 일부 설정을 변경한 다음 자동으로 복원할 수 있습니다.

이 패턴은 with 블록 내에서 예외가 발생하더라도 리소스가 제대로 정리되도록 보장하므로 매우 중요합니다.

테스트를 마쳤으면 Python 인터프리터를 종료할 수 있습니다.

>>> exit()

요약

이 Lab 에서는 __repr__ 메서드를 사용하여 객체의 문자열 표현을 사용자 정의하고, __eq__ 메서드를 사용하여 객체를 비교 가능하게 만들고, __enter____exit__ 메서드를 사용하여 컨텍스트 관리자를 만드는 방법을 배웠습니다. 이러한 특수 "dunder 메서드"는 Python 의 객체 지향 기능의 초석입니다.

클래스에서 이러한 메서드를 구현하면 객체가 내장된 유형처럼 동작하고 Python 의 언어 기능과 원활하게 통합될 수 있습니다. 특수 메서드는 사용자 정의 문자열 표현, 객체 비교 및 컨텍스트 관리와 같은 다양한 기능을 가능하게 합니다. Python 을 진행하면서 강력한 객체 모델을 활용하기 위한 더 많은 특수 메서드를 발견하게 될 것입니다.