주석은 프로그래밍에서 어려운 문법을 가지고 있는 것도 아니고, 프로그램의 실행시간의 지대한 영향을 끼치는 것도 아니다. 그런데 왜 필자는 주석을 Python과 친해지기 포스팅이 아닌 Advanced버전인 Python과 매우 친해지기에 올렸을까? 아래의 두 가지 이유가 있다.

 

1. 주석은 프로그래머들 사이의 협업의 규약이기에 매우 중요하므로, 이는 어느 정도 실력이 보증된 프로그래머가 작성해야된다.
2. 필자는 까먹었다.

 

자 그럼 Python세계의 주석을 알아보도록 하자


주석(Comment)

주석은 프로그램의 실행에 영향을 미치지 않으며, 프로그래머가 필요에 의해서 코드에 추가적인 정보를 입력하는 것이다.

Python에서는 다음과 같은 방법으로 주석을 남길 수 있다.

 

한 줄 주석(#)

def comment(value) :
    print(value)	# 한줄의 주석을 추가합니다.

 

여러 줄 주석(''' ~ ''')

def comment(value) :
    print(value)

'''
여러줄의 주석을 추가하는
방법은 작은따옴표
세개를 연달아 쓰는것입니다.
끝내는 방법도 동일합니다.
'''

 

훌륭한 주석에 대한 고찰

좋은 주석은 무엇일까? 보기에 번잡스럽지 않으면서도, 필요한 내용을 정확하게 알려주는 주석이 좋은 주석이라고 할 수 있겠다. 대표적으로 아래와 같은 주석이 많이 쓰인다.

 

① 머리글 : 프로그램의 제작자, 저작권, 생성일, 변경 일등을 기술한다.

#######################
# Python calc program
# Python 3.8.4
# written by 리덕토 at 2021_04_10
# This code is protected by XXX
# 
# revise-1 2021_04_XX : add sub function
# revise-2 2021_05_XX : add div function
#######################

def add(val1, val2):
    return val1 + val2

def sub(val1,val2) :
    return val1-val2

def div(val1,val2) :
    return val1/val2

 

② 함수 동작 주석 : 함수의 파라미터, 리턴 값, 동작 방식을 설명한다.(프로그래머도 사람이라, 자연어 기술이 더 읽기 좋다.)


# function_name : do_something
#
# purpose : to get property of user's request 
#
# parameters : 
#  - req : String, should include ":"
#  - parameter : Integer, 1 = length of seperated req, 2 = sum of seperated req
#
# return : Integer, length of handled data or sum of that
#
# Warn : req should only contained Integer value and ":" 
 
def do_something(req, parameter) :
   	data = data.strip(":")
    if(parameter == 1) :
        return len(data)
    elif(parameter == 2) :
    	sum_data = 0 
        for i in range(len(data)) :
        	sum_data += data[i]
        return sum_data

좋은 주석을 단다는 건 어려운 일이다. 주석을 다는 규약이 없는 조직이라는 필연적으로 프로그래머 자기 자신 나름대로의 주석 습관이 생기기 마련이다.

누군가는 모든 코드에 주석을 달 것이고(이건 아닌 거 같다.) 누군가는 나만 보려고 주석 없는 코드를 만들 수도 있다.

다만 좋은 형상관리 도구들(Github)들을 가보면 모두가 사용하는 코드에 주석이 한 줄도 없는 경우는 찾아보기 힘들다.

매너 있는 프로그래머가 되기 위해서, 또 게으른 우리 자신을 위해서 주석을 달아두는 건 분명 도움되는 습관이다. 다음 시간은 Iterator에 대해서 알아보자

오랜만이다. 한 3일 만에 글을 쓰는 것 같은데 바쁜 현생을 살고 왔다. 이번 시간에는 Python에 존재하는 여러 가지 예약어에 대해서 알아보도록 하자. 이미 우리가 배운 것도 많이 있으며 어? 이건 새로 보는 건데? 하는 건 사용법으로 알아보자. 

 

※ 주의 ※ 이번글은 굳이 다 읽은 필요가 없다.


and

논리연산 and, 2개의 bool값을 받아 둘 다 참이면 True, 하나라도 False인 경우 False 가 된다. 

더보기
>>> a = 3
>>> b = 5
>>> print(a>2 and b>4)
True
## a>2와 b>4는 둘다 True라 둘을 and 한 결과도 True이다.

>>> print(a<2 and b>4)
False 
## a<2가 거짓이기 때문에 비록 b>4가 True더라도 and의 결과는 False이다.

 

as

별칭을 지정한다. f객체를 open하며 사용하거나, import 되는 모듈의 이름을 별칭으로 만들기도 한다.

더보기
import random as r

print(r.randrange(0,10))

with open("filename.txt","r") as f:
    print(f.read())

 

assert

가정설정문, 특별한 경우를 지정하여 Exception을 발생시킨다.(값 검증에 사용된다.) 여기서 발생되는 Exception은 AssertionError라고 한다. 

더보기
## 코드 : 
fruits = ["apple","orange","strawberry","kiwi"]

loc = int(input("몇 번째 과일을 출력하시겠습니까? : "))
try :
    assert len(fruits)>loc-1, "배열초과"
    print(fruits[loc])
except AssertionError :
    print("올바른 값을 입력")

## 실행 :
몇 번째 과일을 출력하시겠습니까? : 2
strawberry

## 재실행 :
몇 번째 과일을 출력하시겠습니까? : 5
올바른 값을 입력

 

async - await

import asyncio와 같이 사용되며, 비동기 처리방식을 만들 때 사용된다. 함수를 정의할 때 async를 지정하여 네이티브 코 루틴을 생성할 수 있고, await로 해당 코 루틴이 끝나는 것을 기다릴 수 있다.(추후에 자세한 내용 포스팅)

더보기
## 코드 : 
import asyncio

async def add_five(val) :
    print(val,"is val")
    await asyncio.sleep(1.0)
    return val+5

async def add_print(val) :
    print(await add_five(val))

loop = asyncio.get_event_loop()
loop.run_until_complete(add_print(4))
loop.close()

## 출력 : 9는 1초후에 출력된다.
4 is val
9

 

break

가장가까운 반복문을 중단한다

더보기
## 코드 :
for i in range(10) :
	if(i>2) :
		break
	print("HELLO",i)

## 출력 :    
HELLO 0
HELLO 1
HELLO 2

 

class

클래스를 정의한다.(성의 없다.)

더보기
class dog :
    name = ""
    owner = ""

 

continue

가장 가까운 반복문을 다음 step으로 강제 이동시킨다.

더보기
## 코드 : 
for i in range(5) :
	print("loop is not end")
	if(i>2) :
		continue
	print("HELLO",i)

## 출력 : 
loop is not end
HELLO 0
loop is not end
HELLO 1
loop is not end
HELLO 2
loop is not end
loop is not end

 

def

함수를 정의한다.(성의 없다.)

더보기
def my_foo(value):
    print(value)

 

del 

변수 / 객체를 삭제한다. 삭제된 변수 / 객체는 접근할 수 없다.(원칙상 그렇다. 취약점으로 접근하는 경우는 논외로 하자)

더보기
## 코드 :
a=3
print(a)

del a
print(a)

## 출력 :
3
Traceback (most recent call last):
  File "C:/Users/82109/Desktop/123.py", line 5, in <module>
    print(a)
NameError: name 'a' is not defined

 

finally - else

try - except과 같이 사용되며, finally 예약어는 try 나 except에서 실행되고 실행될 블록을, else는 try에서 에러가 검출되지 않을때 실행될 블럭을 지시한다.

더보기
## 코드 :
fruits = ["apple","orange","strawberry","kiwi"]

try :
    print(fruits[3])
except IndexError:
    print("인덱스 에러입니다.")
else :
    print("인덱스 에러가 아닙니다.")
finally :
    print("2케2케해서 출력은 끝입니다.")
## else - finally 중 하나만 써도 되지만, 둘다 쓴다면
## else 다음 finally가 나와야한다.

## 출력 : 
kiwi
인덱스 에러가 아닙니다.
2케2케해서 출력은 끝입니다.

 

from - import

다른 모듈을 import 한다. from과 같이 사용하여 대상을 지정할 수 있고, 이렇게 가져온 모듈은 앞의 클래스 명의 생략이 가능하다.

더보기
## 코드 :
import random
print(random.randrange(0,10))

from random import *
print(randrange(0,10))

from random import randrange
print(randrange(0,10))

## 출력 : 
3
8
8

 

for

반복을 지시하는 예약어, iteratable 한 객채를 대상으로 반복자를 지정하여 반복한다. 

더보기
## 코드 : 
fruits = ["apple","orange","strawberry","kiwi"]

print("일반적인 for문")
for i in range(len(fruits)) :
    print(fruits[i])

print("\nAdvanced for문")
for fruit in fruits :
    print(fruit)

## 출력 :
일반적인 for문
apple
orange
strawberry
kiwi

Advanced for문
apple
orange
strawberry
kiwi

 

global

변수를 전역 변수로 지정한다.

더보기
## 코드 :
fruits = ["apple","orange","strawberry","kiwi"]

def print_fruits() :
    global fruits
    print(fruits)

print_fruits()
   
## 실행 : 
['apple', 'orange', 'strawberry', 'kiwi']

 

if - elif - else

프로그램 제어문으로 프로그램의 분기를 생성한다.

더보기
## 코드 :
value = int(input("값을 입력하세요 : "))

if(value>50) :
    print("50이상의 값")
elif(value>30) :
    print("30이상의 값")
else :
    print("뭐죠")
   
   
## 실행 : 
값을 입력하세요 : 60
50이상의 값

## 재실행
값을 입력하세요 : 40
30이상의 값

## 재실행
값을 입력하세요 : 20
뭐죠

 

in 

포함을 검사하는 예약어. [원소] in [자료구조]로 사용하여 bool값(True, False)을 반환한다. not과 같이 사용하여 not in으로 사용할 수 있다.

더보기
## 코드 : 
fruits = ["apple","orange","strawberry","kiwi"]

if("apple" in fruits) :
    print("apple은 있다")
    if("app" in fruits[fruits.index("apple")]) :
        print("물론 app도 있다")

## 출력 : 
apple은 있다
물론 app도 있다

 

is

동등함을 검사하는 예약어 == 과 다른 점은 같은 값을 가리키느냐(==), 객채를 가리키느냐(is) 차이이다. not과 같이 사용하여 is not으로 쓸 수 있다.

더보기
## 코드 : 
fruits = ["apple","orange","strawberry","kiwi"]

if("apple" is fruits[0]) :
    print("그렇죠 같습니다")

## 출력 :
그렇죠 같습니다

 

lambda

Python에서 익명 함수를 생성하는 예약어이다. (자세한 사용법은 포스팅 예정)

더보기
my_foo = lambda x:x+3
print(my_foo(3))

 

None

값이 없다는 의미이다. NoneType이라는 자료형에 하위 객체인데, 다른 언어의 null처럼 사용된다

 

nonlocal

인라인 함수에서(Nested Function) 자기 위에 존재하는 변수를 지정할 때 사용된다.(추후 자세한 내용 포스팅)

더보기
## 코드 : 
def outer_fun(crit) :
    data = "outer_fun_data"
    def inter_fun() :
        nonlocal data
        data = "inter_fun_data"
        print(data)
    if(crit == 1) :
        inter_fun()
    print(data)

crit = int(input())
outer_fun(crit)

## 실행 :
기준을 입력하세요(1:nonlocal) : 0
outer_fun_data

## 재실행 :
기준을 입력하세요(1:nonlocal) : 1
inter_fun_data
inter_fun_data

 

not

논리 연산 not, 2개의 bool값을 받아 True - False를 반전 시킨다. 

더보기
>>> a=3
>>> a>3
False

>>> not a>3
True
## not의 결과는 반전이다.

 

or

논리연산 or, 2개의 bool값을 받아 둘 중 하나라도 True인 경우 True, 나머지는 False가 된다.

더보기
>> a = 3
>> b = 5
>> print(a>2 or b>4)
True
## b<4가 False이지만, a>2가 True 이기때문에 둘의 or는 True이다.

>> print(a<2 or b<4)
False 
## 둘다 False인 경우 or의 결과또한 False이다.

 

pass

실행할 코드가 없음을 명시한다. 이는 except같이 필수적으로 무언가 실행을 지시하여야 하나, 아무 처리를 하지 않을 때 사용된다.

더보기
## 코드 : 
fruits = ["apple","orange","strawberry","kiwi"]

loc = int(input("몇 번째 과일을 출력하시겠습니까? : "))
try :
    print(fruits[loc])
except IndexError :
    pass

## 실행 : 
몇 번째 과일을 출력하시겠습니까? : 1
orange

## 재실행 :
몇 번째 과일을 출력하시겠습니까? : 5

 

raise

강제 Exception을 발생시킨다.

더보기
## 코드 : 
fruits = ["apple","orange","strawberry","kiwi"]

loc = int(input("몇 번째 과일을 출력하시겠습니까? : "))
try :
    if(loc-1 > len(fruits)) :
        raise IndexError
    print(fruits[loc])
except IndexError :
    print("올바른 값을 입력")

## 실행 : 
몇 번째 과일을 출력하시겠습니까? : 1
orange

## 재실행 :
몇 번째 과일을 출력하시겠습니까? : 5
올바른 값을 입력

 

return 

함수 실행결과의 반환 값을 지정한다.

더보기
## 코드 : 
def add_five(value) :
    return value+5

print(add_five(3))

## 출력 :
8

 

try - except

try 코드 블록에서 에러가 발생할 블록을 검사하여 에러를 except 코드블록에서 처리한다.

더보기
## 코드 :
fruits = ["apple","orange","strawberry","kiwi"]

try :
    print(fruits[6])
except IndexError:
    print("인덱스 에러입니다.")

## 출력 :
인덱스 에러입니다.

 

with

객체에 대한 __enter__()와 __exit__()을 자동으로 실행시켜준다. 정말 훌륭한 기능은 file 객체의 f.open()과 f.close()를 자동 호출해준다는 점이다.

더보기
with open("filename.txt","r") as f :
    print(f.read())

 

while 

반복을 지시하는 예약어 조건이 참 일동 안 loop를 반복한다.

더보기
## 코드 : 
fruits = ["apple","orange","strawberry","kiwi"]

cnt = 0
while(cnt<len(fruits)) :
    print(fruits[cnt])
    cnt+=1

## 출력 : 
apple
orange
strawberry
kiwi

 

yield

generator 객체의 다음 반환될 값을 설정한다.(추후에 자세히 포스팅 예정) yield from으로 다른 iteratable 한 객체를 반환할 수 있다.

더보기
## 코드 :
def my_generator() :
    a=[1,2,3]
    for i in range(len(a)) :
        yield a[i]

my = my_generator()
print(next(my))
print(next(my))
print(next(my))

## 출력 :
1
2
3

파이선 3.8을 기준으로 만든 35개의 예약어 사전이다(맞겠지? 다 적긴 한 거 같은데...). 필요한 게 생길 때 와서 보면 좋을 것 같다. Python에는 내장 모듈로 keyword를 제공한다. keyword는 파이선에서 제공되는 여러 가지 예약어와 관련된 함수를 정의해두었으니, 사용해보는 것을 추천한다. 

이번 시간은 Python과 매우 친해지며 배웠던 객체지향과 에러 처리를 이용해서 학생관리 프로그램을 전면 개정해보도록 하자! 언제나 그랬든 요구사항부터 확인하자

 

* 요구사항 *

① 반별로 학생관리하는 프로그램을 만들어야 한다.

② 학생은 학생 번호, 학생 이름, 학생 성적을 가져야 한다.

③ 학생을 추가 / 제거 / 이름 변경하는 기능도 만들자

④ 학생 번호를 입력받아서 학생정보를 출력하는 기능을 만들자

⑤ 학생 추가 시 학생 번호가 중복되면 받지 번호만 다시 받는 기능을 만들자

⑥ 반을 추가하는 기능을 만들자

⑦ 현재 반의 개수를 출력하는 기능을 만들자

 

이 프로그램 또한 정답은 없으니 배운 내용을 마음껏 사용하여 자신만의 프로그램을 만들고 필자의 구현 방법이 궁금한 사람들은 아래를 확인하자.

 

* 학생관리 프로그램의 ver2의 함수를 옮겨올지는 여러분께 맡기겠다. 일반 함수와 클래스 함수가 크게 다르진 않지만, 작성하면 도움은 될 것이다. 필자는 생략하겠다.

 


일단 어떤 객체를 만들지 파악하기 위해서 우리가 해야 되는 일을 정의해보자

우리는 "학생"이 등록된 "반"정보를 "관리하는 프로그램"을 만들어야 한다.

오호 그러니까 우리가 만들어야 되는 객체는 학생, 반, 관리하는 프로그램이다. 각각의 멤버 변수와 기능은 조건에 요구조건에 따라 다음과 같이 생성해보자

지금 당장은 이렇게 만들고, 필요한 기능을 추가하면 될 거 같다. 다음의 변수와 생성자만 정의된 구조를 코딩하자

class student :
    num = -1
    name = ""

    def __init__(self,num,name) :
        self.num = num
        self.name = name

    def get_info(self) :
        ##

class room :
    num = -1

    def __init__(self,num) :
        self.stu_dict = dict()

    def is_in_stu_num(self,num) :
        ##

    def get_stu_info(self,num) :
        ##
    
    def add_stu(self, stu) :
        ##
   
    def mod_stu(self, stu_num, stu_name) :
        ## 

class manager :

    def __init__(self) :
        self.room_dict = dict()

    def add_room(self) :
        ##

    def add_stu(self, room_num, stu) :
        ##

    def del_stu(self, stu_num) :
        ##

    def mod_stu(self, stu_num, mod_name) :
        ##

    def print_stu(self, stu_num) :
        ##
    
    def show_room(self) :
    	##

파라미터는 필요하다고 생각되는 것을 넣으면 된다. 다음은 student의 구현 부이다

class student :
    num = -1
    name = ""

    def __init__(self,num,name) :
        self.num = num
        self.name = name

    def get_info(self) :
    	return [self.num, self.name]

student의 get_info를 사용하는 곳은 room 클래스에서 이다. 따라서 처리의 편의성을 위해서 get_info는 [학생의 번호, 학생의 이름]을 배열로 반환토록 하였다 다음은 room의 구현 부이다.

class room :
    num = -1

    def __init__(self,num) :
        self.num = num
        self.stu_dict = dict()

    def is_in_stu_num(self,num) :
        if(num in self.stu_dict) :
            return True
        else :
            return False

    def get_stu_info(self,num) :
        try :
            return self.stu_dict[num]
        except KeyError :
            return ""

    def add_stu(self, stu) :
        self.stu_dict[stu.get_info()[0]] = stu.get_info()[1]

    def del_stu(self, num) :
        try :
            del self.stu_dict[num]
        except KeyError :
            pass

    def mod_stu(self, stu_num, stu_name) :
        try :
            self.stu_dict[stu_num] = stu_name
        except KeyError :
            pass

 

학생 번호가 있는지 확인하는 기능이 있어야하니 is_in_stu_num은 학생번호가 있는지 학생 dict에서 찾아서 있다면 True 없으면 False를 반환토록 하였다. 또한 get_stu_info로 학생정보를 가져올 수 있게 하였고, add_stu로 매개변수로 넘어오는 stu의 정보를 등록할 수 있게 하였다. 

* 예외처리 구문의 pass는 코드를 무시한다는 예약어이다. 문법적으로 코드 블록 내부에 필수적으로 무언가를 해야 되는데, 하기 원하지 않는 경우 사용한다.

 

다음은 manager의 구현 부이다.

class manager :

    def __init__(self) :
        self.room_dict = dict()

    def add_room(self) :
    	self.room_dict[len(self.room_dict)+1] = room(len(self.room_dict)+1)

    def add_stu(self, room_num, stu) :
        try :
            self.room_dict[room_num].add_stu(stu)
        except KeyError :
            print(" 없는 교실입니다.")

    def del_stu(self, stu_num) :
        for i in range(len(self.room_dict)) :
            self.room_dict[i+1].del_stu(stu_num)

    def mod_stu(self, stu_num, mod_name) :
        for i in range(len(self.room_dict)) :
            self.room_dict[i+1].mod_stu(stu_name)

    def print_stu(self, stu_num) :
        for i in range(len(self.room_dict)) :
            value = self.room_dict[i+1].get_stu_info(stu_num)
            if(len(value) != 0 ):
                print(value)
                break

    def show_room(self) :
        print("현재 "+str(self.room_dict)+"의 교실이 있습니다.)

이렇게 매니저의 구현부까지 구현하였다. 여기까지 보고 이해가 되셨다면, 이것보다 훌륭한 코드를 만들 수 있을 것이다.

 

* 코드에 멤버 변수에 접근할 때(예를 들면 student) 직접 접근(student.name)을 하지 않는 것을 볼 수 있다. 코드의 보안성을 위해서라도 이렇게 코딩하는 것이 안전하다.(궁금한 사람들은 접근제어 지시자를 찾아보자 Python에 적용되는 항목은 아니지만, 실력 향상에 도움이 된다.)

* 위 예제 코드는 객체 지향을 억지로 맞춘다고 만든 상당히 비효율적인 코드이다. 사실 student는 클래스로 만들 것이 아니라, 자료구조로 room의 멤버변수처리하는 것이 공간적으로 이득이고, add, del, mod는 room별로 전부 에러처리를 하는것이 아닌 manager에서 room의 학생 번호를 가져와서 있으면 실행하는 것이 빠르다.


 

이렇게 객체지향과 에러 처리가 포함된 새로운 학생관리 프로그램을 만들어 보았다. 다음 시간에는 지금까지 배운 예약어를 포함해서 여러 가지 예약어를 살펴보도록 하자.

프로그램 포스팅을 따라오면서 수많은 ERROR를 확인했을 것이다. 프로그래밍은 에러와 프로그래머의 싸움이다. 물론 코딩을 할 때는 싸워도 된다. 그러나 프로그램을 배포를 할 때나 동작을 확인하고 싶은데 계속되는 에러로 프로그램이 계속 멈추는 것을 보고 싶은 사람은 없을 것이다.


try ~ except ~ finally

따라서 프로그램에 실행되는 도중 에러를 적절한 방법으로 처리해서 안정적인 프로그램을 만드는것은 매우 중요한 일이다. 뭐 간단하게 다음 에러로 에러에 대해서 일단 알아보도록 하자

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다. : "))

        print(100//user_input)
        print("다시 입력해주세요!")
        
출력 : 
100을 다음 수로 나눕니다. : 3
33
다시 입력해주세요!
100을 다음 수로 나눕니다. : 0
Traceback (most recent call last):
  File "C:/Users/82109/Desktop/student.py", line 8, in <module>
    print(100//user_input)
ZeroDivisionError: integer division or modulo by zero

위 코드에서 user_input이 0이되면 ZeroDivisionError로 print("다시 입력해주세요!")는 실행되지 않는다. 이러한 에러 또한 우리가 저번 시간에 배운 클래스이다. 또한 최상위 Exception부터 위에 있는 ZeroDivisionError 클래스까지 다음과 같은 계층구조를 가진다.

<출처 : https://w3.cs.jmu.edu/lam2mo/cs240_2014_08/lab05-exceptions.html>

이런 Exception클래스들은 try ~ except ~ finally 구문으로 적당하게 처리될 수 있다. 다음의 코드를 확인하자

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다."))

        try :
            print(100//user_input)
        except ZeroDivisionError :
            print("0으로는 나눌 수 없습니다.")
        finally :
            print("다시 입력해주세요!")

상기 코드는 보이는 것처럼 에러가 발생할 수 있는 print(100//user_input)을 try 코드블럭에 넣어두고, 이를 except으로 ZeroDivisionError가 발생하면 except 코드 블록이 실행되며, try가 실행되던 except이 실행되던 결국 finally가 실행되도록 동작한다.

 

 

에러의 처리

except 구문으로 핸들될 exception은 as 예약어로 excpetion의 별칭을 지정하고 이를 그대로 출력할 수 있다. 다음의 코드를 확인하자 

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다."))

        try :
            print(100//user_input)
        except ZeroDivisionError :
            print("0으로는 나눌 수 없습니다.")
        finally :
            print("다시 입력해주세요!")
            
출력 : 
100을 다음 수로 나눕니다.0
integer division or modulo by zero
다시 입력해주세요!
100을 다음 수로 나눕니다.

또한 에러처리에 관련해서는 다음과 같은 방법 또한 사용된다.

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다."))

        ## 에러처리1 ##
        try :
            print(100//user_input)
        except (ZeroDivisionError, ValueError) :
            
            print("이상한 값인듯 싶네요.")

        ## 에러처리2 ##
        try :
            print(100//user_input)
        except Exception as e:
            print(e)

        ## 에러처리3 ##
        try :
            print(100//user_input)
        except (ZeroDivisionError, ValueError) :
            print("이상한 값인듯 싶네요.")
        else :
            print("에러가 발생하지 않았습니다.")

1. 에러처리1 처럼 여러 가지 에러를 튜플로 묶어서 핸들 할 수 있다.
2. 최상의 에러인 Exception을 묶어서 에러처리할 수 있다.(사실 추천하지는 않는다. 자세하게 어떠한 에러가 발생했는지 출력하는 것이 좋다.)
3. 마지막 else문을 이용해서 에러가 발생하지 않았을때만 처리할 구문을 쓸 수 있다.(finally와 다르다)

 

raise

에러를 강제로 발생시킬 수도 있다. 이때 에러는 이미 생성된 error class로 생성할 수도 있고, 프로그래머가 적당한 에러를 만들어 그 에러를 사용할 수도 있다. 아래는 그 예시이다.

### except.py ###

class my_error(Exception) :

    def __init__(self,expression) :
        self.expression = expression

if __name__ == "__main__" :

    while(True) :
        try :
            user_input = int(input("100을 다음 수로 나눕니다."))

            if(user_input == 0) :
                raise my_error("0을 입력하셨습니다.","0의 입력")
        except my_error as e:
            print(e)

Exception을 상속받아서 my_error 클래스를 만들었다. my_error의 생성자에는 expression만을 넣었지만, Exception에서는 내용을 의미하는 message들도 있으니, 공식 제공 문서와는 항상 친하게 지내자 또한 중간에

(참고 :  docs.python.org/3/library/exceptions.html

 


이번시간에는 프로그램의 영원한 친구인 에러를 적당하게 처리하는 방법을 알아보았다. 이제 우리 프로그램은 좀 더 안정적인 방향으로 발전하게 되었다. 다음 시간에는 여태까지 배운 예외처리와 객체지향의 특성을 이용해서 학생 관리 프로그램을 발전시켜 보자

아 한 2억만 상속받고 싶다

- 리덕토 - 

 

Python과 매우 친해지기 그 3번째 시간은 클래스의 상속(Inherit)이다. 상속은 객체지향에서 빼놓을 수 없는 중요한 기능이고, 약간의 이해도가 필요하기에 별도의 포스팅을 작성하였다.


상속(Inherit)의 정의와 사용

우리의 친구 위키백과에서 상속이 무엇인지 정의를 가져와 보자

객체 지향 프로그래밍(OOP)에서, 상속(inheritance)은 객체들 간의 관계를 구축하는 방법이다

- 출처 : 위키백과(ko.wikipedia.org/wiki/%EC%83%81%EC%86%8D_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D))

으흠; 조금 모호한 부분이 있는 거 같다. 객체들 간의 관계를 상속만으로 구축되는 것은 아니지만; 위키가 필자보다 몇만 배는 똑똑하기 때문에 쟤가 저러면 그런 거다. 정의는 저렇고 상속은 "다른 객체로부터 새로운 객체를 재생성하는 것"으로 이해해주면 될 거 같다. 

 

이런 상황을 가정하자 우리는 객체 지향 프로그램의 방식으로 프로그램을 만들고 있는 도중인데, 요리사와 소방관의 클래스를 만들어야 된다. 둘은 사람이라는 기본 전제를 가지고 있다. 따라서 사람으로 가질 수 있는 모든 속성이나 공통된 행동이 있을 것이다. 상속은 이러한 아이디어를 정확하게 표현해준다.

여기서 상속을 하는 클래스를 부모(Parent) 클래스라고 하며, 상속을 받는 Fire Fighter이나 Cook을 자식(Sub) 클래스, 같은 레벨에서 상속을 받은 Fire Fighter와 Cook은 서로를 형제(Sibling) 클래스라고 지칭한다. 이렇게 실행한 상속은 다음과 같은 특징을 가진다.

 

1. 자식은 부모의 클래스 변수, 매서드에 접근할 수 있다.
2. 부모는 자식의 클래스변수, 메서드에 접근할 수 없다.
3. 여러 개의 부모를 상속받을 수 있다.(여기서 다이아몬드형 상속이라는 문제가 발생한다. 궁금한 인원은 더보기를 확인)
더보기

위와 같은 형태가 다이아몬드 상속이다. 물론 상속받은 객체를 상속받는 것은 문제가 되지 않으나, Fire Fighter가 human에게 상속받은 some_function()와 Cook이 human에게 상속받은 some_function() 중 GoodMan이 사용해야 하는 매서드가 무엇인지 정할 수 없는 문재가 발생한다.

 

상속은 오로지 조금 더 구체적인 자식으로 단방향으로만 진행된다. 아래의 코드를 확인하면서 Python에서 상속의 사용법을 익혀보자

class human :
    
    name = ""

    def __init__(self,name) :
        self.name = name

    def introduce(self) :
        print("hello im human "+self.name)

    def job(self) :
        print("I have no jobs")


class firefighter(human) :      ## firefighter은 human을 상속받음

    def __init__(self,name) :
        super().__init__(name)  ## 부모의 생성자를 호출

    def job(self) :             ## job은 firefighter가 오버라이딩
        print("my job is firefighter")

    def new_introduce(self) :
        super().introduce()     ## super를 이용, 부모의 매서드 호출
    
    def extinguish(self) :
        print("you off fire!")

        
if __name__ == "__main__" :

    cheolsu = human("cheolsu")
    younghee = firefighter("younghee")

    cheolsu.introduce()
    cheolsu.job()

    younghee.extinguish()
    younghee.introduce()        ## 부모의 매서드인 introduce사용가능
    younghee.job()
    
    

우선 human 클래스는 우리가 저번 시간에서 모두 배운 내용으로 이루어져 있다.(생성자, 매머드의 정의, 클래스 변수 name 등) 제일 먼저 눈에 띄는 것은 상속을 할 때 class [클래스명](상속받을 클래스명) :으로 정의한 점이다.(여러 개를 상속받으려 먼 상속받을 클래스명을 콤마로 나누어 적어주면 된다.)

 

프로그램의 main을 보면 human객체 cheosufirefighter객체 younghee가 있다. 주석에 적어두었듯 younghee 또한 부모의 매서드인 introduce를 사용하고 있다.

 

자식에서 부모를 호출할 때는 앞에 super()를 붙이는데 super() 매더스는 자신의 부모를 반환한다. 따라서 younghee의 super(). introduce()는 human.introduce()로 해석되며, firefighter 생성자의 super().__init__(name)은 부모 생성자를 호출하는 것과 같다.

 

오버 라이딩(Overriding)

부모에서 정의된 메서드를 자식에서 재정의하는 것을 오버 라이딩(Overriding)이라고 한다. 코드에서는 human에서 정의된 job 함수가 firefighter에서 재정의되며 오버 라이딩(Overriding)된 것을 확인할 수 있다.

* 혹시 다른 언어에서 넘어와서 오버 로딩(Overloading)의 개념을 찾으시는 분이 계시다면 아쉽게도 Python은 오버 로딩을 허용하지 않는다는 비보를 전한다. 단, Pythoin은 파라미터를 받을 수 있는 방법을 상당히 다양하게 제공한다.)


이번 시간에는 클래스와 상속에 대해서 알아보았다. 어떤가, 이제 객체 지향과 클래스에 대해서 조금은 익숙해 느낌이 드는가? 다음 시간에는 우리의 프로그램을 조금 더 안정적으로 만들 수 있는 예외처리에 대해서 알아보도록 하자

지난 시간 우리는 객체 지향 프로그램이 무엇이고, 클래스가 무엇인지, 그리고 간단하게 클래스 사용의 예시를 살펴보았다. 이번 시간에는 Python에서 클래스를 사용하는 방법을 자세하게 알아보도록 하자


클래스(Class)의 정의와 사용

우선 다음의 코드를 타이핑하고 이야기를 나누어보자. 주석은 각 코드에 대한 설명이니 한번씩 읽어보길 바란다.

### class_example.py ###

class dog :					## class의 이름을 dog로 지정하였다.
    dog_name = ""				## dog의 멤버변수 dog_name을 정의하고 ""로 초기화

    def __init__(self,name) :			## 생생자, name을 파라미터로 받아 dog_name대입
        self.dog_name = name

    def bark(self) :				## 생성된 객체에서 호출하는 bark
        print("WOW "+self.dog_name)

    def self_bark() :				## class에서 호출하는 bark
        print("BOW")

if __name__ == "__main__" :
    my_dog = dog("puppy")			## my_dog(객체)를 dog(클래스)로 생성
    my_dog.bark()				## 생성된 my_dog의 bark메서드 호출

    dog.self_bark()				## dog(클래스)에서 직접 self_bark메서드 호출
    
    
출력 :
WOW puppy
BOW

self

일단 이 self 예약어는 어떤 의미가 있는 것일까?

Python에서 객체를 생성을 하면 자기자신을 지칭할 때 self라고 한다. 재미있는 점은 class내부에서 메서드를 정의할 때  생성된 객체가 이 메서드를 사용하려면 암묵적으로 self를 첫 번째 파라미터로 넘겨야 한다는 점이다.(실제 사용 시에는 이 self는 없는 것으로 취급한다. 예를 들어 some_function(self)가 정의되어있으면 사용할 때는 some_funtion()으로 사용하면 된다는 것이다.)

 

비교를 위해서 코드에서 2가지 함수를 다음과 같이 정의해보았다.

1. bark(self) : self를 인자로 받는 메서드이다. self를 인자로 받기에, dog에서 객체를 직접 넘겨주거나 dog로 생성이된 객체에서만(위 예시에서는 my_dog)이 bark를 사용할 수 있다.

2. self_bark() : self를 인자로 받지 않는 메서드이다. 따라서 생성이 된 객체는 사용할 수 없으며(생성된 객체는 함수호출시 묵시적으로 첫번째파라미터로 자기 자신을 전달한다. self_bark는 self파라미터를 받지 않는다.) dog클래스에서 직접 호출하면 사용 가능하다.

 

생성자

처음 보는 메서드로 __init__(self, name)이라는 메서드가 눈에 뜨인다. 이 __init__메서드는 이미 정의된 것으로 객체가 생성되는 순간(위 코드에서는 my_dog = dog("puppy")가 처리되는 순간)에서 자동적으로 동작하는 함수를 의미한다. 객체가 생성되는 순간 동작하는 함수이기에 "생성자"라고 부른다. 이 생성자에도 파라미터를 넘길 수 있는데, 위 예제에서는 객체 생성에 사용될 메서드이니 self와 이름인 name을 파라미터로 전달하고 있다. 전달받은 name변수는 dog_name에 저장한다. Python에 생성자처럼 사전 정의된 다음과 같은 것들도 있다.

1. __del__(self) : 소멸자, 객체가 사라질 때 행동을 정의한다.

2. __repr__(self) : 프린팅 지시자, print로 객체 자신이 호출될 때 return 값으로 무엇을 출력할지 지시한다.

3. __add__(self,other) : 연산자 "+"가 객체에 취해질 때 행동을 정의한다.

4. __cmp__(self,other) : 비교 연산자를 객체에 사용할 수 있게 행동을 정의한다.

 

클래스 변수

아래 코드에서 dog클래스 바로 아래 dog_name, travel처럼 정의된 변수를 클래스 변수라고 한다.

객체는 자신의 변수를 접근할 때 self. [변수명]을 붙인다. 코드에서 new_puppy = dog("happy")를 했다면 my_dog.dog_name은 "puppy", new_puppy.dog_name은 "happy"가 된다.(dog.dog_name은 ""로 출력이 된다)

 

이 클래스 변수는 재미있는 특징이 있다. 다음의 코드 main을 보자

class dog :
    dog_name = ""
    travel = list()

    def __init__(self,name) :
        self.dog_name = name

    def bark(self) :
        print("WOW "+self.dog_name)

    def self_bark() :
        print("BOW")

    def add_travel(self, value) :
        self.travel.append(value)

if __name__ == "__main__" :
    my_dog = dog("puppy")
    new_dog = dog("happy")

    my_dog.add_travel("home")
    

가장 마지막의 my_dog.add_travel("home")을 통해서 my_dog의 travel list에 "home"을 추가해 주었다. 그런데 웬걸?

>> new_dog.travel
["home"]
>> dog.travel
["home"]

이상하게 다른 객체인 new_dog와 객체의 틀인 dog클래스의 travel list에도 "home"이 추가된 걸 확인할 수 있다.

 

이는 클래스가 언제 객체로 바뀌느냐(이를 인스턴스화라고 한다.)의 문제이다.

 

Python에서 리터럴을 제외한 자료구조와 변수들은 class 바로 아래 정의를 했다면 객체끼리 공유된다. 이는 생성자에 자료구조를 정의하는 것으로 해결된다. 위에 코드로 해결책을 예를 들면 생성자에 self.travel = list()로 정의한다.(이러면 객체가 생성(인스턴스화)이 되고 self.travel이라는 list가 생성이 된 것이니 객체끼리 공유되지 않고, 생성된 객체 고유의 자료구조로 본다.)


이번 시간에는 객체 지향 프로그래밍을 하기 위한 가장 기본적인 방법인 클래스 생성과 사용에 대해서 알아보았다. 다음 시간에는 객체 지향 프로그래밍에서 빼놓은 수 없는 개념인 상속과 python에서의 상속 사용을 알아보도록 하자

+ Recent posts