[7일차] 파이썬 중급5~6

하은·2023년 10월 21일
0

020. 객체와 메모리

메모리와 객체에 대한 이해

- 객체와 메모리

- 변수는 객체의 메모리 주소를 저장하고 이를 이용해서 객체를 참조한다.

  • 객체는 class로부터 생성자를 호출해서 생성되고, 메모리에 생성된다.
    = 어떤 클래스가 하나 있을 때 생성자를 호출하면, 메모리에 객체가 생성된다.

  • 생성한 객체를 사용하기 위해서는, 변수를 만들어서 객체를 할당해 사용된다.
    = *객체는 변수에 객체데이터로 들어가는게 아니고, 변수는 다른 곳에 저장돼있다.

  • 예) Car라는 클래스로부터 객체를 만들어서, myCar라는 변수에 저장했다?
    -> 생성된 객체는 다른 곳에 저장돼있고,
    -> myCar라는 변수는 또 다른 곳에 저장돼있는 것
    = 변수에 직접 객체가 저장 안 되는데,
    객체가 생성됐을 때 메모리 주소가 변수에 저장되는 것
    = 실제로 변수안에 있는 객체가 아니라, 객체의 메모리 주소를 이용하는 것
    = 변수에 어떤 명령을 내리면 그 주소를 찾아가서 그 객체의 기능이나 속성이 호출되거나 변경되는 것.

  • 레퍼런스 변수 = 주소값을 가지고 객체를 참조하는 변수

class Robot:

    def __init__(self, color, height, weight):
        self.color = color
        self.height = height
        self.weight = weight

    def printRobotInfo(self):
        print(f'color : {self.color}')
        print(f'height : {self.height}')
        print(f'weight : {self.weight}')

rb1 = Robot('red', 200, 80)
#class를 이용해서 객체를 생성할 수 있다.
#생성자 호출하기 = class와 이름이 똑같다
#()안에 초기 속성값을 넣어줌
#rb1 같은 변수를 설정해줘야 접근이 가능함
#rb1이라는 레퍼런스변수에 바로 들어가는게 아니라, 생성된 객체가 있는 메모리의 주소값을 넣어줌

rb1.printRobotInfo()
#명령을 내리면, 주소값을 알고 있으니까 주소에 가서 printRobotInfo라는 매소드를 찾아서 호출하는 기능이 실행되는 것

rb2 = Robot('Blue', 300, 120)
rb3 = rb1 #얕은 복사 #rb1의 메모리 주소값이 rb3에 다시 할당되는 것

rb1.printRobotInfo()
rb2.printRobotInfo()
rb3.printRobotInfo() #그 결과로 값이 똑같음

rb1.color = 'gray'
rb1.height = '250'
rb1.weight = '100'

rb1.printRobotInfo()
rb2.printRobotInfo()
rb3.printRobotInfo() #rb1의 수정결과가 rb3의 값과 똑같음. 하나의 레퍼런스변수를 수정하면 다른쪽에서 가르키는 값도 바뀐다.

-->
color : red
height : 200
weight : 80

color : Blue
height : 300
weight : 120

color : red
height : 200
weight : 80

color : gray
height : 250
weight : 100

color : Blue
height : 300
weight : 120

color : gray
height : 250
weight : 100

? 실습) 국어, 영어, 수학 점수를 입력받아 리스트에 저장하고 원본을 유지한 상태로, 복사본을 만들어 과목별 점수를 10% 올렸을 경우에 평균을 출력해보자

scores = [int(input('국어점수 입력:')),
          int(input('영어점수 입력: ')),
          int(input('영어점수 입력: '))]

print(scores)

copyScores = scores.copy()
#복사하는 함수 = 주소만 복사한 게 아니라, 실제 똑같은 객체가 만들어진 것

for idx, scores in enumerate(copyScores):
    result = scores * 1.1
    copyScores[idx] = 100 if result > 100 else result

print(f'이전 평균: {sum(scores) / len(scores)}')
print(f'이후 평균: {sum(copyScores) / len(copyScores)}')

-->
  File "C:\Users\User\Desktop\python_ex\5_020\work.py", line 14, in <module>
    print(f'이전 평균: {sum(scores) / len(scores)}')
                    ^^^^^^^^^^^
TypeError: 'int' object is not iterable

021. 얇은 복사와 깊은 복사

객체 복사에 대한 이해

- 얕은 복사

- 얕은 복사란, 객체 주소를 복사하는 것으로 객체 자체가 복사되지 않는다.

하나의 객체를 공유해서 쓰는 두개의 변수

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 #얕은 복사 #객체 하나, 변수 두개

tc1.printClsInfo()
tc2.printClsInfo()

tc2.number = 3.14
tc2.str = 'bye'

tc1.printClsInfo()

- 깊은 복사

- 깊은 복사란, 객체 자체를 복사하는 것으로 또 하나의 객체가 만들어진다.

객체가 하나 더 생기고, 새로운 변수가 만들어진다.

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 #얕은 복사 #객체 하나, 변수 두개

tc1.printClsInfo()
tc2.printClsInfo()

tc2.number = 3.14
tc2.str = 'bye'

tc1.printClsInfo()
'''
#깊은 복사
import copy

tc1 = TemCls(10, 'hello')
tc2 = copy.copy(tc1) #copy모듈의 copy라는 기능 + 레퍼런스 값을 넣어주면 객체를 새로 복사해서 할당해줌

tc1.printClsInfo()
tc2.printClsInfo()

tc2.number = 3.14
tc2.str = 'bye'

tc1.printClsInfo()
tc2.printClsInfo()

? 실습) 선수의 원본 점수를 이용해서 평균을 출력하고, 최고값과 최저값을 제외한 평균을 출력하는 프로그램을 만들어보자

plaOriSco = [8.7, 9.1, 8.9, 9.0, 7.9, 9.5, 8.8, 8.3]
plaCopSco = plaOriSco.copy()

plaOriSco.sort()

plaCopSco.sort()
plaCopSco.pop(0)
plaCopSco.pop()

print(f'plaOriSco: {plaOriSco}')
print(f'plaCopSco: {plaCopSco}')

oriTot = round(sum(plaOriSco), 2)
oriAvg = round(oriTot / len(plaOriSco), 2)
print(f'original Total: {oriTot}')
print(f'original Average: {oriAvg}')

copTot = round(sum(plaCopSco), 2)
copAvg = round(oriTot / len(plaCopSco), 2)
print(f'copy Total: {copTot}')
print(f'copy Average: {copAvg}')

print(f'oriAvg - copAvg: {oriAvg - copAvg}')

-->
plaOriSco: [7.9, 8.3, 8.7, 8.8, 8.9, 9.0, 9.1, 9.5]
plaCopSco: [8.3, 8.7, 8.8, 8.9, 9.0, 9.1]
original Total: 70.2
original Average: 8.78
copy Total: 52.8
copy Average: 11.7
oriAvg - copAvg: -2.92

022. 클래스 상속

클래스의 기능을 내 것처럼 사용하자

- 상속이란?

- 클래스는 또 다른 클래스를 상속해서 내 것처럼 사용할 수 있다.

  • Class 1
    -> Class2는 Class1을 상속한다
  • Class 2 = Class2는 Class1의 모든 기능을 사용할 수 있다.
    -> Class3은 Class2를 상속한다
  • Class 3 = Class3은 Class1, Class2의 모든 기능을 사용할 수 있다.
class NormalCar:

    def drive(self): #기능을 하나 만들어줌
        print('[NormalCar] drive() called')

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


class TurboCar(NormalCar): #NormalCar 상속하면 기능 두개 가질수 있음
    #class 다음에 () 안에 상속을 할 클래스를 명시해주면 끝

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

myTurboCar = TurboCar()

myTurboCar.turbo()
myTurboCar.drive()
myTurboCar.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

myCalculator = CalculatorChild()

print(myCalculator.add(10, 20))
print(myCalculator.sub(10, 20))
print(myCalculator.mul(10, 20))
print(myCalculator.div(10, 20))

-->
30
-10
200
0.5

023. 생성자(1)

객체 생성에 대해 좀 더 자세히 알아보자

- 생성자

- 객체가 생성될 때 생성자를 호출하면 init() 자동 호출된다

class Calculator:

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

cal = Calculator()
# (눈에 보이지 않지만) 생성자를 호출하면
# __init__() 를 자동으로 호출해서
# 객체가 메모리에 만들어진다 -> [Calculator] __init__() called

-->
[Calculator] __init__() called

- init()

- init()

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}')
#매개변수로 값을 받아서 그때그때 객체가 생성될 때 값이 초기화됨


class Calculator:

    def __init__(self, n):
        print('[Calculator] __init__() called')
        self.num1 = n
        self.num2 = 100

cal = Calculator(3.14)
print(f'cal.num1 : {cal.num1}')
print(f'cal.num2 : {cal.num2}')
#하나는 매개변수로 값 넣고, 하나는 지정해둘 수도 있음

- super()

- 상위 클래스의 속성을 초기화하기 위해 super()를 사용한다

class P_class: #상속해주는 클래스

    def __init__(self, pNum1, pNum2):
        print('[P_class] __init__() called')

        self.pNum1 = pNum1
        self.pNum2 = pNum2

class C_class(P_class):

    def __init__(self, cNum1, cNum2):
        print('[C_class] __init__() called')

        # 쓰기 위한 방법 1
        P_class.__init__(self, cNum1, cNum2)
        # 쓰기 위한 방법 2
        super().__init__(cNum1, cNum2)
        # 상위로 올라가서, init을 호출하고, 값은 c1과 c2로 초기화해라
        # self값 따로 안 써줘도 됨

        self.cNum1 = cNum1
        self.cNum2 = cNum2


cls = C_class(10, 20)
# C_class의 init method만 호출됨. = 속성도 초기화 돼서 값을 쓸 수 있는 것
# P_class의 속성값은 초기화 되지 않아서 쓸 수 없음

-

# 기능은 상속만 하면 바로 쓸 수 있는데
# 속성은 init이 호출돼야 초기화된다.
# super()로 강제적으로 init을 호출해서 속성을 초기화해서 사용할 수 있다.

-->
[C_class] __init__() called
[P_class] __init__() called
  • 쓰기 위한 방법 1
    P_class.init(self, cNum1, cNum2)
  • 쓰기 위한 방법 2
    super().init(cNum1, cNum2)
    -> 상위로 올라가서, init을 호출하고, 값은 c1과 c2로 초기화해라
    -> self값 따로 안 써줘도 됨

024. 생성자(2)

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

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 printScore(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}')

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

    def printScore(self):
        super().printScore()
        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}')

    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.printScore()
print(f'TotalScore: {exam.getTotalScore()}')
print(f'AverageScore: {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
TotalScore(): 518
AverageScore(): 86.33

025. 다중상속

2개 이상의 클래스를 상속한다

- 다중 상속

- 2개 이상의 클래스를 상속한다.

**필요할 때만 사용하기

( )안에 상속받고 싶은 클래스 이름을 나열하면 사용가능

class Car01:
    def drive(self):
        print('drive() method called')

class Car02:
    def turbo(self):
        print('turbo() method called')

class Car03:
    def fly(self):
        print('fly() method called')

class Car(Car01, Car02, Car03):
    def __init__(self):
        pass

myCar = Car()
myCar.drive()
myCar.turbo()
myCar.fly()

-->
drive() method called
turbo() method called
fly() method called

실습) BasicCalculator와 DeveloperCalculator 클래스를 다음과 같이 만들고, 이들 클래스를 상속해서 Calculator 클래스를 만들고 사용해보자

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

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

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

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

class DeveloperCalculator:
    def mod(self, n1, n2):
        return n1 % n2

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

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

class Calculator(BasicCalculator, DeveloperCalculator):
    def __init__(self):
        pass

cal = Calculator()
print(f'cal.add(10, 20): {cal.add(10, 20)}')
print(f'cal.sub(10, 20): {cal.sub(10, 20)}')
print(f'cal.mul(10, 20): {cal.mul(10, 20)}')
print(f'cal.div(10, 20): {cal.div(10, 20)}')

print(f'cal.mod(10, 20): {cal.mod(10, 20)}')
print(f'cal.flo(10, 20): {cal.flo(10, 20)}')
print(f'cal.exp(2, 5): {cal.exp(2, 5)}')

026. 오버라이딩

method를 재정의한다

- 오버라이딩

- 하위클래스에서 상위클래스의 메서드를 재정의(override)한다.

class Robot:
    def __init__(self, c, h, w):
        self.color = c
        self.height = h
        self.weight = w

    def fire(self):
        print('미사일 발사')

    def printRobotInfo(self):
        print(f'self.color : {self.color}')
        print(f'self.height : {self.height}')
        print(f'self.weight : {self.weight}')

class NewRobot(Robot):
    def __init__(self, c, h, w):
        super().__init__(c, h, w)

    # 오버라이딩
    def fire(self):
        print('레이저 발사') 


myRobot = NewRobot('red', 200, 300)
myRobot.fire()
myRobot.printRobotInfo()

-->
레이저 발사
self.color : red
self.height : 200
self.weight : 300

실습) 삼각형 넓이를 계산하는 클래스를 만들고 이를 상속하는 클래스에서 getArea()를 오버라이딩해서 출력결과가 다음과 같을 수 있도록 클래스를 만들어보자

class TriangleArea:

    def __init__(self, w, h):
        self.width = w
        self.height = h

    def printTriangleArea(self):
        print(f'self.width: {self.width}')
        print(f'self.height: {self.height}')

    def getArea(self):
        return self.width * self.height / 2

class NewTriangleArea(TriangleArea):
    def __init__(self, w, h):
        super().__init__(w, h)

    def getArea(self):
        return str(super().getArea()) + '㎤'#str = 문자열변환

ta = NewTriangleArea(7, 5)
ta.printTriangleArea()
triangleArea = ta.getArea()
print(f'triangleArea: {triangleArea}')

-->
self.width: 7
self.height: 5
triangleArea: 17.5㎤

027. 추상클래스

메서드 구현을 강요한다.

- 추상클래스

- 상위클래스에서 하위클래스에 메서드 구현을 강요한다.

강제적으로 구현해서 써야한다는 클래스 = 추상클래스

  • 추상클래스를 쓰는 이유
    : 특정 기능을 상속받았을 때, 각자 입맛에 맞게 고쳐써라
from abc import ABCMeta #1. abc 패키지로부터 ~ 모듈을 가져와야함
from abc import abstractmethod #2. abc 패키지로부터 ~ 모듈을 가져와야함
class Airplane(metaclass=ABCMeta): #3. 강제성을 띄게 하는 상위클래스에 ~ 이렇게 명시해주고

    @abstractmethod #4. 강제로 구현해야하는 메서드에 데코레이션을 붙여줌
    def flight(self): #상속하는 child class에서 구현해줘야 오류안남
        pass        #5. -> 이 클래스를 상속하는 클래스에서 반드시 구현해줘야함.

    def forward(self):
        print('전진')

    def backward(self):
        print('후진')

class Airliner(Airplane):
    def __init__(self, c):
        self.color = c

    def flight(self):
        print('시속 400km/h 비행')

class FighterPlane(Airplane):
    def __init__(self, c):
        self.color = c

    def flight(self):
        print('시속 700km/h 비행')
    # 입맛에 맞게 고쳐씀

al = Airliner('red')
al.flight()
al.forward()
al.backward()

fp = FighterPlane('red')
fp.flight()
fp.forward()
fp.backward()

-->
시속 400km/h 비행
전진
후진
시속 700km/h 비행
전진
후진

실습) 계산기 추상 클래스를 만들고 이를 이용해서 새로운 계산기 클래스를 만들어보자. 추상클래스에는 덧셈, 뺄셈, 곱셈, 나눗셈 기능이 선언되어 있어야 한다.

from abc import ABCMeta
from abc import abstractmethod

class Calculator:
    @abstractmethod
    def add(self, n1, n2):
        pass
    @abstractmethod
    def sub(self, n1, n2):
        pass
    @abstractmethod
    def mul(self, n1, n2):
        pass
    @abstractmethod
    def div(self, n1, n2):
        pass

class DeveloperCalculator(Calculator):

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

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

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

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

    def mod(self, n1, n2):
        print(n1 % n2)

    def flo(self, n1, n2):
        print(n1 // n2)

dc = DeveloperCalculator()
dc.add(10, 20)
dc.sub(10, 20)
dc.mul(10, 20)
dc.div(10, 20)
dc.mod(10, 20)
dc.flo(10, 20)

028. 예외란?

예상하지 못한 문제로 프로그램 실행이 어려운 상태

- 예외란?

- 예외란, 문법적인 문제는 없으나 실행 중 발생하는 예상하지 못한 문제

  • 에러 = 소프트웨어적 문제로 실행x
    : 네트워크 문제, 천재지변으로 시스템 기동상황x, 전기 끊김 등
  • 예외
    : 문법적 문제없고, 네트워크 정상, 프로그램 잘 실행되지만, 예상하지 못한 이유로 문제가 발생한 것

예)

def add(n1, n2):
    print(n1 + n2)

def sub(n1, n2):
    print(n1 - n2)

def mul(n1, n2):
    print(n1 * n2)

def div(n1, n2):
    print(n1 / n2)

firstNum = int(input('firstNum: '))
secondNum = int(input('secondNum: '))

add(firstNum, secondNum)
sub(firstNum, secondNum)
mul(firstNum, secondNum)
div(firstNum, secondNum)

-->
firstNum: 10
** secondNum: 0     # 예외 발생
10
10
0
** ZeroDivisionError: division by zero  # 예외 발생
# 10 / 0 이 안 되기 때문에

엄격하게 에러와 예외를 구분할 수 있으나, 같은 의미로도 사용한다.

- 예외종류

- 예외관련 클래스는 Exception클래스를 상속한다.

파이썬 뿐 아니라 다른 언어에서도 예외가 발생할 수 있고, 내부적으로 정의된 담당 클래스가 다 있다.

  • ZeroDivisionError
    -> ArithmeticError : 수학관련

  • IOError : 입출력 관련
    -> EnvironmentError

  • IndexError : 예) 길이가 5인데 index가 4일때
    -> LookupError

  • IndetationError : 들여쓰기 에러
    -> SyntaxError
    ...
    => Exception = 최상위 클래스
    이것을 상속한 여러가지 예외를 담당하는 에러가 있다.

  • 예)
    print(10 / 0)
    print(int('hello'))
    lists = [1, 2, 3, 4, 5, 6]
    print(lists[3])

0개의 댓글