2.4 Palatable Python_Basic Syntax#4_Class_Revise

mseokq23·2025년 1월 2일

Palatable Python

목록 보기
8/11
post-thumbnail

2.4 Class

2.4.1 Class e.g.

클래스를 정의하는 간단한 예제

class Person:
    pass
  • class Person:

Person이라는 이름의 클래스를 정의하는 구문
보통 클래스 이름은 대문자로 시작하는 ‘카멜 케이스(CamelCase)’로 작성하는 것이 관례 (예: Person, MyClass, StudentInfo 등)

  • pass

파이썬에서 ‘아무것도 하지 않고 넘어간다’는 의미로 쓰이는 키워드.
우리는 아직 클래스를 비워 둘 것이므로 pass를 사용.

이렇게만 작성하면 아무런 속성(변수)과 메서드(함수)가 없는 완전히 빈 클래스된다.

2.4.2 Create object (instance) from a class

클래스를 정의했다면, 이제 실제로 사용할 객체(인스턴스)를 만들 수 있다. 클래스를 “붕어빵 틀”이라고 생각한다면, 객체는 그 틀로부터 찍어낸 “붕어빵”과 비슷하다.

2.4.2.1 Create object

# Person 클래스를 정의한 후
class Person:
    pass

# Person 클래스로부터 객체(인스턴스) 생성
person1 = Person()
person2 = Person()

print(person1)  # <__main__.Person object at ...>
print(person2)  # <__main__.Person object at ...>
  • Person()를 호출하면 Person 클래스의 인스턴스가 하나 만들어져서 person1 변수에 담긴다.
  • 마찬가지로 person2는 다른 하나의 인스턴스가 된다.
    이렇게 생성된 person1과 person2는 모두 Person 클래스의 객체이며, 서로 다른 고유한 객체이다.

2.4.2.2 Define class attributes (variables) and methods (functions)

이제 클래스 안에 실제로 동작할 수 있는 요소들을 만들어 봅시다. 파이썬 클래스에서 다룰 수 있는 요소는 크게 다음 두 가지가 있다.

  1. 인스턴스 변수(Instance Variable) 혹은 속성(Attribute)
  2. 메서드(Method) – 클래스 내부에서 정의된 함수
class Person:
    # 생성자(초기화 메서드)
    def __init__(self, name, age):
        self.name = name  # 인스턴스 변수 name
        self.age = age    # 인스턴스 변수 age

    # 메서드
    def say_hello(self):
        print(f"안녕하세요. 저는 {self.name}이고, {self.age}살이다.")

(1) init 메서드
init(이닛)는 클래스가 인스턴스화될 때(즉, Person()으로 객체를 생성할 때) 자동으로 호출되는 메서드이다.

  • self는 메서드가 호출될 때 자동으로 전달되는 인스턴스 자기 자신을 가리키는 매개변수이다.
  • 클래스 내부에 있는 메서드 정의에서는 항상 self를 첫 번째 인자로 넣어야 합니다(예외적으로 클래스/스태틱 메서드는 다름).
    self.name = name은 생성자에 전달된 name 값을 인스턴스 변수 name에 할당한다는 의미이다.
  • 여기서 self.name와 name은 각각 다른 이름공간(namespace)에 존재합니다. 즉, name은 생성자의 매개변수, self.name은 인스턴스 변수이다.

(2) 메서드(say_hello)
클래스 내부에 정의된 일반적인 함수이다.

  • say_hello 메서드가 호출되면, 인스턴스가 가진 name, age 값 등을 이용하여 동작을 수행한다.
  • 메서드도 self를 인자로 받으므로, 인스턴스 변수를 참조할 때 self.name, self.age 같은 형태로 사용한다.

2.4.3 How to use Class(Create instance & Call methods)

이제 완성한 Person 클래스를 실전에서 사용.

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

    def say_hello(self):
        print(f"안녕하세요. 저는 {self.name}이고, {self.age}살입니다.")

# Person 클래스를 사용해 인스턴스(객체) 생성
person1 = Person("Alice", 30)
person2 = Person("Bob", 25)

# say_hello 메서드 호출
person1.say_hello()  # 안녕하세요. 저는 Alice이고, 30살입니다.
person2.say_hello()  # 안녕하세요. 저는 Bob이고, 25살입니다.
  • Person("Alice", 30)를 실행하면 init 메서드로 name="Alice", age=30이 전달된다.
  • 이때 person1.name은 "Alice", person1.age는 30이 된다.
  • say_hello 메서드를 호출하면, person1.name과 person1.age를 바탕으로 메시지를 출력한다.

2.4.4 Differences between Class and Instance variables

파이썬 클래스에는 클래스 변수와 인스턴스 변수가 있다.

  1. 인스턴스 변수
  • 각각의 인스턴스마다 별도로 존재하는 변수이다.
    예: self.name, self.age
  1. 클래스 변수
  • 클래스 자체에 속하는 변수로, 모든 인스턴스가 공유이다.
    예를 들어, 모든 사람은 “포유류”라는 공통된 특성을 공유한다고 가정.
class Person:
    # 클래스 변수
    species = "Homo sapiens"  # 모든 'Person' 인스턴스가 공유

    def __init__(self, name, age):
        # 인스턴스 변수
        self.name = name
        self.age = age

    def say_hello(self):
        print(f"안녕하세요. 저는 {self.name}이고, {self.age}살입니다.")
        print(f"저는 {Person.species} 종입니다.")
  • Person.species처럼 클래스 이름을 통해 접근할 수도 있고,
  • 인스턴스를 통해 person1.species 형태로도 접근이 가능(동일 값 참조).
person1 = Person("Alice", 30)
person2 = Person("Bob", 25)

print(person1.species)  # Homo sapiens
print(person2.species)  # Homo sapiens
print(Person.species)   # Homo sapiens

2.4.5 Encapsulation & Information hiding

객체지향 프로그래밍에서는 캡슐화(encapsulation), 정보 은닉(information hiding) 이라는 개념 존재.

  • 간단히 말해, 객체 내부의 구현 세부 사항을 외부에 노출하지 않고, 필요한 기능만 공개하여 안정적인 코드를 작성하는 기법.

파이썬에서는 다른 언어(C++, Java 등)와 달리 특별한 키워드(private, public)가 없지만, 다음과 같이 접두사로 _(언더스코어)를 사용하여 “이 변수는 클래스 내부에서만 쓰는 것이 좋겠다”는 관례를 표시.

class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self._balance = balance  # _balance : 외부에서 직접 건드리지 말라는 의미 (관례)

    def deposit(self, amount):
        self._balance += amount

    def withdraw(self, amount):
        if amount <= self._balance:
            self._balance -= amount
        else:
            print("잔액이 부족합니다.")

    def get_balance(self):
        return self._balance

# 사용 예시
account = BankAccount("Alice", 1000)
account.deposit(500)
account.withdraw(300)

print(account.get_balance())  # 1200
# 굳이 account._balance 로 직접 접근하기보다, get_balance() 메서드를 이용하는 것이 권장됨.

이처럼 _balance 같은 “비공개로 하고 싶은” 속성의 직접 접근을 최소화하고, deposit, withdraw, get_balance 같은 메서드를 통해 간접적으로 조작하거나 조회하도록 설계.

2.4.6 Inheritance

클래스를 정의하다 보면, 기존에 만든 클래스를 확장해 사용하고 싶을 수 있다. 예를 들어 Person 클래스를 상속해서 학생(Student) 클래스를 만들면, Person의 속성과 메서드를 물려받아 재사용하고, 필요한 부분만 새로 추가할 수 있다.

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

    def say_hello(self):
        print(f"안녕하세요. 저는 {self.name}이고, {self.age}살입니다.")

# Person 클래스를 상속하는 Student 클래스
class Student(Person):
    def __init__(self, name, age, student_id):
        # 부모 클래스의 __init__ 호출
        super().__init__(name, age)
        self.student_id = student_id

    def study(self):
        print(f"{self.name} 학생이 공부하고 있습니다.")

# 사용 예시
student1 = Student("Charlie", 20, "S12345")
student1.say_hello()  # 안녕하세요. 저는 Charlie이고, 20살입니다.
student1.study()      # Charlie 학생이 공부하고 있습니다.
  • class Student(Person):
    Student 클래스는 Person 클래스를 상속받는다.

  • super().init(name, age)
    부모 클래스의 생성자를 명시적으로 호출하여, Student 객체도 Person이 갖는 속성을 정상적으로 초기화할 수 있다.

  • 이제 Student 객체는 Person이 가진 say_hello() 메서드도 그대로 사용할 수 있고, 추가로 study() 메서드 등 새로운 기능을 추가할 수 있다.

2.4.7 Class Inheritance, Encapsulation, Polymorphism, Abstraction

2.4.7.1 Inheritance

상속은 이미 정의해 놓은(부모) 클래스의 속성과 메서드를 “그대로” 물려받고, 필요한 기능만 추가하거나 변경할 수 있게 해 주는 기법.

  • 코드 재사용성: 동일한 코드를 반복해서 작성할 필요 없이, 부모 클래스에서 정의한 것을 그대로 가져다 쓴다.
  • 유지보수 편의성: 공통된 부분은 부모 클래스에만 수정해도, 이를 상속하는 자식 클래스에서 자동으로 반영.

아래 예시는 Person 클래스를 만들어 두고, 이를 상속받아 Student 클래스를 정의

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

    def say_hello(self):
        print(f"안녕하세요. 저는 {self.name}이고, {self.age}살입니다.")

# Person 클래스를 상속하여 Student 클래스 정의
class Student(Person):
    def __init__(self, name, age, student_id):
        # 부모 클래스의 __init__() 호출
        super().__init__(name, age)
        self.student_id = student_id

    def study(self):
        print(f"{self.name} 학생이 공부하고 있습니다.")

# 사용 예시
student1 = Student("Charlie", 20, "S12345")
student1.say_hello()  # 안녕하세요. 저는 Charlie이고, 20살입니다.
student1.study()      # Charlie 학생이 공부하고 있습니다.
  • class Student(Person):
    Student는 Person을 상속받습니다.

  • super().init(name, age)
    부모 클래스 Person의 초기화 로직을 재사용(호출)하여 name, age를 세팅.

  • 상속받은 자식 클래스(Student)는 부모 클래스(Person)의 메서드 say_hello를 그대로 사용하며, 추가로 study 라는 메서드를 갖는다.

2.4.7.1.1 Multiple inheritance

파이썬은 다중 상속(한 클래스가 여러 부모 클래스를 동시에 상속받는 것)을 지원.

하지만 복잡도가 올라가기 때문에, 꼭 필요한 상황이 아니라면 단일 상속만으로도 충분한 경우가 많음.

class A:
    pass

class B:
    pass

class C(A, B):  # A와 B를 동시에 상속
    pass

다중 상속을 사용하면 메서드 탐색 순서(MRO, Method Resolution Order)가 다소 복잡.

MRO는 파이썬이 메서드를 찾기 위해 어떤 클래스부터 탐색하는지 순서를 정의한 규칙.

2.4.7.2 Encapsulation

캡슐화는 객체가 내부에서 사용하는 데이터(속성)와 메서드를 외부에서 직접 건드리지 못하게 보호하고, 꼭 필요한 기능만 공개(Public)하는 것을 말한다.

  • 내부 구현을 숨기고(정보 은닉, Information Hiding)
  • 외부에는 명확하고, 필요한 “인터페이스”만 공개하여 사용하는 방식을 의미.

2.4.7.2.1 Encapsulation (in Python)

C++이나 Java 같은 언어와 달리, 파이썬에는 private 혹은 protected 같은 접근 제한자 키워드가 없다. 하지만 네이밍 컨벤션(naming convention)을 통해 “이건 건드리지 않는 편이 좋다”고 암묵적으로 표시이다.

2.4.7.2.2 단일 언더스코어(_)

_balance처럼 단일 언더스코어로 시작하는 이름은, “외부에서 직접 접근하기보다는 내부용으로만 쓰는 게 좋다”는 관례이다.

2.4.7.2.3 이중 언더스코어(__)

__balance처럼 이중 언더스코어로 시작하면, 파이썬이 이름을 맹렬히 바꿔버리는(name mangling) 기법을 사용.

  • 예: __balance -> _BankAccount__balance
  • 외부에서 동일한 이름을 직접 사용하기 어렵도록 하여, 사실상 “외부 접근을 막는” 효과를 낸다.
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self._balance = balance  # 내부용(관례)

    def deposit(self, amount):
        self._balance += amount

    def withdraw(self, amount):
        if amount <= self._balance:
            self._balance -= amount
        else:
            print("잔액이 부족합니다.")

    def get_balance(self):
        return self._balance

account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.get_balance())     # 1500
print(account._balance)          # 1500 (접근은 되지만, 권장하지 않음)
  • 파이썬에서는 “접근을 원천적으로 금지”하지는 않는다.
  • 단, 이런 식으로 언더스코어를 사용해서 “사용하지 않는 것이 좋다”는 의사를 표현하고, 필요한 경우에는 메서드(deposit, withdraw, get_balance)를 통해서만 접근하게 설계.
  • 이를 통해 데이터 무결성(Data Integrity)을 유지하고, 코드 유지보수성을 높일 수 있다.

2.4.7.3 Polymorphism

다형성은 같은 이름의 메서드(또는 연산자)를 호출했을 때, 그 대상 객체의 종류(클래스)에 따라 다르게 동작하는 성질을 말함.

예: “동물” 클래스의 make_sound() 메서드가 “개” 클래스에서는 “멍멍”을 출력하고, “고양이” 클래스에서는 “야옹”을 출력하게 하는 것.

2.4.7.3.1 파이썬에서의 다형성

  • 메서드 오버라이딩(Method Overriding)
    상속에서 자식 클래스가 부모 클래스의 메서드를 재정의(override)하면, 자식 클래스의 메서드가 우선순위를 갖는다.
class Animal:
    def make_sound(self):
        print("동물이 소리를 냅니다.")

class Dog(Animal):
    def make_sound(self):
        print("멍멍!")

class Cat(Animal):
    def make_sound(self):
        print("야옹!")

animals = [Animal(), Dog(), Cat()]
for a in animals:
    a.make_sound()
    # Animal: 동물이 소리를 냅니다.
    # Dog:    멍멍!
    # Cat:    야옹!
  • 같은 make_sound() 메서드가 호출되지만, 객체가 어떤 클래스의 인스턴스인지에 따라 서로 다른 결과가 나옴.
  • 이것이 다형성의 대표적인 예임.

2.4.7.3.2 덕 타이핑(Duck Typing)

파이썬에서는 덕 타이핑(“오리처럼 걷고 소리 내면 오리다”라는 의미) 을 많이 사용.

  • 다른 언어처럼 “메서드 시그니처(매개변수 타입)나 클래스를 정확히 일치시켜야만” 다형성이 적용되는 것이 아니라,
  • 해당 메서드나 속성을 가지고 있으면(실제로 동작할 수 있으면) “OK”로 간주.

예를 들어, 아래와 같이 make_sound() 메서드만 있다면, 클래스가 달라도 함께 처리할 수 있다.

class Dog:
    def make_sound(self):
        print("멍멍!")

class Car:
    def make_sound(self):
        print("빵빵!")

def play_sound(obj):
    obj.make_sound()

dog = Dog()
car = Car()

play_sound(dog)  # 멍멍!
play_sound(car)  # 빵빵!
  • dog은 Dog 클래스, car는 Car 클래스이지만, 둘 다 make_sound() 메서드를 갖고 있으므로 문제없이 동작.
  • 이처럼 “타입”에 덜 얽매이고, “행동(메서드)” 중심으로 코드를 짜는 기법이 파이썬에서 흔히 볼 수 있는 다형성 패턴.

2.4.7.4 Abstraction

추상화는 구체적인 구현은 감추고, 필요한 공통 인터페이스나 개념만 표현하는 것.

  • 예: “동물” 클래스가 “동물이 공통적으로 가져야 할 메서드(먹기, 자기, 소리 내기 등)”를 정의해 두고, 실제 ‘소리를 내는’ 구체적인 방식은 각 종(개, 고양이, 호랑이 등)에 따라 다르게 구현하게 하는 것.

2.4.7.4.1 Abstract class & Abstract method

파이썬에서 추상화는 주로 abc(Abstract Base Classes) 모듈을 통해 구현.

  • 추상 클래스(ABC): 직접 인스턴스를 만들 수 없고, 자식 클래스에서 반드시 구현해야 하는 추상 메서드(abstract method)를 지정할 수 있는 클래스

  • 자식 클래스가 추상 메서드를 구현(override)하지 않으면 에러.

  • 예시

from abc import ABC, abstractmethod

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

    def sleep(self):
        print("동물이 잠을 잡니다.")

class Dog(Animal):
    def make_sound(self):
        print("멍멍!")

dog = Dog()
dog.make_sound()  # 멍멍!
dog.sleep()       # 동물이 잠을 잡니다.
  • class Animal(ABC):
    ABC를 상속함으로써 추상 클래스를 만들 수 있음.
  • @abstractmethod
    이 데코레이터가 붙은 메서드는 “반드시 자식 클래스에서 오버라이딩 해야 하는 메서드”.
    만약 자식 클래스에서 make_sound()를 구현하지 않으면, TypeError: Can't instantiate abstract class ... 에러가 발생.
  • 추상 클래스인 Animal 자체로는 인스턴스를 만들 수 없음.
    예: animal = Animal() -> 에러 (추상 클래스는 인스턴스화할 수 없음)

2.4.7.4.2 Why Abstraction Is Important

  • 공통된 인터페이스를 강제로 지키도록 하여, 여러 자식 클래스가 동일한 ‘사용 방법’을 제공하게끔 만들 수 있다.
  • “이 메서드는 자식 클래스들마다 꼭 있어야 한다”라는 가이드라인을 추상 클래스를 통해 제시한다.

0개의 댓글