소개
이 랩에서는 특수 메서드를 재정의하여 객체의 동작을 사용자 정의하는 방법을 배우게 됩니다. 또한 사용자가 정의한 객체가 인쇄되는 방식을 변경하고 객체를 비교 가능하게 만들 것입니다.
또한 컨텍스트 관리자 (context manager) 를 만드는 방법을 배우게 됩니다. 이 랩에서 수정할 파일은 stock.py입니다.
이 랩에서는 특수 메서드를 재정의하여 객체의 동작을 사용자 정의하는 방법을 배우게 됩니다. 또한 사용자가 정의한 객체가 인쇄되는 방식을 변경하고 객체를 비교 가능하게 만들 것입니다.
또한 컨텍스트 관리자 (context manager) 를 만드는 방법을 배우게 됩니다. 이 랩에서 수정할 파일은 stock.py입니다.
__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 객체 a와 b가 속성 (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))
이 메서드가 수행하는 작업을 자세히 살펴보겠습니다.
isinstance 함수를 사용하여 other 객체가 Stock 클래스의 인스턴스인지 확인합니다. 이는 Stock 객체만 다른 Stock 객체와 비교하려는 경우 중요합니다.other가 Stock 객체인 경우, self 객체와 other 객체의 속성 (name, shares, price) 을 비교합니다.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
이 컨텍스트 관리자가 수행하는 작업을 자세히 살펴보겠습니다.
__init__: 이는 초기화 메서드입니다. redirect_stdout 클래스의 인스턴스를 만들 때 파일 객체를 전달합니다. 이 메서드는 해당 파일 객체를 인스턴스 변수 self.out_file에 저장합니다. 따라서 출력을 리디렉션하려는 위치를 기억합니다.__enter__:
sys.stdout을 저장합니다. 나중에 복원해야 하므로 중요합니다.sys.stdout을 파일 객체로 바꿉니다. 이 시점부터 일반적으로 콘솔로 이동하는 모든 출력은 대신 파일로 이동합니다.with 블록 내부에서 파일 객체를 사용하려는 경우 유용합니다.__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 을 진행하면서 강력한 객체 모델을 활용하기 위한 더 많은 특수 메서드를 발견하게 될 것입니다.