TeamH4C_ProjectH4C/프로그래밍 언어

[TeamH4C_Python] 함수의 사용

P4P3R_H 2021. 1. 31. 13:57

안녕하세요! 이번에는 함수에 대해 정리하겠습니다.

어려운 내용이니 정리를 하고 다시 정리하는 형식의 공부를 하는 것이 좋다고 생각이 듭니다.

 

1. 함수의 정의

함수(function)이라는 기능을 파이썬에서 제공하는데 함수는 특정 용도의 코드를 한 곳에 모아놓은 것을 뜻합니다. 함수는 처음 한 번만 작성해 놓으면 나중에 필요할 때 계속 불러 사용할 수 있는 간편함이 있습니다.

print, input과 같은 함수도 파이썬에서 미리 만들어 둔 함수입니다.

함수의 사용의 이점으로 코드의 용도를 구분할 수 있으며, 코드를 재사용할 수 있습니다. 코드의 재사용으로 코드의 실수 또한 줄어들게 될 것입니다.

함수를 만들기 위해서는  def 함수이름()의 형식으로 만들게 됩니다. def 뒤에는 함수 이름을 지정하여 호출할 때 사용이 되게 합니다. def는 정의하다(define)에서 따온 키워드입니다. 함수의 예제를 보겠습니다.

# 함수를 만드는 코드
# def 함수이름():
#     코드

>>> def hello():
	print('hello')

hello함수를 정의함으로 hello를 출력할 수 있는 함수를 만들었습니다. 함수()와 같은 형식으로 호출을 하면 함수의 내용이 실행이 될 것입니다. 여기서 호출(call)은 함수를 사용한다는 뜻입니다.

함수의 실행 순서는 다음과 같습니다.

hello 함수 호출

hello 함수 실행

print 함수 실행 및 'hello' 출력

hello 함수 종료

함수를 만들고 호출할 때 주의점이 있습니다. 함수를 만들기 전에 함수를 먼저 호출할 수 없다는 점입니다. 파이썬은 위에서 밑으로 코드가 진행되는 언어이기 때문에 만들기 전에 호출을 한다면 정의(define)되지 않은 함수라는 에러가 나올 것입니다.

여기서 빈 함수를 만들 수도 있는데 빈 함수를 만들 때는 코드 부분에 pass를 넣어주면 됩니다. 함수의 틀을 유지할 필요가 있을 때 빈 함수를 만들게 될 것입니다.

값이 고정되어 있지 않은 함수도 만들 수 있는데 함수에서 값을 받으려면 괄호(())안에 변수 이름을 지정하면 됩니다. 이 변수를 매개변수(parameter)이라 하는데 함수를 호출하는 데 있어 넣을 값을 가져오게 됩니다. 이때, 함수를 호출할 때 넣는 값을 인수(argument)라 합니다.

함수 안에서 return을 사용하면 값을 함수 바깥으로 반환합니다. return에 값을 지정하지 않으면 None을 반환하게 됩니다.

return을 사용하면 값을 함수 바깥으로 반환할 수 있고, 함수에서 나온 값을 변수에 저장할 수 있습니다. return으로 반환하는 값을 반환 값이라 하고, 함수를 호출해준 바깥에 결과를 알려주기 위해 사용됩니다. 반환 값을 변수에 저장하지 않고 바로 다른 함수에 넣어 사용할 수 있습니다.

>>> def add(a, b):
	return a + b
    
>>> def add_sub(a, b):
	return a + b, a - b    
    
>>> x, y = add_sub(10, 20)
>>> x
30
>>> y
-10

# 계산 결과를 바로 return받을 수 있습니다.
# 여러개의 결과를 return받을 수 있습니다.

함수에서 여러 개의 값을 반환할 때는 값이나 변수를 콤마(,)로 구분해서 지정해주면 됩니다.

또한, 함수의 설명을 작성할 수 있는데 함수 콜론(:) 바로 다음 줄에 큰 따옴표 세 개(""")로 문자열을 입력하면 함수에 대한 설명을 작성할 수 있습니다. 이것을 함수의 독스트링이라 합니다. __doc__를 사용하며 독스트링을 적절하게 사용하면 코드를 쉽게 볼 수 있을 것입니다.

 

함수를 만드는 과정을 봤는데, 이제는 함수 여러 개를 만든 뒤 각 함수의 호출 과정을 스택 다이어그램(stack diagram)을 알아보겠습니다.

스택은 입/출력이 한 곳에서 이루어지며 위에서부터 꺼내는 방식입니다. 파이썬에서의 함수는 밑에서부터 입/출력이 이루어지는 형식입니다. 예로 다음과 같습니다.

>>> def mul(a, b):
	c = a * b
	return c

>>> def add(a, b):
	c = a + b
	print(c)
	d = mul(a, b)
	print(d)

>>> x = 10
>>> y = 20
>>> add(x, y)
30
200

위 코드에서 y = 20까지의 코드를 1번 코드부터 실행하게 되면 전역 프레임(global frame)에는 함수 mul, add, 변수 x, y가 들어갈 것입니다.

여기서 프레임은 메모리에서 함수와 함수에 속하 변수가 저장되는 독립적인 공간입니다. 전역 프레임은 파이썬 전체에서 접근할 수 있어 전역 프레임이라 부릅니다.

y =20 코드를 지나 add(x, y)의 코드를 만나 add함수 스택 프레임이 쌓이고 add함수 안에 mul함수가 실행되며, mul함수 스택 프레임이 그 밑에 쌓이는 형식일 것입니다. 그 후, 다시 add함수로 돌아가 print(d)를 수행하기 위해 mul함수의 스택 프레임은 반환해주며 add함수의 스택 프레임으로 돌아갑니다. 이런 순서로 파이썬 코드는 나중에 입력이 된 프레임이 제일 먼저 빠져나오는 형태의 스택 구조로 호출됩니다.

 

함수는 고정 인수와 가변 인수를 사용하는데 함수에 인수를 순서대로 넣는 방식을 위치 인수(positional argument)라고 하고, 인수의 개수가 정해지지 않는 것을 가변 인수(variable argument)라 합니다.

즉, 위치 인수는 인수의 위치가 정해져 있고, 가변 인수는 함수에 인수가 정해져 있지 않고 몇 개를 넣어도 됩니다.

인수를 순서대로 넣을 때는 리스트나 튜플을 사용하기도 하는데 리스트와 튜플 앞에 애스터리스크(*)를 붙여 함수에 넣어줍니다.

애스터리스크(*)를 붙이면 언패킹(unpacking)이 되어 리스트나 튜플을 하나하나의 요소로 분리합니다.

>>> def print_num(a, b, c):
	print(a)
	print(b)
	print(c)
	
>>> x = [10, 20, 30]
>>> print_num(*x)
10
20
30
>>> print_num(*[10, 20, 30])
10
20
30

>>> def print_num(*args):
	for arg in args:
		print(arg)
		
>>> print_num(10)
10
>>> print_num(10, 20, 30)
10
20
30
>>> print_num(*[10, 20, 30])
10
20
30

# 함수(*리스트)
# 함수(*튜플)
# def 함수이름(*매개변수):
#     코드
# 매개변수의 이름은 argments를 줄여 args로 사용하는 것이 일반적입니다.

지금까지 함수에 인수를 넣을 대 값이나 변수를 그대로 넣어 각각의 인수가 무슨 용도인지 정확하게 파악하기 어려웠습니다. 보통 함수는 인수의 순서와 용도를 함께 외워야 하는데 개인정보를 출력하는 함수의 예를 보겠습니다.

>>> def personal_info(name, age, address):
	print('이름 : ', name)
	print('나이 : ', age)
	print('주소 : ', address)

	
>>> personal_info('홍길동', 30, '서울시')
이름 :  홍길동
나이 :  30
주소 :  서울시

다음과 같이 순서를 지키며 하는데 순서가 달라지면 결괏값도 달라질 것입니다. 그렇기에 순서와 용도를 매번 기억하지 않도록 키워드 인수(keyword argument)라는 기능을 제공합니다. 인수에 이름을 붙여 키워드 = 값의 형식으로 순서가 달라도 키워드만 잘 입력하면 그대로 출력할 수 있습니다. 위에서 함수를 호출할 때 함수의 매개변수 이름으로 name = '홍길동'과 같이 지정해주면 됩니다.

이러한 키워드 인수를 이용하여 딕셔너리 언패킹에 사용할 수 있습니다. 딕셔너리 앞에 애스터리스크 두 개(**)를 붙여 함수에 넣어주면 됩니다.

딕셔너리에 '키워드': 값의 형식으로 인수를 저장하고 앞에 애스터리스크 두 개(**)를 붙여 함수에 넣어줍니다. 이때 딕셔너리의 키워드(키)는 반드시 문자열 형태여야 합니다. 애스터리스크를 두 개 사용하는 이유는 하나를 사용하였을 때는 키만 언패킹 되기에 두 개를 사용합니다.

키워드 인수를 사용하는 가변 인수 함수를 만들기 위해서는 함수의 매개변수 앞에 애스터리스크 두 개(**)를 붙여 만듭니다. 매개변수의 이름은 관례적으로 keyword arguments를 줄여 kwargs로 사용합니다. kwargs는 딕셔너리라 for로 반복할 수 있습니다.

>>> def personal_info(name, age, address):
	print('이름 : ', name)
	print('나이 : ', age)
	print('주소 : ', address)

>>> x = {'name': '홍길동', 'age': 30, 'address': '서울시'}
>>> personal_info(**x)
이름 :  홍길동
나이 :  30
주소 :  서울시
>>> personal_info(**{'name': '홍길동', 'age': 30, 'address': '서울시'})
이름 :  홍길동
나이 :  30
주소 :  서울시

>>> def personal_info(**kwargs):
	for kw, arg in kwargs.items():
		print(kw, ': ', arg, sep='')
		
>>> personal_info(name='홍길동')
name: 홍길동
>>> personal_info(name='홍길동', age=30, address='서울시')
name: 홍길동
age: 30
address: 서울시
>>> x = {'name': '홍길동'}
>>> personal_info(**x)
name: 홍길동

# 매개변수의 이름과 딕셔너리의 키 이름이 같아야 합니다.
# 인수를 직접 넣어도 되고, 딕셔너리 언패킹을 사용해도 됩니다.
# 매개변수에 **를 붙여 키워드 인수를 사용하는 가변 인수 함수를 만들 수 있습니다.

보통 **kwargs를 사용한 가변 인수 함수는 함수 안에서 특정 키가 있는지 if조건을 넣어 확인해줍니다.

매개변수에 초기값을 주어 인수를 생략할 수 있습니다. 매개변수=값의 형태로 선언되며 초기값이 지정된 매개변수는 초기값이 없는 매개변수 앞에 올 수 없어 뒤에 배치되어 있습니다.

초기값 지정은 주로 사용하는 값이 있으면서 가끔 다른 값을 사용해야 할 때 활용이 주로 됩니다.

 

함수의 활용으로 함수 안에서 자기 자신을 호출하는 방법이 있습니다. 재귀 호출(recursive call)이라 하며 재귀 호출은 알고리즘을 구현할 때 매우 유용합니다. 보통 알고리즘을 만들 때 반복문보다 재귀 호출로 구현된 코드를 사용하는데 좀 더 직관적이고 이해하기 쉽기에 유용하기 때문입니다.

재귀 호출의 기본 코드입니다. 재귀 호출은 파이썬에서 최대 재귀 깊이가 1000으로 정해져 있어 1000번 이상 호출하면 에러가 발생됩니다. 그렇기에 언제까지 반복할 것인지 정해줘 언제 종료할 것인지 종료 조건을 맞춰줘야 합니다.

>>> def hello():
	print('hello')
	hello()
    
>>> def hello(count):
	if count == 0:
		return
	print('hello',count)
	count -= 1
	hello(count)

# hello()함수를 호출하면 1000번 정도 반복되다가  에러가 출력될 것입니다.
# 조건을 추가하여 조건만큼 반복하여 출력하도록 조건을 걸어야합니다.

조건문을 사용한 재귀 함수로 팩토리얼 출력도 활용할 수 있는데 조건의 숫자를 1이 될 때, 1을 반환하고 재귀 호출을 끝내고 1이 아니면 조건으로 받은 수를 받은 수에서 1을 뺀 수를 재귀 함수 한 것과 곱하면 팩토리얼 코드가 완성될 것입니다.

 

2. 익명의 함수와 범위

함수를 def형식으로 선언을 하지 않고 식 형태로 되어있는 함수가 있습니다.

식 형태로 되어 있다 하여 람다 표현식(lambda expression)이라 하며 람다 표현식은 함수를 간편하게 작성할 수 있어 다른 함수의 인수로 넣을 때 주로 사용됩니다.

람다 표현식은 변수로 저장되어 값을 반환받거나 표현식 자체를 호출하기도 합니다. 람다 표현식은 lambda에 매개변수를 지정하고 콜론(:) 뒤에 반환 값으로 사용할 식을 지정하여 만듭니다. 또한, 이름이 없는 함수를 만들기 때문에 익명 함수(anonymous function)로 부르기도 합니다.

>>> plus_ten = lambda x: x + 10
>>> plus_ten(1)
11
>>> (lambda x: x + 10)(1)
11

# (lambda 매개변수들: 식)
# (lambda 매개변수들: 식)(인수들)

람다 표현식 안에는 변수를 만들 수 없습니다. 반환 값 부분은 변수 없이 식 한 줄로 표현할 수 있어야 하며, 변수가 필요한 코드일 경우 def로 함수를 정의해야 합니다. 대신, 바깥의 변수는 사용할 수 있습니다.

람다 표현식의 장점인 람다 표현식을 인수로 사용하겠습니다. 대표적인 예로 map의 사용이 있습니다. map의 결과는 map객체이므로 list를 사용하여 리스트로 변환하여 확인해보겠습니다.

>>> list(map(lambda x: x + 10, [1, 2, 3]))
[11, 12, 13]

# 간단하게 다른 함수의 인수로 넣어 표현식으로 표현이 가능합니다.

람다 표현식의 인수 사용은 map 이외에도 filter, reduce함수와 함께 사용하기도 합니다.

람다 표현식에서 조건부 표현식을 사용하는 방법은 lambda 매개변수들: 식1 if 조건식 else 식2로 사용이 됩니다. 요소를 각각 처리하는 함수를 사용할 때는 lambda의 반환 값도 요소여야 하며 조건부 표현식 if, else만 사용이 가능하고 사용할 때는 콜론(:)을 붙이지 않습니다. 특히, if를 사용하였다면 else 또한 사용해야만 합니다.

>>> a = [1, 2, 3, 4, 5]
>>> list(map(lambda x: str(x) if x % 3 == 0 else x, a))
[1, 2, '3', 4, 5]
>>> list(map(lambda x: str(x) if x % 3 == 0 else str(x) if x % 5 == 0 else x, a))
[1, 2, '3', 4, '5']

>>> a = [1, 2, 3, 4, 5]
>>> b = [2, 4, 6, 8, 10]
>>> list(map(lambda x, y: x * y, a, b))
[2, 8, 18, 32, 50]

>>> list(filter(lambda x: x > 5 and x <10, b))
[6, 8]

>>> from functools import reduce
>>> reduce(lambda x, y: x + y, a)
15

# lambda 매개변수들: 식1 if 조건식1 else 식2 if 조건식2 else 식3
# map에 객체를 콤마(,)로 구분하여 여러 개 넣을 수 있습니다.
# filter(함수, 반복가능한객체)
# filter에 지정한 함수의 반환값이 True일 때만 해당 요소를 가져옵니다.
# from functools import reduce
# reduce(함수, 반복가능한객체)
# reduce는 이전 결과와 현재의 값을 누적해서 반환하는 함수입니다.

변수의 사용 범위와 함수를 클로저 형태로 만드는 법을 알아보겠는데 변수의 사용범위에는 전역 변수와 지역 변수가 있다는 것을 알고 있으면 됩니다.

함수를 포함해서 스크립트 전체에서 접근할 수 있는 변수를 전역 변수(global variable)라 하고, 전역 변수에 접근할 수 있는 범위를 전역 범위(global scope)라 합니다.

일반적으로 함수 바깥에서 만들어진 변수이며 어디에든 접근이 가능한 변수입니다. 모든 곳에서 접근할 수 있기에 전역 범위는 크다고 볼 수 있습니다.

함수 안에서 실행되는 변수를 지역 변수(local variable)이라 하며 지역 변수는 변수를 만든 함수 안에서만 접근이 가능하고 함수의 바깥에서는 접근할 수 없는 범위를 가진 범위를 지역 범위(local scope)라 합니다.

함수 안에서 전역 변수의 값을 변경할 수 있는데 함수 안에서 global 전역 변수를 선언하여 값을 바꿀 수 있습니다. 전역 변수가 없는 상태의 변수를 global로 사용하면 해당 변수는 전역 변수가 됩니다.

함수 안에 함수를 만들 수 있는데 예제를 보겠습니다.

>>> def print_hello():
	hello = 'hello'
	def print_message():
		print(hello)
	print_message()

# print_hello > print_message 순으로 실행되어 두 함수가 동작하려면 print_hello를 호출해주어야 합니다.

함수 안에 함수를 만들고 변수를 만들면 현재 함수의 지역 변수가 되기 때문에 바깥의 함수의 변수 이름과 동일해도 다른 변수가 되는 것입니다.

현재 함수의 바깥쪽에 있는 지역 변수의 값을 변경하고 싶다면 nonlocal 키워드를 사용하여 함수 안에서 지역 변수를 변경할 수 있습니다. nonlocal은 현재 함수의 지역변수가 아니라는 뜻이며 현재 함수의 바깥쪽에 있는 지역 변수를 찾을 때 가장 가까운 함수부터 찾아 값을 변경합니다.

여기서 global 키워드는 함수가 몇 단계든 상관없이 무조건 전역 변수를 사용하게 됩니다.

함수를 클로저 형태로 만드는 방법은 다음과 같습니다.

클로저를 사용하면 프로그램의 흐름을 변수에 저장할 수 있고, 지역변수와 코드를 묶어 사용하고 싶을 때 활용합니다. 클로저에 속한 지역변수는 바깥에서 직접 접근할 수 없으므로 데이터를 숨기고 싶을 때 활용합니다.

함수를 반환할 때는 함수 이름만 반환해야 하며 괄호(())를 붙이면 안 됩니다. 함수를 둘러싼 환경(지역변수, 코드 등)을 유지하다가, 함수를 호출할 때 다시 꺼내어 사용하는 함수를 클로저(closure)라고 합니다. 다음은 예제입니다.

>>> def calc():
	a = 3
	b = 5
	def mul_add(x):
		return a * x + b
	return mul_add

>>> c = calc()
>>> print(c(1), c(2), c(3))
8 11 14

# c에 저장된 함수가 클로저입니다.

lambda로 클로저를 만들 수 있는데 함수 안의 함수 부분을 return lambda x: a * x + b처럼 표현식을 만들면 람다 표현식을 반환할 수 있습니다.

클로저에서의 지역변수의 변경도 nonlocal로 가능합니다. 바깥 함수에 값을 지정하여 안쪽 함수에서 값을 변경하여 저장하는 형식으로 진행됩니다.

 

3. 클래스

클래스는 개체를 표현하기 위한 문법입니다. 게임의 예로 직업별로 클래스를 만들어 표현이 가능한 것을 말합니다.

프로그래밍에서는 현실의 개념뿐 아니라 컴퓨터 안에 쓰이는 개념들도 클래스로 만들어 표현됩니다.(스크롤 바, 버튼, 체크 박스 등) 이러한 것처럼 특정 개념이나 모양으로 존재하는 것을 객체(object)라 하며 프로그래밍으로 객체를 만들 때 사용하는 것을 클래스(class)라고 합니다.

클래스로 표현하기 위해 데이터가 필요한데 데이터를 클래스의 속성(attribute)이라 하고, 기능들이 있는데 기능은 메서드(method)라고 합니다. 이런 클래스를 사용하는 프로그래밍 방법을 객체지향(object oriented) 프로그래밍이라 합니다.

객체지향 프로그램은 복잡한 문제를 잘게 나눠 객체로 만들고, 객체를 조합하여 문제를 해결합니다. 기능을 개선하고 발전시킬 때도 해당 클래스만 수정하면 되므로 효율적입니다.

클래스를 만들기 위해서는 class에 클래스 이름을 지정하고 콜론(:)을 붙여 다음 줄부터 def로 메서드를 정의하면 됩니다.

메서드는 클래스 안에 들어 있는 함수를 뜻합니다. 파이썬에서는 클래스 이름은 대문자로 시작하고 메서드 작성방법은 함수와 같으며 메서드의 첫 번째 매개변수는 반드시 self를 지정해야 합니다.

>>> class Person:
	def greeting(self):
		print('hello')
        
# class 클래스이름:
#    def 메서드(self):
#        코드

클래스를 사용하기 위해서는 클래스에 괄호(())를 붙인 뒤 변수에 할당하면 됩니다. 이 변수는 클래스의 인스턴스(instance)가 됩니다. 클래스는 특정 개념을 표현만 할 뿐 사용을 하려면 인스턴스를 생성해야 합니다.

메서드는 클래스가 아닌 인스턴스를 통해 호출하는데 인스턴스 뒤에 점(.)을 붙여 메서드를 호출하면 됩니다. 호출하면 메서드에 있던 내용들이 출력되게 될 것입니다. 이렇게 인스턴스를 통해 호출하는 메서드를 인스턴스 메서드라고 합니다.

빈 클래스를 만들기 위해서는 함수와 같이 코드 부분에 pass를 넣어주면 됩니다.

지금까지 메서드를 만들었는데 이제 속성을 만들어보겠습니다. 속성을 만들 때는 __init__메서드 안에 self.속성에 값을 할당합니다.

__init__메서드는 클래스에 괄호를 붙여 인스턴스를 만들 때 호출되는 특별한 메서드입니다. __init__(initialize)라는 이름 그대로 인스턴스(객체)를 초기화합니다. 앞 뒤로 밑줄 두 개(__)가 붙는 메서드는 파이썬이 자동으로 호출해주는 메서드인데 스페셜 메서드(special method) 또는 매직 메서드(magic method)라고 불립니다.

여러 가지의 기능을 사용할 때 이 스페셜 메서드를 채우는 식으로 사용하게 될 것입니다.

>>> class Person:
	def __init__(self):
		self.hello = 'hello'
	def greeting(self):
		print(self.hello)
		
>>> h = Person()
>>> h.greeting()
hello

# class 클래스이름:
#    def __init__(self):
#        self.속성 = 값

self의 의미는 인스턴스 자기 자신을 의미합니다. 인스턴스가 생성될 때 self.hello = 'hello'처럼 자기 자신에 속성을 추가했습니다. self가 완성된 뒤 h에 할당하여 이후에 메서드를 호출하면 현재 인스턴스가 자동으로 매개변수 self에 들어오게 됩니다.

인스턴스를 만들 때 값을 방법은 __init__메서드에서 self 다음에 값을 받을 매개변수를 지정하면 됩니다. 그리고 매개변수에 self.속성에 넣어줍니다.

>>> class Person:
	def __init__(self, name, age, address):
		self.hello = 'hello'
		self.name = name
		self.age = age
	def greeting(self):
		print('{0} 저는 {1}입니다.'.format(self.hello, self.name))
		
>>> larry = Person('래리', 20, '서울시')
>>> larry.greeting()
hello 저는 래리입니다.
>>> print('이름 : ', larry.name)
이름 :  래리

# class 클래스이름:
#     def __init__(self, 매개변수1, 매개변수2):
#         self.속성1 = 매개변수1
#         self.속성2 = 매개변수2

이렇게 인스턴스를 통해 접근하는 속성을 인스턴스 속성이라 합니다.

앞에서 만든 속성들 이외에도 클래스 바깥에서는 접근할 수 없고 클래스 안에서만 사용할 수 있는 비공개 속성(private attribute)을 사용해 보겠습니다.

비공개 속성은 __속성과 같이 밑줄 두 개로 시작해야 합니다. 양옆에 밑줄 두 개는 비공개 속성이 아닙니다.

>>> class Person:
	def __init__(self, name, age, address, wallet):
		self.name = name
		self.age = age
		self.address = address
		self.__wallet = wallet
	def pay(self, amount):
    	self.wallet -= amount
		print('{0}원'.format(self.__wallet))
		
>>> larry = Person('larry', 20, '서울시', 10000)
>>> larry.pay(3000)
7000원

# class 클래스이름:
# def __init__(self, 매개변수)
#     self.__속성 = 값

# 클래스 바깥으로 드러내고 싶지 않은 값에 비공개 속성을 사용합니다.

클래스에 속해있는 클래스 속성을 사용해보고 인스턴스를 만들지 않고 클래스로 호출하는 정적 메서드와 클래스 메서드를 보겠습니다.

인스턴스 속성은 인스턴스 별로 독립되어 있어 클래스 안에서 값을 주는 것이 아닌 인스턴스 속성으로 만들어 값을 넣어야 값이 서로의 영향을 받지 않게 됩니다.

클래스 속성은 모든 인스턴스가 공유되고 인스턴스 전체가 사용해야 하는 값을 저장할 때 사용합니다.

인스턴스 속성은 인스턴스 별로 독립되어 있어 각 인스턴스가 값을 따로 저장해야 할 때 사용합니다.

비공개 클래스의 속성은 __속성 = 값으로 정의할 수 있는데 클래스 안에서만 사용이 가능합니다.

클래스의 메서드를 사용할 때 인스턴스를 통해 호출하였지만, 인스턴스를 통하지 않고 클래스에서 바로 호출할 수 있는 정적 메서드와 클래스 메서드에 대해 보겠습니다.

정적 메서드는 메서드 위에 @staticmethod를 붙여 정적 메서드를 표시합니다. 정적 메서드는 매개변수에 self를 지정하지 않아 인스턴스 속성에는 접근할 수 없습니다. 그렇기에 정적 메서드는 인스턴스 속성, 인스턴스 메서드가 필요 없을 때 사용됩니다.

@이 붙은 것을 데코레이터라고 하며 메서드에 추가 기능을 구현할 때 사용합니다. 정적 메서드는 메서드의 실행이 외부 상태에 영향을 끼치지 않는 순수 함수를 만들 때 사용이 됩니다. 인스턴스의 상태를 변화시키지 않는 메서드를 만들 때 사용됩니다.

클래스 메서드는 메서드 위에 @classmethod를 붙입니다. 메서드는 첫 번째 매개변수에 cls를 지정해야 하며 class에서 따왔다 합니다. 클래스 메서드는 예제를 보겠습니다.

>>> class Person:
	count = 0
	def __init__(slef):
		Person.count += 1
	@classmethod
	def print_count(cls):
		print('{0}명'.format(cls.count))

>>> a = Person()
>>> b = Person()
>>> Person().print_count()
2명

# class 클래스이름:
# @classmethod
# def 메서드(cls, 매개변수1, 매개변수2):
#     코드

사람 클래스로 학생 클래스를 만들면 클래스 상속(inheritance)으로 만들어집니다. 사람 클래스는 기반 클래스(base class)이고, 학생 클래스는 파생 클래스(derived class)가 됩니다.

기반 클래스는 부모 클래스(parent class), 슈퍼 클래스(super class)라고 부르고, 파생 클래스는 자식 클래스(child class), 서브 클래스(sub class)라고도 합니다.

클래스의 상속에서는 기반 클래스의 능력을 그대로 활용하면서 새로운 클래스를 만들 때 사용합니다. 클래스의 상속은 클래스를 만들 때 괄호(())를 붙이고 안에 기반 클래스 이름을 넣으면 됩니다. 상속을 받으면 기반 클래스의 기능을 물려받아 기반 클래스 메서드의 사용이 가능하니다.

클래스 상속은 연관되면서 동등한 기능일 때 사용합니다. 학생과 사람은 연관된 개념이고, 학생은 사람에서 역할만 확장되었을 뿐 동등한 개념이기에 상속을 해도 됩니다. 이런 관계를 is - a관계라고 합니다.

만약 학생 클래스가 아닌 사람 목록을 관리하는 클래스를 만든다면 리스트 속성에 Person인스턴스를 넣어 관리하면 됩니다. 상속을 사용하지 않고 속성에 인스턴스를 넣어 관리하므로 PersonList가 Person을 포함하고 있습니다. 이럴 때는 동등한 관계가 아닌 포함관계로 has - a관계라고 합니다.

파생 클래스에서 기반 클래스의 __init__메서드를 호출하려 하면 에러가 발생되는데 super()로 기반 클래스 초기화를 하여 메서드를 호출할 수 있습니다.

super() 뒤에 점(.)을 붙여 메서드를 호출하는 방식입니다.

>>> class Person:
	def __init__(self):
		print('Person __init__')
		self.hello = 'hello'

>>> class Student(Person):
	def __init__(self):
		print('Student __init__')
		super().__init__()
		self.school = 'school'

		
>>> a = Student()
Student __init__
Person __init__
>>> print(a.school)
school
>>> print(a.hello)
hello

# super().메서드()

기반 클래스를 초기화하지 않아도 되는 경우가 있는데 파생 클래스에서 __init__메서드를 생략하게 된다면 기반 클래스의 __init__가 자동으로 호출되어 super()을 사용하지 않아도 됩니다.

파생 클래스에서 기반 클래스의 메서드를 새로 정의하는 메서드 오버라이딩(overriding)은 무시하다, 우선하다는 뜻으로 두 클래스에 같은 이름의 메서드를 가지고 있을 때 기반 클래스의 메서드를 무시하고 파생 클래스에 새로운 메서드를 만들어 냅니다.

보통 프로그램에서 어떤 기능이 같은 메서드 이름으로 계속 사용되어야 할 때 메서드 오버라이딩을 활용합니다. 이럴 때 기반 클래스의 메서드를 재활용하면 중복을 줄일 수 있는데 오버라이딩된 메서드에서 super()로 기반 클래스의 메서드를 호출하면 중복되는 기능은 파생 클래스에서 다시 만들지 않고, 기반 클래스의 기능을 사용하면 됩니다.

메서드 오버라이딩은 원래 기능을 유지하면서 새로운 기능을 덧붙일 때 사용합니다.

다중 상속은 여러 기반 클래스로부터 상속을 받아서 파생 클래스를 만드는 방법입니다. 클래스를 만들 때 괄호(())안에 클래스 이름을 콤마(,)로 구분하여 넣으면 됩니다.

# class 기반클래스이름1:
#     코드
# class 기반클래스이름2:
#     코드
# class 파생클래스이름(기반클래스이름1, 기반클래스이름2):
#     코드

조금 복잡한 다중 상속으로 다이아몬드 상속의 형태가 있습니다. 하나의 파생 클래스가 두 개를 상속받고 각각 하나의 클래스는 기반 클래스를 상속받는 형태로 명확하지 않고 애매한 상속입니다. 그렇기에 이러한 상속은 잘 사용하지 않습니다.

다이아몬드 상속에 대한 해결책으로 메서드 탐색 순서(Method Resolution Order, MRO)입니다.

클래스 D에 메서드 mro를 사용하면 탐색 순서를 알 수 있어 상속이 어떻게 이루어졌나 확인이 가능합니다. 다중 상속일 경우 왼쪽에서 오른쪽 순서로 메서드를 찾습니다.

추상 클래스는 메서드의 목록만 가진 클래스이며 상속받는 클래스에서 메서드 구현을 강제하기 위해 사용됩니다.

추상 클래스는 import로 abc모듈을 가져와야 하며 abc는 abstract base class의 약자입니다. 그리고 클래스의 괄호 안에 metaclass=ABCMeta를 지정하고, 메서드를 만들 때 위에 @abstractmethod를 붙여 추상 메서드로 지정해야 합니다.

# from abc import *
# class 추상클래스이름(metaclass=ABCMeta):
#    @abstractmethod
#    def 메서드이름(self):
#        코드

추상 클래스를 상속받을 때는 @abstractmethod가 붙은 추상 메서드를 모두 구현해야 되며 추상 클래스는 인스턴스로 만들 수가 없습니다.

추상 클래스는 인스턴스로 만들 때는 사용하지 않으며 오로지 상속에만 사용되며 파생 클래스에서 반드시 구현해야 할 메서드를 정해 줄 때 사용합니다.

추상 클래스를 빈 메서드로 만들 때가 있는데 추상 메서드는 인스턴스를 만들 수 없어 추상 메서드의 호출도 안 하기에 빈 메서드를 만들기도 합니다.

 

4. 두 점 사이의 거리

클래스를 활용한 2차원 평면에서의 위치를 표현하여 두 점 사이의 거리를 구해보겠습니다.

>>> class Point2D:
	def __init__(self, x, y):
		self.x = x
		self.y = y

		
>>> p1 = Point2D(x = 30, y = 20)
>>> p2 = Point2D(x = 60, y = 50)
>>> print('p1: {} {}'.format(p2.x - p1.x,p2.y - p1.y))
p1: 30 30
>>> import math
>>> print(math.sqrt((30 * 30) + (30 * 30)))
42.42640687119285

# 가로와 세로 각각 (30,20)(60,50)을 이용하여 사이의 거리를 구하여 피타고라스 정리를 이용

math.pow(값, 지수)로 제곱근을 구할 수 있고 math.sqrt(값)으로 루트의 값을 구할 수 있습니다.

math함수를 쓰기 위해서는 math모듈이 필요합니다. 거리를 구하는데 음수가 나왔다면 abs(값)을 통해 절댓값을 정수로 반환받아 계산하면 됩니다. 실수로 계산되면 절댓값을 실수로 반환합니다.

절댓값을 실수로만 반환받고자 하면 math.fabs(값)을 사용하면 절댓값이 실수(float)로 반환됩니다.