함수

스코프 (=영역)

파이썬에서는 코드 작성 시, 각 함수마다 독립적인 스코프(영역)을 가진다. 메인 프로그램(현재 동작하는 프로그램의 최상위 위치)의 영역은 전역 영역(Global Scope)라고 하며, 전역 스코프 내부에서 독립적인 영역을 갖고 있는 경우에는 지역 영역(Local Scope)라고 부른다.

# 전역변수 선언
In [1]: champion = 'Lux'

# champion 변수가 함수의 로컬 스코프에 존재하지 않기 때문에,
# 글로벌 스코프에서 해당 변수를 찾아 출력
In [2]: def show_global_champion():
   ...:     print('show_global_champion : {}'.format(champion))
   ...:

In [3]: show_global_champion()
show_global_champion : Lux

In [4]: print('print champion {}'.format(champion))
print champion Lux

# change_global_champion 함수 추가
In [5]: def change_global_champion():
   ...:     print('before chage_global_champion : {}'.format(champion))
   ...:     champion = 'Ahri'
   ...:     print('after chage_global_champion : {}'.format(champion))
   ...:

In [6]: show_global_champion()
show_global_champion : Lux

# change_global_champion 호출
# 내부에 또다른 champion 변수가 존재하기 때문에,
# 할당 전인 변수를 사용한 것으로 판단하여 프로그램에서 오류를 발생시킨다.
In [7]: change_global_champion()
----------------------------------------------------------------
UnboundLocalError: local variable 'champion' referenced before assignment

# change_global_champion 수정
In [8]: def change_global_champion():
   ...:     #print('before chage_global_champion : {}'.format(champion))
   ...:     champion = 'Ahri'
   ...:     print('after chage_global_champion : {}'.format(champion))
   ...:

In [9]: change_global_champion()
after chage_global_champion : Ahri

In [10]: show_global_champion()
show_global_champion : Lux

# champion 변수 ID 확인
# 두 변수는 다른 객체
In [11]: def show_global_champion():
    ...:     print('show_global_champion : {}'.format(champion))
    ...:     print('show_global_champion ID : {}'.format(id(champion)))
    ...:

In [12]: show_global_champion()
show_global_champion : Lux
show_global_champion ID : 4426265184

In [13]: def change_global_champion():
    ...:     champion = 'Ahri'
    ...:     print('chage_global_champion : {}'.format(champion))
    ...:     print('chage_global_champion : {}'.format(id(champion)))
    ...:

In [14]: change_global_champion()
chage_global_champion : Ahri
chage_global_champion : 4427075344

In [15]: show_global_champion()
show_global_champion : Lux
show_global_champion ID : 4426265184

# locals() 함수를 사용해 각 영역의 데이터 확인
# 전역 영역의 데이터 확인은 globals() 함수
In [30]: def show_global_champion():
    ...:     print('show_global_champion : {}'.format(champion))
    ...:     print('show_global_champion ID : {}'.format(id(champion)))
    ...:     print(locals())
    ...:

In [31]: show_global_champion()
show_global_champion : Lux
show_global_champion ID : 4426265184
{}

In [32]: def change_global_champion():
    ...:     champion = 'Ahri'
    ...:     print('chage_global_champion : {}'.format(champion))
    ...:     print('chage_global_champion : {}'.format(id(champion)))
    ...:     print(locals())
    ...:

In [33]: change_global_champion()
chage_global_champion : Ahri
chage_global_champion : 4427075344
{'champion': 'Ahri'}

스코핑룰

스코프는 지역(Local), 전역(Global)외에도 내장(Built-in)영역이 존재하며, 내장영역이 가장 바깥, 그 내부에 전역, 그 내부에 지역 순으로 정의된다.

분리된 영역에서, 외부 영역에서는 내부 영역의 데이터를 사용할 수 없지만 내부 영역에서는 자신의 외부 영역에 있는 데이터를 참조할 수 있다. (반대의 경우에는 함수의 인자로 데이터를 전달한다)

내장(Built-in) > 전역(Global) > 지역(Local), 지역, ... > 지역 :  바깥에서 안은 사용 못함, 안에서 바깥은 사용 가능
파이썬에서 같은 변수명은 글로벌변수나 지역변수 둘중 하나만 사용가능

내장 함수와 내장 영역

printdict등 지정하지 않고 사용했던 내장(Built-in) 함수들은 위 스코핑 룰의 내장(Built-in) 스코프에 존재하는 함수들이다. 전역스코프의 __builtin__변수에 할당되어 있으며, 전역 스코프에서는 해당 변수의 내부를 참조할 수 있도록 파이썬이 시작될 때 자동으로 처리된다.

확인시 dir함수를 사용하며, dir함수는 해당 객체가 사용 가능한 속성 및 함수들을 리스트 형태로 나타내준다.

로컬 스코프에서 글로벌 스코프의 변수 사용

In [1]: champion = 'Lux'

In [5]: def change_global_champion():
   ...:     champion = 'Ahri'
   ...:     print('after change_global_champion: {}'.format(champion))
   ...:     print('id: {}'.format(id(champion)))
   ...:

In [6]: change_global_champion()
after change_global_champion: Ahri
id: 4489157072

In [7]: print('print global champion: {}'.format(champion))
print global champion: Lux

In [8]: print(id(champion))
4489012984


In [9]: champion = 'Lux'

# champion 변수에 global 선언
In [13]: def change_global_champion():
    ...:     global champion
    ...:     champion = 'Ahri'
    ...:     print('after change_global_champion : {}'.format(champion))
    ...:     print('id: {}'.format(id(champion)))
    ...:

In [14]: change_global_champion()
after change_global_champion : Ahri
id: 4489157072

In [15]: print('print global champion: {}'.format(champion))
print global champion: Ahri

In [16]: print(id(champion))
4489157072

내부함수에서의 로컬 스코프 (nonlocal)

로컬 스코프 내부에 또 다른 로컬 스코프가 존재할 수 있다.

champion = 'Lux'

def local1():
    champion = 'Ahri'
    print('local1 locals : {}'.format(locals()))

    def local2():
        champion = 'Ezreal'
        print('local2 locals : {}'.format(locals()))
    local2()
    print('local1 locals :{}'.format(locals()))

print('global locals():{}'.format(locals()))
local1()

# 결과
$ python inner_function.py
global locals():{'champion': 'Lux', '__builtins__': <module 'builtins' (built-in)>, '__file__': 'inner_function.py', '__cached__': None, '__doc__': None, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x109aff630>, 'local1': <function local1 at 0x109a8abf8>, '__package__': None, '__name__': '__main__', '__spec__': None}
local1 locals : {'champion': 'Ahri'}
local2 locals : {'champion': 'Ezreal'}

# local1의 champion='Ahri'
local1 locals :{'champion': 'Ahri', 'local2': <function local1.<locals>.local2 at 0x109c25840>}

전역 스코프가 아닌, 자신의 바로 바깥 영역의 로컬 스코프(자신보다 한 단계 위의 로컬 스코프)의 데이터를 참조하고자 한다면, nonlocal키워드를 사용한다. (nonlocal은 위로 타고 간다.)

champion = 'Lux'

def local1():
    champion = 'Ahri'
    print('local1 locals : {}'.format(locals()))

    def local2():
        nonlocal champion
        champion = 'Ezreal'
        print('local2 locals : {}'.format(locals()))
    local2()
    print('local1 locals(): {}'.format(locals()))

print('global locals():{}'.format(locals()))
local1()
# 결과
$ python nonlocal_inner_function.py
global locals():{'__name__': '__main__', '__file__': 'nonlocal_inner_function.py', '__builtins__': <module 'builtins' (built-in)>, 'local1': <function local1 at 0x1021f7bf8>, 'champion': 'Lux', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x10226c5f8>, '__spec__': None, '__cached__': None}
local1 locals : {'champion': 'Ahri'}
local2 locals : {'champion': 'Ezreal'}

# local1의 champion='Ezreal'
local1 locals(): {'local2': <function local1.<locals>.local2 at 0x102392840>, 'champion': 'Ezreal'}

global키워드와 인자(argument)전달의 차이

인자로 전달한 경우

In [1]: global_level = 100

# 인자로 전달한 경우,
# 인자로 전달받은 값을 value라는 파라미터로 사용
# 같은 객체 '100'을 가르키는 글로벌 변수 global_level과 매개변수 value가 존재
# 리터럴 값은 주소만 참조
In [2]: def level_add(value):
   ...:     value += 30
   ...:     print(value)
   ...:     print(id(value))

In [3]: level_add(global_level)
130
4308083968

In [4]: print(global_level)
100

In [8]: print(id(global_level))
4308083008

global 키워드를 사용한 경우

In [9]: global_level = 100

# 100이란 값을 참조하는 것이 아니라
# global_level 변수를 그대로 가져와서 사용
# 즉, global 키워드를 사용할 경우 같은 변수
In [10]: def level_add():
    ...:     global global_level
    ...:     global_level += 30
    ...:     print(global_level)
    ...:     print(id(global_level))

In [11]: level_add()
130
4308084928

In [12]: print(global_level)
130

In [16]: print(id(global_level))
4308084928

리스트 변수가 전달될 경우

# 리스트는 값과 주소 모두 참조하므로
In [1]: global_level = [100]

# 인자로 전달 받은 값을 value라는 파라미터로 사용
# value 파라미터의 값을 30 증가시키고 출력
In [2]: def argument_level_add(value):
   ...:     value[0] += 30
   ...:     print('argument_level_add, value : {}'.format(value))
   ...:     print('argument_level_add, id : {}'.format(id(value)))
   ...:

# global scope의 global_level 변수의 값을 30 증가시키고 출력
In [4]: def global_level_add():
   ...:     global global_level
   ...:     global_level[0] += 30
   ...:     print('global_level_add, value : {}'.format(global_level))
   ...:     print('global_level_add, id : {}'.format(id(global_level)))
   ...:

In [15]: print('global_level : {}'.format(global_level))
global_level : [100]

In [16]: print('global_level : {}'.format(id(global_level)))
global_level : 4531515656

In [17]: argument_level_add(global_level)
argument_level_add, value : [130]
argument_level_add, id : 4531515656

In [18]: print('global_level : {}'.format(global_level))
global_level : [130]

In [19]: print('global_level : {}'.format(id(global_level)))
global_level : 4531515656

In [20]: global_level_add()
global_level_add, value : [160]
global_level_add, id : 4531515656

In [21]: print('global_level : {}'.format(global_level))
global_level : [160]

In [22]: print('global_level : {}'.format(id(global_level)))
global_level : 4531515656

람다함수

한 줄 짜리 표현식으로 이루어지며, 반복문이나 조건문 등의 제어문은 포함될 수 없다. 또한, 함수이지만 정의/호출이 나누어져 있지 않으며 표현식 자체가 바로 호출된다. 함수를 정의하고 동시에 사용가능하다.

In [1]: import string

In [3]: for char in string.ascii_lowercase:
   ...:     if char > 'i':
   ...:         print(char.upper())
   ...:     else:
   ...:         print(char)
   ...:

# 람다 함수
In [5]: for char in string.ascii_lowercase:
   ...:     print((lambda x : x.upper() if x > 'i' else x)(char))
   ...:

클로져 (Closure)

함수가 정의된 환경을 말한다. 파이썬 파일이 여러개일 경우 각 파일은 하나의 모듈역할을 하고, 각 모듈은 독립적인 환경을 가진다. 독립된 환경은 각자의 영역을 전역 영역으로 사용한다.

# module_a.py
level = 100
def print_level():
    print(level)
# module_b.py
import module_a

# 명시적으로 이렇게 쓸수도잇음
# from module_a import print_level as a_print_level

level = 50
def print_level():
    print(level)

module_a.print_level()
print_level()
# 결과
$ python module_b.py
100
50
내부함수 클로져
# 전역 변수
level = 0

def outter():
    # 지역 변수
    level = 50

    def inner():
        # nonlocal 키워드를 사용하므로,
        # outter에 새로 정의된 지역변수 level 사용
        nonlocal level
        level += 3
        print(level)
    # return inner() : inner 함수의 호출 결과
    # 가 아닌 함수자체를 반환
    # 이때, outter 안쪽의 모든 환경을 포함해서 반환
    # 함수자체도 closuer를 갖는다!
    return inner

func = outter()
func()
func()
func()
func2 = outter()
func2()
func2()
func2()
print(id(func))
print(id(func2))
$ python inner_closure.py
53
56
59
53
56
59
# 서로 다른 객체이므로 메모리에서 다른 곳 참조
4538980552
4538980416

데코레이터 (decorator)

함수를 받아 다른 함수를 반환하는 함수. 예를 들면, 기존에 존재하던 함수를 바꾸지 않고 전달된 인자를 보기위한 디버깅 print함수를 추가한다던가 하는 기능을 할 수 있다. (예: 로그를 남기거나 특정상황에 메일을 발송하게 할 때 많이 사용한다.)

  1. 기능을 추가할 함수를 인자로 받음
  2. 데코레이터 자체에 추가할 기능을 함수로 정의
  3. 인자로 받은 함수를 데코레이터 내부에서 적절히 호출
  4. 위 2가지를 행하는 내부 함수를 반환
def print_args(func):
    def inner_func(*args, **kwargs):
        print('args : {}'.format(args))
        result = func(*args, **kwargs)
        return result
    return inner_func

def decorate_test(func):
    def inner_func(*args, **kwargs):
        print('test_inner_func')
        return func(*args, **kwargs)
    return inner_func

# 데코레이터 사용
# 데코레이터를 여러개 사용할 경우,
# 함수에서 가장 가까운 것 부터 실행
@print_args
@decorate_test
def multi(arg1, arg2):
    # 똑같은 기능을 하는 부분
    # print('args : {}, {}'.format(arg1, arg2))
    result = arg1 * arg2
    print(result)
    return result

@decorate_test
@print_args
def divide(arg1, arg2):
    # print('args : {}, {}'.format(arg1, arg2))
    result = arg1 / arg2
    print(result)
    return result

multi(3, 5)
divide(10, 2)

# 명시적으로 이렇게 쓸수도잇음
func1 = decorate_test(multi)
func1(100, 2)
func2 = print_args(divide)
func2(200, 2)
$ python calculator_decorator.py
args : (3, 5)
test_inner_func
15
test_inner_func
args : (10, 2)
5.0
test_inner_func
args : (100, 2)
test_inner_func
200
args : (200, 2)
test_inner_func
args : (200, 2)
100.0

제너레이터

제네레이터는 함수는 파이썬의 시퀀스 데이터를 생성하는데 사용된다. 실제 시퀀스 데이터와 다른 점은, 시퀀스 전체를 가지고 있는 것이 아니라 시퀀스 데이터를 생성하기 위한 어떠한 루틴만을 가지고 있는 것이다. 이 방식을 택했을 때의 장점은, 전체 크기만큼의 메모리를 가지고 있는 시퀀스 데이터와는 달리 메모리를 적게 사용할 수 있다.

제네레이터는 마지막으로 호출한 위치(항목)에 을 기억하고 있으며, 한 번 순회할 때 마다 그 다음 값을 반환한다. 제네레이터는 함수를 통해서 만들어지며, 함수 내부의 반복문에서 yield키워드를 사용하면 제네레이터가 된다.

한번만 수행할 수 있으며, 다 순회한 순간 사라진다.

In [1]: def range_gen(num):
   ...:     i = 0
   ...:     while i < num:
   ...:         yield i
   ...:         i += 1
   ...:

In [2]: gen = range_gen(5)

# __next__로 호출할 수 있다.
In [3]: gen.__next__()
Out[3]: 0

In [4]: gen.__next__()
Out[4]: 1

In [5]: gen.__next__()
Out[5]: 2

In [6]: gen.__next__()
Out[6]: 3

In [7]: gen.__next__()
Out[7]: 4

In [8]: gen.__next__()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-8-25e7f8cd3e00> in <module>()
----> 1 gen.__next__()

StopIteration:

In [9]: gen = range_gen(5)

In [10]: gen_list = [item for item in gen]

In [11]: gen_list
Out[11]: [0, 1, 2, 3, 4]

실습

  1. 매개변수로 문자열을 받고, 해당 문자열이 red면 apple을, yellow면 banana를, green이면 melon을, 어떤 경우도 아닐 경우 I don't know를 리턴하는 함수를 정의하고, 사용하여 result변수에 결과를 할당하고 print해본다.
  2. 1번에서 작성한 함수에 docstring을 작성하여 함수에 대한 설명을 달아보고, help(함수명)으로 해당 설명을 출력해본다.
  3. 한 개 또는 두 개의 숫자 인자를 전달받아, 하나가 오면 제곱, 두개를 받으면 두 수의 곱을 반환해주는 함수를 정의하고 사용해본다.
  4. 두 개의 숫자를 인자로 받아 합과 차를 튜플을 이용해 동시에 반환하는 함수를 정의하고 사용해본다.
  5. 위치인자 묶음을 매개변수로 가지며, 위치인자가 몇 개 전달되었는지를 print하고 개수를 리턴해주는 함수를 정의하고 사용해본다.
  6. 람다함수와 리스트 컴프리헨션을 사용해 한 줄로 구구단의 결과를 갖는 리스트를 생성해본다.

코드

알고리즘

  1. 문자열과 키 문자 1개를 받는 함수 구현
  2. while문을 이용, 문자열에서 키 문자가 존재하는 index위치를 검사 후 해당 index를 리턴
  3. 찾지 못했을 경우 0을 리턴
def sequential_search(word, key):
    index = 0
    while word:
        if word[index] == key:
            return index
        index += 1
    else:
        return 0

def search2(key, source):
    for index, char in enumerate(source):
        if char == key:
            return index
    return 0

result = sequential_search('legend', 'g')
print(result)

result2 = search2('g', 'legend')
print(result2)

선택정렬(Selection sort)

  1. 리스트 중 최소값을 검색
  2. 그 값을 맨 앞의 값과 교체
  3. 나머지 리스트에서 위의 과정을 반복
for i in (1 ~ n-1):
	for j in (i+1 ~ n-1):
		if (최소값이 변경되었을 때) index저장
	if(index가 변경되었을경우) 두 값을 바꾼다
source = [9, 1, 6, 8, 4, 3, 2, 0, 5, 7]
length = len(source)

def selection_sort():
    for index in range(length-1):
        cur_min_index = index

        for inner_index in range(index+1, length):
            if source[cur_min_index] > source[inner_index]:
                cur_min_index = inner_index
        source[cur_min_index], source[index] = source[index], source[cur_min_index]

    print(source)

selection_sort()