[Python – 기초 강좌] 13. 클래스(Class) & 객체(Object) (feat. Overriding, Overloading)

Python에서 클래스(Class)와 객체(Object)는 객체 지향 프로그래밍(Object-Oriented Programming, OOP)의 핵심 요소입니다.

이들은 프로그램을 보다 효율적이고 체계적으로 구성할 수 있도록 돕습니다.

이 글에서는 Python의 클래스(Class)와 객체(Object)에 대해 자세히 설명하고, 실제 사용 예를 들어 설명하겠습니다.

클래스(Class)란?

개요

Class는 객체를 생성하기 위한 Template 또는 Blue-print라고 할 수 있으며, 특정 유형의 객체가 공유할 변수(Variable)과 메서드(Method)를 정의합니다.

Class는 실제 세계의 개념을 모델링하여 Variable과 Method를 하나의 단위로 묶어 표현합니다.

예를 들어, ‘Dog’ 클래스를 만든다고 가정해 보겠습니다.

이 Class 는 모든 개가 가질 수 있는 일반적인 속성(예: 이름, 나이, 품종)과 행동(예: 짖다, 먹다)을 정의할 수 있습니다.

예제 코드

class Dog:
    def __init__(self, name, age, species):
        self.name = name      # Initialize the name of the dog
        self.age = age        # Initialize the age of the dog
        self.species = species  # Initialize the species of the dog

    def bark(self):
        return f"{self.name} barks: Woof woof!"  # Method for the dog to bark

    def eat(self, food):
        return f"{self.name} eats {food}."  # Method for the dog to eat some food

객체(Object)란?

개요

객체(Object)는 클래스(Class)에 의해 생성된 인스턴스입니다.

즉, Class에서 정의한 Template을 바탕으로 실제 메모리 상에 할당된 실체입니다.

각 Object는 Class의 속성을 가지며 독립적인 상태와 행동을 가집니다.

예제 코드

# Create an instance of the Dog class (object)

my_dog = Dog("Choco", 3, "Siberian Husky")
print(my_dog.bark())  # Outputs: Choco barks: Woof woof!
print(my_dog.eat("dog food"))  # Outputs: Choco eats dog food.

기본 사용 방법

Class 정의 및 초기화

Python에서 클래스를 정의하는 기본적인 구조는 class 키워드를 사용합니다.

클래스 내부에서 __init__ 메소드는 생성자 역할을 하며, 객체가 생성될 때 자동으로 호출됩니다.

이 메소드를 통해 객체의 초기 상태를 설정할 수 있습니다.

self는 인스턴스 자신을 가리키는 참조자로, 클래스 내부에서 메소드와 속성에 접근할 때 사용합니다

# class definition
class Dog:
    def __init__(self, name, age, species):
        self.name = name      # Initialize the name of the dog
        self.age = age        # Initialize the age of the dog
        self.species = species  # Initialize the species of the dog

메소드 정의

클래스 내부에서 메소드를 정의할 때도 self를 첫 번째 인자로 포함해야 합니다.

이를 통해 해당 메소드가 특정 객체의 메소드임을 명시합니다.

# class definition
class Dog:
    def __init__(self, name, age, species):
        self.name = name      # Initialize the name of the dog
        self.age = age        # Initialize the age of the dog
        self.species = species  # Initialize the species of the dog

    def bark(self):
        return f"{self.name} barks: Woof woof!"  # Method for the dog to bark

클래스 내부 변수

클래스 내부 변수는 해당 클래스의 모든 인스턴스가 공유하는 변수입니다.

self.(변수이름) 을 통해 Class 내의 변수를 정의하여, 해당 객체 Lifecycle 동안 내부에서 사용할 수 있게 됩니다.

아래 예제에서 __init__()에서 정의한 self.name을 아래 bark()에서 사용할 수 있음이 그 예 입니다.

# class definition
class Dog:
    def __init__(self, name, age, species):
        self.name = name      # Initialize the name of the dog
        self.age = age        # Initialize the age of the dog
        self.species = species  # Initialize the species of the dog

    def bark(self):
        return f"{self.name} barks: Woof woof!"  # Method for the dog to bark

클래스 상속(Class inheritance)

개요

클래스 상속(Class inheritance) 은 한 Class가 다른 Class의 속성과 메소드를 물려받는 기능을 말합니다.

여기서 물려 받는 Class를 자식 Class(child class) 혹은 파생 Class (derived class) 라고 하며,

물려주는 Class를 부모 Class(parent Class) 라고 합니다.

상속을 통해 기존 코드를 재사용하면서도, 새로운 기능을 추가하거나 기존 기능을 수정할 수 있습니다.

이를 통해 코드의 중복을 최소화하고, 유지 관리를 용이하게 할 수 있습니다.

class-inheritance-상속

예제

아래 예제에서 Parent Class Dog를 상속받은 Child Class SiberianHusky 는 오버라이딩 하여 메소드를 재정의 하거나, 새로운 메소드를 정의하여 사용할 수 있습니다.

__str__ 메소드는 객체의 문자열 표현을 정의합니다.

# class inheritance example with Dog class
class SiberianHusky(Dog):
    def __init__(self, name, age, species, color):
      super().__init__(name, age, species)  # Call the parent class constructor
        
      self.color = color  # Initialize the color of the dog
        
    def howl(self):
      return f"{self.name} howls: Awoooooo!"
    
    def eat(self, food):
      return f"{self.name} eats {food}."
    
    def bark(self):
      return f"{self.name} barks: Woof woof!"
    
    def __str__(self):
      return f"{self.name} is a {self.color} Siberian Husky."
    
my_husky = SiberianHusky("Choco", 3, "Siberian Husky", "black and white")
print(my_husky.howl())  # Outputs: Choco howls: Awoooooo!
print(my_husky.eat("dog food"))  # Outputs: Choco eats dog food.
print(my_husky.bark())  # Outputs: Choco barks: Woof woof!
print(my_husky)  # Outputs: Choco is a black and white Siberian Husky.

결과

Choco howls: Awoooooo!
Choco eats dog food.
Choco barks: Woof woof!
Choco is a black and white Siberian Husky.

Overriding(오버라이딩)

Overriding는 자식 클래스(child class)에서 부모 클래스(parent class)의 메서드를 재정의하는 것을 말합니다.

이를 통해 상속받은 메서드와 같은 이름, 같은 매개변수를 가지면서도 다른 기능을 수행하게 할 수 있습니다.

Overriding는 부모 클래스(parent class)의 구현을 대체하여, 자식 클래스(child class)에 특화된 동작을 구현하고자 할 때 사용됩니다.

위 상속 예에서 SiberianHusky class에서 eat()bark() 메소드를 overriding하여 구현했습니다.

Overloading(오버로딩)

Overloading은 같은 이름의 메서드가 다른 매개변수를 가질 수 있도록 하는 기능입니다.

Python은 다른 언어들과 달리 직접적인 메소드 Overloading을 지원하지 않습니다.

대신, 기본 값을 갖는 매개변수나 가변 매개변수를 사용하여 유사한 기능을 구현할 수 있습니다.

예제

이 Class에서 add 메소드는 세 개의 매개변수를 받지만, 세 번째 매개변수 c는 선택적입니다. 이를 통해 두 개 또는 세 개의 숫자를 더하는 것처럼 메소드 오버로딩과 유사한 기능을 할 수 있습니다.

class Sum:
    def add(self, a, b, c=0):  # c는 기본값이 0
        return a + b + c
      
sum_instance = Sum()
print(sum_instance.add(1, 2))    # 3
print(sum_instance.add(1, 2, 3)) # 6

결과

3
6

super()

super() 함수는 Child Class 에서 Parent Class의 메소드를 호출할 때 사용됩니다.

예를 들어 위 예제에서 Child Class(SiberianHusky)는 Parent Class(Dog)의 __init()__메소드를 호출했습니다.

이를 통해 다음의 장점을 얻어갈수 있습니다.

  1. 코드 재사용성: super()를 사용하면 Parent Class의 메소드를 재정의할 때 Parent Class의 구현을 재사용할 수 있으므로 중복 코드를 줄일 수 있습니다.
  2. 유지보수성: Parent Class의 메소드가 변경되었을 때, 모든 Child Class에 그 변경 사항을 일일이 반영하지 않아도 됩니다. Child Class에서 super()를 통해 Parent Class의 메소드를 호출하면 변경된 Parent Class가 자동으로 적용됩니다.
  3. 확장성: 새로운 기능이 필요할 때 기존 코드를 크게 변경하지 않고, 메소드를 확장할 수 있습니다.

객체의 복사 (Object Copy)

얕은 복사(Shallow Copy)

얕은 복사는 객체의 상위 수준만 복사하는 방식으로, 원본 객체의 주소만을 복사합니다.

이 경우, 복사된 객체와 원본 객체는 같은 메모리 주소를 공유하게 되므로, 한 쪽의 변경이 다른 쪽에도 영향을 미칩니다.

Python에서는 copy 모듈의 copy() 함수를 사용하여 얕은 복사를 수행할 수 있습니다.

예제

이 예제에서 볼 수 있듯이, b의 내부 리스트 요소를 변경하였을 때 a의 내부 리스트도 변경되었습니다.

이는 얕은 복사가 내부 객체(여기서는 리스트)의 참조를 복사하기 때문입니다

# Shallow copy
import copy

a = [1, 2, [3, 4]]
b = copy.copy(a)

b[2][0] = 999
print(a)  # print: [1, 2, [999, 4]]

결과

[1, 2, [999, 4]]

깊은 복사(Deep Copy)

깊은 복사는 객체의 내부에 있는 모든 요소를 새롭게 복사하는 방식입니다.

이를 통해 원본 객체와 복사된 객체가 완전히 독립적이 되어, 한 객체에서의 변경이 다른 객체에 영향을 미치지 않습니다.

Python에서는 copy 모듈의 deepcopy() 함수를 사용하여 깊은 복사를 수행할 수 있습니다.

예제

이 예제에서 b의 내부 리스트를 변경해도 a의 내부 리스트는 변경되지 않았습니다.

deepcopy()는 모든 내부 객체까지 새로운 복사본을 만들기 때문입니다.

import copy

a = [1, 2, [3, 4]]
b = copy.deepcopy(a)

b[2][0] = 999
print(a)  # print: [1, 2, [3, 4]]

결과

[1, 2, [3, 4]]

참고 문헌

Leave a Comment