본문 바로가기
IT/파이썬 기초 완전 정복

파이썬 클래스와 인스턴스 만들기 (21)

by 지식 발전소 2024. 4. 20.
728x90
반응형

 

객체지향 프로그래밍 개념

안녕하세요. 오늘은 파이썬 클래스와 인스턴스 만들기에 대해 알아보겠습니다. 클래스와 인스턴스는 객체지향 프로그래밍(OOP, Object-Oriented Programming)의 핵심 개념입니다. 객체지향 프로그래밍이란 무엇일까요?

 

객체지향 프로그래밍은 실제 세계의 개체(객체)를 모방하여 프로그램을 작성하는 프로그래밍 패러다임입니다. 예를 들어 자동차는 엔진, 바퀴, 문 등의 속성을 가지고 있고, 주행하기, 문 열기 등의 행동(메서드)를 할 수 있죠. 이렇게 실제 객체를 추상화한 속성과 행동으로 프로그램을 모델링하는 것이 객체지향 프로그래밍의 핵심 개념입니다.

 

객체지향 프로그래밍을 사용하면 프로그램의 모듈성, 재사용성, 확장성 등이 좋아집니다. 이를 통해 대규모 소프트웨어 프로젝트 개발과 유지보수가 용이해집니다. 파이썬을 포함한 대부분의 현대 프로그래밍 언어가 객체지향 프로그래밍을 지원합니다.

 

그렇다면 객체지향 프로그래밍의 핵심 개념인 클래스와 인스턴스는 무엇일까요? 클래스는 객체의 설계도 역할을 하고, 인스턴스는 그 설계도로부터 실제 생성된 객체를 말합니다. 간단한 예시를 통해 이해해보겠습니다.

 

 

간단한 클래스 정의하기

파이썬에서 클래스를 정의할 때는 class 키워드를 사용합니다. 클래스 이름은 일반적으로 대문자로 시작하며 CamelCase 네이밍 규칙을 따릅니다.

class Car:
    pass  # 아무 내용 없는 클래스 정의

 

위 코드는 Car라는 이름의 클래스를 정의했습니다. pass는 아무 내용도 없다는 것을 의미합니다. 물론 보통은 속성과 메서드를 추가하게 되죠.

 

어떤 속성과 메서드를 추가할지 객체의 추상화에 따라 달라집니다. 자동차 객체라면 다음과 같은 속성과 메서드를 추가할 수 있습니다.

class Car:
    def __init__(self, model, year):
        self.model = model
        self.year = year
        self.speed = 0

    def accelerate(self, amount):
        self.speed += amount

    def brake(self):
        self.speed = 0

    def get_speed(self):
        return self.speed
  • __init__ 메서드는 클래스의 생성자(constructor) 역할을 합니다. 인스턴스 생성 시 자동으로 호출되며, 초기값을 설정합니다.
  • model과 year는 클래스의 속성(attribute) 입니다. 각 인스턴스마다 고유한 값을 가집니다.
  • accelerate()와 brake(), get_speed()는 클래스의 메서드(method) 입니다. 인스턴스의 행동을 정의합니다.
  • self는 인스턴스 자신을 가리키는 참조자입니다. 파이썬에서는 묵시적으로 전달됩니다.

위와 같이 클래스에 속성과 메서드를 정의하면 실제 객체의 속성과 행동을 추상화할 수 있게 됩니다. 그렇다면 실제 인스턴스는 어떻게 생성할까요?

 

인스턴스 생성하기

클래스로부터 실제 인스턴스를 만들려면 클래스 이름 뒤에 소괄호 ()를 붙여서 호출하면 됩니다. 이때 생성자 __init__ 메서드의 매개변수로 초기값을 전달할 수 있습니다.

car1 = Car("Toyota Prius", 2021)
car2 = Car("Tesla Model S", 2019)

위 코드에서 Car 클래스로부터 car1과 car2 인스턴스를 생성했습니다. 각 인스턴스는 클래스에 정의된 속성과 메서드를 모두 가지게 됩니다.

생성한 인스턴스에 대해 속성에 접근하거나 메서드를 호출할 수 있습니다. 다음과 같이 사용할 수 있습니다.

print(car1.model, car1.year)  # Toyota Prius 2021
car2.accelerate(30)
print(car2.get_speed())  # 30

car2.brake()
print(car2.get_speed())  # 0

이처럼 . 연산자로 속성에 접근하고, 메서드이름(매개변수)로 메서드를 호출할 수 있습니다. 이를 통해 각 인스턴스의 상태와 행동을 제어할 수 있습니다.

앞서 정의한 Car 클래스는 매우 간단한 예시일 뿐입니다. 실제 프로그램에서는 더 복잡한 클래스를 작성하게 되겠죠. 또한 클래스에는 다양한 개념들이 있는데, 하나씩 살펴보도록 하겠습니다.

클래스 상속

객체지향 프로그래밍의 중요한 개념 중 하나인 상속(Inheritance)에 대해 알아보겠습니다. 상속이란 기존 클래스를 상속받아 새로운 클래스를 만드는 것을 말합니다. 이를 통해 코드 재사용성이 높아지고 중복 코드를 줄일 수 있습니다.

class ElectricCar(Car):
    def __init__(self, model, year, battery):
        super().__init__(model, year)  
        self.battery = battery

    def charge(self, amount):
        print(f"Battery charged by {amount}%")
        self.battery += amount

electric_car = ElectricCar("Tesla Model 3", 2022, 90)
print(electric_car.model, electric_car.year)  # Tesla Model 3 2022
print(electric_car.battery) # 90
electric_car.charge(10)  # Battery charged by 10%

 

위 예시에서 Car 클래스를 상속받아 ElectricCar 클래스를 새로 정의했습니다. ElectricCar는 Car의 속성인 model과 year를 그대로 물려받으면서, 자신만의 새로운 속성인 battery와 메서드 charge()를 추가로 가지게 됩니다.

상속할 때는 부모 클래스의 __init__ 메서드를 호출해야 하는데, 이때 super() 내장 함수를 사용해 부모 클래스의 __init__을 명시적으로 호출하는 것이 좋습니다.

 

상속을 통해 기존 코드를 재사용하면서도, 기능을 확장하거나 일부 기능을 변경할 수 있습니다. 이를 통해 효율적인 코드 작성이 가능해집니다.

메서드 오버라이딩

메서드 오버라이딩(Method Overriding)이란, 자식 클래스에서 부모 클래스의 메서드를 재정의하는 것을 말합니다.

예를 들어 Car 클래스에 start() 메서드가 있다고 가정해 봅시다.

class ElectricCar(Car):
    def start(self):
        print("Electric car started silently")

electric_car = ElectricCar()
electric_car.start()  # Electric car started silently

ElectricCar 클래스에서 start() 메서드를 재정의하면서 Car 클래스의 start() 메서드를 오버라이딩했습니다. 따라서 ElectricCar 인스턴스에서 start() 메서드를 호출하면 부모 클래스에서 정의한 메서드가 아닌 자식 클래스에서 재정의한 메서드가 실행됩니다.

이렇게 부모 클래스의 메서드를 오버라이딩하면 자식 클래스에서 특정 메서드의 동작을 변경할 수 있습니다. 상속의 장점을 살리면서 자식 클래스에 특화된 기능을 추가할 수 있는 것이죠.

클래스 메서드와 정적 메서드

파이썬의 클래스는 일반 메서드 외에도 클래스 메서드(Class Method)와 정적 메서드(Static Method)를 가질 수 있습니다.

 

클래스 메서드는 클래스 변수를 다루거나 인스턴스 생성 등의 목적으로 사용됩니다. 메서드 정의 시 @classmethod 데코레이터를 사용하며, cls 인자를 받아 클래스 자체에 접근할 수 있습니다.

class Car:
    max_speed = 300
    
    @classmethod
    def set_max_speed(cls, new_max_speed):
        cls.max_speed = new_max_speed
        
Car.set_max_speed(250)
print(Car.max_speed)  # 250

 

위 예시에서 set_max_speed() 메서드는 @classmethod 데코레이터를 사용하여 클래스 메서드로 정의되었습니다. 이 메서드는 cls 인자를 통해 클래스 변수 max_speed에 접근하여 그 값을 변경할 수 있습니다.

 

반면 정적 메서드(Static Method)는 인스턴스 속성이나 클래스 속성을 별도로 사용하지 않는 함수를 클래스에 포함시킬 때 사용합니다. 메서드 정의 시 @staticmethod 데코레이터를 붙입니다.

class Math:
    @staticmethod
    def add(x, y):
        return x + y
    
result = Math.add(3, 5)
print(result)  # 8

정적 메서드는 외부 함수처럼 클래스 바깥에서도 동작하지만, 클래스의 네임스페이스에 종속되어 코드의 가독성과 유지관리성을 높여줍니다.

정리하면 세 가지 메서드의 차이점은 다음과 같습니다.

  • 일반 메서드(인스턴스 메서드): 인스턴스 속성에 접근하며, self를 통해 인스턴스를 참조합니다.
  • 클래스 메서드: 클래스 속성에 접근하며, cls를 통해 클래스를 참조합니다.
  • 정적 메서드: 인스턴스나 클래스를 별도로 참조하지 않습니다. 외부 함수와 동일하지만 클래스의 네임스페이스에 포함됩니다.

이처럼 메서드의 종류에 따라 인스턴스 속성, 클래스 속성, 외부 데이터 등에 접근할 수 있습니다. 상황에 맞게 선택하여 사용하면 유연하고 효율적인 코드 작성이 가능해집니다.

속성과 getter, setter 메서드

객체지향 프로그래밍에서는 객체의 캡슐화(Encapsulation) 또한 중요한 개념입니다. 캡슐화란 객체의 속성을 은닉하고, 메서드로만 접근할 수 있게 하는 것을 말합니다.

파이썬에서는 속성의 이름 앞에 __를 붙이면 비공개 속성(Private Attribute) 이 되어 외부에서 직접 접근할 수 없게 됩니다. 하지만 이는 단순히 네이밍 규칙일 뿐이므로 강제력은 없습니다.

class Car:
    def __init__(self, model):
        self.model = model  # 공개 속성
        self.__price = 30000  # 비공개 속성
        
    def get_price(self):
        return self.__price

car = Car("Toyota")
print(car.model)   # 공개

## 속성과 getter, setter 메서드

비공개 속성을 사용하면 외부에서 직접 접근할 수 없게 되지만, 때로는 속성값을 가공하거나 값 설정 시 특별한 로직을 실행해야 할 수도 있습니다. 이럴 때 getter와 setter 메서드를 활용할 수 있습니다.

**getter 메서드**는 속성값을 가공하여 반환하는 메서드입니다. 파이썬에서는 `@property` 데코레이터를 사용하여 정의합니다.

```python
class Car:
    def __init__(self, model, price):
        self.model = model
        self.__price = price
        
    @property
    def price(self):
        return self.__price * 1.3  # 30% 부가세 적용
    
car = Car("Toyota", 20000)
print(car.price)  # 26000.0

 

위 예시에서 price 속성에 접근할 때마다 30% 부가세가 추가되어 반환됩니다. 외부에서 car.price로 접근하지만 내부적으로는 비공개 속성인 __price를 가공하여 계산된 값이 반환되는 것입니다.

 

반대로 setter 메서드는 속성값을 설정할 때 특별한 처리를 하는 메서드입니다. @속성이름.setter 형태로 정의합니다.

class Car:
    def __init__(self, model, price):
        self.model = model
        self.__price = price
        
    @property
    def price(self):
        return self.__price
    
    @price.setter
    def price(self, new_price):
        if new_price > 0:
            self.__price = new_price
        else:
            print("가격은 0보다 커야 합니다.")
            
car = Car("Toyota", 20000)
print(car.price)  # 20000.0

car.price = 30000  
print(car.price)  # 30000.0

car.price = -1000  # 가격은 0보다 커야 합니다.

@price.setter 데코레이터를 통해 setter 메서드 price()를 정의했습니다. 이제 car.price = 새로운값과 같이 할당할 때마다 setter 메서드가 호출되면서 속성값을 검증하고 필요한 로직을 수행할 수 있습니다.

getter와 setter 메서드를 사용하면 속성에 직접 접근하지 않고도 특정 로직을 수행할 수 있습니다. 이를 통해 속성값의 무결성을 보장하고 객체의 캡슐화를 높일 수 있습니다.

파이썬 고유의 특별 메서드

파이썬에서는 클래스에 특별한 메서드들을 정의할 수 있습니다. 이들은 이중 언더스코어(__)로 시작하고 끝나는 특별 메서드(Special Method) 또는 매직 메서드(Magic Method) 라고 불립니다.

이 특별 메서드들을 통해 클래스의 인스턴스 객체가 특정 연산이나 함수에 어떻게 응답할지를 정의할 수 있습니다. 내장 함수나 연산자를 클래스에 맞게 오버라이딩할 때 주로 사용됩니다.

몇 가지 주요 특별 메서드를 살펴보겠습니다.

1) __str__(self)와 __repr__(self)

이 메서드들은 인스턴스 객체를 문자열로 표현할 때 사용됩니다. __str__은 객체의 비공식적인 문자열 표현을, __repr__은 공식적이고 에러 추적에 사용 가능한 문자열 표현을 정의합니다.

class Car:
    def __init__(self, model):
        self.model = model
        
    def __str__(self):
        return f"Car: {self.model}"
    
    def __repr__(self):
        return f"Car('{self.model}')"
        
car = Car("Toyota")
print(str(car))  # Car: Toyota
print(repr(car)) # Car('Toyota')

2) __len__(self)

len() 함수가 인스턴스 객체에 어떻게 반응할지를 정의합니다. 예를 들어 리스트나 문자열 같은 시퀀스 타입 클래스에서 유용하게 사용됩니다.

class SampleList:
    def __init__(self, items):
        self.items = items
        
    def __len__(self):
        return len(self.items)
        
sample = SampleList([1, 2, 3])
print(len(sample))  # 3

3) __iter__(self)와 __next__(self)

이 메서드들은 클래스의 인스턴스 객체를 이터레이터(iterator)로 사용할 수 있게 해줍니다. __iter__는 이터레이터 객체를 반환하고, __next__는 이터레이터의 다음 값을 반환합니다.

class Counter:
    def __init__(self, start, stop):
        self.current = start
        self.stop = stop
        
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current < self.stop:
            num = self.current
            self.current += 1
            return num
        raise StopIteration

for num in Counter(1, 6):
    print(num)
    
# 출력 결과
# 1
# 2
# 3
# 4 
# 5

4) __add__(self, other) 등의 연산자 오버로딩

특별 메서드를 정의하면 연산자를 인스턴스 객체에 맞게 오버로딩할 수 있습니다. 예를 들어 __add__는 + 연산자를 오버로딩합니다.

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __str__(self):
        return f"({self.x}, {self.y})"
    
    def __add__(self, other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector(x, y)
        
v1 = Vector(1, 2)
v2 = Vector(3, 4)

v3 = v1 + v2
print(v3)  # (4, 6)

 

이처럼 다양한 특별 메서드를 정의하면 인스턴스 객체의 동작을 파이썬 방식에 맞게 커스터마이징할 수 있습니다. 각 특별 메서드의 역할을 이해하고 상황에 맞게 활용한다면 보다 풍부한 객체지향 프로그래밍이 가능해집니다.

실전 예제: 은행 계좌 클래스

지금까지 배운 클래스와 인스턴스, 메서드 등의 개념을 종합하여 실전 예제를 통해 실습해보겠습니다. 은행 계좌 클래스를 정의하고 계좌 관리 프로그램을 만들어보겠습니다.

class BankAccount:
    interest_rate = 0.02  # 클래스 변수: 이자율
    
    def __init__(self, name, balance=0):
        self.name = name
        self.balance = balance
        
    def deposit(self, amount):
        self.balance += amount
        
    def withdraw(self, amount):
        if self.balance >= amount:
            self.balance -= amount
        else:
            print("잔액이 부족합니다.")
            
    def add_interest(self):
        self.balance *= (1 + BankAccount.interest_rate)
        
    @property
    def balance(self):
        return self.__balance
    
    @balance.setter
    def balance(self, new_balance):
        if new_balance >= 0:
            self.__balance = new_balance
        else:
            print("잔액은 0 이상이어야 합니다.")
            
    def __str__(self):
        return f"{self.name}님 계좌 \n 잔액: {self.balance:.2f}원"

# 사용 예시
account = BankAccount("철수", 1000)
print(account)  # 철수님 계좌 \n 잔액: 1000.00원

account.deposit(5000)
print(account)  # 철수님 계좌 \n 잔액: 6000.00원

account.withdraw(7000)  # 잔액이 부족합니다.
account.withdraw(3000) 
print(account)  # 철수님 계좌 \n 잔액: 3000.00원

account.add_interest()
print(account)  # 철수님 계좌 \n 잔액: 3060.00원

위 예제에서는 BankAccount 클래스를 만들어 계좌를 나타내는 인스턴스를 생성할 수 있습니다. 클래스 변수로 이자율을 설정하고, 인스턴스 메서드로 입금(deposit), 출금(withdraw), 이자 지급(add_interest)을 구현했습니다.

또한 balance 속성에 대한 getter와 setter를 정의하여 값 검증 로직을 추가했습니다. __str__ 메서드도 오버라이딩하여 객체의 문자열 표현을 수정했습니다.

이처럼 객체지향 프로그래밍을 통해 실제 세계의 객체를 추상화하고 모델링할 수 있습니다. 이번 예제는 간단하지만 클래스를 잘 활용하면 복잡한 도메인 문제도 효과적으로 해결할 수 있습니다.

정리

지금까지 파이썬 클래스와 인스턴스 만들기에 대해 자세히 살펴보았습니다. 주요 내용을 요약하면 다음과 같습니다.

  • 클래스는 객체의 설계도이고, 인스턴스는 실제 생성된 객체입니다.
  • 클래스에 속성과 메서드를 정의하여 객체를 추상화합니다.
  • 상속을 통해 기존 클래스를 재사용하고 기능을 확장할 수 있습니다.
  • 메서드 오버라이딩으로 자식 클래스에 특화된 기능을 정의할 수 있습니다.
  • 클래스 메서드, 정적 메서드를 통해 클래스와 데이터에 유연하게 접근할 수 있습니다.
  • getter와 setter로 속성 접근을 제어하여 객체의 캡슐화를 높일 수 있습니다.
  • 특별 메서드로 클래스 인스턴스의 연산과 동작을 커스터마이징할 수 있습니다.

객체지향 프로그래밍은 대규모 소프트웨어를 구축하고 유지보수할 때 큰 이점을 제공합니다. 실제 프로젝트에서 클래스를 잘 설계하고 활용한다면 코드의 모듈성과 재사용성이 높아질 것입니다.

 

이번 내용을 통해 파이썬 클래스와 인스턴스, 객체지향 프로그래밍에 대한 깊이 있는 이해를 하셨기를 바랍니다. 부족한 부분이나 추가 설명이 필요한 부분은 언제든 질문해주시기 바랍니다.

참고 자료

[1] 박응용 박사, "혼자 공부하는 파이썬"

[2] Python 공식 문서, "객체지향 프로그래밍 소개"

[3] Luciano Ramalho, "Fluent Python"

[4] David Beazley, "Python Cookbook"

[5] Raymond Hettinger, "Python의 매직 메서드 탐구"

 

 

 

한 고대 문서 이야기

여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오

gospel79.tistory.com

 

유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법

올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을

stock79.tistory.com

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

728x90
반응형

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

댓글