위코드 6기 day5 2020.02.01
함수는 일정한 작업을 수행하는 코드블럭입니다. 함수를 사용하면 반복되는 코드를 간결하게 줄일 수 있어 코드의 가독성이 높아집니다.
수학에서 함수를 f = ax + b
로 정의한 이후에는 f(x)
만 쓰는 방식과 같습니다. 프로그래밍에서 f = ax + b
함수를 만들고 input(x)를 함수 f를 실행하면 함수 기능에 의해 output(ax+b)가 결과값으로 나오게 됩니다.
>>> def f(x):
return 3 * x + 2
>>> f(3)
11
f(x) 에서 x를 parameter라고 하며 함수는 여러 개의 parameter를 가질 수 있습니다.
https://docs.python.org/3.7/glossary.html#term-parameter
>>> def func(a,b):
return a + b
>>> func(2,3)
5
parameter가 꼭 있어야 하는 것은 아닙니다.
>>> def sayhi():
return "Hi"
>>> sayhi()
'Hi'
parameter는 string, list, dictionary, function 모두 가능합니다.
# string
>>> def sayhi(name):
return f"Hi, {name}"
>>> sayhi("Tom")
'Hi, Tom'
# list
>>> def returnlist(my_list):
return my_list
>>> returnlist([1,2,3])
[1, 2, 3]
# dictionary
>>> def returndic(my_dic):
return my_dic
>>> returndic({"one" : 1})
{'one': 1}
# function은 밑에서 다룹니다.
argument는 함수를 calling(실행) 할 때 실제 passed(입력된)값을 말합니다. parameter는 함수를 정의할 때 사용하는 것이며 argument는 실제 실행할 때의 값입니다.
https://docs.python.org/3.7/glossary.html#term-argument
https://docs.python.org/3.7/faq/programming.html#faq-argument-vs-parameter
# a, b는 parameter 2, 3은 argument
>>> def add(a, b):
return a+b
>>> add(2, 3)
5
함수를 call(실행 또는 호출) 하기 위해서는 함수 이름 뒤에 parentheses(bracket)을 열고 닫아줘야 합니다. 함수를 call하지 않고 사용하는 경우도 있기 때문입니다. (first class function에서 함수 사용의 예를 다룰 예정입니다.)
>>> def sayhi():
return "hi"
>>> sayhi #함수를 실행하는 것이 아니라 확인만 할 수 있습니다.
<function sayhi at 0x7ff1e5b1e840>
>>> sayhi()
'hi'
>>>
parameter가 없다면 없이, 있다면 개수를 맞춰서 입력한 후 실행해야 합니다.
>>> def func():
print('Hi!')
>>> func('yo')
Traceback (most recent call last):
File "<pyshell#22>", line 1, in <module>
func('yo')
TypeError: func() takes 0 positional arguments but 1 was given
# argument가 없기 때문에 parameter가 없어야 한다.
>>> def func(a,b):
return a+b
>>> func(1)
Traceback (most recent call last):
File "<pyshell#28>", line 1, in <module>
func(1)
TypeError: func() missing 1 required positional argument: 'b'
# argument가 2개이기 때문에 parameter를 두 개 넣어줘야 한다.
함수를 실행한 결과값으로 이어지는 다른 동작을 하기 위해서는 return으로 결과값을 반환해줘야 합니다. print로는 함수의 기능을 확인할 수 있을 뿐 결과값이 반환되는 것은 아닙니다.
>>> def sayhi():
print("hi")
>>> sayhi()
hi
>>> a = sayhi()
hi
>>> a
>>>
아래 예시처럼 변수 a가 값을 가지려면 print가 아닌 return으로 값을 반환해줘야 합니다.
>>> def sayhi():
return "hi"
>>> sayhi()
'hi'
>>> a = sayhi()
>>> a
'hi'
코드 예제는 유투브 Corey Schafer 영상에서 가져왔습입니다.
파이썬 함수는 first class citizen(object)입니다. first class citizens를 찾아보니 세 가지의 특성을 갖고 있었습니다.
https://en.wikipedia.org/wiki/First-class_citizen
In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include
- being passed as an argument, 함수의 argument로 쓸 수 있고
- returned from a function, 함수의 결과값으로 return 할 수 있고
- modified, and assigned to a variable. 변수로 지정할 수 있다.
>>> def square(x):
return x * x
# 함수 square 를 변수 fu 로 지정
>>> fu = square
>>> square
<function square at 0x7ff1e5b1e598>
>>> fu
<function square at 0x7ff1e5b1e598>
# 함수 square의 리턴값을 변수 nc 로 지정
>>> square(5)
25
>>> nc = square(5)
>>> nc
25
# 함수1 square 생성
>>> def square(x):
return x * x
# 함수2 cube 생성
>>> def cube(x):
return x * x * x
# 함수3 my_map 생성
>>> def my_map(func, arg_list):
result = []
for i in arg_list:
result.append(func(i))
return result
# 변수 squares에 함수 my_map의 argument로 함수 square과 리스트 [1, 2, 3, 4, 5]를 넣고 실행한 결과값을 할당
>>> squares = my_map(square, [1, 2, 3, 4, 5])
>>> print(squares)
[1, 4, 9, 16, 25]
# 함수 square 대신에 함수 cube를 argument로 넣음
>>> squares = my_map(cube, [1, 2, 3, 4, 5])
>>> print(squares)
[1, 8, 27, 64, 125]
# 함수1 logger 생성
>>> def logger(msg):
def log_message():
print('Log:', msg)
# 함수 logger의 리턴값은 함수 log_message, 실행하지 않은 상태
return log_message
# 변수 log_hi에 함수 lobber를 할당하면 log_hi(변수) == logger(함수)
>>> log_hi = logger
>>> log_hi
<function logger at 0x7ff1e5b1ef28>
# 변수 log_hi 에 함수 logger 리턴값을 할당하면 log_hi(변수) == lob_message(함수)
>>> log_hi = logger('Hi!')
# 함수 logger는 parameter를 받기 때문에 parameter없는 상태로 변수에 할당할 수 없다.
>>> log_hi = logger()
Traceback (most recent call last):
File "<pyshell#127>", line 1, in <module>
log_hi = logger()
TypeError: logger() missing 1 required positional argument: 'msg'
# 변수 lob_hi는 이제 함수(log_message)가 됐다.
>>> log_hi
<function logger.<locals>.log_message at 0x7ff1e5b1ee18>
# 함수 log_message의 parameter가 없기 때문에 log_hi도 parameter없이 실행한다. log_hi()를 실행하면 logger에 기억해둔 msg == 'Hi!' 를 기억하고 있어(closure의 개념) 아래와 같은 리턴값이 나온다.
>>> log_hi()
Log: Hi!
앞의 예시에서 중첩(내부)함수인 log_message
가 부모(외부)한수인 logger
있던 'Hi'를 기억한 것이 closure입니다. 두 함수가 따로 있었다면 'Hi'는 로컬변수가 아니기 때문에 기억할 수 없었을 겁니다. 하지만 중첩함수 이기에 기억을 하고 있는 것입니다.
코드 예제는 유투버 Corey Schafer 영상에서 가져왔습니다.
>>> def outer_func():
message = 'Hi'
def inner_func():
print(message)
return inner_func
>>> my_func = outer_func()
# 변수 my_func 함수 inner_func이 된 상태
>>> my_func
<function outer_func.<locals>.inner_func at 0x7f42493c1e18>
# 함수 outer_func을 6번째 줄에서 이미 실행한 이후인데도 inner_func은 outer_func 영역에 있는 message = 'Hi'를 기억하고 가져올 수 있다. → closure
>>> my_func()
Hi
# outer_func에 parameter msg가 생김
>>> def outer_func(msg):
message = msg
def inner_func():
print(message)
return inner_func
>>> hi_func = outer_func('Hi')
>>> hello_func = outer_func('Hello')
>>> hi_func()
Hi
>>> hello_func()
Hello
코드 예제는 유투버 Corey Schafer 영상에서 가져왔습니다.
decorator는 어떤 함수를 실행하기 전에 특정 동작(함수)을 실행하도록 하기 위한 기능입니다. 함수를 정의할 때 위에 @로 장식하는 것처럼 보이기 때문에 decorator란 이름이 붙었습니다.
아래 예시를 보면 original_function을 실행하기 전에 특정 문구를 print 하는 decorator 함수를 정의했습니다. 이 때 중요한 것은 original_function이 함수의 argument인 nested function의 형태여야 하고 앞서 봤던 closure의 개념이 사용된다는 것입니다.
# decorator 함수 정의
>>> def decorator_function(original_function):
def wrapper_function():
print(f'wrapper executed this before {original_function.__name__}')
return original_function()
return wrapper_function
# display 함수를 정의 할 때 위에 @decorator 로 장식
>>> @decorator_function
def display():
print('display function ran')
# 바로 위 세줄은 display함수를 argument로 가지는 decorated_display 함수가 같음
>>> decorated_display = decorator_function(display)
# decorated_display를 실행하면 아래와 같은 결과가 나옴
>>> decorated_display()
wrapper executed this before display
display function ran
이번에는 display_info라는 함수에 같은 decorator_function을 적용 후 실행하니, wrapper 함수는 parameter가 없는데 argument가 있어서 오류가 났습니다.
>>> def decorator_function(original_function):
def wrapper_function():
print(f'wrapper executed this before {original_function.__name__}')
return original_function()
return wrapper_function
>>> @decorator_function
def display_info(name, age):
print(f'display_info ran with arguments ({name}, {age})')
>>> display_info('John', 25)
Traceback (most recent call last):
File "<pyshell#31>", line 1, in <module>
display_info('John', 25)
TypeError: wrapper_function() takes 0 positional arguments but 2 were given
>>> display_info
<function decorator_function.<locals>.wrapper_function at 0x7f17de210598>
함수의 argument로 *args
, **kwargs
를 적용해 다시 실행했습니다.
>>> def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
print(f'wrapper executed this before {original_function.__name__}')
return original_function(*args, **kwargs)
return wrapper_function
>>> @decorator_function
def display_info(name, age):
print(f'display_info ran with arguments ({name}, {age})')
>>> display_info('John', 25)
wrapper executed this before display_info
display_info ran with arguments (John, 25)
function의 기초 내용부터 decorator까지 살펴본 이유는 이 문제 때문입니다. 답을 어떻게 찾아서 넘어갔지만 잘 이해가 되지 않았습니다.
문제는 아래처럼 greeting 함수를 정의하고 실행했을 때 "Hello, 정우성"이라는 결과값이 나오도록 name_decorator 함수를 정의하는 것입니다.
@name_decorator("정우성")
def greeting():
return "Hello, "
greeting()
"Hello, 정우성"
문제를 풀기 위해 아래 내용을 생각하고 코드를 짜 봅니다.
>>> def name_decorator(name):
def wrapper(func):
return func + name # error
return wrapper
>>> @name_decorator("정우성")
def greeting():
return "Hello, "
Traceback (most recent call last):
File "<pyshell#48>", line 1, in <module>
@name_decorator("정우성")
File "<pyshell#46>", line 3, in wrapper
return func + name
TypeError: unsupported operand type(s) for +: 'function' and 'str'
리턴값으로 함수와 string을 같이 출력하는 것이 불가능하기에 func() 실행값을 입력해 봅니다.
>>> def name_decorator(name):
def wrapper(func):
return func() + name # error 수정
return wrapper
>>> @name_decorator("정우성")
def greeting():
return "Hello, "
>>> greeting()
Traceback (most recent call last):
File "<pyshell#54>", line 1, in <module>
greeting()
TypeError: 'str' object is not callable
>>> greeting
'Hello, 정우성'
함수 정의에서 문제가 없었는데 greeting
함수를 실행하니 오류가 뜹니다. 함수를 정의할 때 func() + name
으로 실행값을 넣었기 때문에 greeting
만으로도 'Hello, 정우성'
이 나온 것입니다. 그래서 greeting()
으로 함수를 실행하게 되면 결과값인 string에 ()를 붙여 호출하는 꼴이 되어 버립니다. 'Hello, 정우성'()
이렇게 말이죠. 그래서 string은 함수가 아니니 호출할 수 없다는 오류가 떴습니다. TypeError: 'str' object is not callable
따라서 func()은 return 값에 올 수 없고 변수로 처리해야 하는 상황이 됩니다. 변수를 받아 처리하는 함수를 새로 추가해 원하는 결과를 얻어냈습니다.
def name_decorator(name):
def decorator(func):
result = func()
def wrapper():
return result + name
return wrapper
return decorator
>>> @name_decorator("정우성")
def greeting():
return "Hello, "
>>> greeting()
'Hello, 정우성'