객체지향 프로그래밍

  • object-oriented programming(OOP)

  • 프로그램을 단순히 데이터와 처리 방법으로 나누는 것이 아니라, 프로그램을 수많은 '객체(object)'라는 기본 단위로 나누고 이들의 상호작용으로 서술하는 방식이다. 객체란 하나의 역할을 수행하는 '메소드와 변수(데이터)'의 묶음으로 봐야 한다.

  • 큰 문제를 작게 쪼개는 것이 아니라, 먼저 작은 문제들을 해결할 수 있는 객체들을 만든 뒤, 이 객체들을 조합해서 큰 문제를 해결하는 상향식(Bottom-up) 해결법을 도입한 것이다.
    이 객체란 것을 일단 한번 독립성/신뢰성이 높게 만들어 놓기만 하면 그 이후엔 그 객체를 수정 없이 재사용할 수 있으므로 개발 기간과 비용이 대폭 줄어들게 된다.(출처: 나무위키)

메모리와 객체에 대한 이해

class Robot:
    def __init__(self,color,height,weight):
        self.color = color
        self.height = height
        self.weight = weight
    def printRobotInfor(self):
        print(f'color: {self.color}')
        print(f'height: {self.height}')
        print(f'weight: {self.weight}')
rb1 = Robot('red',200,80)
rb2 = Robot('blue',300,90)
  • 위 예제를 살펴보면,
    클래스 생성자로부터 Robot 이라는 클래스를 생성하였고 객체를 두개 생성하였다.

  • 중요한것은 이 두 객체를 참조하는 rb1과 rb2라는 변수는 메모리에 저장이 되어있는 것이아니라 메모리 바깥의 공간에 존재하며 객체의 '주소값'을 저장한 것 이다.

  • 즉, 변수 rb1과 rb2는 객체의 메모리 주소를 저장하고 이를 이용해서 객체를 참조하고 있는 것이다. (아래 그림 참조)

1) 얕은 복사

  • 위 예제에서 rb1과 rb2가 하나의 객체를 참조하게 하려면 다음과 같은 코드를 입력하면 된다.

    rb2 = rb1
    rb2.color = 'pink'
    print(rb1.color)
  • 살펴보면 rb2의 색상을 핑크로 변경했는데 rb1의 색상을 출력했을 때 핑크로 나오는 것을 확인할 수 있다. 즉 두 변수가 하나의 객체를 참조하고 있다는 것을 알 수 있다.

2) 깊은 복사

  • 그렇다면 하나의 객체를 참조하는 것이 아닌 객체 자체를 복사하여 두개의 다른 객체를 만들어보자

    import copy
    rb2 = copy.copy(rb1)
    rb2.printRobotInfo()
    #실행결과
    color: pink
    height: 200
    weight: 80
  • copy 모듈의 copy 함수를 사용하여 rb1을 복사하여 새로운 rb2가 생성되었다. rb2를 출력해보면 색상을 위에서 변경한 핑크와 rb1의 height, weight을 그대로 받아 복사가 된 것을 확인 할 수 있다.

  • 실습

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

scores = [int(input('국어 점수 입력:')),
          int(input('영어 점수 입력:')),
          int(input('수학 점수 입력:'))]
print(scores)
copyScores = scores.copy()
for idx, score in enumerate(copyScores):
    result = score * 1.1
    copyScores[idx] = 100 if result > 100 else result
print(f'이전 평균: {sum(scores) / len(scores)}')
print(f'이후 평균: {sum(copyScores) / len(copyScores)}')
#실행결과 
국어 점수 입력:80
영어 점수 입력:90
수학 점수 입력:100
[80, 90, 100]
이전 평균: 90.0
이후 평균: 95.66666666666667
  • copy() 함수 : 위 예제에서 copy() 함수는 copyScores 라는 변수를 통해 scores의 동일한 주소값을 할당 받는 것이 아니라 실제로 새로운 복사본을 생성하는 것이다.
  • enumerate 함수 : 이터러블한 객체를 순회하며 각 요소에 순서를 부여하는데 사용되는 파이썬 내장 함수 입니다.

클래스 상속

  • 클래스도 다른 클래스에 그 기능을 상속할 수 있고 상속 받은 클래스는 상속받은 기능을 사용할 수 있다.

  • 예제
    ➩덧셈, 뺄셈 기능이 있는 클래스를 만들고, 이를 상속하는 클래스를 만들어서 곱셈과 나눗셈 기능을 추가해보자.

    class Calculator1:
        def plus(self, n1, n2):
            result = n1 + n2
            return result
        def sub(self, n1, n2):
            result = n1 - n2
            return result
    class Calculator2(Calculator1):
        def mul(self,n1, n2):
            result = n1 * n2
            return result
        def div(self,n1, n2):
            result = n1 / n2
            return result
    cal = Calculator2()
    print(cal.plus(10,2))
    print(cal.sub(10,2))
    print(cal.mul(10,2))
    print(cal.div(10,2))
  • class Calculator2(Calculator1) 으로 Calculator1의 기능을 2가 상속받았다. print로 출력을 해보면 1의 기능을 모두 사용할 수 있는 것을 알 수 있다.

생성자 이해하기

  • 생성자란 클래스로부터 객체가 생성될 때 파이썬 인터프린터에 의해 자동으로 호출되는 특별한 메서드이다.

  • 객체가 생성될 때 생성자를 호출하면 __init__() 메서드가 자동 호출되며, 메모리에 생성된 객체 공간에 데이터를 넣거나 초기화하는 목적으로 사용한다.
    (초기화 기능이 있다고 우선 생각하자)

  • 메서드라는 용어도 헷갈릴 수 있으니 정의하자면 메서드는 클래스 내에 정의된 함수를 메서드라고 부른다. 메서드의 첫 번째 인자는 항상 self 이어야한다고 기억해두자.

  • 예제

    class A:
        def __init__(self,num1A,num2A):
            print('A클래스 생성자 호출')
            self.num1A = num1A
            self.num2A = num2A
        def A_function(self,num1A,num2A):
            return num1A + num2A
    class B(A):
        def __init__(self,num1B,num2B):
            print('B클래스 생성자 호출')
            self.num1B = num1B
            self.num2B = num2B
        def B_function(self):
            return num1B + num2B
    cal = B(10,20)
    #실행결과 
    B클래스 생성자 호출

위의 코드를 보면 클래스B가 A의 기능을 상속받았는데도 실행결과가 'B클래스 생성자 호출'만 출력되는 것을 볼 수 있다.

이것은 B클래스의 init메서드를 호출했기때문에 그 속성도(num1B=num1B, num2B=num2B)초기화 되었지만 A클래스의 속성값은 초기화 되지 않았기 때문에 출력이 안되는 것이다.

하지만 우리는 A의 속성값은 출력이 안되지만 A의 기능은 출력이 가능하다는 것은 클래스 상속을 배우면서 이해하였다.

cal = B('','')
print(cal.A_function(10,20))
#실행결과 
B클래스 생성자 호출
30

속성값도 출력하는 방법은 당연히(?)있다. super()라는 함수를 사용하면 된다. super().__init__(num1B,num2B)를 위 예제에 다시 추가해보자.

class A:
   def __init__(self,num1A,num2A):
       print('A클래스 생성자 호출')
       self.num1A = num1A
       self.num2A = num2A
   def A_function(self,num1A,num2A):
       return num1A + num2A
class B(A):
   def __init__(self,num1B,num2B):
       print('B클래스 생성자 호출')
       self.num1B = num1B
       self.num2B = num2B
       super().__init__(num1B,num2B)
   def B_function(self):
       return num1B + num2B
cal = B('','')
print(cal.A_function(10,20))
#실행결과 
B클래스 생성자 호출
A클래스 생성자 호출
30
  • 즉 정리해보면, 클래스 상속을 받으면 기능은 상속하여 사용할 수 있지만, 속성은 init메서드가 호출되어야지 속성이 초기화되면서 사용할 수 있다. (중요***)

  • 실습

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

class midExam:
    def __init__(self,a1,a2,a3):
        self.mid_korscore = a1
        self.mid_engscore = a2
        self.mid_matscore = a3
    def printscores(self):
        print(f'중간고사 국어점수: {self.mid_korscore}')
        print(f'중간고사 영어점수: {self.mid_engscore}')
        print(f'중간고사 수학점수: {self.mid_matscore}')
class finalExam(midExam):
    def __init__(self,a1,a2,a3,a4,a5,a6):
        super().__init__(a1,a2,a3)
        self.final_korscore = a4
        self.final_engscore = a5
        self.final_matscore = a6
    def printscores(self):
        super().printscores()
        print(f'기말고사 국어점수: {self.final_korscore}')
        print(f'기말고사 영어점수: {self.final_engscore}')
        print(f'기말고사 수학점수: {self.final_matscore}')
    def getTotalScore(self):
        total = self.mid_korscore+self.mid_engscore+self.mid_matscore
        total += self.final_korscore+self.final_engscore+self.final_matscore
        return total
    def getAverageScore(self):
        return self.getTotalScore() / 6
exam1 = finalExam(90,80,100,70,80,100)
exam1.printscores()
print(f'총점 :{exam1.getTotalScore()}')
print(f'평균: {round(exam1.getAverageScore(),2)}')
#실행결과 
중간고사 국어점수: 90
중간고사 영어점수: 80
중간고사 수학점수: 100
기말고사 국어점수: 70
기말고사 영어점수: 80
기말고사 수학점수: 100
총점 :520
평균: 86.67

다중상속

상속과 동일한 개념으로 2개 이상의 클래스를 상속받는 경우이고
이경우도 형식은 동일하다.

그러나 다중상속은 꼭 필요한 경우가 아니면 사용을 남발하지 않도록 해야한다. 왜냐하면 동일한 메소드를 각 클래스에 갖고 있는 경우 출력시 원치 않는 결과값을 갖게 되기도 하는 등 프로그램을 개발하는데 읽기 쉬운 코드가 되지 않을 수 있다.

오버라이딩

  • 하위 클래스에서 상위 클래스의 메서드를 재정의(override)한다.
  • 실습
    ➩삼각형 넓이를 계산하는 클래스를 만들고 이를 상속하는 클래스에서 getArea()를 오버라이딩 해서 출력 결과에 제곱센치미터가 추가 될 수 있도록 만들어보자

    class Rectangular_calculator:
        def __init__(self,num1,num2):
            self.width = num1
            self.height = num2
        def getArea(self):
            result = self.width * self.height / 2
            return result
    class Overriding(Rectangular_calculator):
        def __init__(self,num1,num2):
            super().__init__(num1,num2)
        def getArea(self):
            return str(super().getArea()) + 'cm²'
    a = Overriding(3,2)
    result = a.getArea()
    print(result)

하위 클래스의 getArea()메서드에서 상위 클래스를 강제 상속받아 문자열(str)로 고쳐준 다음 제곱센치미터를 붙여준 것을 확인 할 수 있다.(숫자+문자는 오류가 나므로 숫자를 문자형태로 바꾸어줍니다)

추상클래스

  • 아래 그림과 같이 상위 클래스에서 하위 클래스에 메서드 구현을 강요하는 것이다. 보통 하위클래스에서 입맛에 맞게 구현을 할 수 있도록 상위클래스에서 적용하는 것으로 사용이 된다.

  • 추상클래스를 사용하려면 다음과 같은 모듈을 import 해야한다.

    from abc import abstractmethod
    class 함수명(metaclass=ABCMeta):
        @abstractmethod
        def 함수명
  • 실습
    ➩계산기 추상 클래스를 만들고 이를 이용해서 새로운 계산기 클래스를 만들어보자. 추상 클래스에는 덧셈, 뺄셈, 곱셈, 나눗셈 기능이 선언되어 있어야 한다.

    from abc import abstractmethod
    class Calculator(metaclass=ABCMeta):
        @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 UpgradeCalculator(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)
    cal = UpgradeCalculator()
    cal.add(1,2)
    cal.sub(1,2)
    cal.mul(1,2)
    cal.div(1,2)
    #실행결과 
    3
    -1
    2
    0.5

예외와 예외 처리

  • 엄격하게 구분한다면 예외와 에러는 의미가 다르다. 에러는 문법적인 오류 또는 소프트웨어적인 오류를 얘기한다. 예외는 문법적인 문제는 없으나 실행 중 발생하는 예상하지 못한 문제이다.

  • 예외를 처리하는 방법은 예외 발생 예상 구문을 try~except 로 감싸는 것이다.

  • 실습
    ➩ 사용자로부터 숫자 5개를 입력받을 때 숫자가 아닌 자료형이 입력되면 예외 처리하는 프로그램을 만들어보자.

    list = []
    a = 1
    while a < 6:
        try:
            number = int(input('숫자를 입력하세요:'))
        except:
            print('다시 입력해주세요.')
            continue
        list.append(number)
        a += 1
    print('숫자:{}'.format(list))
    #실행결과 
    숫자를 입력하세요:1
    숫자를 입력하세요:4
    숫자를 입력하세요:1
    숫자를 입력하세요:3
    숫자를 입력하세요:2
    숫자:[1, 4, 1, 3, 2]
    #숫자가 아닌 문자를 입력했을 경우 실행결과 
    숫자를 입력하세요:1
    숫자를 입력하세요:coffee
    다시 입력해주세요.
    숫자를 입력하세요:

예외처리(try~except~else)

  • ~else 는 예외가 발생하지 않은 경우 실행하는 구문이다.

  • 위에서 배운 try ~except 이후 넣어준다.

  • 실습

    eveList = [] #짝수 리스트
    oddList = [] #홀수 리스트
    floatList = [] #실수 리스트
    n = 1
    while n < 6:
        try:
            num = float(input('숫자를 입력 하세요:'))
        except:
            print("예외 발생")
            continue
        else:
            if num % 2 == 0:
                eveList.append(int(num))
            elif num % 2 == 1:
                oddList.append(int(num))
            elif num % 2 != 1:
                floatList.append(num)
        n += 1
    print('짝수리스트:{}'.format(eveList))
    print('홀수리스트:{}'.format(oddList))
    print('실수리스트:{}'.format(floatList))
    #실행결과 
    짝수리스트:[2]
    홀수리스트:[1, 5]
    실수리스트:[2.1, 3.2]

finally

  • finally 는 어떠한 작업을 하는 과정에서 예외 발생과 상관없이 항상 실행한다.
    즉 위에서 본 try~except 에서 예외가 발생하든 else에서 예외가 발생하지 않든 상관없이 무조건 실행한다라는 의미이다.

  • 실습(바로 위 예제와 동일문제)

    eveList = [] #짝수 리스트
    oddList = [] #홀수 리스트
    floatList = [] #실수 리스트
    sumList = []
    n = 1
    while n < 6:
        try:
            data = input('숫자를 입력 하세요:')
            floatNum = float(data)
        except:
            print("예외 발생")
            continue
        else:
            if floatNum % 2 == 0:
                eveList.append(int(floatNum))
            elif floatNum % 2 == 1:
                oddList.append(int(floatNum))
            elif floatNum % 2 != 1:
                floatList.append(floatNum)
            n += 1
        finally:
            sumList.append(data)
    print('짝수리스트:{}'.format(eveList))
    print('홀수리스트:{}'.format(oddList))
    print('실수리스트:{}'.format(floatList))
    print('데이터 집합:{}'.format(sumList))
    #실행결과 
    숫자를 입력 하세요:파이썬
    예외 발생
    숫자를 입력 하세요:1
    숫자를 입력 하세요:3
    숫자를 입력 하세요:1.3
    숫자를 입력 하세요:8
    숫자를 입력 하세요:10
    짝수리스트:[8, 10]
    홀수리스트:[1, 3]
    실수리스트:[1.3]
    데이터 집합:['파이썬', '1', '3', '1.3', '8', '10']
  • 문자열 '파이썬'을 입력해여 예외가 발생했지만 입력한 모든 데이터가 출력되게끔 finally가 묶여서 위와 같이 모두 출력되는 결과를 갖는다.

Exception 클래스

  • 예외 담당 클래스 Exception

  • 예외가 발생하면 어떤 이유로 예외가 발생했는지를 명시해줄 수 있는 기능이다.

  • 위의 예제를 다시 불러와 except 구문에 다음과 같이 표시해보고 실행을 돌려보자

      except Exception as e:
            print('예외 발생 원인:{}'.format(e))
            continue
     #실행결과 
    숫자를 입력 하세요:파이썬 
    예외 발생 원인:could not convert string to float: '파이썬 '
  • 위와 같이 float 실수형이 아닌 파이썬이라는 문자열을 입력했기 때문에 예외가 발생 했음을 알 수 있다.

raise

  • 강제로 예외를 발생시킬 수 있는 키워드 이다.

  • 실습

    def sendSMS(msg):
        if len(msg) > 10:
            raise Exception('길이초과. MMS전환 후 발송', 1)
        else:
            print('SMS 발송')
    def sendMMS(msg):
        if len(msg) <= 10:
            raise Exception('길이 미달. SMS 으로 발송', 2)
        else:
            print('MMS 발송')
    msg = input('문자 메시지 입력:')
    try:
        sendMMS(msg)
    except Exception as e:
        print('e: {}'.format(e.args[0]))
        print('e: {}'.format(e.args[1]))
        if e.args[1] == 1:
            sendMMS(msg)
        elif e.args[1] == 2:
            sendSMS(msg)
    #실행결과 
    문자 메시지 입력:안녕하세요
    e: 길이 미달. SMS 으로 발송
    e: 2
    SMS 발송

*참고(위의 예제는 try구문에서 어떤 함수를 실행시키냐에따라 기준이 달라져서 결과가 달라지게된다.)

텍스트 파일 쓰기

  • 파이썬에서는 외부파일을 불러와 읽기('r'), 쓰기('w') 기능을 할 수 있다. 아래 코드는 컴퓨터상 제로베이스취업스쿨이라는 폴더안에 메모장을 open 하여 쓰기('w')를 하였다.

  • 참고로 폴더안에 파일이 없는 상태여도 'open'을 하고 경로주소값을 입력하면 폴더안에 새로운 파일이 생성된다.

  • 파일은 open() → 'w','r'→ close() 순으로 열고 닫아야 한다.(순서 중요!!)

file = open('/Users/seayoon/Desktop/제로베이스취업스쿨/text.txt','w')
strCnt = file.write('hello world!')
print(strCnt) #문자열의 길이를 출력하였다. 
file.close()
#실행결과 
12 
  • 실습
import time
lt = time.localtime()
datestr = '['+'오늘날짜:'+str(lt.tm_year)+'년'+str(lt.tm_mon)+'월'\
           +str(lt.tm_mday)+'일]'
file = open('/Users/seayoon/Desktop/제로베이스취업스쿨/text.txt','w')
str = input('오늘의 할일:')
file.write(datestr+str)
file.close()
  • 실행결과

텍스트 파일 읽기(+특정단어 수정!)

1) 쓰기(w) 모드로 열어줌, 내용을 작성하여 저장한다.

file = open('/Users/seayoon/Desktop/제로베이스취업스쿨/test.txt','w')
writing = file.write(' 클래스 내에 정의된 함수를 메서드라고 부른다고 했습니다. 그리고 메서드의 첫 번째 인자는 항상 self여야 한다고 했습니다. 하지만 메서드의 첫 번째 인자가 항상 self여야 한다는 것은 사실 틀린 말입니다. 이번 절에서는 파이썬 클래스에서 self의 정체를 확실히 이해해 봅시다.')
file.close()

2) 읽기(r) 모드로 열어줌, 특정단어를 변경해준다.(replace함수 사용), 마찬가지로 변경 후 파일 닫아줌

file = open('/Users/seayoon/Desktop/제로베이스취업스쿨/test.txt','r')
reading = file.read()
print(reading)
changedWord = reading.replace('self','셀프')
print(changedWord)
file.close()

3) 쓰기(w)모드로 열어주고 변경된 문장(changedWord)를 덮어 씌어준다. 파일을 닫아준다

file = open('/Users/seayoon/Desktop/제로베이스취업스쿨/test.txt','w')
file.write(changedWord)
file.close()

4) 결과물

다양한 방식으로 파일 열기(open)

😃오늘의 짧막한 후기

오늘은 파이썬에서 너무 중요한 개념을 많이 배운것 같다.
객체지향프로그래밍, 클래스, 생성자, 메서드 등 용어부터 낯설었는데 제대로 안익혀두면 나중에 강의교수님이 하는 말도 무슨말인지 모를것같다는 생각이 들었다.

그리고 평소에 간식 잘 안먹는데 코딩할때 자꾸 뭔가 먹고싶은 이유는 뭘까...ㅎㅎ

profile
우직한 거북이

0개의 댓글