파이썬 강의

파이썬 클래스와 객체 이해하기

마블e 2025. 2. 21. 21:57

파이썬을 학습할 때 흔히 접하게 되는 '클래스'와 '객체' 개념은 객체 지향 프로그래밍의 핵심 구성 요소입니다. 이를 통해 우리는 프로그램을 보다 조직적으로 작성할 수 있으며, 코드의 재사용성을 높이고 유지보수를 쉽게 할 수 있습니다.

객체 지향 프로그래밍이란?

객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 데이터와 데이터 처리 방식을 객체라는 단위로 묶어 처리하는 프로그래밍 방식입니다. 이 방식에서는 현실 세계를 모델링하여 코드를 작성하는 것이 핵심입니다. 예를 들어, 자동차를 프로그래밍할 때 각각의 부품을 객체로 만들어 상황에 맞게 상호작용하도록 설정할 수 있습니다. 클래스는 이러한 객체를 만들기 위한 청사진 역할을 하며, 속성과 메서드로 구성됩니다.

클래스(Class)란?

클래스는 객체를 생성하기 위한 템플릿 또는 설계도입니다. 파이썬에서 클래스는 class 키워드를 사용하여 정의할 수 있습니다. 클래스는 하나 이상의 함수로 구성될 수 있으며, 이 함수는 해당 클래스에 속한 객체가 사용할 수 있는 동작이나 기능을 제공합니다. 클래스 정의 시 일반적으로 생성자(constructor) 메서드와 객체의 상태를 나타내는 속성들을 정의하여 객체의 초기 상태를 설정합니다.

class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model

def display_info(self):
return f"Car brand: {self.brand}, Model: {self.model}"

객체(Instance)란?

객체는 클래스를 기반으로 생성된 개별 사용 가능한 단위입니다. 각 객체는 클래스의 인스턴스이며, 클래스에 정의된 데이터와 기능을 사용할 수 있습니다. 객체를 생성하는 과정을 인스턴스화라고 합니다. 동일한 클래스를 기반으로 여러 객체를 생성할 수 있으며, 각 객체는 독립적으로 동작합니다. 위의 클래스 예제를 기반으로 객체를 생성하게 되면,

car1 = Car("Toyota", "Corolla")
car2 = Car("Honda", "Civic")

처럼 여러 개의 자동차 객체를 생성할 수 있습니다. 각각의 car1car2는 독립적인 상태와 동작을 가집니다.

생성자 함수와 초기화

모든 클래스에는 생성자 함수인 __init__ 메서드를 사용할 수 있습니다. 이 메서드는 클래스로부터 객체가 생성될 때 자동으로 호출되며, 객체의 초기 속성을 설정합니다. 생성자는 일반적으로 객체에 필요한 데이터를 전달받아 객체의 인스턴스 변수를 초기화하는 역할을 합니다. 정확한 데이터 초기화를 통해 객체는 예상대로 작동할 수 있습니다.

상속(Inheritance)

상속은 기존 클래스를 확장하여 새로운 클래스를 정의하는 강력한 기능입니다. 상속을 통해 새로운 클래스는 기존 클래스의 속성과 메서드를 물려받아 사용할 수 있습니다. 또한, 새로운 클래스에서 추가적인 속성이나 메서드를 정의하여 기존 클래스를 확장할 수 있습니다. 이를 통해 중복된 코드 작성을 최소화하고, 프로그램의 구조를 개선할 수 있습니다.

class ElectricCar(Car):  # Car 클래스 상속
def __init__(self, brand, model, battery_capacity):
super().__init__(brand, model)
self.battery_capacity = battery_capacity

def display_battery_info(self):
return f"Battery capacity: {self.battery_capacity} kWh"

위 예시에서 ElectricCar 클래스는 Car 클래스를 상속하고 있으며, 기존의 속성과 메서드를 모두 사용할 수 있습니다. 여기에 battery_capacity라는 새로운 속성을 추가하여 전기 자동차의 특정 정보를 관리할 수 있습니다.

다형성(Polymorphism)

다형성은 같은 인터페이스를 통해 다른 기능을 구현할 수 있도록 하는 객체지향 프로그래밍의 중요한 속성입니다. 이는 동일한 메서드가 서로 다른 객체에서 다양한 동작을 할 수 있게 합니다. 예를 들어, Car 클래스의 display_info 메서드는 ElectricCar 클래스에서 동일한 이름의 메서드를 정의하여 다른 정보를 포함하도록 할 수 있습니다. 다형성을 통해 코드는 보다 유연하고 확장 가능하게 됩니다.

캡슐화(Encapsulation)

캡슐화는 객체의 데이터와 메서드를 내부에서 하나의 단위로 묶고 외부로부터 직접적인 접근을 제한하는 개념입니다. 이는 데이터의 무결성을 보호하고, 객체 간의 상호 작용을 명확히 정의하는데 도움을 줍니다. 파이썬에서는 언더스코어 _ 기호를 사용하여 비공개 속성이나 메서드를 정의할 수 있으며, 이를 통해 캡슐화를 구현할 수 있습니다.

class Person:
def __init__(self, name, age):
self._name = name
self._age = age

def get_name(self):
return self._name

def set_name(self, name):
self._name = name

위 예시에서는 _name_age 속성을 비공개로 설정하고, 이를 접근하고 변경하는 메서드를 제공함으로써 캡슐화를 달성하고 있습니다.

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

클래스 메서드는 클래스 자체에 접근할 수 있는 메서드로, @classmethod 데코레이터를 통해 정의됩니다. 이러한 메서드는 객체의 인스턴스가 아닌 클래스 자체의 데이터를 처리하기 위해 사용됩니다. 반면, 정적 메서드는 클래스의 속성이나 메서드를 변경하지 않고 단순한 연산이나 기능을 제공할 때 사용됩니다. @staticmethod 데코레이터로 정의하며, 클래스나 객체의 상태에 접근하지 않습니다.

class MathOperations:
@staticmethod
def add(a, b):
return a + b

@classmethod
def multiply(cls, a, cls_factor):
return a * cls_factor

위 예시에서는 정적 메서드 add와 클래스 메서드 multiply를 정의하고 있습니다.

인터페이스와 추상 클래스

추상 클래스는 하나 이상의 추상 메서드를 포함하는 클래스로, 특정 기능이 구현되지 않은 상태로 이를 상속받는 클래스에서 반드시 구현해야 합니다. 이는 클래스의 특정 구조를 정의하고, 이를 상속받는 클래스들이 특정 메서드를 구현하도록 강제하는 수단입니다. 추상 클래스를 정의하기 위해서는 abc 모듈을 임포트하고, ABC 클래스를 상속받아야 합니다.

from abc import ABC, abstractmethod

class Animal(ABC):
@abstractmethod
def make_sound(self):
pass

class Dog(Animal):
def make_sound(self):
return "Bark"

위의 예시에서 Animal 클래스는 추상 클래스이며, make_sound 메서드는 반드시 하위 클래스에서 구현되어야 합니다.

클래스와 객체의 차이점

클래스와 객체는 객체 지향 프로그래밍을 이해하는 데 필수적입니다. 클래스를 설계도나 청사진으로 생각할 수 있으며, 설계도를 기반으로 만든 실체가 객체라고 볼 수 있습니다. 클래스는 정의되어 있는 상태지만, 객체는 실제 메모리에 할당되어 구체적인 동작을 수행할 수 있는 상태입니다. 클래스는 단지 데이터의 구조와 행위를 정의하지만, 객체는 이러한 정의를 바탕으로 실제로 기능을 수행합니다.

클래스와 객체 비교

구분 클래스(Class) 객체(Object)
정의 객체 생성의 템플릿 클래스의 인스턴스
데이터 설계 데이터 실제 데이터
메모리 없음 있음
접근 기능 및 속성 정의 기능 및 속성 사용

모듈과 패키지를 통한 코드 재사용

파이썬에서 클래스와 객체는 모듈과 패키지를 통해 보다 효율적으로 관리할 수 있습니다. 모듈은 파이썬 코드를 파일 단위로 분리하여 저장한 것이며, 패키지는 이러한 모듈을 계층적으로 관리하기 위한 디렉토리 구조입니다. 모듈과 패키지를 사용하면 코드를 보다 체계적으로 관리하고, 필요한 기능을 쉽게 재사용할 수 있습니다. import 키워드를 사용하여 다른 모듈이나 패키지의 클래스를 로드하고 사용할 수 있습니다.

오버로딩(Overloading)

파이썬에서 전통적인 의미의 메서드 오버로딩은 지원되지 않지만, 변수의 개수나 타입에 따라 다른 동작을 원한다면 기본값 인수 사용이나 *args**kwargs를 통해 이를 구현할 수 있습니다. 이러한 방식은 메서드가 다양한 상황에 맞게 유연하게 작동할 수 있도록 도와줍니다. 예를 들어, 동일한 이름의 메서드를 매개 변수에 따라 다르게 동작시키고자 할 때 사용할 수 있습니다.

class Example:
def multiply(self, a, b=1):
return a * b

def add(self, *args):
return sum(args)

위 코드에서 multiply 메서드는 두 개의 인수를 받아 기본적으로 두 번째 인수가 1로 설정되어 있습니다. add 메서드는 가변 인수를 받아들여 모든 인수의 합을 리턴합니다. 이처럼 다양한 형태로 메서드를 정의할 수 있습니다.

연산자 오버로딩(Operator Overloading)

파이썬 클래스는 내장 연산자를 객체 조작에 사용할 수 있도록 허용하는 "연산자 오버로딩"을 지원합니다. 이를 통해 클래스를 사용자가 자연스럽게 사용할 수 있도록 만들어 줍니다. 연산자 오버로딩은 특별한 메서드를 정의하여 구현할 수 있으며, 이는 특정 연산자가 호출될 때 코드가 실행되도록 합니다. 예를 들어, 두 객체의 덧셈이나 뺄셈을 정의할 수 있습니다.

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)

def __repr__(self):
return f"Vector({self.x}, {self.y})"

위의 Vector 클래스에서 __add__ 메서드는 + 연산자를 오버로딩하여 두 벡터의 합을 구합니다. 이렇게 사용자 정의 객체에 자연스러운 수학적 연산을 수행할 수 있게 합니다.

메타클래스(Metaclass)

메타클래스는 클래스를 생성하기 위한 '클래스의 클래스'입니다. 파이썬의 모든 클래스는 본질적으로 특정 메타클래스의 인스턴스입니다. 기본적으로 type이 있다는 사실은 클래스를 생성하고 관리할 때 사용할 수 있는 확장된 기능을 제공한다는 것을 의미합니다. 메타클래스를 사용하면 클래스의 생성과정에 개입하여 클래스의 속성이나 메서드를 변화시킬 수 있습니다.

class Meta(type):
def __new__(cls, name, bases, dct):
dct['hello'] = lambda self: "Hello, world!"
return super().__new__(cls, name, bases, dct)

class Greet(metaclass=Meta):
pass

g = Greet()
print(g.hello())

위의 코드는 클래스 Greet가 생성될 때 메타클래스 Meta를 이용해 hello 메서드를 추가합니다. 이는 클래스의 탄생 과정에 직접 개입하여 클래스의 속성을 조작할 수 있는 강력한 도구를 제공합니다.

사용자 정의 예외

파이썬에서는 표준 라이브러리를 통해 다양한 내장 예외 클래스를 제공하지만, 필요에 따라 사용자가 직접 예외를 정의할 수 있습니다. 새로 정의되는 예외 클래스는 일반적으로 Exception 클래스를 상속받아 작성하며, 특정한 오류를 효과적으로 처리하고 사용자에게 명확한 오류 메시지를 제공할 수 있습니다. 사용자 정의 예외는 예외가 발생해야 하는 구체적인 조건이나 상황을 코드로 명확히 표현할 수 있도록 돕습니다.

class CustomError(Exception):
def __init__(self, message):
super().__init__(message)

def risky_operation(n):
if n < 0:
raise CustomError("Negative value error!")
return n * 10

위의 코드는 CustomError라는 사용자 정의 예외를 선언하고, 특정 조건에서 이 예외를 발생시키는 risky_operation 함수의 예를 보여줍니다. 이처럼 특정한 오류 상황을 감지하여 명확한 오류 메시지를 제공하는 것이 가능합니다.

데코레이터(Decorator)

데코레이터는 기존의 함수를 수정하지 않고도 함수에 새로운 기능을 추가할 수 있는 간편하고 유연한 방법입니다. 고차 함수로써 함수를 인자로 받아 함수를 반환하는 형태를 유지하며, 함수의 전처리나 후처리를 다룰 수 있습니다. 이를 통해 코드의 중복을 줄이고 특정 행동의 일관성을 유지할 수 있습니다.

def logging_decorator(func):
def wrapper(*args, **kwargs):
print(f"Logging: {func.__name__} was called")
return func(*args, **kwargs)
return wrapper

@logging_decorator
def greet():
return "Hello!"

print(greet())

위 예제에서는 logging_decorator 데코레이터가 greet 함수 호출 시마다 로깅 메시지를 출력하게 만들어 줍니다. 이러한 기능은 함수의 전반적인 동작을 이해하고 추적하는 데 유용합니다.

클래스와 JSON 변환

파이썬에서 클래스 객체를 JSON 형식으로 변환한다는 것은 객체의 데이터를 직렬화하여 문자열로 표현한 후, 이를 다시 객체로 복원하는 작업을 의미합니다. 이는 데이터를 저장하거나 네트워크를 통해 전송하는 등 다양한 용도로 활용할 수 있습니다. 파이썬의 json 모듈은 JSON 직렬화와 파싱을 위한 직접적인 방법을 제공합니다.

import json

class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def to_json(self):
return json.dumps(self, default=lambda o: o.__dict__)

@staticmethod
def from_json(data):
info = json.loads(data)
return Student(**info)

s = Student("Alice", 20)
serialized = s.to_json()
deserialized = Student.from_json(serialized)

위 코드에서 Student 객체를 JSON 형식으로 직렬화하고 다시 객체로 복원하는 과정을 보여줍니다. 이러한 작업은 데이터의 이동성 및 저장성을 증가시키는 데 유용합니다.