20/03/27 TIL

Magit·2020년 3월 27일
0

TIL

목록 보기
5/16

Python

Nested Function

  • 함수도 함수안에 중첩되어 함수를 선언할 수 있다.
  • 중첩함수 (nested function) 혹은 내부 함수는 상위 부모 함수 안에서만 호출 가능하다. 부모 함수를 벗어나서 호출이 될 수 없다.

why use nested function?

  • 중첩함수는 왜 사용할까? 이유는 일반적으로 두 가지이다.
    • 가독성
    • Closure

가독성

  • 함수를 사용하는 이유 중 하나는 반복되는 코드블럭을 함수로 정의해서 효과적으로 코드를 관리하고 가독성을 높이기 위함이다.
  • 중첩함수를 사용하는 이유도 동일한데, 함수 안의 코드 중 반복되는 코드가 있다면 중첩함수로 선언해서 부모함수의 코드를 효과적으로 관리하고 가독성을 높일 수 있다.
def print_all_elements(list_of_things):
	## 중첩함수 선언
    def print_each_element(things):
    	for thing in things:
        	print(thing)
    if len(list_of_things) > 0:
    	print_each_element(list_of_things)
    else:
    	print("There is nothing!")

Closure

  • closure 란 사전적 의미는 폐쇄이다. 뭔가를 닫아서 가둔다는 의미가 있는 단어이다.

  • 파이썬에서 사용하는 closure는 어떤 것을 외부로부터 격리해서 사용한다는 느낌이 더 강하다.

  • 바로 중첩함수가 부모함수의 변수나 정보를 가두어 사용하는 것을 closure 라고 한다. 부모함수는 중첩함수를 리턴해주는데, 그러면 부모함수의 변수를 외부로부터 직접적인 접근은 격리하면서 중첩함수를 통해 격리된 부모함수의 변수를 사용한 연산은 가능하게 해준다.

    • 중첩 함수가 부모 함수의 변수나 정보를 중첩 함수 내에서 사용한다.
    • 부모 함수는 리턴값으로 중첩 함수를 리턴한다.
    • 부모 함수에서 리턴 했으므로 부모 함수의 변수는 직접적인 접근이 불가능하지만 부모하수가 리턴한 중첩함수를 통해서 사용될 수 있다.
  • 그렇다면 closure는 언제 사용되는가?

    • 어떤 정보를 기단으로 연산을 실행하고 싶지만 기반이 되는 정보에 접근을 제한하여 노출이 되거나 수정이 되지 못하게 싶을때 사용한다.
    • 주로 factory 패턴을 구현할때 사용되는데, factory란 공이란 뜻이다. 즉, 무너가를 생성해내는 패턴이다. 주로 함수나 오브젝트를 생성하는데 사용된다.
    • factory에서 뭔가를 생성해내려면 설정값이 필요한데, 그 설정값을 노출하지 않고 수정이 불가능하게 하면서 해당 설정값을 기반으로 연산을 할 수 있는 함수를 만들 때, closure를 사용한다.

  • 예를 들어보면 만일 주어진 어떠한 숫자의 수를 구하는 함수는 다음과 같을 것이다.

def calculate_power_of_two(power):
    return 2 ** power

calculate_power_of_two(7)
> 128
calculate_power_of_two(10)
> 1024
  • 하지만 위 함수는 2의 승밖에 구할 수 없다.
  • 만일 특정 숫자의 승을 구하는 함수가 필요하지만 2가 아니라 설정되는 수의 승을 구하는 함수는 어떻게 구현 될까?
  • 이 때, closure를 사용할 수 있다.
def generate_power(base_number):
    def nth_power(power):
        return base_number ** power

    return nth_power
 
calculate_power_of_two = generate_power(2)
calculate_power_of_two(7)
> 128
calculate_power_of_two(10)
> 1024

calculate_power_of_seven = generate_power(7)
calculate_power_of_seven(3)
> 343
calculate_power_of_seven(5)
> 16907



  • Decorator를 구현해보세요.
    Decorator는 앞서 배운 closure를 한단계 더 나아가 사용하는 고급 기능입니다.

  • Decorator는 closure처럼 중첩함수를 리턴하는 함수 이며 다른 함수에 적용해서, 적용된 함수가 실행되기 전에 무조건 실행됩니다. 즉 특정 함수를 실행하기 전에 강제적으로 다른 함수가 먼저 실행된후 실행되도록 하는 강제성을 제공하는 기능입니다.

  • 더 자세한 내용은 아래 링크의 스택오버플로우 질문을 참조하세요:

https://stackoverflow.com/c/wecode/q/64/1

decorator란 장식 호는 장식하는 사람이라는 뜻이 있다., 장식에 관련되어 있다는 의미이다.
먼저 대박 주식 정보를 리턴하는 함수가 있다고 가정하자.

def jackpot_stock_information():
	return "계시가 내려왔는데, 삼성전자를 사세요."

그러나 이 대박 주식 정보를 유료 회원만 받을 수 있다. 그러므로 이 함수가 호출되기 전에 해당 유저가 유료회원인지를 확인해야한다.
해당 회원이 유료회원인지 확인하는 함수는 다음가 같다. 일단 모든 유저가 유료 회원이라고 가정하겠다.

def is_paid_user():
	return True

그럼 jackpot_stock_information 함수를 호출하기 전에 항상 is_paid_user 함수가 먼저 호출되어야 한다. 다음과 같이 말이다.

if is_paid_user():
	jackpot_stock_information()

지금까지는 문제없어 보이지만, 문제는 jackpot_stock_information 함수가 여러 곳에서 자주 사용될 때이다.
jackpot_stock_information 함수가 호출될 때, is_paid_user 가 무조건 먼저 호출되야 하는데, 이 연결고리를 잊어먹을 확률이 있다., jackpot_stock_information 가 is_paid_user 함수 호출 없이 호출될 수 있다는 것이다. 개발자들도 사람이니 실수를 할 수 있는 부분이다.

그럼 jackpot_stock_information 함수가 호출될 때, 자동으로 is_paid_user 함수가 강제적으로 먼저 호출되게 할 수 있는 방법은 무엇일까?
여기서 decorator가 사용된다. decorator를 적용하면 다음과 같이 된다.

@is_paid_user
def jackpot_stock_information():
	return "계시가 내려왔는데, 삼성전자를 사세요."

jackpot_stock_information 함수 정의 위에 is_paid_user 함수가 골뱅이 마크(@)와 함께 달려있은것을 볼 수 있다. 그래서 장식, decorator 라고 하는 것이다.
함수위에 다른 함수를 골뱅이 마크를 사용해서 장식처럼 달아놓는 것이다.

저렇게 decorator로 달아놓으면 해당 함수가 호출되기전에 장식으로 달린 함수가 먼저 호출되고 난 후 본 함수가 호출된다.


그럼 아무나 decorator로 장식할 수 있을까?
아니다. decorator로 장식할 수 있는 함수는 중첩함수를 리턴하는 함수만 decorator 함수로 사용될 수 있다.
이유는 decorator의 기능을 다르게 설명하면 chain of functions, 즉 여러개의 함수가 연속적으로 호출이 자동으로 되게 해주는 것이다.

![](https://velog.velcdn.com/images%2Fmagnoliarfsit%2Fpost%2Fbc40e269-ab6d-4a67-b110-481333686eb5%2Fdecorator.png)

그럴려면 마지막 함수를 제외한 함수들은 그 다음 함수를 리턴해주어야 파이썬이 함수들을 차례대로 호출할 수 있다. 만약 다음 함수를 리턴하지 않고 다른 함수를 리턴해버리면, 그 다음 함수로 넘어가지 못하고 그냥 함수 실행이 종료되기 때문이다. 그렇기 때문에 decorator 함수는 그 다음 함수를 리턴해주어야 한다.

그럼 is_paid_user 함수를 decorator로 만들어 보자.

def is_paid_user(func):
	user_paid = True # 간단화 하기 위해 무조건 True
	
    def wrapper(*args, **kwargs):
    	if user_paid:
        	func()
        else:
        	return
    return wrapper
    
이제 is_paid_user를 decorator로 장식하면 된다.

@is_paid_user
def jackpot_stock_information():
	return "계시가 내려졌는데, 삼성전자를 사세요."
    

![](https://velog.velcdn.com/images%2Fmagnoliarfsit%2Fpost%2Faa23feb1-a372-411b-96fe-675eb943fbaa%2Fdecorator_chain.png)
왼쪽 상단의 greeting 함수에 적용될 decorator 함수를 구현하여 greeting 함수에 적용해주세요.
Greeting 함수가 호출 되었을때 decorator 함수에 parametor 값이 greeting 함수 리턴값의 다음에 붙혀져서 리턴되어야 합니다.  Decorator 함수의 이름은 name_decorator 여야 합니다.

예를 들어, 다음 처럼 정의 하여 name_decorator decorator에 "정우성"을 parameter로 적용하여 greeting을 호출하면:

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

greeting()

결과값은  다음과 같아야 합니다.

"Hello, 정우성"



# My Solution
def name_decorator(x):
  def deco(func):
      def wrapper():
          result = func() + x
          return result
      return wrapper
  return deco


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

# 코딩도장 - 매개변수가 있는 데코레이터를 참조하면서 만들었다.
# 조금더 공부해보자.

closure에 대한 추가 개념

코딩도장 - 매개변수가 있는 데코레이터 만들기

  • 데코레이터는 함수를 장식하는데, 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용하곤 한다.
  • 데코레이터 trace는 호출할 함수를 매개변수로 받는다. (trace는 추적하다라는 뜻인데, 프로그래밍에서 함수의 실행 상황을 추적할 때 trace라는 말을 사용한다.)
def trace(func):
# 호출할 함수를 배개변수로 받는다.
  • trace 함수 안에서는 호출할 함수를 감싸는 함수 wrapper를 만든다.
def wrapper():
  • 이제 wrapper 함수에서는 함수의 시작을 알리는 문자열을 출력하고, trace에서 매개변수로 받은 func를 호출한다. 그리고 함수의 끝을 알리는 문자열을 출력한다. 여기서 매개변수로 받은 함수의 원래 이름을 출력할 때는 name 속성을 활용한다. 마지막으로 wrapper 함수를 다 만들었으면 return 을 사용하여 wrapper 함수 자체를 반환한다.
def trace(func):                             # 호출할 함수를 매개변수로 받음
    def wrapper():                           # 호출할 함수를 감싸는 함수
        print(func.__name__, '함수 시작')    # __name__으로 함수 이름 출력
        func()                               # 매개변수로 받은 함수를 호출
        print(func.__name__, '함수 끝')
    return wrapper                           # wrapper 함수 반환
  • 즉, 함수 안에 함수를 만들고 반환하는 클로저이다.
  • 데코레이터를 사용할 할때는 trace에 호출할 함수 hello 또는 world를 넣는다.
  • 그 다음에 데코레이터에서 반환된 함수를 호출해준다. 이렇게 하면 데코레이터에 인수로 넣은 함수를 호출하고 시작과 끝을 출력할 수 있다.

매개변수 반환값을 처리하는 데코레이터

def trace(func):          # 호출할 함수를 매개변수로 받음
    def wrapper(a, b):    # 호출할 함수 add(a, b)의 매개변수와 똑같이 지정
        r = func(a, b)    # func에 매개변수 a, b를 넣어서 호출하고 반환값을 변수에 저장
        print('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r))  # 매개변수와 반환값 출력
        return r          # func의 반환값을 반환
    return wrapper        # wrapper 함수 반환
 
@trace              # @데코레이터
def add(a, b):      # 매개변수는 두 개
    return a + b    # 매개변수 두 개를 더해서 반환
 
print(add(10, 20))

#실행 결과
add(a=10, b=20) -> 30
30
  • add 함수를 호출했을 때 데코레이터를 통해 매개변수와 반환값이 출력된다.
  • 매개변수와 반환값을 처리하는 데코레이터를 만들 때는 먼저 안쪽 wrapper 함수의 매개변수를 호출할 함수 add(a, b)의 매개변수와 똑같이 만들어 준다.
  • wrapper 함수 안에서는 func를 호출하고 반환값을 변수에 저장한다. 그 다음에 print로 매개변수와 반환값을 출력한다. 이 때, func에는 매개변수 a와 b를 그대로 넣어준다. 또한, add함수는 두 수를 더해서 반환해야 하므로 func의 반환값을 return으로 반환해준다.
  • 만약 wrapper 함수에서 func의 반환갑을 반환하지 않으면 add 함수를 호출해도 반환값이 나오지 않는다. 참고로 wrapper 함수에서 func의 반환값을 출력할 필요가 없으면, return func(a, b) 처럼 func를 호출하면서 바로 반환해도 된다.



매개변수가 있는 데코레이터 만들기

코딩도장 - 매개변수가 있는 데코레이터 만들기

def is_multiple(x):              # 데코레이터가 사용할 매개변수를 지정
    def real_decorator(func):    # 호출할 함수를 매개변수로 받음
        def wrapper(a, b):       # 호출할 함수의 매개변수와 똑같이 지정
            r = func(a, b)       # func를 호출하고 반환값을 변수에 저장
            if r % x == 0:       # func의 반환값이 x의 배수인지 확인
                print('{0}의 반환값은 {1}의 배수입니다.'.format(func.__name__, x))
            else:
                print('{0}의 반환값은 {1}의 배수가 아닙니다.'.format(func.__name__, x))
            return r             # func의 반환값을 반환
        return wrapper           # wrapper 함수 반환
    return real_decorator        # real_decorator 함수 반환
 
@is_multiple(3)     # @데코레이터(인수)
def add(a, b):
    return a + b
 
print(add(10, 20))
print(add(2, 5))

#실험 결과
add의 반환값은 3의 배수입니다.
30
add의 반환값은 3의 배수가 아닙니다.
7




Scope

  • scope는 범위라는 의미이다.
  • 프로그래밍에서 scope란 어떤 객체 (변수, 함수 등)가 유효한 범위를 이야기한다. 범위를 벗어나면 해당 객체는 사용될 수 없다.
  • Python에서 scope는 항상 객체가 선언된 지점에서 위로는 상위 객체까지, 아래로는 모든 하위 객체들과 그 안에까지가 범위이다.
  • 범위에는 크게 4가지가 있다.
    • Local Scope
    • Enclosed Scope
    • Global Scope
    • Built-in Scope
  • Local Scope가 가장 범위가 제한적이고 built-in scope가 가장 범위가 광범위하다.

Local Scope

  • Local scope을 가지고 있는 변수나 함수 혹은 객체는 이름 그대로 특정 범위에서만 유효합니다.
    주로 함수 안에서 선언된 변수나 함수가 local scope을 가지고 있습니다. 그리고 이러한 변수들은 해당 함수 안에서만 유효합니다.


Enclosing Scope

  • Enclosing Scope는 중첩함수가 있을때 적용되는 scope입니다.
    부모 함수에서 선언된 변수는 중첩함수 안에서도 유효한 범위를 가지고 있습니다.


Global Scope

  • Global scope은 함수 안에서 선언된것이 아닌 함수 밖에서 선언된 변수나 함수를 이야기 합니다. 앞부분에서 설명했듯이, 변수나 함수는 선언된 지점과 동일한 level의 지역, 그리고 더 안쪽의 지역들까지 범위가 유효합니다.

  • 그리고 global scope을 가지고 있는 변수와 함수들은 선언된 지점이 해당 파일에서 가장 바깥쪽에서 선언되므로 해당 파일에서 선언된 지점 아래로는 다 유효한 범위를 가지고 있습니다. 그래서 "Global" scope 이라고 하는 것입니다.


Built-in Scope

  • Built-in scope은 scope중 가장 광범위한 scope입니다. 파이썬안에 내장되어 있는, 파이썬이 제공하는 함수 또는 속성들이 built-in scope를 가지고 있습니다.
  • 그리고 built-in scope는 따로 선언이 없이도 모든 파이썬 파일에서 유효한 범위를 가지고 있습니다.
  • 예를 들어, list등과 같은 자료구조의 element 총 개수를 리턴하는 len 함수가 바로 built-in scope를 가지고 있습니다.

Shadowing

  • 파이썬은 변수나 함수의 정의를 찾을때 다음 순서의 scope들 안에서 찾습니다.

Local => Enclosing => Global => Built-in

  • 즉 가장 좁은 유효범위 부터 시작 해서 넓은 범위로 나아가며 사용되는 변수나 함수의 정의를 찾습니다.
  • 그러므로 만일 동일한 이름의 변수들이 서로 다른 scope에서 선언이 되면 더 좁은 범위에 있는 변수(혹은 함수)가 더 넓은 범위에 있는 변수를 가리는 (shadowing)효과가 나타납니다.


왼쪽 상단에 코드를 수정하셔서 scope_test 함수에 parameter 값에 상관 없이 무조건 63이 리턴되도록 수정해주세요.

예를 들어 다음과 같이 되어야 합니다.

scope_test(10) == 63
scope_test(20) == 63
scope_test(333) == 63



Class

  • 코딩에서 사용되는 class 라는 단어가 가지는 의미는 "부류"에 가장 가깝다.
  • "부류" 는 사전에 "동일한 범주에 속하는 대상들을 일정한 기준에 따라 나누어 놓은 갈래" 라고 나오는데, 공통점이 많으면 "같은 부류" 라고 하곤 한다.
  • Comedian 이 class가 되는데, 이건 개념일 뿐이다.
  • 실체 (instance)는 이영자, 송영이, 양세형, 유병재이다.
  • 이 실체(instance)를 object(객체)라고 한다.

class 정의 하기

  • 파이썬에서 class를 선언하기 위해서는 다음과 같이 class 키워드를 사용하면 된다.
class ClassName:
	...class 내용 코드들
  • 참고로 class 이름은 각 단어의 앞글자를 대문자로 사용한다.
  • 한 단어 이상으로 이루어져 있다면 밑줄(underscore) 없이 모든 단어를 다 붙이되 각 단어의 앞글자는 대문자로 해서 단어를 구분하게 된다.
  • Car class는 다음처럼 정의 할 수 있다.
class Car:
	pass
  • class가 정의되면 class로 부터 실체화(instantiate) 할 수 있다. 클래스를 실체화 하는 방법은 간단하다. 다음과 같이 함수를 호출 하듯 클래스도 호출하면 된다.
hyundai = Car()
bmw = Car()
  • Car class를 실체(instance)화 한것이 hynndai와 bmw라는 객체(object)이다.
  • 하지만 Car class는 아직 아무것도 안하는 비어있는 class이다.
  • 앞서 class는 부류라고 했는데, 어떠한 공통점이 있어야한다고 말했다. 즉, 공통점이 중요한데, 자동차라면 브랜ㄷ, 엔진의 마력 등이 공통 요소일것이다. 이런 공통 요소를 class에서 정의를 해줘야 한다.

class의 attribute(속성)

  • class에 정의되는 공통 요소들을 전문어로 class의 attribue(성질 혹은 속성)이라고 한다.
  • 모든 자도아는 다음과 같은 공통 요소를 가지고 있다.
    • Maker
    • 모델명
    • 마력
  • 이 외에도 많겠지만 예제의 간단함을 위해서 위 3개만 고려해보겠다.
  • 위 3개의 속성들을 class에서 정의하기 위해서는 다음과 같이 __init__ 함수를 통해서 정의해주면 된다.
  • 참고로 class 안에서 정의해주는 함수(function)는 function이라고 하지 않고 method 라고 한다.
class Car:
    def __init__(self, maker, model, horse_power):
        self.maker       = maker
        self.model       = model
        self.horse_power = horse_power
  • 먼저 __init__ 메소드의 이름을 자세히 보면, init이라는 단어의 앞 뒤에 밑줄 2개가 있다. 이렇게 앞뒤로 2개가 있는 메소드들을 special methods 라고 한다. 이들은 특별취급을 받는다.
  • 이 중에서 __init__ 메소드는 class가 실체화 될 때 사용되는 함수이다.
  • 예를 들어, hyundai = Car("현대", "제네시스", 500)
  • 여기서 이미 __init 메소드가 이미 호출이 되었다. __init 이라고 메소드 이름을 명확하게 명시하지는 않았지만, 클래스가 실체화 될 때 자동으로 __init__ 메소드가 호출된다.
  • 근데 여기서 __init__ 메소드의 parameter는 총 4개인데, 앞 서 클래스를 실체화 할 때는 3개만 넘겨주었다. self 파라미터를 빼먹었는데, 이 self 파라미터는 도대체 무엇일까?
  • self는 자신이라는 의미인데, class에서 self는 class 자체는 아니다. class는 부류를 가리키는 개념이기 때문이다. 반면에 self는 어떠한 실체를 가리키는 단어이다.
  • 그렇다면 self는 당연히 class의 실체(instance)를 가르킨다.
  • self는 class의 실체(instance)인 객체(object)를 가르킨다.
  • 그러므로 Car class에서 "self" 는 Car class의 객체인 hyundai 나 bmw를 가르킨다. 그리고 클래스를 실체화 할 때, 파이썬이 해당 객체(self)를 자동으로 __init__ 함수에 넘겨준다.
    • __init__ 메소드는 클래스가 실체화 될 때, 자동으로 호출된다.
    • __init__ 메소드의 self 파라미터는 클래스가 실체화 된 객체를 넘겨줘야하며, 파이썬이 자동으로 넘겨준다.
    • __init__ 메소드의 self 파라미터는 항상 정의되어 있어야하며 맨 처음 파라미터로 정의되어야한다. (그래야 파이썬이 알아서 넘겨줄 수 있으므로)


 class Car:
    def __init__(self, maker, model, horse_power):
        self.maker       = maker
        self.model       = model
        self.horse_power = horse_power
  • __init__ 메소드 안의 코드이다.
  • parameter로 넘겨진 maker, model, horse_power 값을 동일한 이름으로 self에 저장하는 것을 볼 수 있다.
  • self는 객체라고 방금 배웠는데, 객체에 maker, model, horse_power를 저장하는 것이다.
  • 그건 다른 메소드에서 사용하기 위함이다.
profile
이제 막 배우기 시작한 개발자입니다.

0개의 댓글