본문 바로가기
Flask

Flask - flask의 이해를 돕는 파이썬 문법 정리

by DGK 2021. 11. 21.

 

Flask 입문 수업을 듣고 중요한 내용을 정리했습니다.
개인 공부 후 자료를 남기기 위한 목적이므로 내용 상에 오류가 있을 수 있습니다.

 

flask의 이해를 돕는 파이썬 문법(Python)

flask를 다루기 위해 필요한 Python 중급 문법을 연습하고자 한다.

 

__name__ 문법

print('test.py __name__:', __name__)

 

```

결과 :

test.py __name__: __main__

 

```

 

import test

print('test2.py __name__:', __name__)

 

```

결과 :

test.py __name__: test
test2.py __name__: __main__

 

```

 

__name__ 변수를 사용하면, 파이썬에서는 자동으로 해당 모듈의 이름이 들어간다.

하지만, 직접 실행하는 파이썬 코드에서는 모듈의 이름 대신 __main__이 이름으로 들어간다.

 

 

def add_one(data):
    return data + 1
 
def add_two(data):
    return data + 2
 
if __name__ == '__main__':
    print(add_one(10))
    print(add_two(10))

 

```

결과 :

11

12

 

```

 

이는 test.py의 파이썬 파일을 실행시킨 결과이다.

해당 파일에서 직접 실행되는 코드이기 때문에 __name__ 변수의 이름은 __main__이 되고, if절의 조건이 충족되어 print문의 결과 값인 11, 12가 출력되는 것이다.

 

 

import test

print('test2.py __name__:', __name__)

 

```

결과 :

test2.py __name__: __main__

 

```

 

이는 test2.py의 파이썬 파일을 실행시킨 결과이다.

이 때, 결과 값으로 test2.py __name__: __main__만 출력되는 이유는 test.py의 파일을 모듈로 임포트하여 실행했기 때문이다. (즉, test.py 파일의 __name__에는 __main__이 아닌 모듈 이름(test)이 저장됨)

 

이처럼 test2.py 파일에서 test.py를 모듈로 실행시키는 경우에는 두 함수를 정의하는 코드만 실행될 뿐, if절의 코드는 실행되지 않는다. 그 이유는 if절의 조건이 __name__ = '__main__'이기 때문이며, if절의 코드는 test.py 파일을 직접 실행시킬 때에만 정상적으로 작동한다. (test.py 파일을 직접 실행시킬 때에만, if절의 조건이 충족되기 때문)

 

이러한 __name__ 문법은 해당 파일을 직접 실행시킬 경우에만 특정 코드가 실행되고, 모듈로 파일을 실행시킬 경우에는 특정 코드가 실행되지 않도록 하는 기능으로 주로 사용된다.

 

 

import test

print('test2.py __name__:', test.add_one(10))

 

```

결과 :

11

 

```

 

모듈로 임포트하여 test.py의 코드(기능)를 실행하려면 위와 같이 코드를 작성해야 한다. (ex. test.add_one(10))

즉, 단순히 test.py 파일을 모듈로 실행시키는 것으로는 해당 파일의 if절을 실행시킬 수 없다.

 

 

중첩 함수(nested function)

def outer_func():
    print('call outer_func function')
    
    # 중첩 함수의 정의
    def inner_func():
        return 'call inner_func function'
    
    # 중첩 함수 호출 
    print(inner_func())
    
outer_func()

 

```

결과 :

call outer_func function
call inner_func function

 

```

 

중첩 함수는 함수 내부에 정의된 또 다른 함수를 의미한다.

중첩 함수는 해당 함수가 정의된 원함수 내에서만 호출 및 반환이 가능하다.

이는 함수 안에 선언된 변수가 함수 안에서만 사용 가능한 원리와 동일하다.(로컬 변수)

 

 

inner_func()

 

```

결과 :

NameError: name 'inner_func' is not defined

 

```

 

*참고

일반적으로 함수를 호출하면, 스택(stack)에 함수 관련 정보가 생성되며 중첩 함수는 해당 스택에서 다시 한 번 정의(선언)되는 것이다. 이 때 원래의 함수가 실행되고 실행된 결과 값이 리턴(return)되면 스택에 생성되었던 원함수의 관련 정보는 사라진다. 따라서, 원함수를 호출하지 않고(스택에서 원함수의 정보를 통해 중첩 함수가 정의(선언)되지 않은 상태), 중첩 함수를 원함수 밖에서 호출하면 스택에는 중첩 함수 관련 정보가 없기 때문에 중첩 함수가 실행되지 않는 것이다.

 

하지만 중첩 함수를 원함수 밖에서도 호출할 수 있는 방법이 있다. (First-class function, closure function을 활용하여 호출가능)

 

 

*참고(First-class function, closure function을 활용하여 원함수 밖에서 중첩 함수를 호출하는 예시)

 

def outer_func(num):
    # 중첩 함수에서 외부 함수의 변수에 접근 가능
    def inner_func():
        print(num)
        return 'complex'
    
    return inner_func

fn = outer_func(10)    # <--- First-class function
print(fn())            # <--- Closure 호출

 

```

결과 :

10

complex

 

```

 

 

First-class function

First-class 함수는 아래와 같은 특징을 가진다.

 

   - 함수 자체를 변수에 저장 가능

   - 함수의 인자에 다른 함수를 인수로 전달 가능

   - 함수의 반환 값(return 값)으로 함수를 전달 가능

 

사실상 파이썬에서는 모든 것이 객체로 이루어져있고, 파이썬의 함수도 객체로 되어있기 때문에 파이썬의 함수들은 모두 First-class 함수로 사용할 수 있다. (지금까지 배운 언어의 맥락과는 다른 사고 : 함수형 프로그래밍에서 고안된 기법)

 

*참고

First-class 함수를 지원하는 언어

 

   - Python, Go, Javascript, Kotlin 등

   - 참고로, C언어는 First-class 함수 지원x

 

이제 First-class 함수의 특징을 예시로 살펴보자.

 

  • 함수 자체를 변수에 저장 가능
def calc_square(digit):
    return digit * digit
    
func1 = calc_square  # 변수 func1에 함수를 저장(할당) 가능
print(func1)
print(func1(4))

 

```

결과 :

<function calc_square at 0x10fb43dc0>

16

 

```

 

함수가 할당된 변수(func1)는 동일한 함수처럼 사용 가능하다.

참고로, </function calc_square at 0x10fb43dc0> 결과는 변수 func1이 함수라는 의미이다.

 

 

  • 함수의 인자에 다른 함수를 인수로 전달 가능
def calc_square(digit):
    return digit * digit

def calc_plus(digit):
    return digit + digit

def calc_quad(digit):
    return digit * digit * digit * digit
    
    
def list_square(function, digit_list):
    result = list()
    for digit in digit_list:
        result.append(function(digit)) 
    print(result)
    
    
num_list = [1, 2, 3, 4, 5]

list_square(calc_square, num_list)
list_square(calc_plus, num_list)
list_square(calc_quad, num_list)

 

```

결과 :

[1, 4, 9, 16, 25]
[2, 4, 6, 8, 10]
[1, 16, 81, 256, 625]

 

```

 

 

  • 함수의 반환 값(return 값)으로 함수를 전달 가능
def logger(msg):
    message = msg
    def msg_creator():    # <--- 함수 안에 함수를 만들 수도 있음
        print('[HIGH LEVEL]: ', message)
    return msg_creator
    
log1 = logger('Kim Log-in')

print(log1)
log1()

 

```

결과 :

[HIGH LEVEL]:  Kim Log-in

 

```

 

 

다음은 First-class 함수의 활용 예시이다.

 

def html_creator(tag):
    def text_wrapper(msg):
        print('<{0}>{1}</{0}>'.format(tag, msg))
    return text_wrapper
    
h1_html_creator = html_creator('h1')
print(h1_html_creator)
h1_html_creator('H1 태그는 타이틀을 표시하는 태그입니다.')

p_html_creator = html_creator('p')
p_html_creator('P 태그는 문단을 표시하는 태그입니다.')

 

```

결과 :

<function html_creator.<locals>.text_wrapper at 0x10fb673a0>

<h1>H1 태그는 타이틀을 표시하는 태그입니다.</h1>

<p>P 태그는 문단을 표시하는 태그입니다.</p>

 

```

 

 

def list_creator(tag):
    def text_wrapper(list_data):
        for item in list_data:
            print ('{0} {1}'.format(tag, item))
    return text_wrapper

data_list_minus = list_creator('-')
data_list_minus(['안녕', '하세요'])

data_list_mul = list_creator('*')
data_list_mul(['안녕', '하세요'])

data_list_x = list_creator('X')
data_list_x(['안녕', '하세요'])

 

```

결과 :

- 안녕
- 하세요
* 안녕
* 하세요
X 안녕
X 하세요

 

```

 

 

import requests
from bs4 import BeautifulSoup

res = requests.get('https://davelee-fun.github.io/blog/crawl_html_css.html')
soup = BeautifulSoup(res.content, 'html.parser')
link_titles = soup.select("ul#hobby_course_list > li")
data = list()
for link_title in link_titles:
    data.append(link_title.get_text())
    
def list_creator(tag):
    def text_wrapper(list_data):
        for item in list_data:
            print ('{0} {1}'.format(tag, item))
    return text_wrapper

data_list_minus = list_creator('-')    
data_list_minus(data)

 

```

결과 :

- (왕초보) - 클래스 소개
- (왕초보) - 블로그 개발 필요한 준비물 준비하기
- (왕초보) - Github pages 설정해서 블로그 첫 페이지 만들어보기
- (왕초보) - 초간단 페이지 만들어보기
- (왕초보) - 이쁘게 테마 적용해보기
- (왕초보) - 마크다운 기초 이해하고, 실제 나만의 블로그 페이지 만들기
- (왕초보) - 다양한 마크다운 기법 익혀보며, 나만의 블로그 페이지 꾸며보기

 

```

 

 

Closure function

Closure 함수는 특정 함수와 그 함수가 가지고 있는 데이터를 함께 복사, 저장해서 별도 함수로 활용하는 기법으로 First-class 함수와 유사한 기능을 한다. 또한, Closure 함수를 통해 원함수가 소멸되더라도 원함수 안에 있는 로컬 변수 값과 중첩 함수를 그대로 사용할 수 있다.

 

다음의 예제를 통해 Closure 함수의 기능을 살펴보자.

 

def outer_func(num):
    # 중첩 함수에서 외부 함수의 변수에 접근 가능
    def inner_func():
        print(num)
        return '안녕'
    
    return inner_func    # 중첩 함수 이름을 리턴
    
closure_func = outer_func(10)    # <--- First-class function
closure_func()                   # <--- Closure 호출

 

```

결과 :

10

'안녕'

 

```

 

위의 예시코드에서 변수 closure_func이 바로 Closure 함수가 되는 것이다.

closure_func = outer_func(10) 코드로 outer_func() 함수는 호출된 것이고, 이후에 closure_func() 코드는 inner_func() 함수를 호출하는 것이다.

 

참고로, outer_func(10)코드를 통해 outer_func() 함수 호출 시 num 값은 이미 없어졌지만, closure_func()에서 inner_func() 함수가 호출되면서 이전의 num 값을 여전히 사용할 수 있다. 

심지어, outer_func() 함수를 아예 삭제해버려도 변수 closure_func 안에 저장된 inner_func() 함수의 리턴 값과 num값은 여전히 사용할 수 있다. (Closure 함수의 고유 기능)

 

del outer_func

closure_func()

 

```

결과 :

10

'안녕'

 

```

 

이처럼, 원함수를 아예 삭제해도 Closure 함수를 활용하면 중첩 함수의 기능을 여전히 사용할 수 있다. (앞서 중첩 함수의 데이터를 저장한 변수를 활용하는 원리)

 

그렇다면 언제 Closure 함수를 사용해야 하는가?

Closure 함수는 객체와 유사하여 일반적으로 제공해야할 기능(method)이 적은 경우에는 Class를 대신하여 Closure 함수를 사용하기도 한다.

 

 

다음은 Closure 함수의 활용 예시이다.

 

# 일반적인 함수를 선언하는 경우

def calc_square(digit):
    return digit * digit

def calc_power_3(digit):
    return digit * digit * digit

def calc_quad(digit):
    return digit * digit * digit * digit
    
print(calc_square(2))
print(calc_power_3(2))
print(calc_quad(2))

 

```

결과 :

4

8

16

 

```

 

# Closure 함수를 사용하는 경우

def calc_power(n):
    def power(digit):
        return digit ** n
    return power
    
power2 = calc_power(2)
power3 = calc_power(3)
power4 = calc_power(4)

print(power2(2))
print(power3(2))
print(power4(2))

 

```

결과 :

4

8

16

 

```

 

Closure 함수를 사용하면, 일반 함수를 사용하는 것보다 간편하게 동일 기능을 구현할 수 있다. (동일한 결과)

 

 

def calc_power(n):
    def power(digit):
        return digit ** n
    return power

list_data = list()
for num in range(1, 6):
    list_data.append(calc_power(num))

for func in list_data:
    print(func(2))

 

```

결과 :

2

4

8

16

32

 

```

 

매번 기능이 다른 함수를 정의하는 것보다 Closure 함수를 사용하는 것이 효율적이다.

 

 

데코레이터(Decorator)

데코레이터(Decorator)는 Closure function을 활용하여, 손쉽게 특정 함수의 앞 뒤에 기능을 추가할 수 있다.

즉, 여러 함수에 동일한 기능을 @데코레이터 하나로 간편하게 추가할 수 있으며 추가하는 기능의 코드가 길수록 효과적이다.

 

  • 일반적인 함수를 사용하는 경우
import datetime

def logger_login_david():
     print(datetime.datetime.now())
     print("David login")
     print(datetime.datetime.now())
     
logger_login_david()     
     
     
def logger_login_anthony():
     print(datetime.datetime.now())
     print("Anthony login")
     print(datetime.datetime.now())
     
logger_login_anthony()     


def logger_login_tina():
     print(datetime.datetime.now())
     print("Tina login")
     print(datetime.datetime.now())

logger_login_tina()

 

일반적인 함수를 사용하면, 이처럼 시간을 알려주는 기능을 추가하고자 할 때마다 매번 동일한 코드를 작성해야 한다.

 

 

  • 데코레이터를 사용하는 경우
def datetime_decorator(func):                          # <--- datetime_decorator는 데코레이터 이름, func가 이 함수 안에 넣을 함수가 됨
    def wrapper():                                     # <--- 호출할 함수를 감싸는 함수
        print('time ' + str(datetime.datetime.now()))  # <--- 함수 앞에서 실행할 내용
        func()                                         # <--- 함수  
        print(datetime.datetime.now())                 # <--- 함수 뒤에서 실행할 내용
    return wrapper
    

# @데코레이터
@datetime_decorator           
def logger_login_david():
     print("David login")

logger_login_david()
    

# @데코레이터
@datetime_decorator            
def logger_login_anthony():
     print("Anthony login")

logger_login_anthony()


# @데코레이터
@datetime_decorator            
def logger_login_tina():
     print("Tina login")

logger_login_tina()

 

데코레이터를 사용하여, detetime_decorator() 함수의 인자(func)로 들어갈 함수만 만들어주면 매번 동일한 기능의 코드를 작성할 필요가 없다. (새롭게 정의한 함수 -> detetime_decorator 함수의 인자(func)로 들어감 -> wrapper() 함수 안의 func()에서 실행됨)

 

 

  • Nested function, Closure function과 함께 데코레이터를 풀어서 작성하는 경우
# decorator 함수 정의
def outer_func(function):
    def inner_func():
        print('decoration added')
        function()
    return inner_func

# decorating할 함수
def log_func():
    print('logging')
    
decorated_func = outer_func(log_func)
decorated_func()  # <--- 결과는 데코레이터를 사용할 때와 동일함

 

```

결과 :

decoration added

logging

 

```

 

 

  • 위와 동일한 결과를 데코레이터로 작성하는 경우
# decorator 함수 정의
def outer_func(function):
    def inner_func():
        print('decoration added')
        function()
    return inner_func
    
# @데코레이터    
@outer_func
def log_func():
    print('logging')

log_func()

 

```

결과 : 위와 동일함

 

```

 

 

  • 파라미터가 있는 함수에 Decorator 적용하기
# 데코레이터
def outer_func(function):
    def inner_func(digit1, digit2):
        if digit2 == 0:                   # <--- 유효성 검사의 예
            print('cannot be divided with zero')
            return
        function(digit1, digit2)
    return inner_func
  
  
# 데코레이터 사용하기(유효성 검사)
@outer_func
def divide(digit1, digit2):
    print(digit1 / digit2)
    
divide(4, 2)
divide(9, 0)

 

```

결과 :

2.0

cannot be divided with zero

 

```

 

만약, 파라미터를 가지는 함수(divide())에 데코레이터를 사용할 경우에는 반드시 중첩 함수의 인자와 중첩 함수 안에 존재하는 함수 호출의 인자를 파라미터를 가지는 함수와 동일하게 만들어줘야 한다.

즉, divide() 함수의 인자 == inner_func() 함수의 인자 == 중첩 함수 안에서 호출되는 함수의 인자 : (digit1, digit2)

 

# 데코레이터
def type_checker(function):
    def inner_func(digit1, digit2):
        if(type(digit1) != int) or (type(digit2) != int):   # <--- 유효성 검사의 예
            print('the only int is supported')
            return 
        return function(digit1, digit2)
    return inner_func

# 데코레이터 사용하기(유효성 검사)
@type_checker
def multiply(digit1, digit2):
    return digit1 * digit2

divide(0.1, 1)

 

```

결과 :

the only int is supported

 

```

 

 

  • 파라미터와 관계없이 모든 함수에 적용가능한 Decorator 만들기
# 데코레이터 작성하기
def general_decorator(function):
    def wrapper(*args, **kwargs):
        print('function is decorated')
        return function(*args, **kwargs)
    return wrapper
    

# 데코레이터 적용하기
@general_decorator
def calc_square(digit):
    return digit * digit

@general_decorator
def calc_plus(digit1, digit2):
    return digit1 + digit2

@general_decorator
def calc_quad(digit1, digit2, digit3, digit4):
    return digit1 * digit2 * digit3 * digit4
    
    
# 함수 호출하기
print(calc_square(2))
print(calc_plus(2, 3))
print(calc_quad(2, 3, 4, 5))

 

```

결과 :

function is decorated
4
function is decorated
5
function is decorated
120

 

```

 

데코레이터의 중첩 함수 파라미터를 (*args, **kwargs)로 작성하면, 어떤 함수의 어떤 인자이든 데코레이터를 적용할 수 있다.

 

 

  • 한 함수에 데코레이터 여러 개 지정하기
# 여러 데코레이터 작성하기
def decorator1(function):
    def wrapper():
        print('decorator1')
        function()
    return wrapper
 
def decorator2(function):
    def wrapper():
        print('decorator2')
        function()
    return wrapper
    
    
# 여러 데코레이터를 함수에 한번에 적용하기
@decorator1
@decorator2
def hello():
    print('hello')
    

hello()

 

```

결과 :

decorator1
decorator2
hello

 

```

 

def mark_bold(function):
    def wrapper(*args, **kwargs):
        return '<b>' + function(*args, **kwargs) + '</b>'
    return wrapper

def mark_italic(function):
    def wrapper(*args, **kwargs):
        return '<i>' + function(*args, **kwargs) + '</i>'
    return wrapper

@mark_bold
@mark_italic
def add_html(string):
    return string

print(add_html('안녕하세요'))

 

```

결과 :

<b><i>안녕하세요</i></b>

 

```

 

이처럼 함수에 여러 개의 데코레이터를 지정할 수 있으며, 데코레이터를 나열한 순서대로 실행된다.

 

 

  • Class 함수에 데코레이터 적용하기(심화)
# 데코레이터 작성하기(for method)
def h1_tag(function):
    def func_wrapper(self, *args, **kwargs):                           # <--- self를 무조건 첫 파라미터로 넣어야 메서드에 적용가능
        return "<h1>{0}</h1>".format(function(self, *args, **kwargs))  # <--- function 함수에도 self 를 넣어야 함
    return func_wrapper
    
    
# 클래스 선언시 메서드에 데코레이터 적용하기
class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

    @h1_tag
    def get_name(self):
        return self.first_name + ' ' + self.last_name      # <--- self는 디폴트 인자이므로, 인자 개수에 포함되지 않음


# 데코레이터 적용 확인해보기
name = Person('Kim', 'John')
print(name.get_name())

 

```

결과 :

<h1>Kim John</h1>

 

```

 

클래스의 함수에도 데코레이터를 적용할 수 있으며, 클래스의 함수는 첫 파라미터가 self이기 때문에 이 부분을 데코레이터 작성시에 포함시켜야 한다. (ex. func_wrapper(self, *args, **kwargs):, format(function(self, *args, **kwargs))

 

 

  • 파라미터를 가지는 Decorator 만들기(심화)
# 중첩 함수를 하나 더 생성
def decorator1(num):
    def outer_wrapper(function):
        def innter_wrapper(*args, **kwargs):
            print('decorator1 {}'.format(num))
            return function(*args, **kwargs)
        return innter_wrapper
    return outer_wrapper
    
    
@decorator1(1)
def print_hello():
    print('hi')
    
print_hello()


@decorator1(num=2)
def print_hello():
    print('say hello')
    
    
print_hello()

 

```

결과 :

decorator1 1
hi

 

decorator1 2

say hello

 

```

 

 

  • Nested function, Closure function과 함께 파라미터를 가지는 Decorator를 풀어서 작성하는 경우
# 중첩 함수를 하나 더 생성
def decorator1(num):
    def outer_wrapper(function):
        def innter_wrapper(*args, **kwargs):
            print('decorator1 {}'.format(num))
            return function(*args, **kwargs)
        return innter_wrapper
    return outer_wrapper
    
    
def print_hello():
    print('hello')
    
    
print_hello2 = decorator1(1)(print_hello)
print_hello2()

 

```

결과 :

decorator1 1
hi

 

```

 

print_hello2 = decorator1(1)(print_hello) 코드에서 1은 decorator1() 함수의 인자인 num으로 들어가며, 두 번째 괄호의 print_hello는 outer_wrapper() 함수의 인자로 들어간 후 inner_wrapper() 함수의 인자로 들어가서 결국 중첩 함수 안의 retrun function(*args, **kwargs) 코드로 실행된다. 

 

 

  • 다른 예제(파라미터를 가지는 Decorator)
def mark_html(tag):
    def outer_wrapper(function):
        def inner_wrapper(*args, **kwargs):
            return '<' + tag + '>' + function(*args, **kwargs) + '</' + tag + '>'
        return inner_wrapper
    return outer_wrapper

@mark_html('b')
def print_bold(title):
    return title

@mark_html('h1')
def print_title(title):
    return title

print(print_bold('Hello World'))
print(print_title('Hello World'))

 

```

결과 :

<b>Hello World</b>
<h1>Hello World</h1>

 

```

 

 

댓글