[Python] 6. 함수(Function)

Wonder_Land🛕·2022년 6월 16일
0

[Python]

목록 보기
6/12
post-thumbnail
  1. 함수의 개념과 목적
  2. 함수 호출 및 선언
  3. 함수의 유형
  4. 함수와 매개변수
  5. 고급 함수 사용범
  6. Q&A
  7. 마치며

우선 시작하기에 앞서, 여러분들은 공부하기에 정돈된 책상어지러운 책상 중 어느것을 택하시나요?
아마 대부분이 정돈된 책상을 택하겠죠.
분류에 따라 잘 정리되어 있고, 라벨이나 파일링이 잘 된 책상은 효율을 높인다고 하네요.

이와 마찬가지로 프로그램을 작성하다보면, 자주 사용하는 명령어들은 구조화할 필요성을 느낄 때가 종종 있습니다.
이 때, 우리는 함수(Function)라는 것을 만들어서 사용합니다.
함수를 사용한다면 효율적이고 구조화된 프로그램을 작성할 수 있습니다.

그럼 오늘은 함수(Function)에 대해서 알아보겠습니다.


1. 함수의 개념과 목적

  • 함수(Function)
    : 프로그램에서 어떤 특정 기능을 수행할 목적으로 만들어진 재사용 구조의 코드 부분입니다.

1) 함수의 장점

  • 구조적 프로그래밍이 가능합니다.
    : 하나의 큰 프로그램을 여러 작은 부분으로 나눌 수 있기 때문이죠.

  • 동일 함수를 여러 곳에서 필요할 때마다 호출할 수 있습니다.

  • 수정이 용이합니다.

2) 함수 사용 방법

우선, 함수를 호출할 때 어떤 값을 함수에 넘겨줄 수 있는데, 이 때 어떤 값을 인자라고 하며,
함수 내에서 인자로 받은 것을 매개변수라고 합니다.
호출된 함수가 완료되면 결과로 반환값을 반환합니다.

함수는 호출된 이후, 반환값을 통해 외부에 영향을 주기도 합니다.

그러나 외부에 영향을 주지 않기도 하는데요, 그러한 함수를 '순수 함수(pure function)'이라고 합니다.

  • 순수 함수(Pure Function)
    : 결과값 반환 외에 외부에 영향을 주지 않는 함수

함수형 프로그래밍 지원 언어(Python 등)에서는 순수 함수를 인자, 반환값으로 사용할 수 있습니다.


2. 함수 호출 및 선언

1) 함수의 호출

우리가 기본적으로 보는 print()함수를 볼까요?

표준 입출력에서 정의하는 print함수는 ()를 통해서 호출되며 ()사이에 인자를 전달하면 문자열이 출력되죠.

2) 함수의 선언

def (함수명) (매개변수):
	명령문
    명령문
    ...
    return

이 때 매개변수()안에서 작성해야 합니다.

예시로, 두 개의 값을 받아 합을 구하는 함수 cal_sum()을 만들어보죠.

def cal_sum(x, y):	#매개변수에 인자값 전달
   return x + y	#함수를 호출한 위치에 반환값 전달
    
a, b = 2, 3
c = cal_sum(a, b)	#반환값 5가 변수 c에 저장

만약, cal_sum()함수의 위치를 바꾸면 어떻게 될까요?

아래의 예시에서는 cal_sum()의 위치를 맨 아래로 바꾸어보았습니다.

a, b = 2, 3
c = sum(a, b)

def sum(x, y):
   return x + y

_**[Result]**_
NameError: name 'cal_sum' is not defined

cal_sum()이 정의되어 있지 않다고 Error가 나오네요.

이처럼 Python과 같은 ¹Interpreter Language의 경우 함수 선언 위치가 매우 종요합니다.

Interpreter Language는 명령을 위에서부터 한 줄씩 해석하기 때문에,
실행 공간에 함수 정보가 없을 경우 Error가 발생할 수 있습니다.


3. 함수의 유형

함수는 1) 매개변수의 유무, 2) 반환 값의 유무에 따라 형태를 분류할 수 있습니다.

  • 매개변수의 유무
    : 함수에 입력값을 전달해야 하는가를 결정
  • 반환값의 유무
    : 함수가 수행결과를 호출한 곳으로 반환할 필요가 있는가를 결정

그러면 다음과 같이 4가지 유형이 있겠죠?😉

1. 매개변수 O, 반환값 O
2. 매개변수 O, 반환값 X
3. 매개변수 X, 반환값 O
4. 매개변수 X, 반환값 X


4. 함수와 매개변수

  • 매개변수(Parameter)
    : 함수 호출 시 입력값을 전달 받기 위한 변수
  • 매개변수 역시 '변수'이므로, 전달받은 인자의 type에 의해 type이 결정됩니다.

  • Python에서는, 선언된 매개변수의 개수만큼 인자를 전달 할 수 있습니다.

def cal_sum(x, y):
    return x + y

c = cal_sum(1)

_**[Result]**_
TypeError: cal_sum() missing 1 required positional argument: 'y'

위의 예시에서, 매개변수는 x, y2개이지만,
호출할 때 인자는 11개만 전달됐습니다.

결과로는, 매개변수 y에 해당하는 인자가 빠졌다고 Error를 호출하네요 🤔

def cal_sum(x, y):
    return x + y

c = cal_sum(1, 2, 3)

_**[Result]**_
TypeError: cal_sum() takes 2 positional arguments but 3 were given

위의 예시에서 마찬가지로, 매개변수는 x, y2개이지만,
호출할 때 인자는 1, 2, 33개를 전달했습니다.

결과로는, 매개변수는 2개이지만, 인자가 3개가 주어졌다고 Error를 호출하네요 🤔


Python에서는 매개변수의 여러가지 특성이 있습니다. 한 번 살펴보죠

1) 언팩 연산자(*)

  • 언팩 연산자(*)
    : 매개변수의 개수를 가변적으로 사용할 수 있게하는 연산자
    : 매개변수에 인자들이 Tuple 형식으로 전달됩니다.
def cal_sum(*para):
    total = 0
    for val in para:
        total += val
    return total

result1 = cal_sum(1, 2)
result2 = cal_sum(1, 2, 3)

코드에서 보듯이, 인자의 개수를 가변적으로 사용할 수 있죠

하지만 주의할 점도 있습니다.

  • 가변형 매개변수는 1개만 지정할 수 있습니다.

  • 가변형 매개변수는, 가장 마지막 매개변수로 지정해야 부작용 없이 사용할 수 있습니다.

def cal_sum(first, *para):
    total = 0
    for val in para:
        total += val
    return first + total

result = cal_sum(1, 2, 3)

위의 코드 cal_sum()을 호출하게 되면

첫번째 매개변수 first에 인자값이 가장 먼저 전달됩니다.

그리고 나머지 인자들은 *paraTuple 형식으로 전달됩니다.


2) 키워드 언팩 연산자(**)

  • 키워드 언팩 연산자(**)
    : 매개변수의 개수를 가변적으로 사용할 수 있게하는 연산자
    : 키워드 인자들을 전달해 매개변수를 Dictionary type으로 처리합니다.
def cal_sum(**para):	# para는 Dictionary type
    for k in para.keys():
        print(f'{k} : {para[k]}')

result = cal_sum(num1 = 1, num2 = 2, num3 = 3)	# '키 = 값' 형식의 인자

위의 코드 cal_sum()을 호출할 때,
인자들을 키-값형식으로 전달하면, 매개변수 para에 Dictionary type으로 전달됩니다.

2번째 줄에서는, para.keys()함수를 호출하여 키 리스트를 구해서 for문에서 사용합니다.


3) 매개변수의 기본값 지정

만약 매개변수에 전달할 인자값이 생략되었을 때, 매개변수로 사용할 수 있는 '기본값'을 지정할 수 있습니다.

단, 기본값을 가지는 매개변수는 일반 매개변수 앞에 위치할 수 없습니다.

def cal_sum(x, y = 1):	# 매개변수 y는 기본값 1로 지정
    return x + y

result = cal_sum(1)

4) Scope (변수의 유효범위)

변수는 scope에 의해 두 종류로 볼 수 있습니다.

  • 전역 변수(Global Variable)
    : 코드 상 어디에서나 접근 가능한 변수
  • 지역 변수(Local Variable)
    : 코드 상, 특정 구간에서만 접근 가능한 변수

우선, 변수에 접근하는 절차에 대해 살펴보겠습니다.

a = 1	# Global

def scope():
    a = 2	#Local
    print(a)
    
scope()
print(a)

scope()함수를 실행하게 되면

먼저, 함수 scope 내에서 변수를 찾습니다.

만약, 함수 scope 내에서 변수를 찾지 못한다면, 전역 scope에서 변수를 찾습니다.

그래도 찾지 못한다면, 선언되지 않은 변수로 취급하고 NameError를 호출합니다.

따라서, Global Variable과 Local Variable의 이름이 같다면, Local Variable을 우선 찾기 때문에 Global Variable에 접근 못할 수 있습니다.

만약 Global Variable로 접근하고자 한다면, Global Variable 앞에 'global'을 작성하면 됩니다.

def scope():
    global a
    a += 2

a= 1
scope()
print(a)

[Result]
3


함수에 대해서 살펴봤는데요, 이제 위에서 살펴본 기본적인 함수가 아닌, 보다 복잡한 함수도 살펴볼께요 😉

5. 고급 함수 사용법

1) 중첩 함수(Nested Function)

(1) 정의 및 특징

중첩함수를 설명함에 있어 용어를 정리하겠습니다!

  • 중첩함수(Nested Function) : 함수 내에 정의된 함수
  • 외부함수(Outer Function) : 중첩함수를 포함하는 함수
  • 내부함수(Inner Function) : 중첩함수에 포함된 함수
def calc(operator, x, y):	#외부함수(Outer)
    return operator(x,y)	#중첩함수 호출

def plus(x, y):	  #내부함수(Inner)
    return x + y

def minus(x,y):	  #내부함수(Inner)
    return x - y

val1 = calc(plus, 10, 5)
print(val1)

val2 = calc(minus, 10, 5)
print(val2)

**[Result]**
15
5

  • 내부함수는 외부함수 내에서만 호출이 가능합니다.
  • 내부함수는 외부함수의 Scope에도 접근이 가능합니다.
    즉, 내부함수의 매개변수외부함수의 인자를 전달받아 호출할 수 있습니다.

이러한 특정 덕분에,
프로그램의 유연성을 높이기 위해 내부함수를 매개변수로 전달하는 방식을 선호하고 있습니다.

하지만, 매번 함수를 선언해 사용하는 것이 불편할 수 있죠.

이런 불편함을 해소하기 위해 제공되는 방법이 바로 '람다식/함수(Lambda Expression/Function)'입니다.

  • 람다식/함수(Lanmbda Expression/Function)
lambda 매개변수 : 반환값

의 형태로 사용가능합니다.

  1. 변수에 저장해 재사용이 가능한 함수처럼 사용합니다.

  2. 기존의 함수처럼 매개변수의 인자로 전달합니다.

  3. 함수의 매개변수에 직접 인자로 전달할 수 있습니다.

다음 코드는, 직접 인자로 전달하는 방식입니다.

def calc(operator, x, y):
    return operator(x,y)

val1 = calc(lambda a, b: a + b, 10, 5)
print(val1)

val2 = calc(lambda a, b: a - b, 10, 5)
print(val2)

위의 코드에서 lambda a, b: a + b, lambda a, b: a - b'람다식'이 되며,
calc()에서 operator함수의 역할을 수행하게 됩니다.


만약, 중첩함수 자체를 반환값으로 사용한다면 어떻게 될까요.

모든 정보가 기본적으로 공개되는 Python의 경우 다음의 효과가 있습니다.
1. 정보 은닉 구현 가능
2. 전역변수의 남용 방지
3. Method가 하나밖에 없는 객체를 만드는 것보다 우아한 구현 가능

(2) 클로저(Closure)

  • 클로저(Closure)
    : 함수 자신을 둘러싼 Scope의 상태값을 기억하는 함수

    클로저는 다음 세 가지 조건을 만족해야합니다.
  1. 해당 함수는 어떤 함수 내의 중첩된 함수여야 한합니다.
  2. 해당 함수는 외부함수 내의 상태값을 반드시 참조해야 합니다.
  3. 외부함수는 해당 함수를 반환해야 합니다.

말이 참 어렵네요 😂...
예시로 살펴보아야겠어요ㅠㅠ

예시에서 클로저는 inner_func()함수 입니다.
1. 해당 함수는 outer_func()함수에 중첩되었으며
2. 외부함수(outer_func())의 id를 참조하고 있으며
3. 외부함수(outer_func())는 inner_func()반환하고 있기 때문이죠

 1 def outer_func():
 2 id = 0  #Local Variable
 3 
 4     def inner_func():
 5         nonlocal id
 6         id += 1
 7         return id
 8    
 9     return inner_func
10 make_id = outer_func()

2 : id는 Local변수로 outer_func()함수 내부 또는 내부함수(inner_funct())에서만 접근할 수 있습니다.

5 : nonlocal id의 의미는, 변수 id가 내부함수인 inner_func()함수의 Local변수가 아니라는 말입니다.
따라서, 변수 id에 접근할 때 내부함수(inner_func())이 아닌, 외부함수(outer_func())에서 찾게 만듭니다.

8 : 반환할 때는 inner_func()함수의 호출이 아닌, 함수에 대한 참조를 반환해야 합니다.


6. Q&A

1) Interpreter Language

용어 정리!

  • Source Code(Language) : 사용자가 작성한 원래의 코드
  • Target Code(Language) : 프로그램을 실행하기 위한 코드(assembly코드, object코드, machine코드)
  • 컴파일(Compile) : Source Code를 Target Code로 바꾸는 과정

(1) Interpreter Language(인터프리터 언어)

(1.1) 특징

  • Interpreter는 Source Code를 한 줄 단위로 번역하고 동시에 프로그램을 실행하는 프로그램입니다.

  • Target Code가 생성되지 않습니다.

(1.2) 장점

  • 번역 과정이 비교적 간단하고, 빠릅니다.

  • 한 줄씩 번역과 실행이 이루어지기 때문에, 프로그램 수정이 용이합니다.
    (이 덕분에, 개발단계에서 개발자가 원하는 작은 규모의 테스트를 하는데 자주 쓰인다고 합니다.)

(1.3) 단점

  • 프로그램의 실행 속도가 느립니다.

(1.4) 종류

  • Python, Ruby, Javascript, ...

(2) Compile Language(컴파일 언어)

(2.1) 특징

  • Compiler는 Source Code 전체를 Target Code로 번역한 후, Linking을 통해 실행 파일을 생성합니다.

(2.2) 장점

  • 한 번 번역이 이루어지면, 다시 하지 않아도 되기 때문에 실행 속도가 빠릅니다.

(2.3) 단점

  • Source Code 전체를 한꺼번에 번역하므로, 번역 과정이 비교적 복잡하고, 느립니다.

(2.4) 종류

  • C, C++, JAVA, ...

7. 마치며

오늘은 프로그램을 구조화하는데 필요한 함수에 대해 알아보았습니다.
사실 c나 c++이 익숙한 저에게 Python의 함수는 생소한 부분도 있었습니다.
예를 들어, 함수의 이름을 매개변수로 넘기는 것은 정말 신기하군요. (사실 이해하는데 시간이 걸렸습니다😂)
그리고 중첩함수에 대한 개념도 굉장히 생소하네요. 아직은 익숙하지 않은 개념입니다.
이 부분에 대한 내용은 더 공부해서 익숙해지도록 해야겠네요😉

[Reference] : 위 글은 다음 내용을 참고, 인용하여 만들어졌습니다.

profile
아무것도 모르는 컴공 학생의 Wonder_Land

0개의 댓글