먼길 오느라 수고들 많으셨습니다. 이번 포스팅은 Python과 매우 친해지기 그 마지막 시간인 Decorator이다. 물론 여기까지로 Python의 100%를 안다고 생각하면 큰 오산이다. 다음 시간부터는 모듈 알아보기 포스팅을 올릴 예정이니, 계속해서 Python에 대한 애정을 멈추지 말아주었으면 한다.


Decorator

여러분이 만들고 있는 함수의 프로그램 동작시간을 측정한다고 가정을 하자. 간단하게 import datetime모듈의 현재시간을 출력하는 datetime.datetime.now() 함수를 이용해보자. 

## 코드 : 
import datetime

def add(val1, val2) :
    print(datetime.datetime.now())
    print(val1+val2)
    print(datetime.datetime.now())
    return val1+val2

def sub(val1, val2) :
    print(datetime.datetime.now())
	print(val1-val2)
    print(datetime.datetime.now())
    return val1-val2

def div(val1, val2) :
    print(datetime.datetime.now())
    print(val1/val2)
    print(datetime.datetime.now())
    return val1/val2

if __name__=="__main__" :
    print(add(3,5))
    print("\n")
    print(sub(3,5))
    print("\n")
    print(div(3,5))
    
## 출력 :

2021-04-10 21:40:10.219000
8
2021-04-10 21:40:10.271000
8


2021-04-10 21:40:10.309000
-2
2021-04-10 21:40:10.337000
-2


2021-04-10 21:40:10.373000
0
2021-04-10 21:40:10.400000
0

으흠; 모든 함수에 대해서 datetime.datetime.now()를 2번씩 처주는 동일한 일을 해줄 필요가 있을까? Decorator는 이러한 문제의 해결책이다. 다음의 예시를 보자

import datetime

def deco(func) :
    def decorated() :
        print(datetime.datetime.now())
        func()
        print(datetime.datetime.now())
    return decorated
    
@deco
def function() :
    print("Function start")

if __name__=="__main__" :
    function()

우선 함수 그 자체를 파라미터로 받아들이는 또다른 함수 deco를 정의하였다. deco의 내부에는 인라인 함수, decorated를 정의하여 func() 실행 앞뒤로 우리가 원하는 행위인 print(datetime.datetime.now())를 실행해주었고, deco함수는 decorated 함수를 리턴하는 것으로 정의부는 마무리된다. 

 

decorated 받을 함수인 functiond은 앞부분에 @deco를 기술하여 이 함수가 deco함수로 decorated 될 것을 명시해 주었다.

 

* Decorator에서 인자를 사용하려면, @deco("some")으로 사용하면된다. 


자 이렇게 Python과 매우 친해지기 포스팅이 마무리되었다. 필자도 수고했고, 여러분도 수고 많았다. 처음에 Python을 그림에 비유했는데, 이는 아직도 유효하다. 여러분들은 그림의 기초적인 테크닉을 배운 것이고, 이제 여러 가지 프로그램을 만들 때 이 정보들이 여러분에게 큰 도움이 될 것이라고 확신한다. 

lambda는 간단하게 함수를 생성할 수 있게 도와주는 예약어이다. 이렇게 생성된 함수를 람다 함수 또는 익명함수라고도 부른다. 람다로 생성을 한 함수들은 여러 가지 내장 함수들로 조금 더 정밀한 코드를 작성할 수 있는데, 자세한 것은 아래 사용 코드를 보고 이야기하자

 


Lambda의 정의와 사용

 

람다함수는 다음과 같이 생성, 사용한다. 

>>> my_foo = lambda x : x+3
>>> my_foo(3)
6
>>> my_foo(5)
8

일반 함수처럼 정의했다면 다음과 같은 코드였을 것이다.

def my_foo(value) :
    return value+3

물론 여러개의 인자를 받을 수도 있다.

>>> my_foo = lambda x,y,z : x*y*z
>>> my_foo(1,2,3)
6

사용법이 어렵게 느껴지지 않기를 바란다. (그래야 아래가 쉽다)

 

 

map()

map은 함수와, iterable 한 객체를 인자로 받아들인다. 아래의 코드를 예시로 보자

>>> my_list = list(map(lambda x : x+3, range(5))
>>> my_list
[3, 4, 5, 6, 7]

* range 객체는 iterable한 객체로, 낮은 용량으로 list를 구현할 수 있게 해 준다. 완전한 list는 아니고, range라는 객체로 생성된다.

 

 

filter()

filter또한 함수와, iterable 한 객체를 인자로 받아들이는데, 함수의 결과가 True인 것만을 원소로 추가한다. 아래의 코드를 예시로 보자

>>> my_list = list(filter(lambda x: x%2==0, range(10)))
>>> my_list
[0, 2, 4, 6, 8]

사실 람다와 친구인 reduce라는 함수도 있다. functools의 하위 함수인데, 모듈을 추가하여 설명하는 건 되도록 나중으로 미루고 싶어서 설명을 거두었다. 다음 시간은 Python과 친해지기 그 마지막 편인 Decorator를 만나보도록 하자

오랜만이다. 한 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
인덱스 에러가 아닙니다.
22케해서 출력은 끝입니다.

 

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의 학생 번호를 가져와서 있으면 실행하는 것이 빠르다.


 

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

아 한 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