제로베이스 데이터 파트타임 스쿨 학습 일지 [25.04.01]

[강의 요약]

[Ch 03. 파이썬 중급] 강의 수강

21_얕은복사와 깊은 복사부터 24_생성자(02)까지 강의 수강하였음

🐢 100일 챌린지 🔥 : [■■■■■■■■                                              ] 16/100일 (16%)

할 게 너무 많은데 머리가 따라가질 못하는 느낌이라 아쉽다.

조금 더 효율적으로 공부할 수 있는 방법을 시도해 봐야겠다.

지쳐가는 것 같은데 아직 100일까지 한참 남았다! 이런

 

 

 

[얕은복사와 깊은복사]

  • 얕은 복사
    • 얕은 복사는 '객체 주소만 복사'하는 방식
    • 변수끼리 연결만 되어 있고, 실제 데이터는 하나만 존재함
    • 하나를 수정하면 다른 것도 함께 바뀐다.
class TemCls:
    def __init__(self, n, s):
        self.number = n
        self.str = s

    def printClsInfo(self):
        print(f'self.number: {self.number}')
        print(f'self.str: {self.str}')

# 얕은 복사
tc1 = TemCls(10, 'Hello')
tc2 = tc1  # 주소만 복사됨

tc2.number = 3.14
tc2.str = 'Bye'

tc1.printClsInfo()  # 변경된 값 출력됨
tc2.printClsInfo()

★ 출력 결과 ★

둘 다 출력하면 3.14와 'Bye'가 나옴 같은 객체를 가리키고 있음

 

 

 

  • 깊은 복사
    • 깊은 복사는 '객체를 통째로 복제'하는 방식
    • 두 개의 완전히 독립적인 객체가 생김
    • 하나를 바꿔도 다른 객체에 영향 없음
import copy

tc1 = TemCls(10, 'Hello')
tc2 = copy.copy(tc1)  # 깊은 복사

tc2.number = 3.14
tc2.str = 'Bye'

tc1.printClsInfo()  # 여전히 10, Hello
tc2.printClsInfo()  # 3.14, Bye

★ 출력 결과 

각자 다른 값을 유지 → 복사된 진짜 '새로운 객체'

 

 

 

  • 리스트에서의 복사 차이
scores = [9, 8, 5]
scoresCopy = scores  # 얕은 복사
scoresCopy.append(10)

print(scores)       # [9, 8, 5, 10]
print(scoresCopy)   # [9, 8, 5, 10]

★ 출력 결과 

둘 다 바뀜 → 주소만 복사했기 때문

 

# 깊은 복사 방식
scoresCopy = scores.copy()
# 또는
scoresCopy = scores[:]

★ 출력 결과 

복사 후 각자 다르게 수정 가능함!

 

 

 

▶ 요약

 

 

 

 

[클래스 상속]

프로그래밍에서는 비슷한 기능을 가진 코드를 반복해서 만들기보다

한 번 만든 기능을 다른 클래스에서 "물려받아" 사용하는 방식을 자주 사용함

이걸 뭐라고 한다? "상속"이라고 한다.

  • 상속이란?
    • 클래스는 또 다른 클래스를 상속해서 마치 자기 것처럼 기능을 사용할 수 있다.
    • 부모 클래스에 있는 기능을 자식 클래스가 그대로 쓸 수 있고, 필요하면 추가하거나 바꿀 수 있다.

 

 

  • 상속의 구조
    • Class2는 Class1을 상속받아 그 안의 기능을 그대로 사용 가능
    • Class3은 Class2를 상속받아 Class1과 Class2의 기능을 모두 사용 가능

Class1  ←  Class2  ←  Class3

상속은 한 번만 가능한 것이 아니라 연쇄적으로도 사용 가능함(예시처럼)

 

 

 

  • 예시 : 자동차 클래스
class NormalCar:

    def drive(self):
        print('[NormalCar] drive() called!!')

    def back(self):
        print('[NormalCar] back() called!!')

# TurboCar는 NormalCar를 상속받음
class TurboCar(NormalCar):

    def turbo(self):
        print('[TurboCar] turbo() called!!')


myCar = TurboCar()
myCar.drive()   # NormalCar의 메서드 사용
myCar.back()    # NormalCar의 메서드 사용
myCar.turbo()   # TurboCar의 고유 메서드

★ 출력 결과 

[NormalCar] drive() called!!
[NormalCar] back() called!!
[TurboCar] turbo() called!!

TurboCar는 NormalCar의 기능을 물려받았기 때문에, drive()back()도 쓸 수 있음

 

 

 

  • 예시 : 자동차 클래스
class CalculatorSuper:

    def add(self, n1, n2):
        return n1 + n2

    def sub(self, n1, n2):
        return n1 - n2

class CalculatorChild(CalculatorSuper):

    def mul(self, n1, n2):
        return n1 * n2

    def div(self, n1, n2):
        return n1 / n2

cal = CalculatorChild()

print(cal.add(10, 20))  # 부모 클래스 기능
print(cal.sub(10, 20))  # 부모 클래스 기능
print(cal.mul(10, 20))  # 자식 클래스 기능
print(cal.div(10, 20))  # 자식 클래스 기능

CalculatorChild는 CalculatorSuper를 상속받았기 때문에

부모의 add, sub 기능을 그대로 사용할 수 있다.

그리고 자식 클래스에서는 mul, div 기능을 새로 추가해 기능을 확장했음

 

 

 

 

[생성자]

  • 생성자란?
    • 객체가 생성될 때 자동으로 실행되는 특별한 함수가 바로 생성자
    • 파이썬에서는 __init__() 메서드가 생성자 역할을 하며, 객체가 만들어질 때 자동으로 호출됨

 

 

  • 기본 생성자 사용
    • cal = Calculator() 객체를 생성하면 __init__()이 자동으로 실행
class Calculator:

    def __init__(self):
        print('[Calculator] __init__() called!!')

cal = Calculator()

★ 출력 결과 

[Calculator] __init__() called!!

 

 

  • 생성자에서 속성 초기화하기
    • self.num1과 self.num2는 객체의 속성
    • 객체를 생성하면서 10, 20을 넘기면 각각 속성으로 저장됨
class Calculator:

    def __init__(self, n1, n2):
        print('[Calculator] __init__() called!!')
        self.num1 = n1
        self.num2 = n2

cal = Calculator(10, 20)
print(f'cal.num1: {cal.num1}')
print(f'cal.num2: {cal.num2}')

★ 출력 결과 

[Calculator] __init__() called!!
cal.num1: 10
cal.num2: 20

 

 

  • 상속에서의 생성자와 super()
    • C_Class는 P_Class를 상속한 자식 클래스
    • super().__init__(100, 200)는 부모 클래스의 생성자를 호출해 상속받은 속성도 초기화
class P_Class:

    def __init__(self, pNum1, pNum2):
        print('[pClass] __init__()')
        self.pNum1 = pNum1
        self.pNum2 = pNum2

class C_Class(P_Class):

    def __init__(self, cNum1, cNum2):
        print('[cClass] __init__()')
        super().__init__(100, 200)  # 부모 클래스 초기화
        self.cNum1 = cNum1
        self.cNum2 = cNum2

cls = C_Class(10, 20)

★ 출력 결과 

[cClass] __init__()
[pClass] __init__()
cls.cNum1: 10
cls.cNum2: 20
cls.pNum1: 100
cls.pNum2: 200

 

 

 

  • 실습 코드
    • 중간고사 클래스와 기말고사 클래스를 상속 관계로 만들고 각각의 점수를 초기화
    • 또한 총점 및 평균을 반환하는 기능도 만들어야 함 

 

  • MidExam 클래스 정의
    • 생성자 __init__()는 중간고사 점수 3개를 객체 속성으로 저장
    • printScores()는 점수를 출력하는 메서드
class MidExam:

    def __init__(self, s1, s2, s3):
        print('[MidExam] __init__()')
        self.mid_kor_score = s1
        self.mid_eng_score = s2
        self.mid_mat_score = s3

    def printScores(self):
        print(f'mid_kor_score: {self.mid_kor_score}')
        print(f'mid_eng_score: {self.mid_eng_score}')
        print(f'mid_mat_score: {self.mid_mat_score}')

 

 

  • EndExam 클래스 정의 (MidExam 상속)
    • super().__init__()를 통해 부모 클래스의 점수 초기화도 함께 처리
    • 추가로 기말고사 점수도 객체에 저장
class EndExam(MidExam):

    def __init__(self, s1, s2, s3, s4, s5, s6):
        print('[EndExam] __init__()')
        super().__init__(s1, s2, s3)  # 부모 클래스 초기화
        self.end_kor_score = s4
        self.end_eng_score = s5
        self.end_mat_score = s6

 

 

  • 점수 출력 메서드
    • 부모 클래스의 printScores()도 호출하고,
    • 자신만의 출력도 추가(메서드 오버라이딩)
    def printScores(self):
        super().printScores()  # 중간고사 점수 먼저 출력
        print(f'end_kor_score: {self.end_kor_score}')
        print(f'end_eng_score: {self.end_eng_score}')
        print(f'end_mat_score: {self.end_mat_score}')

 

 

  • 총점 및 평균 계산
    • 총 6개의 과목 점수를 더하고 평균을 계산
    def getTotalScore(self):
        total = self.mid_kor_score + self.mid_eng_score + self.mid_mat_score
        total += (self.end_kor_score + self.end_eng_score + self.end_mat_score)
        return total

    def getAverageScore(self):
        return self.getTotalScore() / 6

 

 

  • 실행
exam = EndExam(85, 90, 88, 75, 85, 95)
exam.printScores()
print(f'Total: {exam.getTotalScore()}')
print(f'Average: {round(exam.getAverageScore(), 2)}')

★ 출력 결과 

[EndExam] __init__()
[MidExam] __init__()
mid_kor_score: 85
mid_eng_score: 90
mid_mat_score: 88
end_kor_score: 75
end_eng_score: 85
end_mat_score: 95
Total: 518
Average: 86.33

 

 

 

▶ 요약

 

 

 

[나의 생각 정리]

  • 클래스를 상속받아 코드를 효율적으로 구성하는 법을 배웠다.
  • super()을 통해 부모 클래스의 기능을 재사용하는 것이 유용하다는 것을 알았다.

 

[적용점]

  • 클래스 설계 시 중복을 줄이기 위해 상속과 super() 활용을 하면 좋을 것 같다.

 

 

 

“이 글은 제로베이스 데이터 스쿨 주 3일반 강의 자료 일부를 발췌하여 작성되었습니다.”