Python

Python(16) 일급 함수와 고차 함수, 클로저, 데코레이터

UserDonghu 2023. 9. 20. 19:24

일급 함수 : 함수나 메서드를 일급 객체(값, 주소) 취급

고차 함수 : 함수를 아규먼트로 받거나 return값으로 반환하는 함수

 

donghu = print
donghu('Hello World') # Hello World
class Cat:
    def sound(self):
        print('냐옹')

licat = Cat()
licat_sound = licat.sound
licat_sound() # licat.sound()와 같다.
l = [10, 20, 30]
la = l.append
la(40)
print(l) # [10, 20, 30, 40]
def add(x, y):
    return x + y

def sub(x, y):
    return x - y

funcs = [add, sub]
print(funcs[0](2, 3)) # 5
Class Operator:
	def add(x, y):
    	return x + y
        
op = Operator()

logical_op = {'add': op.add}

logical_op['add'](2, 3) # 5

 

함수를 인자로

def Cat(p)
	p('야옹')
    
Cat(print) # 야옹
def square(x):
    return x ** 2

def width_circle(r, s):
    return s(r) * 3.14

print(width_circle(10, square)) # 314.0

 

함수를 결과로 반환

def create_adder(x):
    def adder(y):
        return x + y
    return adder

add_5 = create_adder(5)
print(add_5(10))  #  15
def create_exponent(x):
    def exponent(y):
        return y ** x
    return exponent

exponent_2 = create_exponent(2) # exponent
exponent_3 = create_exponent(3) # exponent
print(exponent_2(10)) # 출력: 100
# print(exponent(10)) => return y ** 2(이 숫자는 상수취급되어 변경시킬 수 없게 됨.)
print(exponent_3(10)) # 출력: 1000
# print(exponent(10)) => return y ** 3

 

함수 안의 변수는 항상 휘발되나?

def f():
    l = [10, 20, 30]
    print(id(l))
    return l

sample = f()
id(sample) # 주소가 같다. 참조가 되어있으면 휘발되지 않음
import sys

def ff():
    l = [20, 30, 40]
    return l

sample_ = ff()
sys.getrefcount(sample_) # win, mac, linux 출력 값이 다름
# 중요한 포인트는 getrefcount의 작동 원리가 아니라
# 함수가 종료되어도 참조하는 변수가 있다면 값이 사라지지 않는다는 것
# getrefcount가 0이 되면 휘발됨

 

클로저

함수 내의 함수가 바깥쪽 변수를 참조하여 보존하는 것.

외부에서 접근 할 수 없게 데이터를 은닉하거나 캡슐화를 구현 할 수 있음.

# 클로저 아닌 경우
# 보통 클로저는 outer_function에 은닉할 값을 매개변수로 값을 넘겨줌
def outer_function():
    def inner_function():
        return 100+100
    return inner_function

# 클로저인 경우
def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

inner = outer_function(100)
inner(200) # inner 입장에서 100을 변경할 수 있는 방법이 없음.
def make_counter():
    count = 0

    def counter():
        nonlocal count
        count += 1
        return count

    return counter

counter_a = make_counter()
print(counter_a())  # 1 # count에 접근 할 수 있는 방법이 없음.
print(counter_a())  # 2

 

데코레이터

함수 또는 메서드를 꾸며주는 함수

Django에서는 다음과 같이 사용

def login(arg):
    pass

@login
def 게시판읽기():
    pass

 

데코레이터는 고차 함수.

함수를 인자로 받아서 새로운 함수 반환

def simple_decorator(function):
    def wrapper():
        print("Before the function call")
        function()
        print("After the function call")
    return wrapper

@simple_decorator
def hello():
    print("Hello, World!")

hello() # 이렇게만 하면 simple_decorator에 hello를 아규먼트로 넣어주고 실행
# 데코레이터가 없는 상태에서는 simple_decorator(hello)() 와 같음


# 데코레이터가 없는 경우
def simple_decorator(function):
    def wrapper():
        print("Before the function call")
        function()
        print("After the function call")
    return wrapper

def hello():
    print("Hello, World!")

simple_decorator(hello)()
# simple_decorator(hello) => wrapper
# simple_decorator(hello)() => wrapper()

 

# step 1
def data_Preprocessing(function):
    def wrapper():
        pass
    return wrapper

@data_Preprocessing
def mean(data):
    return sum(data)/len(data)

mean([1, 2, '3', 4, '5']) # TypeError 발생
# step 2
def data_Preprocessing(function):
    def wrapper(data):
        print(data)
    return wrapper

@data_Preprocessing
def mean(data):
    return sum(data)/len(data)

mean([1, 2, '3', 4, '5']) # 데이터만 출력하고 함수는 None을 반환
# step 3
def data_Preprocessing(function):
    def wrapper(data):
        return function(list(map(int, data)))
    return wrapper

@data_Preprocessing
def mean(data):
    return sum(data)/len(data)

mean([1, 2, '3', 4, '5']) # 출력: 3.0 (정상 작동) # mean을 실행하는게 아님

# data_preprocessing(mean) => wrapper
# data_preprocessing(mean)([1, 2, '3', 4, '5']) => wrapper([1, 2, '3', 4, '5'])

 

인자가 어디에 들어가는지 확인

def wrapper(a, b, c):
    print(a, b, c)
    print(function([a, b, c]))
return wrapper

@data_Preprocessing
def mean(data):
    return sum(data)/len(data) # data랑 얘는 상관이 없음. wrapper에 들어가기 때문

mean(10, 20, 30) # data인자가 하나고, 10, 20, 30인자가 3개인데 오류가 없는 이유: 10, 20, 30이 wrapper에 들어감

 

동작 상세

def outer(function):
    def wrapper():
        function()()
        return 'hello'
    return wrapper

@outer
def a():
    def b():
        print('hello b')
    return b

print(a())

# a()가 호출되면 outer(a)로 들어감. return wrapper에서 wrapper()호출,
# function()에서 a()호출, return b 이므로 a()() = b() 호출, print('hello b')하고,
# print(a()) 이므로 wrapper()의 리턴인 'hello' print

이 밑에서 부터는 나중에 필요할 때 다시 공부..

 

중첩 데코레이터

def add_exclamation(function):
    def wrapper(text):
        print(f'add_exclamation 데코레이터 시작')
        result = function(text) + "!"
        print(f'add_exclamation 데코레이터 종료')
        return result
    return wrapper

def add_question_mark(function):
    def wrapper(text):
        print(f'add_question_mark 데코레이터 시작')
        result = function(text) + "?"
        print(f'add_question_mark 데코레이터 종료')
        return result
    return wrapper

def add_dot(function):
    def wrapper(text):
        print(f'add_dot 데코레이터 시작')
        result = function(text) + "."
        print(f'add_dot 데코레이터 종료')
        return result
    return wrapper

@add_dot
@add_question_mark
@add_exclamation
def greet(message):
    print('함수실행!')
    return message

result = greet('Hello')
print(result)

# add_dot 데코레이터 시작
# add_question_mark 데코레이터 시작
# add_exclamation 데코레이터 시작
# 함수실행!
# add_exclamation 데코레이터 종료
# add_question_mark 데코레이터 종료
# add_dot 데코레이터 종료
# Hello!?.

 

정적 데코레이터

데코레이터를 감싸는 함수를 더 만들어서 아규먼트를 받는 방법

def add(n):
    def decorator(function): # 여기서부터의 기능은 같음
        def new_function(a, b):
            print(f'plus 함수가 {n}만큼 증가시키는 데코레이터가 시작됩니다.')
            result = function(a, b)
            print(result)
            print(f'plus 함수가 {n}만큼 증가시키는 데코레이터가 종료됩니다.')
            return result + n
        return new_function
    return decorator

@add(1000)
def plus(a, b):
    print('plus 함수가 호출되었습니다.')
    return a + b

result = plus(10, 20)
print(f'result : {result}')

# plus 함수가 1000만큼 증가시키는 데코레이터가 시작됩니다.
# plus 함수가 호출되었습니다.
# 30
# plus 함수가 1000만큼 증가시키는 데코레이터가 종료됩니다.
# result : 1030

 

 

'Python' 카테고리의 다른 글

Python(18) f-string 문법  (0) 2023.09.21
Python(17) args, kwargs, 이터레이터와 제너레이터  (1) 2023.09.20
Python(15) 예외 처리와 오류 관리  (0) 2023.09.19
Python(14) 모듈  (0) 2023.09.19
Python(13) 클래스 심화  (1) 2023.09.19