21 Sun

[HEAD FIRST PYTHON] 10강 장식자 : 함수 장식하기

웹의 상태

웹은 상태가 없다. 따라서 사용자 인증 정보를 html 폼 또는 python 코드 변수로 처리하고 기억할 수 없다.

  • 웹 서버로 세 개의 요청을 보내면 서버는 이를 세 번의 독립적인 개별 요청으로 간주하고 처리한다.

    • 동일한 IP로 이루어진 요청인데도 불가하고

  • HTTP 라는 프로토콜의 동작 방식 때문

    • 웹 서버가 모든 요청을 개별 요청으로 취급하도록 강제했다.

      • 웹 서버가 처리해야 할 작업의 양이 최소화되어야 더 많은 요청을 처리할 수 있도록 확장할 수 있기 때문

  • HTTP는 작업의 양을 최소화 하기 위해 상태 라고 하는 정보를 유지하지 않도록 최적화 되어 있다.

    • 이를 stateless 라고 표현한다.

    • 빠른 응답과 빠른 잊기가 웹의 특기

  • 또한 웹 서버는 파이썬 코드를 실행하는 것이 아닌 웹앱 코드를 실행한다.

    • 웹앱 코드가 항상 웹 서버의 메모리에 있는 것이 아니므로 웹앱 코드 상에 있는 변수를 기억할 수 없다.

  • 예를 들어 다음과 같은 코드에서 다음 변수는 문제가 된다.

    • 이 변수는 사용자가 로그인 했는지 안했는지를 알려주는 변수이다.

logged_in = False
  • 문제1

    • 웹 서버는 웹앱 코드를 언제든 메모리에서 해제할 수 있으므로 전역 변수에 저장된 값이 사라질 수 있다.

    • 실제로 로그인을 해서 logged_in 이 True가 되더라도 다시 코드를 임포트 했을 경우 False로 되어버린다.

  • 문제2

    • 웹 서버가 유저 마다 logged_in 변수를 가지고 있어야 한다. 또한, 각각의 유저 변수는 간섭받지 않아야 한다.

  • 해결 책

    • 전역 변수를 이용하지 않고 변수를 저장

    • 각각의 사용자 데이터가 서로 간섭받지 않도록 유지

  • 대부분의 웹앱 개발 프레임워크는 두 가지 요구사항을 세션이라는 기술로 해결한다.

    • 세션은 상태가 없는 정보

세션

  • 웹앱은 여전히 상태가 없는 웹에서 수행되지만 세션을 통해 웹앱이 상태를 기억할 수 있는 능력을 얻는다.

  • 플라스크는 웹앱을 실행할 때마다 세션에 저장된 데이터를 사용할 수 있으며, 몇 번을 읽어 들였는지는 상관없다.

  • 여기서 중요한 점은 플라스크가 어떻게 세션 기능을 처리하는 지 보다는 세션 기능을 제공한다는 점이다. 이 때 쿠키 생성에 필요한 비밀키 정보를 제공해야 하며, 비밀키를 이용해 쿠키를 암호화하고 외부 사용자로부터 정보를 보호한다.

from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'YouWillNeverGuess'

@app.route('/setuser/<user>')
def setuser(user: str) -> str:
    session['user'] = user
    return 'User value set to: ' + session['user']

@app.route('/getuser')
def getuser() -> str:
    return 'User value is currently set to: ' + session['user']
  • 1 : session 임포트

  • 3 : 플라스크의 쿠기 생성에 필요한 비밀키 값을 제공

    • 유추하기 어려운 값으로생성하는 것이 좋음

  • 6~8 : session 딕셔너리에 user변수의 값을 user로 저장

  • 10~12 : user키에 할당된 값에 접근하여 문자열 일부를 반환

장식자

장식자를 구현할 때 알아야 할 사실

  • 함수를 구현하는 법

  • 함수를 함수의 인자로 전달하는 법

  • 함수에서 함수를 반환하는 법

  • 다양한 개수와 유형의 함수 인자를 처리하는 법

함수를 함수의 인자로 전달

msg = "Hello~"
def hello():
    print(msg)

hello()
>>> "Hello~"

id(msg)
>>> 4385961264

type(hello)
>>> <class 'function'>
  • msg 와 같은 문자열이나 hello 와 같은 함수의 이름 자체가 객체이다.

  • 이 객체를 idtype 함수의 인자로 전달할 수 있다.

    • 이 때 인자로 전달된 함수를 호출하지는 않았다.

함수에서 함수를 반환하는 법

def apply(func: object, value: ojbect) -> object:
    return func(value)
  • 이러한 기능은 장식자를 구현할 때 필요하다.

  • 또, 함수안에 함수를 중첩할 수 있는데,

def outer():
    def inner():
        print("Inner")
    
    print("Outer")

inner()
>>> "Outer"
>>> "Inner"
  • 결과가 신기하다. inner 함수를 호출하기 위해 outer 함수가 먼저 호출되는 모습

  • 함수가 많은 행의 코드를 포함하는 복잡한 상황에서 함수 코드의 일부를 중첩 함수로 추상화 할 때 사용한다. 그러면 감싸는 코드를 읽기 쉬워진다.

def outer():
    def inner():
        print("Inner")
    
    print("Outer")
    return inner

i = outer()
>>> "Outer"

type(i)
>>> <class 'function'>

i()
>>> "Inner"
  • 이 때는 함수를 반환한 코드이다.

    • i 는 outer 함수가 만든 별칭이다.

다양한 개수와 유형의 함수 인자를 처리하는 법

  • func 라는 함수가 있을 때

    • func(10)

    • func()

    • func(10, 20, 30, 40, 50)

    • 등을 처리하기에는 생성자가 있더라도 한계가 있다.

  • 이 때, * 를 이용해 임의 개수의 인자를 받을 수 있다.

def func(*args):
    for a in args:
        print(a, end=' ')
  • 호출할 때도 * 를 쓸 수 있는데, 이는 각각의 인자로 전달하도록 하는 기법이다.

values = [1, 2, 3, 4, 5]

func(values)
>>> [1, 2, 3, 4, 5]

func(*values)
>>> 1 2 3 5 7 11
  • 또, ** 를 사용해서 키워드 인자를 처리할 수 있다.

def func2(**kwargs):
    for k, v in kwargs.items():
        print(k, v, sep='->', end=' ')
  • 이해가 어려웠던 부분.

    • 참고로 sep 은 print 함수의 인자들 사이에 구분자를 설정하는 키워드 인자이다. 기본값은 한 칸 공백이다.

  • 기본적으로

    • def func(a, b) 로 함수를 정의하게 되면

    • a와 b의 순서에 맞게 함수를 사용해야 하는데 키워드 인자를 쓸 경우 순서를 무시할 수 있다

      • func(10, 20) => a=10, b=20

      • func(b=20, a=10) => a=10, b=20

    • 이 때 이 키워드를 그대로 사용하고 싶을 수 있고, 이 때 딕셔너리 형태로 사용하겠다는 뜻

  • 다음과 같은 결과가 출력된다.

    • 키워드 인자를 사용하는 모습

func2(a=10, b=20)
>>> b->20 a->10

func(a=10, b=20, c=30, d=40)
>>> b->20 d->40 c->30 a->10
  • 호출할 때도 ** 를 사용할 수 있다.

    • 이미 앞서서 사용했었다

dbconfig = {'host': '127.0.0.1',
                'user': 'vsearch',
                'password': 'vsearchpasswd',
                'database': 'vsearchlogDB', }
                
conn = mysql.connector.connect(**dbconfig)
  • 이는 다음처럼 각각의 키워드 인자를 사용한 것과 같은 효과이다.

    • conn = mysql.connector.connect(host= '127.0.0.1', user= 'vsearch', password= 'vsearchpasswd', database= 'vsearchlogDB'

이제 이를 모두 종합해서 사용 가능하다.

def func3(*args, **kwargs):
    if args:
        for a in args:
            print(a, end=' ')
    if kwargs:
        for k, v in kwargs.items():
            print(k, v, sep='->', end=' ')
  • 이렇게 되면 일반 인자로 들어올 때는 args 로, 키워드 인자로 들어올 때는 kwargs 로 받을 수 있게 된다.

Last updated

Was this helpful?