함수
스코프 (=영역)
파이썬에서는 코드 작성 시, 각 함수마다 독립적인 스코프(영역)을 가진다. 메인 프로그램(현재 동작하는 프로그램의 최상위 위치)의 영역은 전역 영역(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), 지역, ... > 지역 : 바깥에서 안은 사용 못함, 안에서 바깥은 사용 가능
파이썬에서 같은 변수명은 글로벌변수나 지역변수 둘중 하나만 사용가능
내장 함수와 내장 영역
print
, dict
등 지정하지 않고 사용했던 내장(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
함수를 추가한다던가 하는 기능을 할 수 있다. (예: 로그를 남기거나 특정상황에 메일을 발송하게 할 때 많이 사용한다.)
- 기능을 추가할 함수를 인자로 받음
- 데코레이터 자체에 추가할 기능을 함수로 정의
- 인자로 받은 함수를 데코레이터 내부에서 적절히 호출
- 위 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]
실습
- 매개변수로 문자열을 받고, 해당 문자열이
red
면apple
을,yellow
면banana
를,green
이면melon
을, 어떤 경우도 아닐 경우I don't know
를 리턴하는 함수를 정의하고, 사용하여result
변수에 결과를 할당하고print
해본다. - 1번에서 작성한 함수에
docstring
을 작성하여 함수에 대한 설명을 달아보고,help(함수명)
으로 해당 설명을 출력해본다. - 한 개 또는 두 개의 숫자 인자를 전달받아, 하나가 오면 제곱, 두개를 받으면 두 수의 곱을 반환해주는 함수를 정의하고 사용해본다.
- 두 개의 숫자를 인자로 받아 합과 차를 튜플을 이용해 동시에 반환하는 함수를 정의하고 사용해본다.
- 위치인자 묶음을 매개변수로 가지며, 위치인자가 몇 개 전달되었는지를 print하고 개수를 리턴해주는 함수를 정의하고 사용해본다.
- 람다함수와 리스트 컴프리헨션을 사용해 한 줄로 구구단의 결과를 갖는 리스트를 생성해본다.
알고리즘
순차검색(Sequential Search)
- 문자열과 키 문자 1개를 받는 함수 구현
- while문을 이용, 문자열에서 키 문자가 존재하는 index위치를 검사 후 해당 index를 리턴
- 찾지 못했을 경우 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)
- 리스트 중 최소값을 검색
- 그 값을 맨 앞의 값과 교체
- 나머지 리스트에서 위의 과정을 반복
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()