데이터 취업 스쿨 스터디 노트 - 파이썬 (중급 4 ~ 중급 문풀 1)

data-yeon·2025년 2월 5일

Data Science

목록 보기
5/10
post-thumbnail

16_자주 사용하는 외부 모듈

  1. math 모듈
    math 모듈은 수학 관련 함수들을 제공하는 표준 라이브러리다.
import math

print(math.sqrt(16))    # 출력: 4.0 (제곱근) 제곱근 반환 (√x)
print(math.pow(2, 3))   # 출력: 8.0 (거듭제곱) 거듭제곱 (x^y)
print(math.factorial(5)) # 출력: 120 (5!)  팩토리얼 계산 (n!)
print(math.gcd(48, 18)) # 출력: 6 (최대공약수) 최대공약수 (GCD)
print(math.floor(3.9))  # 출력: 3 (내림) 내림 (소수점 이하 버림)
print(math.ceil(3.1))   # 출력: 4 (올림) 올림 (소수점 이하 올림)
print(math.pi)          # 출력: 3.141592653589793 원주율 π 값
print(math.e)           # 출력: 2.718281828459045 자연로그 e 값
  1. random 모듈
    random 모듈은 난수를 생성하는 기능을 제공한다.
import random

print(random.random())   # 0.0 ~ 1.0 사이 난수
print(random.randint(1, 10))  # 1~10 사이 정수 난수
print(random.uniform(1.5, 5.5))  # 1.5~5.5 사이 실수 난수

lst = [1, 2, 3, 4, 5]
print(random.choice(lst))  # 리스트에서 랜덤 선택
print(random.choices(lst, k=3))  # 중복 가능하게 3개 선택
print(random.sample(lst, 2))  # 중복 없이 2개 선택

random.shuffle(lst)  # 리스트 요소 섞기
print(lst)
  1. time 모듈
    time 모듈은 시간 관련 기능을 제공한다.
import time

print(time.time())  # 현재 시간(Unix timestamp)
time.sleep(2)  # 2초 동안 실행 멈춤
print("2초 후 실행됨")

current_time = time.localtime()
print(time.strftime("%Y-%m-%d %H:%M:%S", current_time))  # 현재 시간을 YYYY-MM-DD HH:MM:SS 형태로 출력

17_객체지향 프로그래밍

1️⃣. 객체(Object)란?
객체는 속성(데이터)기능(메서드, 동작)을 포함하는 독립적인 단위다.

예를 들어 자동차 객체를 생각해보자.
🚗 자동차 객체의 예시
• 속성(Attributes, 변수)
• 색상: 빨강
• 브랜드: 현대
• 속도: 60km/h

기능(Methods, 함수)
• 가속(): 속도 증가
• 감속(): 속도 감소
• 멈춤(): 정지

class Car:
    def __init__(self, color, brand, speed=0):
        self.color = color  # 속성
        self.brand = brand  # 속성
        self.speed = speed  # 속성

    def accelerate(self):  # 기능(메서드)
        self.speed += 10
        print(f"{self.brand} 자동차 속도 증가: {self.speed}km/h")

    def brake(self):  # 기능(메서드)
        self.speed -= 10
        print(f"{self.brand} 자동차 속도 감소: {self.speed}km/h")

# 객체 생성
my_car = Car("빨강", "현대")

# 기능 실행
my_car.accelerate()  # 출력: 현대 자동차 속도 증가: 10km/h
my_car.brake()       # 출력: 현대 자동차 속도 감소: 0km/h

2️⃣. 객체지향 프로그래밍의 4대 특징

✅ 객체(Object) = 속성(데이터) + 기능(메서드)
✅ 캡슐화: 데이터를 숨기고 메서드를 통해 접근
✅ 상속: 부모 클래스의 기능을 자식 클래스가 재사용
✅ 다형성: 같은 메서드 이름이지만 클래스마다 다르게 동작
✅ 추상화: 필요한 기능만 노출하고, 세부 사항은 숨김

1) 캡슐화 (Encapsulation)
	•	속성과 메서드를 하나의 객체로 묶고, 외부에서 직접 접근을 제한하는 것.
	•	보통 속성을 private(비공개) 속성으로 만들고, 메서드를 통해 접근한다.
    
    class BankAccount:
    	def __init__(self, owner, balance=0):
        	self.owner = owner      # 공개 속성
        	self.__balance = balance  # 비공개 속성 (외부에서 접근 불가)

    	def deposit(self, amount):
        	self.__balance += amount
        	print(f"{self.owner}님, {amount}원이 입금되었습니다.")

    	def get_balance(self):
        	return self.__balance

    # 객체 생성
    account = BankAccount("홍길동", 1000)

    # balance 속성은 직접 접근 불가
    # print(account.__balance)  # 오류 발생

    # 대신 메서드를 통해 접근
    print(account.get_balance())  # 출력: 1000
    account.deposit(500)
    print(account.get_balance())  # 출력: 1500
    
2) 상속 (Inheritance)
	•	기존 클래스를 재사용하여 새로운 클래스를 만드는 것.**부모 클래스(기본 클래스)**의 속성과 메서드를 **자식 클래스(파생 클래스)**가 상속받아 사용할 수 있다.
    # 부모 클래스
class Animal:
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        print("소리를 낸다.")

# 자식 클래스
class Dog(Animal):
    def make_sound(self):
        print(f"{self.name}가 멍멍 짖는다.")

# 객체 생성
dog = Dog("바둑이")
dog.make_sound()  # 출력: 바둑이가 멍멍 짖는다.

3) 다형성 (Polymorphism)
	•	같은 메서드 이름을 사용하지만, 각 클래스에서 다르게 동작하도록 하는 것.
    class Bird:
    	def fly(self):
        	print("새가 난다.")

	class Airplane:
    	def fly(self):
          print("비행기가 난다.")

    # 동일한 fly() 메서드를 호출하지만, 각 객체마다 다르게 동작
    bird = Bird()
    airplane = Airplane()

    bird.fly()      # 출력: 새가 난다.
    airplane.fly()  # 출력: 비행기가 난다.
    
4) 추상화 (Abstraction)
	•	불필요한 부분은 숨기고, 필요한 부분만 보여주는 것.
	•	추상 클래스를 사용하여 공통적인 기능을 정의하고, 구체적인 구현은 자식 클래스에서 하도록 강제한다.
    from abc import ABC, abstractmethod

    # 추상 클래스
    class Animal(ABC):
        @abstractmethod
        def make_sound(self):
            pass  # 자식 클래스에서 반드시 구현해야 함

    # 자식 클래스
    class Cat(Animal):
        def make_sound(self):
            print("야옹!")

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

    # 객체 생성
    cat = Cat()
    dog = Dog()

    cat.make_sound()  # 출력: 야옹!
    dog.make_sound()  # 출력: 멍멍!

18_클래스와 객체 생성

객체지향 프로그래밍(OOP)에서 클래스(Class)는 객체를 생성하기 위한 설계도(청사진)이고, 객체(Object)는 그 설계도를 기반으로 만들어진 실제 인스턴스(Instance)이다.

1. 클래스(Class)

  • 클래스는 속성(변수)과 메서드(함수)를 포함하는 템플릿(설계도)이다.
  • 객체를 생성하기 위한 틀 역할을 한다.

클래스 정의 방법

class 클래스이름:
    # 생성자 (객체 생성 시 실행되는 특수한 메서드)
    def __init__(self, 속성1, 속성2):
        self.속성1 = 속성1
        self.속성2 = 속성2

    # 메서드 정의
    def 메서드이름(self):
        print("이것은 메서드입니다.")

2. 객체(Object) 생성

  • 클래스를 이용해 객체를 만들려면 클래스이름()을 호출하면 된다.
  • 객체를 생성할 때 init() 메서드가 실행되어 초기 값을 설정한다.
# 클래스 정의
class Person:
    def __init__(self, name, age):  # 생성자 (객체 생성 시 실행)
        self.name = name  # 속성 (name)
        self.age = age    # 속성 (age)

    def introduce(self):  # 메서드
        print(f"안녕하세요, 제 이름은 {self.name}이고, 나이는 {self.age}살입니다.")

# 객체 생성
person1 = Person("홍길동", 25)
person2 = Person("김철수", 30)

# 메서드 호출
person1.introduce()  # 출력: 안녕하세요, 제 이름은 홍길동이고, 나이는 25살입니다.
person2.introduce()  # 출력: 안녕하세요, 제 이름은 김철수이고, 나이는 30살입니다.

3. 클래스의 주요 개념

(1) 생성자 (init)

  • __ init __()는 객체가 생성될 때 자동으로 실행되는 초기화 메서드이다.
  • 객체의 속성(변수)을 설정하는 역할을 한다.
	class Car:
      def __init__(self, brand, color):
          self.brand = brand
          self.color = color

    car1 = Car("현대", "빨강")  # 객체 생성 (자동으로 __init__ 실행)
    print(car1.brand)  # 출력: 현대
    print(car1.color)  # 출력: 빨강

(2) 인스턴스 변수와 클래스 변수

🔹 인스턴스 변수
- self.변수명 형태로 선언된 변수.
- 각 객체(인스턴스)마다 개별적으로 저장된다.

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

dog1 = Dog("바둑이")
dog2 = Dog("초코")

print(dog1.name)  # 출력: 바둑이
print(dog2.name)  # 출력: 초코

🔹 클래스 변수
- 클래스이름.변수명 형태로 선언된 변수.
- 클래스에 속한 모든 객체가 공유한다.

	class Animal:
    species = "포유류"  # 클래스 변수

    dog = Animal()
    cat = Animal()

    print(dog.species)  # 출력: 포유류
    print(cat.species)  # 출력: 포유류

(3) 메서드(Method)

  • 클래스 내부에서 정의된 함수로, 특정 기능을 수행한다.
  • self를 첫 번째 인자로 받으며, 이는 현재 객체(인스턴스)를 의미한다.
	class Calculator:
    	def add(self, a, b):
        	return a + b

    	def subtract(self, a, b):
        	return a - b

    calc = Calculator()  # 객체 생성
    print(calc.add(10, 5))  # 출력: 15
    print(calc.subtract(10, 5))  # 출력: 5

(4) 클래스 메서드와 정적 메서드

🔹 클래스 메서드 (@classmethod)

  • 클래스 변수를 다룰 때 사용되며, 클래스 자체에 접근할 수 있다.
  • 첫 번째 인자로 cls를 받는다.
class Animal:
    species = "포유류"  # 클래스 변수

    @classmethod
    def set_species(cls, new_species):
        cls.species = new_species

Animal.set_species("파충류")
print(Animal.species)  # 출력: 파충류

🔹 정적 메서드 (@staticmethod)

  • self나 cls를 사용하지 않고 독립적으로 동작하는 함수.
  • 일반 함수와 비슷하지만 클래스 내부에서 정의되는 차이점이 있다.
class MathUtils:
    @staticmethod
    def add(a, b):
        return a + b

print(MathUtils.add(3, 7))  # 출력: 10

📌 정리

✅ 클래스: 객체를 생성하는 설계도
✅ 객체: 클래스를 기반으로 만들어진 실제 인스턴스
✅ 생성자 (init): 객체가 생성될 때 실행되는 초기화 함수
✅ 인스턴스 변수: 각 객체별로 개별 저장
✅ 클래스 변수: 모든 객체가 공유
✅ 메서드: 클래스 내부에서 정의된 함수
✅ 클래스 메서드 (@classmethod): 클래스 자체를 조작할 때 사용
✅ 정적 메서드 (@staticmethod): 독립적인 함수 역할 수행


19_객체 속성 변경

객체를 생성한 후, 속성을 변경하는 방법과 객체가 메모리에 어떻게 관리되는지

1. 객체 속성 변경 (Attribute Modification)

🔹 (1) 직접 속성 변경

  • 객체의 속성값은 객체명.속성명 = 새로운 값 형태로 직접 변경할 수 있다.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 객체 생성
p1 = Person("홍길동", 25)

# 속성 변경
p1.age = 30
print(p1.age)  # 출력: 30

🔹 (2) 메서드를 통한 속성 변경

  • 속성 변경을 직접 하지 않고, 메서드를 통해 변경하는 것이 더 안전한 방법이다.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def set_age(self, new_age):  # 나이 변경 메서드
        if new_age > 0:
            self.age = new_age
        else:
            print("나이는 0보다 커야 합니다.")

# 객체 생성
p1 = Person("김철수", 20)

# 메서드를 통한 속성 변경
p1.set_age(35)
print(p1.age)  # 출력: 35

p1.set_age(-5)  # 출력: 나이는 0보다 커야 합니다.

🔹 (3) setattr() 함수로 속성 변경

  • 파이썬 내장 함수 setattr(객체, "속성명", 값)을 사용하면 속성을 변경할 수 있다.
class Car:
    def __init__(self, brand, color):
        self.brand = brand
        self.color = color

# 객체 생성
car = Car("현대", "빨강")

# setattr()을 이용한 속성 변경
setattr(car, "color", "파랑")
print(car.color)  # 출력: 파랑

🔹 (4) dict로 속성 변경

  • 객체의 속성들을__ dict __()속성을 이용해 딕셔너리 형태로 확인하고 변경할 수 있다.
class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

dog = Animal("바둑이", "강아지")

# 객체의 속성 출력
print(dog.__dict__)  # 출력: {'name': '바둑이', 'species': '강아지'}

# 속성 변경
dog.__dict__["name"] = "초코"
print(dog.name)  # 출력: 초코

20_객체와 메모리

객체와 메모리 관리 :
파이썬에서는 객체가 메모리에 어떻게 관리되는지 이해하는 것이 중요하다.

🔹 (1) 객체의 메모리 할당

  • 객체가 생성되면, 힙(Heap) 메모리에 저장된다.
  • 변수는 객체의 참조(Reference)를 저장하며, 변수 자체는 스택(Stack) 메모리에 저장된다.
class Person:
    def __init__(self, name):
        self.name = name

p1 = Person("홍길동")  # p1은 '홍길동' 객체의 참조를 저장함

📌 메모리 구조

  • p1은 스택 메모리에 저장됨 (객체의 참조값 저장)
  • Person("홍길동") 객체는 힙 메모리에 저장됨

🔹 (2) 여러 변수가 같은 객체를 참조할 경우

  • 두 개의 변수가 같은 객체를 참조하면, 하나의 변수를 변경해도 다른 변수에도 영향을 준다.
a = [1, 2, 3]
b = a  # b는 a와 같은 객체를 참조함

b.append(4)

print(a)  # 출력: [1, 2, 3, 4]
print(b)  # 출력: [1, 2, 3, 4]

📌 a와 b는 같은 메모리 주소를 참조하므로 b를 변경하면 a도 변경됨.

✅ 확인 방법 (id())
객체의 메모리 주소(참조 ID)를 확인하려면 id() 함수를 사용하면 된다.

print(id(a))  # a의 메모리 주소 출력
print(id(b))  # b의 메모리 주소 출력 (a와 동일)

🔹 (3) 얕은 복사 vs 깊은 복사

얕은 복사 (Shallow Copy)

  • 복사본이 원본과 같은 객체를 참조하므로, 하나를 변경하면 다른 것도 영향을 받음.
import copy

lst1 = [1, 2, [3, 4]]
lst2 = copy.copy(lst1)  # 얕은 복사

lst2[2].append(5)
print(lst1)  # 출력: [1, 2, [3, 4, 5]]
print(lst2)  # 출력: [1, 2, [3, 4, 5]]

깊은 복사 (Deep Copy)

  • 원본과 독립적인 복사본을 만든다.
lst1 = [1, 2, [3, 4]]
lst2 = copy.deepcopy(lst1)  # 깊은 복사

lst2[2].append(5)
print(lst1)  # 출력: [1, 2, [3, 4]]
print(lst2)  # 출력: [1, 2, [3, 4, 5]]

Immutable과 Mutable
🔹 (4) 가비지 컬렉션 (Garbage Collection)

  • 파이썬은 자동으로 메모리를 관리하며, 사용되지 않는 객체는 가비지 컬렉터(GC)가 자동 삭제한다.
  • 객체의 참조(reference) 개수가 0이 되면 자동으로 메모리에서 삭제됨.
import gc

class Test:
    def __del__(self):
        print("객체가 메모리에서 삭제됨")

obj = Test()  # 객체 생성
del obj  # 객체 삭제 (가비지 컬렉터가 실행되면 __del__이 호출됨)

gc.collect()  # 가비지 컬렉터 수동 실행

📌 정리

✅ 객체 속성 변경

•	객체.속성 = 값 방식으로 직접 변경
•	setattr(객체, "속성", 값)으로 변경
•	__dict__를 활용한 속성 변경

✅ 객체의 메모리 관리

•	객체는 힙(Heap) 메모리에 저장되고, 변수가 이를 참조
•	id(객체)를 사용하면 객체의 메모리 주소 확인 가능
•	copy.copy()(얕은 복사)와 copy.deepcopy()(깊은 복사) 차이 이해 필요
•	참조 카운트가 0이 되면 가비지 컬렉션(GC)에 의해 자동 삭제됨

21_얕은복사와 깊은복사

22_클래스 상속

📌 클래스 상속 (Inheritance)

클래스 상속은 기존 클래스를 재사용하여 새로운 클래스를 만드는 기법이다.
부모 클래스(기본 클래스)의 속성(변수)과 메서드(함수)를 자식 클래스(파생 클래스)가 물려받아 사용할 수 있다.

Class 1

1. 상속의 개념

  • 부모 클래스 (Super Class, Base Class): 기존의 클래스
  • 자식 클래스 (Sub Class, Derived Class): 부모 클래스를 상속받아 확장한 클래스

2. 상속 기본 문법

🔹 기본적인 상속

# 부모 클래스 정의
class Parent:
    def parent_method(self):
        print("부모 클래스 메서드")

# 자식 클래스 정의 (Parent를 상속)
class Child(Parent):
    def child_method(self):
        print("자식 클래스 메서드")

# 객체 생성
child = Child()
child.parent_method()  # 부모 클래스 메서드 호출 가능
child.child_method()   # 자식 클래스 메서드 호출

✅ Child 클래스는 Parent 클래스를 상속받았으므로, parent_method()를 직접 사용할 수 있다.


3. 생성자 (__ init __()) 상속

부모 클래스의__ init __()을 자식 클래스에서 재사용하거나, 확장할 수 있다.
🔹 부모의 생성자 사용하기 (super())

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

    def introduce(self):
        print(f"이름: {self.name}, 나이: {self.age}")

class Student(Person):  # Person 클래스를 상속
    def __init__(self, name, age, student_id):
        super().__init__(name, age)  # 부모 클래스의 __init__() 호출
        self.student_id = student_id  # 자식 클래스만의 속성 추가

    def introduce(self):
        super().introduce()  # 부모 클래스의 introduce() 호출
        print(f"학번: {self.student_id}")

# 객체 생성
s1 = Student("김철수", 20, "2024001")
s1.introduce()

📌 실행 결과

이름: 김철수, 나이: 20
학번: 2024001

✅ super().__ init __(...)을 호출하여 부모 클래스의 생성자를 실행한 후, 추가적인 속성을 설정한다.

4. 메서드 오버라이딩 (Method Overriding)

  • 부모 클래스의 메서드를 자식 클래스에서 재정의(덮어쓰기)하는 것을 오버라이딩(Overriding)이라고 한다.
  • 같은 이름의 메서드를 자식 클래스에서 다시 정의하면 부모의 메서드를 덮어씀.
class Animal:
    def make_sound(self):
        print("동물이 소리를 냅니다.")

class Dog(Animal):
    def make_sound(self):  # 부모 메서드 재정의 (오버라이딩)
        print("멍멍!")

# 객체 생성
dog = Dog()
dog.make_sound()  # 출력: 멍멍!

✅ Dog 클래스에서 make_sound()를 재정의했기 때문에, 부모의 메서드가 아니라 자식 클래스의 메서드가 실행된다.

🔹 부모의 메서드도 호출하고 싶다면?

class Dog(Animal):
    def make_sound(self):
        super().make_sound()  # 부모 메서드 호출
        print("멍멍!")

dog = Dog()
dog.make_sound()

📌 실행 결과

동물이 소리를 냅니다.
멍멍!

5. 다중 상속 (Multiple Inheritance)

파이썬에서는 하나의 클래스가 여러 개의 부모 클래스를 상속받을 수 있다.

class Parent1:
    def method1(self):
        print("Parent1의 메서드")

class Parent2:
    def method2(self):
        print("Parent2의 메서드")

# 다중 상속
class Child(Parent1, Parent2):
    def child_method(self):
        print("자식 클래스 메서드")

# 객체 생성
child = Child()
child.method1()  # Parent1에서 상속받은 메서드
child.method2()  # Parent2에서 상속받은 메서드
child.child_method()  # 자식 클래스 메서드

✅ Child 클래스는 Parent1, Parent2 두 개의 부모 클래스를 상속받았으므로 모두 사용 가능하다.

6. 상속 관계 확인 (issubclass(), isinstance())

🔹 issubclass(자식클래스, 부모클래스)

  • 특정 클래스가 다른 클래스를 상속받았는지 확인
print(issubclass(Student, Person))  # True
print(issubclass(Dog, Animal))  # True
print(issubclass(Dog, Person))  # False

🔹 isinstance(객체, 클래스)

  • 객체가 특정 클래스의 인스턴스인지 확인
s = Student("이영희", 21, "2024002")

print(isinstance(s, Student))  # True
print(isinstance(s, Person))   # True (Student가 Person을 상속했으므로)
print(isinstance(s, Dog))      # False

📌 정리

✅ 상속(Inheritance): 기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받음
✅ super(): 부모 클래스의 생성자(init)나 메서드를 호출할 때 사용
✅ 메서드 오버라이딩(Method Overriding): 부모 클래스의 메서드를 자식 클래스에서 재정의
✅ 다중 상속(Multiple Inheritance): 하나의 클래스가 여러 부모 클래스를 상속받을 수 있음
✅ issubclass(), isinstance(): 클래스 상속 관계 확인


23~ 24_생성자 (01, 02)

🌱 1. __ init __()(생성자)란?

📌 “객체가 생성될 때 자동으로 실행되는 특수한 메서드!”

  • 객체가 생성될 때, 생성자를 호출하면, 자동으로 __ init __()이 호출됨
  • __ init __()가 속성을 초기화함.
  • 객체를 만들 때(클래스이름()) 자동으로 실행돼서 초기값(속성)을 설정함
  • 생성자가 없으면 기본값을 가진 객체가 생성됨.

✅ 예제: __ init __( ) 사용하기

class Person:
    def __init__(self, name, age):  # 생성자
        self.name = name  # 속성 설정
        self.age = age

# 객체 생성 (생성자 실행됨!)
p1 = Person("홍길동", 25)
p2 = Person("김철수", 30)

print(p1.name, p1.age)  # 출력: 홍길동 25
print(p2.name, p2.age)  # 출력: 김철수 30

✅ p1과 p2를 만들 때 __ init __( )이 실행되어 자동으로 name과 age 속성이 설정됨!


🏠 2. 객체 속성 (self.속성명)

📌 “객체마다 고유한 정보를 저장하는 변수!”

  • 객체 속성은 각 객체마다 독립적으로 저장됨.
  • self.속성명 = 값 형태로 선언.
  • self는 현재 객체 자신을 가리킴.

✅ 예제: 객체 속성

class Car:
    def __init__(self, brand, color):
        self.brand = brand  # 자동차 브랜드
        self.color = color  # 자동차 색상

# 객체 생성
car1 = Car("현대", "빨강")
car2 = Car("기아", "파랑")

print(car1.brand, car1.color)  # 출력: 현대 빨강
print(car2.brand, car2.color)  # 출력: 기아 파랑

✅ car1과 car2는 각각 다른 속성 값(브랜드, 색상)을 가진 독립적인 객체야.


🔗 3. super()란?

📌 “부모 클래스의 기능을 자식 클래스에서 가져올 때 사용!”

  • 상속받은 부모 클래스의 __ init __() 또는 메서드를 그대로 실행하고 싶을 때 사용.
  • 상위 클래스의 속성을 초기화 하기위해서 super()을 사용
  • 코드를 중복 없이 깔끔하게 유지할 수 있음.

✅ 예제: super()를 사용한 상속

class Animal:
    def __init__(self, species):
        self.species = species  # 동물 종류 설정

class Dog(Animal):  # Animal을 상속받음
    def __init__(self, species, name):
        super().__init__(species)  # 부모 클래스의 생성자 호출
        self.name = name  # 추가 속성

dog = Dog("포유류", "바둑이")

print(dog.species)  # 출력: 포유류 (부모 클래스에서 상속)
print(dog.name)     # 출력: 바둑이 (자식 클래스에서 추가)

✅ super()._ init __ (species)를 사용해서 부모 클래스의 __init __()을 실행!
✅ Dog 클래스는 부모 속성 + 추가 속성을 가짐.

25_다중 상속

📌 다중 상속 (Multiple Inheritance)

🔹 “하나의 클래스가 두 개 이상의 부모 클래스를 상속받는 것”
✔ 일반적인 상속은 부모 클래스 하나만 상속받지만, 다중 상속은 여러 부모 클래스로부터 기능을 물려받을 수 있음.
✔ 여러 클래스를 조합해서 더 강력한 클래스를 만들 때 유용함.
✔ 다중 상속을 사용하면 여러 부모 클래스의 속성과 메서드를 모두 활용할 수 있음.

1️⃣ 다중 상속 기본 구조

class Parent1:
    def feature1(self):
        return "👨‍👩‍👧 부모1의 기능"

class Parent2:
    def feature2(self):
        return "🧑‍🎨 부모2의 기능"

# 다중 상속
class Child(Parent1, Parent2):
    def feature3(self):
        return "👶 자식 클래스의 기능"

# 객체 생성
child = Child()

print(child.feature1())  # 👨‍👩‍👧 부모1의 기능
print(child.feature2())  # 🧑‍🎨 부모2의 기능
print(child.feature3())  # 👶 자식 클래스의 기능

✅ Child 클래스는 Parent1과 Parent2의 기능을 모두 상속받아 사용할 수 있음.


2️⃣ super()를 활용한 다중 상속

super()는 부모 클래스의 메서드를 호출할 때 사용되는데, 다중 상속에서는 어느 부모 클래스의 메서드가 먼저 실행될지 중요함.

💡 다중 상속에서 super() 사용

class Parent1:
    def __init__(self):
        print("👨‍👩‍👧 Parent1 생성자 호출")

class Parent2:
    def __init__(self):
        print("🧑‍🎨 Parent2 생성자 호출")

class Child(Parent1, Parent2):
    def __init__(self):
        super().__init__()  # 부모 생성자 호출
        print("👶 Child 생성자 호출")

child = Child()
👨‍👩‍👧 Parent1 생성자 호출
👶 Child 생성자 호출

✅ super()는 첫 번째 부모(Parent1)의 init()만 호출되었음!
✅ 다중 상속에서는 MRO(Method Resolution Order)라는 규칙을 따름.


4️⃣ super()를 다중 호출하려면?
위 예제에서는 super()를 사용했을 때 Parent1만 호출되고 Parent2는 호출되지 않았음.
➡ 이를 해결하려면 모든 부모 클래스에서 super()를 호출해야 함!

class Parent1:
    def __init__(self):
        super().__init__()
        print("👨‍👩‍👧 Parent1 생성자 호출")

class Parent2:
    def __init__(self):
        super().__init__()
        print("🧑‍🎨 Parent2 생성자 호출")

class Child(Parent1, Parent2):
    def __init__(self):
        super().__init__()
        print("👶 Child 생성자 호출")

child = Child()
🧑‍🎨 Parent2 생성자 호출
👨‍👩‍👧 Parent1 생성자 호출
👶 Child 생성자 호출

✅ 모든 부모 클래스에서 super().init()을 호출하면 MRO 순서에 따라 모든 부모 클래스의 생성자가 실행됨!


5️⃣ 다이아몬드 상속 문제 (Diamond Problem)
다중 상속을 사용할 때, 부모 클래스가 같은 조상을 공유하면 메서드가 중복 호출될 수 있음.
💡 다이아몬드 상속 예제

class A:
    def show(self):
        print("A 클래스")

class B(A):
    def show(self):
        print("B 클래스")

class C(A):
    def show(self):
        print("C 클래스")

class D(B, C):  # B와 C는 둘 다 A를 상속
    pass

d = D()
d.show()
B 클래스

✅ D는 B와 C를 상속받았지만, MRO에 따라 왼쪽의 B가 먼저 실행됨!
✅ 다이아몬드 문제 해결 방법: super()를 올바르게 사용하면 해결됨.


📌 다중 상속 정리

✔다중 상속 :하나의 클래스가 여러 부모 클래스를 상속

	class C(A, B):

✔super() :부모 클래스의 메서드를 호출할 때 사용

	super().__init__()

✔MRO :메서드 탐색 순서

	Class.mro()

✔다이아몬드 문제 :같은 조상을 공유하는 다중 상속 문제

	class D(B, C):

🚀 한 줄 요약!
✔ 다중 상속은 여러 부모 클래스의 기능을 자식 클래스에서 동시에 사용할 수 있도록 해줌!
✔ super()를 사용하면 MRO 순서에 따라 부모 클래스가 호출됨.
✔ 다이아몬드 문제를 해결하려면 super()를 모든 부모 클래스에서 올바르게 호출해야 함.

26_오버라이딩

🔥 오버라이딩 (Overriding)

✔ “부모 클래스로부터 물려받은 메서드를 자식 클래스에서 재정의(덮어쓰기)하는 것”
✔ 부모 클래스와 같은 이름, 같은 매개변수를 가지지만 동작을 다르게 변경
✔ 상속 관계에서만 발생

class Parent:
    def say_hello(self):
        return "👴 부모: 안녕하세요!"

class Child(Parent):
    def say_hello(self):  # 부모의 메서드를 재정의 (오버라이딩)
        return "👦 자식: 안녕!"

child = Child()
print(child.say_hello())  # 출력: 👦 자식: 안녕!

🔥 오버로딩 (Overloading)

✔ “같은 이름의 메서드를 여러 개 정의하는 것”
✔ 매개변수 개수나 타입이 다를 때, 다른 방식으로 동작
✔ 파이썬에서는 직접 지원되지 않음!

🚨 파이썬에서는 오버로딩을 직접 지원하지 않음!

C++, Java 같은 언어에서는 가능하지만, 파이썬에서는 같은 이름의 메서드를 여러 개 만들면 덮어씌워진다.

class Example:
    def add(self, a, b):
        return a + b

    def add(self, a, b, c):  # 이전 `add`를 덮어씌움 (오버로딩 불가)
        return a + b + c

ex = Example()
print(ex.add(1, 2))  # 오류 발생! (오버로딩이 안됨)

✅ 해결 방법: 기본값을 설정하거나, *args 사용

class Example:
    def add(self, a, b, c=0):  # 기본값 설정으로 오버로딩처럼 구현
        return a + b + c

ex = Example()
print(ex.add(1, 2))    # 출력: 3
print(ex.add(1, 2, 3)) # 출력: 6
class Example:
    def add(self, *args):  # 여러 개의 인자 처리 가능
        return sum(args)

ex = Example()
print(ex.add(1, 2))      # 출력: 3
print(ex.add(1, 2, 3))   # 출력: 6
print(ex.add(1, 2, 3, 4)) # 출력: 10

🚀 오버라이딩 vs 오버로딩 차이 정리
오버라이딩 (Overriding)
✔ 개념: 부모 클래스의 메서드를 자식 클래스에서 덮어쓰기
✔ 어디서 사용?: 상속에서만 사용 가능
✔ 메서드 이름: 같아야 함
✔ 매개변수: 같음 (변경 불가)
✔ 파이썬 지원 여부: ✅ 지원

오버로딩 (Overloading)
✔ 개념: 같은 이름의 메서드를 여러 개 정의
✔ 어디서 사용?: 일반적으로 같은 클래스 내에서 사용
✔ 메서드 이름: 같아야 함
✔ 매개변수: 개수, 타입이 다를 수 있음
✔ 파이썬 지원 여부: ❌ 직접 지원 안됨 (대체 방법 필요)

📌 한 줄 요약

  • 오버라이딩 = 부모 메서드를 덮어씀(재정의) (상속 필수)
  • 오버로딩 = 같은 이름의 메서드를 여러 개 만들기 (파이썬에서는 직접 지원 안됨)

27_추상클래스

📌 추상 클래스 (Abstract Class)란?

🔹 “객체를 직접 생성할 수 없고, 반드시 상속해서 사용해야 하는 클래스”
✔ 공통적인 기능을 정의하지만, 구체적인 구현은 자식 클래스에서 강제하도록 하는 클래스
✔ “틀(템플릿)” 역할을 하며, 상속받은 클래스는 반드시 특정 메서드를 구현해야 함
✔ abc (Abstract Base Class) 모듈을 사용하여 구현


1️⃣ 추상 클래스 기본 문법

✅ ABC 모듈을 사용해 추상 클래스 만들기

from abc import ABC, abstractmethod  # 추상 클래스 관련 모듈

# 추상 클래스 정의
class Animal(ABC):  
    @abstractmethod  # 추상 메서드 (반드시 구현해야 함)
    def make_sound(self):
        pass  # 구현 X → 자식 클래스에서 구현해야 함

# 자식 클래스에서 반드시 추상 메서드를 구현해야 함!
class Dog(Animal):
    def make_sound(self):  
        return "🐶 멍멍!"

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

# 객체 생성
dog = Dog()
cat = Cat()

print(dog.make_sound())  # 출력: 🐶 멍멍!
print(cat.make_sound())  # 출력: 🐱 야옹!

✅ Animal 클래스는 추상 클래스이므로 직접 객체를 생성할 수 없음!
✅ Dog와 Cat 클래스는 반드시 make_sound() 메서드를 구현해야 함.


2️⃣ 추상 클래스를 상속받아 구현하기

자식 클래스에서 추상 메서드를 반드시 구현해야 함!

class Dog(Animal):  # Animal을 상속받음
    def make_sound(self):  # 반드시 구현해야 함!
        return "🐶 멍멍!"

class Cat(Animal):
    def make_sound(self):  # 반드시 구현해야 함!
        return "🐱 야옹!"

# 객체 생성
dog = Dog()
cat = Cat()

print(dog.make_sound())  # 출력: 🐶 멍멍!
print(cat.make_sound())  # 출력: 🐱 야옹!

✅ Dog, Cat 클래스는 Animal을 상속받아 반드시 make_sound()를 구현해야 함.
✅ 만약 구현하지 않으면 에러 발생! (TypeError: Can't instantiate abstract class)


3️⃣ 추상 클래스의 특징
🚨 (1) 추상 클래스는 직접 객체를 생성할 수 없음

a = Animal()  # 오류 발생! (TypeError: Can't instantiate abstract class Animal)

✅ 추상 클래스는 “설계도” 역할을 하기 때문에 직접 객체 생성이 불가능!
✅ 반드시 자식 클래스에서 상속 후, 추상 메서드를 구현해야 사용 가능!


4️⃣ 추상 클래스에서 일반 메서드도 포함 가능

추상 클래스는 추상 메서드뿐만 아니라 일반 메서드도 가질 수 있음.

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

    def sleep(self):  # 일반 메서드 (자식 클래스에서 그대로 사용 가능)
        return "😴 동물이 잠을 잔다."

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

dog = Dog()
print(dog.make_sound())  # 출력: 🐶 멍멍!
print(dog.sleep())       # 출력: 😴 동물이 잠을 잔다.

✅ sleep()은 일반 메서드이므로 자식 클래스에서 그대로 사용할 수 있음.


5️⃣ 추상 클래스를 활용하는 이유
💡 “강제성 부여 & 코드 일관성 유지”
✔부모 클래스를 상속받은 모든 자식 클래스가 반드시 특정 메서드를 구현하도록 강제함.
✔공통적인 기능을 추상 클래스로 정의하면, 유지보수가 쉬워짐.
✔다형성(Polymorphism)을 활용할 수 있음.

animals = [Dog(), Cat()]  # 리스트에 여러 객체 저장 가능 (다형성)

for animal in animals:
    print(animal.make_sound())  # 서로 다른 객체가 같은 메서드를 호출

✅ make_sound()를 모든 자식 클래스가 구현해야 하므로, animals 리스트에서 일관된 방식으로 호출 가능.


📌 정리
✔ 추상 클래스 : 직접 객체를 만들 수 없는 클래스 (ABC 사용)
✔ 추상 메서드 : 자식 클래스에서 반드시 구현해야 하는 메서드 (@abstractmethod)
✔ 일반 메서드 포함 가능 : 추상 클래스에서도 일반 메서드를 정의할 수 있음
✔ 객체 생성 불가 : TypeError 발생 (Animal()처럼 직접 인스턴스화 불가)
✔ 사용 이유 : 코드 강제성 부여, 유지보수 편리, 다형성 활용


🚀 한 줄 요약!
✅ 추상 클래스는 “설계도” 같은 역할을 하며,
자식 클래스에서 반드시 구현해야 하는 메서드를 정해줌!
✅ 객체를 직접 만들 수 없고, 상속받은 자식 클래스에서만 사용 가능!
✅ 추상 클래스를 사용하면 코드의 일관성이 유지되고, 유지보수가 편리해짐!

28_예외란?

29_예외처리

30_try ~ except ~ else

31_finally

32_Exception 클래스

33_사용자 Exception 클래스

34_텍스트 파일 쓰기

35_텍스트 파일 읽기

36_텍스트 파일 열기

37_with ~ as문

38_writelines()

39_readlines(), readline()

40_[중급문제풀이] 함수(01)

41_[중급문제풀이] 함수(02)

42_[중급문제풀이] 함수(03)

profile
잘 갔다 오란다고 잘 갔다 올 수 있는 곳이 아니야

0개의 댓글