python - function, decorator

whybein·2020년 2월 1일
0

Python

목록 보기
4/7
post-thumbnail
post-custom-banner

위코드 6기 day5 2020.02.01

1. function

 

함수는 일정한 작업을 수행하는 코드블럭입니다. 함수를 사용하면 반복되는 코드를 간결하게 줄일 수 있어 코드의 가독성이 높아집니다.

수학에서 함수를 f = ax + b 로 정의한 이후에는 f(x)만 쓰는 방식과 같습니다. 프로그래밍에서 f = ax + b 함수를 만들고 input(x)를 함수 f를 실행하면 함수 기능에 의해 output(ax+b)가 결과값으로 나오게 됩니다.

>>> def f(x):
	return 3 * x + 2

>>> f(3)
11

 

- parameter

 
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

 
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

 
함수를 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'

 

2. first class function

 
코드 예제는 유투브 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

 

- 함수를 argument로 쓸 수 있다.

# 함수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]

 

- 함수의 결과값으로 return 할 수 있다

# 함수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!

 


3. closure

앞의 예시에서 중첩(내부)함수인 log_message가 부모(외부)한수인 logger 있던 'Hi'를 기억한 것이 closure입니다. 두 함수가 따로 있었다면 'Hi'는 로컬변수가 아니기 때문에 기억할 수 없었을 겁니다. 하지만 중첩함수 이기에 기억을 하고 있는 것입니다.

 
코드 예제는 유투버 Corey Schafer 영상에서 가져왔습니다.

- parameter가 없는 경우

>>> 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

 

- parameter가 있는 경우

# 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

 


4. decorator

코드 예제는 유투버 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이 아닌 class로 사용하는 decorator
    (추후 작성)

 


5. 문제

function의 기초 내용부터 decorator까지 살펴본 이유는 이 문제 때문입니다. 답을 어떻게 찾아서 넘어갔지만 잘 이해가 되지 않았습니다.

문제는 아래처럼 greeting 함수를 정의하고 실행했을 때 "Hello, 정우성"이라는 결과값이 나오도록 name_decorator 함수를 정의하는 것입니다.

@name_decorator("정우성")
def greeting():
    return "Hello, "

greeting()

"Hello, 정우성"

 
문제를 풀기 위해 아래 내용을 생각하고 코드를 짜 봅니다.

  • name_decorator 함수가 argument로 함수가 아닌 string이 있다.
  • decorator 함수를 적용하기 위해서는 함수가 argument인 nested function의 형태여야 한다.
>>> 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, 정우성'
profile
Back-End Developer
post-custom-banner

0개의 댓글