[Python] 10. 클래스와 객체 지향 프로그래밍

YJ·2024년 12월 5일

Python 공부하기

목록 보기
11/13
post-thumbnail

본 블로그 글은 박동민·강영민 저자님의 으뜸파이썬 교재를 참고하여 만들어진 글임을 밝힙니다.

클래스와 객체 지향 프로그래밍

객체 (Object)

  • 속성과 메서드를 가진 데이터의 단위로, 컴퓨터 시스템에서 다양한 기능을 수행하도록 설계된 기본 요소
  • 파이썬에서는 모든 것이 객체로 간주되며, 데이터 유형도 객체로 처리된다.

type() - 객체의 자료형 반환

  • 객체의 자료형(Type)을 반환한다.
  • 모든 객체는 생성 시 고유의 id(식별자)를 가지며, id() 함수를 사용하여 확인이 가능하다.

매직 매서드

  • 클래스 내에서 특별한 역할을 수행하는 메서드들로, 주로 객체의 생성, 연산, 출력 등 다양한 기능을 정의할 때 사용된다.
  • 더블 언더스코어(__)로 감싸져있는 형태이다.
매직 메서드설명
__init__(self)객체가 생성될 때 호출되는 생성자 메서드, 객체 초기화 작업을 담당
__str__(self)print()나 str()로 객체를 출력할 때 호출되는 메서드, 객체의 문자열 표현을 정의
__repr__(self)객체를 표현하는 공식적인 문자열을 반환하는 메서드, repr()로 호출
__len__(self)len() 함수 호출 시 객체의 길이를 반환하는 메서드
__getitem__(self, key)객체를 인덱스로 접근할 때 호출되는 메서드. 리스트처럼 동작할 수 있게 해줌
__setitem__(self, key, value)객체에 인덱스를 사용해 값을 설정할 때 호출되는 메서드
__delitem__(self, key)객체에서 인덱스로 값을 삭제할 때 호출되는 메서드
__iter__(self)객체를 반복할 수 있도록 하는 메서드, for문에서 사용될 때 호출됨
__next__(self)반복자 객체에서 다음 값을 반환하는 메서드, __iter__와 함께 사용
__call__(self, ...)객체가 함수처럼 호출될 때 호출되는 메서드, 함수처럼 동작하게 할 수 있음.

int 클래스

  • int와 같은 기본 타입의 자료형도 실제로는 클래스이다.
  • 또한, 이러한 자료형들은 다음과 같은 매직 메서드를 가진다
    메서드연산자설명
    __add__(self, other)+덧셈 연산을 정의
    __sub__(self, other)-뺄셈 연산을 정의
    __mul__(self, other)*곱셈 연산을 정의
    __truediv__(self, other)/나눗셈 연산을 정의
    __floordiv__(self, other)//몫 연산을 정의
    __mod__(self, other)%나머지 연산을 정의
    __pow__(self, other)``**거듭제곱 연산을 정의
    __lshift__(self, other)<<왼쪽 비트 시프트 연산 정의
    __rshift__(self, other)>>오른쪽 비트 시프트 연산 정의

사용 예시

(200).__sub__(100) # 200 - 100과 동일
(200).__sub__(100) # 200 - 100과 동일
(5).__add__(3) # 5 + 3과 동일
(10).__mul__(4) # 10 * 4와 동일

객체 지향 프로그래밍 vs 절차적 프로그래밍

객체 지향 프로그래밍 (Objected Oriented Programming)

  • 프로그램을 짤 때, 프로그램을 실제 세상에 가깝게 모델링하는 기법
  • 컴퓨터가 수행하는 작업을 객체 사이의 상호작용으로 표현한다.
  • 핵심 개념
    • 캡슐화 (Encapsulation) : 데이터와 메서드를 하나로 묶고, 외부에서 직접 접근하지 못하도록 제한하는 것
    • 상속 (Inheritance) : 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 생성하는 것
    • 다형성 (Polymorphism) : 동일한 이름의 메서드가 객체 종류에 따라 다르게 동작하게 하는 것
    • 추상화 (Abstraction) : 불필요한 세부 정보를 숨기고, 중요한 기능만 노출하여 복잡성을 줄이는 것
  • Java, Python, C++, C#, Swift 등 현재 사용 중인 많은 프로그래밍 언어에서 채택

절차적 프로그래밍 언어 (Procedural Programming)

  • 함수나 모듈을 만들어두고 이것들을 문제해결 순서에 맞게 호출하여 수행하는 방식
  • C, Fortran, Basic등의 고전적인 프로그래밍 언어에서 채택

개발이나 소프트웨어 업데이트시의 유지보수 비용이 매우 적게 들기 때문에 최근 프로그래밍 경향은 대부분 객체 지향 방식을 선호

객체 vs 클래스 vs 인스턴스

  • 객체 (Object): 속성과 메서드를 가진 프로그램 내의 실체적인 존재
  • 클래스 (Class): 객체를 생성하기 위한 설계도 또는 템플릿
  • 인스턴스 (Instance): 클래스로부터 생성된 개별적인 객체 (메모리 상에서 존재하는 객체)

생성자

  • 객체를 생성할 때, 인스턴스 내부의 변수가 기본값을 가지도록 초기화하는 역할을 하는 메서드입니다.
  • 다른 언어에서는 생성자를 오버로딩하여 여러 개를 선언할 수 있지만, 파이썬에서는 하나의 생성자만 선언할 수 있습니다.
    • 오버로딩 (Overloading): 동일한 이름의 메서드를 매개변수의 개수나 타입에 따라 다르게 정의하는 것

str() 메서드

  • 객체를 문자열로 표현할 때 사용되는 메서드
  • 어떤 객체의 문자열 표현 방식을 정의하는데 반환 값은 문자열이 된다.

실습

class Cat:
    # 생성자 메서드 (__init__)로 멤버 변수 초기화
    def __init__(self, name, age):
        self.name = name  # 멤버 변수 name
        self.age = age    # 멤버 변수 age
    
    # 메서드 정의
    def meow(self):
        print(f"{self.name} says: Meow!")

    def introduce(self):
        print(f"My name is {self.name} and I am {self.age} years old.")
        
    def __str__(self):
		    return f'Cat(name = {self.name}, age = {self.age}'

# Cat 클래스의 인스턴스(객체) 생성
my_cat = Cat("Whiskers", 3)

# 메서드 호출
my_cat.meow()        # 출력: Whiskers says: Meow!
my_cat.introduce()   # 출력: My name is Whiskers and I am 3 years old.

캡슐화

  • 메서드와 변수를 외부에서 함부로 조작하는 것을 제한하고 데이터를 보호한다.
  • 우연히 값이 변경되는 것을 방지한다.
  • 논리적인 오류를 줄이기 위한 방법이다.
  • 멤버 변수를 선언할때 __ (던더)를 앞에 붙여서 외부에서 클래스에 멤버변수에 직접 접근하는 것을 막는다.
    • 이 경우 멤버변수는 private 속성으로 선언된다.
  • getter와 setter를 선언하여 접근 및 수정을 한다.
    • getter : 외부에서 객체의 값을 조회할 수 있게 해주는 메서드
    • setter : 외부에서 객체의 값을 수정할 수 있게 해주는 메서드
class Cat:
    # 생성자 메서드 (__init__)로 멤버 변수 초기화
    def __init__(self, name, age):
        self.__name = name  # 멤버 변수 name (캡슐화)
        self.__age = age    # 멤버 변수 age (캡슐화)
    
    # getter 메서드
    def get_name(self):
        return self.__name
    
    def get_age(self):
        return self.__age
    
    # setter 메서드
    def set_name(self, name):
        self.__name = name
    
    def set_age(self, age):
        if age > 0:  # 나이가 0보다 클 때만 수정 가능
            self.__age = age
        else:
            print("나이는 양수여야 합니다.")
            
    def __str__(self):
        return f'Cat(name = {self.__name}, age = {self.__age})'

# Cat 클래스의 인스턴스(객체) 생성
my_cat = Cat("Whiskers", 3)

# getter 메서드 호출
print(my_cat.get_name())  # 출력: Whiskers
print(my_cat.get_age())   # 출력: 3

# setter 메서드 호출
my_cat.set_name("Fluffy")
my_cat.set_age(4)

# 수정된 값 출력
print(my_cat.get_name())  # 출력: Fluffy
print(my_cat.get_age())   # 출력: 4

is, is not - 객체의 아이덴티티 연산

  • 두 객체의 값은 같아도, 다른 메모리에 저장되는 객체의 경우 id가 다르게 출력된다.
    • 두 객체의 값을 비교할때는 == 연산자를 사용한다.
  • isis not은 두 객체의 id를 비교하는 함수이다.
    • is :  객체가 같은 메모리 주소를 참조하는지 비교
    • is not : 두 객체가 같은 메모리 주소를 참조하지 않는지 비교

파이썬에서 메모리 절약을 위한 객체 재사용 (Interning

  • 파이썬은 메모리 절약을 위해 특정 객체들을 재사용하는 방식인 객체 인터닝(interning)을 사용한다

문자열 인터닝 (String Interning)

  • 문자열은 파이썬에서 자주 사용되는 불변 객체로, 동일한 문자열 값이 여러 곳에서 사용될 경우 인터닝을 통해 메모리 절약을 한다.
  • 리터럴 문자열의 경우 자동으로 인터닝된다.
    • 리터럴 문자열 : 코드에 하드코딩된 문자열

작은 정수 인터닝 (Small Integer Interning)

  • 파이썬은 5에서 256 사이의 정수에 대해 인터닝을 수행합니다. 이 범위 내의 정수는 같은 값이 여러 번 사용되더라도 하나의 메모리 주소를 참조합니다

사용자 정의 클래스 연산자 오버로딩 구현

  • 연산자 오버로딩은 기존 연산자의 동작을 새로운 데이터 타입에 맞게 재정의하는 것을 의미
  • 매직 메서드를 통해 연산자 오버로딩을 구현
  • 앞서 int 객체의 매직 메서드에 대해 잠깐 언급되었었다.

연산 메서드

연산자매직 메서드설명
x + y__add__두 객체의 덧셈 동작을 정의
x - y__sub__두 객체의 뺄셈 동작을 정의
x * y__mul__두 객체의 곱셈 동작을 정의
`x y`**__pow__두 객체의 거듭제곱 동작을 정의
x / y__truediv__두 객체의 나눗셈 동작(소수점 포함)을 정의
x // y__floordiv__두 객체의 몫을 반환하는 동작을 정의
x % y__mod__두 객체의 나머지를 반환하는 동작을 정의
+x__pos__객체의 양수 연산 동작을 정의
-x__neg__객체의 음수 연산 동작을 정의

비교 메서드

연산자매직 메서드설명
x == y__eq__두 객체의 동등 비교를 정의
x != y__ne__두 객체의 다름 비교를 정의
x < y__lt__두 객체의 작음 비교를 정의
x > y__gt__두 객체의 큼 비교를 정의
x <= y__le__두 객체의 작거나 같음 비교를 정의
x >= y__ge__두 객체의 크거나 같음 비교를 정의

내장 메서드

내장 함수/동작매직 메서드설명
str(obj)__str__객체를 문자열로 표현할 때 호출됨
repr(obj)__repr__객체를 개발자 친화적인 문자열로 표현함
len(obj)__len__객체의 길이를 반환하는 동작을 정의
obj[key]__getitem__객체에서 인덱스 접근 동작을 정의
del obj[key]__delitem__객체에서 인덱스를 삭제하는 동작을 정의
key in obj__contains__객체에 특정 키가 포함되어 있는지 확인함

실습

연산 메서드: 덧셈(+)과 뺄셈(``)

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 __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

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

v1 = Vector(2, 3)
v2 = Vector(4, 1)

# + 연산
result_add = v1 + v2
print(result_add)  # 출력: Vector(6, 4)

# - 연산
result_sub = v1 - v2
print(result_sub)  # 출력: Vector(-2, 2)

비교 메서드: 동등(==) 및 작음(<) 비교

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

    # 동등 비교
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    # 작음 비교
    def __lt__(self, other):
        return (self.x, self.y) < (other.x, other.y)

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

# == 연산
print(p1 == p2)  # 출력: True
print(p1 == p3)  # 출력: False

# < 연산
print(p1 < p3)   # 출력: True
print(p3 < p1)   # 출력: False

내장 메서드: 문자열 표현 및 길이 반환

class CustomList:
    def __init__(self, items):
        self.items = items

    # 문자열 표현
    def __str__(self):
        return f"CustomList({self.items})"

    # 길이 반환
    def __len__(self):
        return len(self.items)

my_list = CustomList([1, 2, 3, 4])

# 문자열 표현
print(str(my_list))  # 출력: CustomList([1, 2, 3, 4])

# 길이 반환
print(len(my_list))  # 출력: 4

클래스 상속

  • 소프트웨어 개발 시 기존에 만들어진 부모 클래스의 속성과 메서드를 자식 클래스가 물려받아 사용하는 것을 말한다.
  • 코드 재사용성을 높이고 효율적인 개발을 가능하게 한다.
  • 상속을 하는 부모 클래스, 상속 받는 자식 클래스로 구분된다.
    • 부모 클래스 (Parent Class) : 다른 클래스가 상속받을 수 있는 기존의 클래스
      • 슈퍼클래스(Super Class), 기본 클래스(Base Class)라고도 부른다.
    • 자식 클래스 (Child Class) : 부모 클래스를 상속받아 그 속성과 메서드를 물려받는 클래스, 부모 클래스의 기능을 그대로 사용할 수도 있고, 새로운 속성이나 메서드를 추가하거나, 기존 메서드를 재정의(Overriding)할 수 있다.
      • 서브 클래스(Sub class), 파생 클래스(Derived Class)라고도 부른다.

super()

  • 자식 클래스에서 부모 클래스의 메서드와 속성을 호출할 때 사용한다.
  • 부모 클래스의 이름을 명시적으로 적어줄 필요가 없다.

실습

class Parent:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hello, I am {self.name}."

class Child(Parent):  # Parent 클래스 상속
    def __init__(self, name, age):
        super().__init__(name)  # 부모 클래스의 __init__ 호출
        self.age = age

    def introduce(self):
        return f"{super().greet()} I am {self.age} years old."

# 객체 생성 및 사용
child = Child("Alice", 12)
print(child.greet())       # 출력: Hello, I am Alice.
print(child.introduce())   # 출력: Hello, I am Alice. I am 12 years old.

클래스 변수

  • 클래스 자체에 선언되는 변수로, 클래스의 모든 인스턴스가 공유하는 변수
  • 클래스가 생성될 때 메모리에 할당된다.

클래스 변수 vs 인스턴스 변수

특징클래스 변수인스턴스 변수
선언 위치클래스 내부, 메서드 외부__init__ 메서드 내부에서 선언
소속클래스 자체에 소속특정 인스턴스에 소속
공유 여부모든 인스턴스가 동일한 값을 공유각 인스턴스가 독립적인 값을 가짐
접근 방법클래스 이름 또는 인스턴스를 통해 접근 가능인스턴스를 통해서만 접근 가능
수정 범위한 인스턴스에서 수정하면 다른 모든 인스턴스에 영향수정해도 다른 인스턴스에는 영향 없음

실습

클래스 변수 선언

class MyClass:
    class_variable = "This is a class variable"  # 클래스 변수

클래스 변수의 접근

print(MyClass.class_variable)  # 클래스 이름을 통해 접근
obj = MyClass()
print(obj.class_variable)      # 인스턴스를 통해 접근
  • 클래스 이름, 인스턴스를 통한 접근 둘다 가능하다.
profile
제 글이 유익하셨다면 ♡와 팔로우로 응원 부탁드립니다.

0개의 댓글