소개
Python 의 객체 지향 프로그래밍 (OOP) 기능은 표현력 있고 사용자 정의된 객체를 생성하기 위한 강력한 도구를 제공합니다. 이 중, 특수 메서드 __init__, __str__, 그리고 __repr__은 Python 객체의 동작과 표현을 정의하는 데 중요한 역할을 합니다.
이 튜토리얼에서는 이러한 특수 메서드를 살펴보고, 그 목적을 이해하며, Python 클래스에서 효과적으로 구현하는 방법을 배웁니다. 이 랩을 마치면, 더욱 직관적이고 사용자 친화적인 Python 객체를 만들 수 있게 될 것입니다.
__init__ 메서드를 사용하여 클래스 생성하기
우리가 살펴볼 첫 번째 특수 메서드는 객체가 생성될 때 호출되는 __init__입니다. 이 메서드를 사용하면 객체의 속성을 초기화할 수 있습니다.
__init__ 메서드 이해하기
생성자 (constructor) 라고도 알려진 __init__ 메서드는 클래스의 새 인스턴스를 생성할 때 자동으로 호출됩니다. 이 메서드의 주요 목적은 속성에 값을 할당하여 객체의 초기 상태를 설정하는 것입니다.
__init__ 메서드를 사용하여 간단한 Python 클래스를 생성하는 것으로 시작해 보겠습니다.
LabEx 환경에서 터미널을 엽니다.
프로젝트 디렉토리로 이동합니다.
cd ~/project코드 편집기를 사용하여
person.py라는 새 Python 파일을 생성합니다.편집기에서 다음 코드를
person.py에 추가합니다.class Person: def __init__(self, name, age): self.name = name self.age = age ## Create a Person object person1 = Person("Alice", 30) ## Access the attributes print(f"Name: {person1.name}") print(f"Age: {person1.age}")파일을 저장합니다.
Python 스크립트를 실행합니다.
python3 person.py
다음과 같은 출력을 볼 수 있습니다.
Name: Alice
Age: 30
__init__ 메서드 설명
위의 코드에서:
name과age의 세 가지 매개변수를 사용하는__init__메서드가 있는Person클래스를 정의했습니다.self매개변수는 생성 중인 인스턴스를 참조하며, 객체가 생성될 때 자동으로 전달됩니다.__init__메서드 내에서self.name및self.age를 사용하여name과age의 값을 객체의 속성에 할당합니다.person1 = Person("Alice", 30)으로 새Person객체를 생성하면__init__메서드가 자동으로 호출됩니다.- 그런 다음 점 표기법을 사용하여 속성에 액세스할 수 있습니다:
person1.name및person1.age.
더 많은 기능 추가하기
현재 연도와 사람의 나이를 기준으로 출생 연도를 계산하는 메서드를 추가하여 Person 클래스를 향상시켜 보겠습니다.
편집기에서
person.py를 엽니다.출생 연도를 계산하는 메서드를 포함하도록 코드를 업데이트합니다.
import datetime class Person: def __init__(self, name, age): self.name = name self.age = age def birth_year(self): current_year = datetime.datetime.now().year return current_year - self.age ## Create a Person object person1 = Person("Alice", 30) ## Access the attributes and call the method print(f"Name: {person1.name}") print(f"Age: {person1.age}") print(f"Birth Year: {person1.birth_year()}")파일을 저장합니다.
Python 스크립트를 실행합니다.
python3 person.py
이제 출력에 계산된 출생 연도가 포함되어야 합니다.
Name: Alice
Age: 30
Birth Year: 1993
(실제 출생 연도는 스크립트를 실행하는 현재 연도에 따라 달라집니다.)
이제 객체 속성을 초기화하기 위해 __init__ 메서드를 사용하고 해당 속성을 기반으로 계산을 수행하는 메서드를 추가하여 Python 클래스를 만들었습니다.
__str__ 메서드 구현하기
이제 __init__ 메서드를 사용하여 클래스를 만드는 방법을 이해했으므로, __str__이라는 또 다른 특수 메서드를 살펴보겠습니다. 이 메서드를 사용하면 객체를 문자열로 표현하는 방법을 정의할 수 있습니다.
__str__ 메서드 이해하기
__str__ 메서드는 객체에 대해 str() 함수를 사용하거나 print() 함수를 사용하여 객체를 인쇄할 때 호출됩니다. 이 메서드는 객체의 사람이 읽을 수 있는 문자열 표현을 반환해야 합니다.
__str__ 메서드를 포함하도록 Person 클래스를 업데이트해 보겠습니다.
편집기에서
person.py를 엽니다.__str__메서드를 포함하도록 코드를 업데이트합니다.import datetime class Person: def __init__(self, name, age): self.name = name self.age = age def birth_year(self): current_year = datetime.datetime.now().year return current_year - self.age def __str__(self): return f"{self.name}, {self.age} years old" ## Create a Person object person1 = Person("Alice", 30) ## Access the attributes and call the method print(f"Name: {person1.name}") print(f"Age: {person1.age}") print(f"Birth Year: {person1.birth_year()}") ## Use the __str__ method implicitly print("\nString representation of the object:") print(person1) ## Use the __str__ method explicitly print("\nExplicit string conversion:") print(str(person1))파일을 저장합니다.
Python 스크립트를 실행합니다.
python3 person.py
다음과 유사한 출력을 볼 수 있습니다.
Name: Alice
Age: 30
Birth Year: 1993
String representation of the object:
Alice, 30 years old
Explicit string conversion:
Alice, 30 years old
__str__의 작동 방식
위의 코드에서:
- 사람의 이름과 나이가 포함된 형식화된 문자열을 반환하는
__str__메서드를 정의했습니다. print(person1)을 호출하면 Python 은 표시할 내용을 결정하기 위해 자동으로__str__메서드를 호출합니다.str(person1)을 사용하여 객체를 문자열로 명시적으로 변환할 수도 있으며, 이 또한__str__메서드를 호출합니다.
__str__이 없으면 어떻게 될까요?
__str__ 메서드의 중요성을 이해하기 위해, 이 메서드를 정의하지 않으면 어떻게 되는지 살펴보겠습니다.
without_str.py라는 새 파일을 생성합니다.다음 코드를 추가합니다.
class SimpleClass: def __init__(self, value): self.value = value ## Create an object obj = SimpleClass(42) ## Print the object print(obj)파일을 저장합니다.
스크립트를 실행합니다.
python3 without_str.py
다음과 같은 출력을 볼 수 있습니다.
<__main__.SimpleClass object at 0x7f2d8c3e9d90>
출력이 그다지 유익하지 않습니다. 클래스 이름과 객체의 메모리 주소를 표시하지만, 내용은 표시하지 않습니다. 이것이 객체를 더 사용자 친화적으로 만들기 위해 적절한 __str__ 메서드를 구현하는 것이 중요한 이유입니다.
실습
__str__ 메서드가 있는 Book이라는 새 클래스를 만들어 보겠습니다.
book.py라는 새 파일을 생성합니다.다음 코드를 추가합니다.
class Book: def __init__(self, title, author, pages): self.title = title self.author = author self.pages = pages def __str__(self): return f'"{self.title}" by {self.author} ({self.pages} pages)' ## Create book objects book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180) book2 = Book("To Kill a Mockingbird", "Harper Lee", 281) ## Print the books print(book1) print(book2)파일을 저장합니다.
스크립트를 실행합니다.
python3 book.py
출력은 다음과 같아야 합니다.
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)
이제 __str__ 메서드를 사용하여 객체의 사람이 읽을 수 있는 문자열 표현을 만드는 방법을 이해했습니다.
__repr__ 메서드 구현하기
__str__ 외에도 Python 은 문자열 표현을 위한 또 다른 특수 메서드인 __repr__을 제공합니다. __str__이 사람이 읽을 수 있는 표현을 제공하기 위한 것이라면, __repr__은 가능한 경우 객체를 다시 생성하는 데 사용할 수 있는 객체의 모호하지 않은 표현을 제공하기 위한 것입니다.
__repr__ 메서드 이해하기
__repr__ 메서드는 객체에 대해 repr() 함수를 사용하거나 대화형 세션에서 객체를 표시할 때 호출됩니다. 이 메서드는 eval()에 전달될 때 (가능한 경우) 동등한 객체를 생성하는 문자열을 반환해야 합니다.
__repr__ 메서드를 포함하도록 Book 클래스를 업데이트해 보겠습니다.
편집기에서
book.py를 엽니다.__repr__메서드를 포함하도록 코드를 업데이트합니다.class Book: def __init__(self, title, author, pages): self.title = title self.author = author self.pages = pages def __str__(self): return f'"{self.title}" by {self.author} ({self.pages} pages)' def __repr__(self): return f'Book("{self.title}", "{self.author}", {self.pages})' ## Create book objects book1 = Book("The Great Gatsby", "F. Scott Fitzgerald", 180) book2 = Book("To Kill a Mockingbird", "Harper Lee", 281) ## Print the books (uses __str__) print("String representation (using __str__):") print(book1) print(book2) ## Get the representation (uses __repr__) print("\nRepresentation (using __repr__):") print(repr(book1)) print(repr(book2))파일을 저장합니다.
스크립트를 실행합니다.
python3 book.py
다음과 같은 출력을 볼 수 있습니다.
String representation (using __str__):
"The Great Gatsby" by F. Scott Fitzgerald (180 pages)
"To Kill a Mockingbird" by Harper Lee (281 pages)
Representation (using __repr__):
Book("The Great Gatsby", "F. Scott Fitzgerald", 180)
Book("To Kill a Mockingbird", "Harper Lee", 281)
__str__과 __repr__의 차이점
__str__과 __repr__의 주요 차이점은 다음과 같습니다.
__str__은 사람이 읽을 수 있는 출력을 위한 것이고,__repr__은 개발자 및 디버깅을 위한 것입니다.__str__이 정의되지 않았지만__repr__이 정의된 경우, Python 은str()또는print()에 대한 대체로__repr__을 사용합니다.__repr__은 이상적으로eval()에 전달될 때 객체를 다시 생성할 수 있는 문자열을 반환해야 하지만, 이것이 항상 가능하거나 필요한 것은 아닙니다.
__repr__과 함께 eval() 함수 사용하기
올바르게 구현되면 __repr__에서 반환된 문자열을 eval()과 함께 사용하여 객체를 다시 생성할 수 있습니다. 이를 실제로 살펴보겠습니다.
repr_eval.py라는 새 파일을 생성합니다.다음 코드를 추가합니다.
class Point: def __init__(self, x, y): self.x = x self.y = y def __str__(self): return f"Point at ({self.x}, {self.y})" def __repr__(self): return f"Point({self.x}, {self.y})" ## Create a point p1 = Point(3, 4) ## Get the repr string repr_str = repr(p1) print(f"Representation: {repr_str}") ## Use eval to recreate the object p2 = eval(repr_str) print(f"Recreated object: {p2}") ## Verify they have the same values print(f"p1.x = {p1.x}, p1.y = {p1.y}") print(f"p2.x = {p2.x}, p2.y = {p2.y}")파일을 저장합니다.
스크립트를 실행합니다.
python3 repr_eval.py
다음과 같은 출력을 볼 수 있습니다.
Representation: Point(3, 4)
Recreated object: Point at (3, 4)
p1.x = 3, p1.y = 4
p2.x = 3, p2.y = 4
이것은 __repr__에서 반환된 문자열과 eval() 함수를 사용하여 원래 객체를 다시 생성할 수 있음을 보여줍니다.
각 메서드를 언제 사용해야 할까요?
- 객체의 초기 상태를 설정하려면
__init__을 사용합니다. - 최종 사용자를 위해 사람이 읽을 수 있는 표현을 제공하려면
__str__을 사용합니다. - 개발자 및 디버깅을 위해 정확하고 모호하지 않은 표현을 제공하려면
__repr__을 사용합니다.
실습: 완전한 예시
세 가지 특수 메서드 모두를 사용하여 Rectangle 클래스를 만들어 보겠습니다.
rectangle.py라는 새 파일을 생성합니다.다음 코드를 추가합니다.
class Rectangle: def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height def perimeter(self): return 2 * (self.width + self.height) def __str__(self): return f"Rectangle with width {self.width} and height {self.height}" def __repr__(self): return f"Rectangle({self.width}, {self.height})" ## Create rectangles rect1 = Rectangle(5, 10) rect2 = Rectangle(3, 7) ## Display information about the rectangles print(f"Rectangle 1: {rect1}") print(f"Area: {rect1.area()}") print(f"Perimeter: {rect1.perimeter()}") print(f"Representation: {repr(rect1)}") print("\nRectangle 2: {0}".format(rect2)) print(f"Area: {rect2.area()}") print(f"Perimeter: {rect2.perimeter()}") print(f"Representation: {repr(rect2)}") ## Recreate a rectangle using eval rect3 = eval(repr(rect1)) print(f"\nRecreated rectangle: {rect3}") print(f"Is it the same area? {rect3.area() == rect1.area()}")파일을 저장합니다.
스크립트를 실행합니다.
python3 rectangle.py
다음과 같은 출력을 볼 수 있습니다.
Rectangle 1: Rectangle with width 5 and height 10
Area: 50
Perimeter: 30
Representation: Rectangle(5, 10)
Rectangle 2: Rectangle with width 3 and height 7
Area: 21
Perimeter: 20
Representation: Rectangle(3, 7)
Recreated rectangle: Rectangle with width 5 and height 10
Is it the same area? True
이 예제는 세 가지 특수 메서드 (__init__, __str__, __repr__) 가 함께 작동하여 잘 설계된 클래스를 만드는 방법을 보여줍니다.
실용적인 애플리케이션 만들기
이제 세 가지 특수 메서드 __init__, __str__, __repr__을 이해했으므로, 실제 시나리오에서 이러한 메서드의 사용을 보여주는 더 실용적인 애플리케이션을 만들어 보겠습니다. BankAccount 클래스를 사용하여 간단한 은행 시스템을 만들 것입니다.
은행 계좌 클래스 구축하기
bank_account.py라는 새 파일을 생성합니다.다음 코드를 추가합니다.
class BankAccount: def __init__(self, account_number, owner_name, balance=0.0): self.account_number = account_number self.owner_name = owner_name self.balance = balance self.transactions = [] def deposit(self, amount): if amount <= 0: print("Deposit amount must be positive") return False self.balance += amount self.transactions.append(f"Deposit: +${amount:.2f}") return True def withdraw(self, amount): if amount <= 0: print("Withdrawal amount must be positive") return False if amount > self.balance: print("Insufficient funds") return False self.balance -= amount self.transactions.append(f"Withdrawal: -${amount:.2f}") return True def get_transaction_history(self): return self.transactions def __str__(self): return f"Account {self.account_number} | Owner: {self.owner_name} | Balance: ${self.balance:.2f}" def __repr__(self): return f'BankAccount("{self.account_number}", "{self.owner_name}", {self.balance})'파일을 저장합니다.
은행 계좌 클래스 테스트하기
이제 BankAccount 클래스를 테스트해 보겠습니다.
bank_test.py라는 새 파일을 생성합니다.다음 코드를 추가합니다.
from bank_account import BankAccount ## Create bank accounts account1 = BankAccount("12345", "John Doe", 1000.0) account2 = BankAccount("67890", "Jane Smith", 500.0) ## Display initial account information print("Initial Account Status:") print(account1) print(account2) ## Perform transactions print("\nPerforming transactions...") ## Deposit to account1 print("\nDepositing $250 to account1:") account1.deposit(250) print(account1) ## Withdraw from account2 print("\nWithdrawing $100 from account2:") account2.withdraw(100) print(account2) ## Try to withdraw too much from account2 print("\nTrying to withdraw $1000 from account2:") account2.withdraw(1000) print(account2) ## Try to deposit a negative amount to account1 print("\nTrying to deposit -$50 to account1:") account1.deposit(-50) print(account1) ## Display transaction history print("\nTransaction history for account1:") for transaction in account1.get_transaction_history(): print(f"- {transaction}") print("\nTransaction history for account2:") for transaction in account2.get_transaction_history(): print(f"- {transaction}") ## Recreate an account using repr print("\nRecreating account1 using repr:") account1_repr = repr(account1) print(f"Representation: {account1_repr}") recreated_account = eval(account1_repr) print(f"Recreated account: {recreated_account}")파일을 저장합니다.
스크립트를 실행합니다.
python3 bank_test.py
다음과 유사한 출력을 볼 수 있습니다.
Initial Account Status:
Account 12345 | Owner: John Doe | Balance: $1000.00
Account 67890 | Owner: Jane Smith | Balance: $500.00
Performing transactions...
Depositing $250 to account1:
Account 12345 | Owner: John Doe | Balance: $1250.00
Withdrawing $100 from account2:
Account 67890 | Owner: Jane Smith | Balance: $400.00
Trying to withdraw $1000 from account2:
Insufficient funds
Account 67890 | Owner: Jane Smith | Balance: $400.00
Trying to deposit -$50 to account1:
Deposit amount must be positive
Account 12345 | Owner: John Doe | Balance: $1250.00
Transaction history for account1:
- Deposit: +$250.00
Transaction history for account2:
- Withdrawal: -$100.00
Recreating account1 using repr:
Representation: BankAccount("12345", "John Doe", 1250.0)
Recreated account: Account 12345 | Owner: John Doe | Balance: $1250.00
구현 이해하기
이 은행 애플리케이션에서:
__init__메서드는 계좌 번호, 소유자 이름 및 선택적 초기 잔액으로 은행 계좌를 초기화합니다. 또한 거래를 추적하기 위해 빈 목록을 만듭니다.deposit및withdraw메서드는 거래를 처리하고 잔액 및 거래 내역을 업데이트합니다.__str__메서드는 계좌 번호, 소유자 이름 및 현재 잔액을 표시하여 계좌의 사용자 친화적인 표현을 제공합니다.__repr__메서드는 계좌 객체를 다시 생성할 수 있는 문자열을 제공합니다 (하지만 이 구현에서는 거래 내역이 유지되지 않습니다).
이 예제는 이러한 특수 메서드를 실용적인 애플리케이션에서 사용하여 보다 직관적이고 사용자 친화적인 객체를 만드는 방법을 보여줍니다.
애플리케이션 확장하기
마지막 연습으로, 여러 계정을 관리하는 간단한 은행 시스템을 만들어 보겠습니다.
banking_system.py라는 새 파일을 생성합니다.다음 코드를 추가합니다.
from bank_account import BankAccount class BankingSystem: def __init__(self, bank_name): self.bank_name = bank_name self.accounts = {} def create_account(self, account_number, owner_name, initial_balance=0.0): if account_number in self.accounts: print(f"Account {account_number} already exists") return None account = BankAccount(account_number, owner_name, initial_balance) self.accounts[account_number] = account return account def get_account(self, account_number): return self.accounts.get(account_number) def list_accounts(self): return list(self.accounts.values()) def __str__(self): return f"{self.bank_name} - Managing {len(self.accounts)} accounts" def __repr__(self): return f'BankingSystem("{self.bank_name}")' ## Create a banking system bank = BankingSystem("Python First Bank") print(bank) ## Create some accounts bank.create_account("A001", "John Doe", 1000) bank.create_account("A002", "Jane Smith", 500) bank.create_account("A003", "Bob Johnson", 250) ## List all accounts print("\nAll accounts:") for account in bank.list_accounts(): print(account) ## Make some transactions account = bank.get_account("A001") if account: print(f"\nBefore deposit: {account}") account.deposit(500) print(f"After deposit: {account}") account = bank.get_account("A002") if account: print(f"\nBefore withdrawal: {account}") account.withdraw(200) print(f"After withdrawal: {account}") ## Try to create an existing account print("\nTrying to create an existing account:") bank.create_account("A001", "Someone Else", 300) ## Final state of the banking system print(f"\n{bank}")파일을 저장합니다.
스크립트를 실행합니다.
python3 banking_system.py
다음과 유사한 출력을 볼 수 있습니다.
Python First Bank - Managing 0 accounts
All accounts:
Account A001 | Owner: John Doe | Balance: $1000.00
Account A002 | Owner: Jane Smith | Balance: $500.00
Account A003 | Owner: Bob Johnson | Balance: $250.00
Before deposit: Account A001 | Owner: John Doe | Balance: $1000.00
After deposit: Account A001 | Owner: John Doe | Balance: $1500.00
Before withdrawal: Account A002 | Owner: Jane Smith | Balance: $500.00
After withdrawal: Account A002 | Owner: Jane Smith | Balance: $300.00
Trying to create an existing account:
Account A001 already exists
Python First Bank - Managing 3 accounts
이 예제는 이전에 만든 BankAccount 클래스를 보다 완벽한 애플리케이션에서 사용하는 방법을 보여줍니다. __init__, __str__, __repr__과 같은 특수 메서드가 직관적이고 사용자 친화적인 클래스를 만드는 데 어떻게 탄탄한 기반을 제공하는지 보여줍니다.
요약
이 랩에서는 더 표현력이 풍부하고 사용자 친화적인 객체를 만드는 데 도움이 되는 Python 의 세 가지 필수 특수 메서드에 대해 배웠습니다.
__init__: 객체가 생성될 때 객체의 속성을 초기화하는 생성자 메서드입니다.__str__: 주로 최종 사용자를 위해 객체의 사람이 읽을 수 있는 문자열 표현을 제공하는 메서드입니다.__repr__: 주로 개발자 및 디버깅을 위해 객체의 모호하지 않은 문자열 표현을 제공하는 메서드입니다.
Person 및 Book과 같은 간단한 클래스부터 은행 시스템과 같은 더 복잡한 애플리케이션까지, 여러 예제에서 이러한 메서드를 구현했습니다. 또한 __str__과 __repr__의 차이점과 각 메서드를 언제 사용해야 하는지 배웠습니다.
이러한 특수 메서드를 마스터함으로써 언어의 내장 기능과 원활하게 통합되는 보다 직관적이고 사용자 정의된 Python 객체를 만들 수 있습니다. 이 지식은 Python 에서 객체 지향 프로그래밍의 필수적인 부분을 형성하며, 더 깨끗하고 유지 관리 가능한 코드를 작성하는 데 도움이 될 것입니다.
Python 여정을 계속 진행하면서, 객체의 동작을 더욱 사용자 정의할 수 있는 다른 많은 특수 메서드가 있다는 것을 기억하십시오. 이러한 메서드를 탐색하면 다양한 컨텍스트에서 객체가 어떻게 동작하는지에 대한 더 많은 제어 권한을 얻을 수 있습니다.



