[day-13] 메서드 체이닝, 일급함수/고차함수, 재귀함수

Joohyung Park·2024년 1월 16일
0

[모두연] 오름캠프

목록 보기
13/95

메서드 체이닝

메서드에 리턴된 값이 순차적으로 해소가 되는 방식을 뜻한다

'Hello World'.replace('Hello', 'hi').lower()
# 그 메서드에 리턴된 값이 순차적으로 해소가 되는 방식

# 'hello world'.a().b().c().d()
# 'Hello World'.split().lower() # error
'Hello World'.split()[0].lower()

첫번째 점부터 마지막점까지 순차적으로 실행이 된다.

짤막상식

문자열을 리스트로 형변환하면 어떻게 될까?

list('hello world')
# 출력 
['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd']

클래스에서 알아보는 메서드 체이닝

class Calculator:
    def __init__(self, value):
        self.value = value

    def add(self, other):
        self.value += other
        return self

    def subtract(self, other):
        self.value -= other
        return self

    def multiply(self, other):
        self.value *= other
        return self

    def get_value(self):
        return self.value

calc = Calculator(1)

result = calc.add(2).subtract(1).multiply(3).get_value()
print(result)  # 결과: 6

multiply까지는 calc를 반환하지만 get_value는 value를 반환하므로 get_value 이후에는 문자열 메서드를 사용하지 못한다. 따라서 형변환을 진행하는 등의 조치가 필요하다.

짤막상식

int, float, complex 자료형에 모두 있는 것은?

a = 3
b = 3.5
c = 3j + 1  # i를 많이 사용하기에 j를 대신 사용함

dir(a)
dir(b)
dir(c)

# 'imag'    # 허수부
# 'real'    # 실수부

dir()로 각 변수를 찍어보면, a, b, c 세 변수 모두 실수부와 허수부를 메서드로 갖고 있다. 이는 복소수와의 연산을 위한 것 같다.


일급함수와 고차함수

일급 함수는 함수를 값(변수)로 취급하는 것

def greet(name):
    return f'Hello, {name}'

say_hello = greet
print(say_hello("World"))  # 출력: Hello, World

위의 예시처럼 함수는 변수로 사용할 수 있다.

짤막상식

변수가 너무 길때, 줄여서 사용하기도

l = [10, 20, 30]
# l.append(100)
add = l.append
add(10)
add(20) 
# 이렇게 사용할 일은 거의 없다.
# 변수가 너무 길 때, 오타가 많이 생기는 변수일 때, 
# 메서드 명도 길때 줄여서 사용하기도 한다!
l	# [10, 20, 30, 10, 20]

팀프로젝트에서는 사용하면 안되겠지만 개인 프로젝트에서는 간단히 줄여서 사용하기도 한다고 한다.

함수명 변수처럼 사용하기 예제

def add(x, y):
    return x + y

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

funcs = [add, subtract]
print(funcs[0](2,3))    # 출력 5
class Operator:
    def add(self, x, y):
        return x + y

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

    def mul(self, x, y):
        return x * y

    def div(self, x, y):
        return x / y

    def _and(self, x, y):
        pass

    def _or(self, x, y):
        pass

op = Operator()
logical_op = {
    'add': op.add,
    'sub': op.sub,
    'mul': op.mul,
    'div': op.div,
}
arithmetic_op = {
    '_and': op._and,
    '_or': op._or,
}

print(logical_op['add'](2, 3)) # 출력: 5 마치 op.add(2, 3)인 것 처럼 사용할 수 있다. 
print(op.add(2, 3)) # 출력: 5. 위와 동일

for _, f in logical_op.items():
    print(f(2, 3)) # 4칙연산 모두 계산

위의 함수와 아래 함수는 같은 역할을 한다.

def add(a, b):
    return a + b

add(2, 3)

정리하면 인스턴스나 클래스에 직접 접근하지 않고도 내가 만든 변수로 관리 할 수 있다는 장점이 있다.

함수는 함수를 반환 가능하다

def add(a, b):
    return a + b

def say_hello(name):
    return f'Hello, {name}'

def greet(func, name):
    return func(name)

print(greet(say_hello, 'World'))  # 출력: Hello, World

위와 같이 작성할수도 있고, 아래와 같이 사용할 수도 있다.

# 별표 1개 => 데코레이터를 이해하기 위해 중요한 개념
# 함수를 아규먼트로 받거나 반환할 수 있다 => 고차함수
def add(a, b):
    return a + b

def say_hello(name):
    return f'Hello, {name}'

def greet(func):
    return func

print(greet(say_hello)('World'))  # 출력: Hello, World

다른 예시를 보자.

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

add_5 = create_adder(5)
print(add_5(10))  # 출력: 15

위 함수에서 x=5라는 값이 들어가는데 분명이 함수가 끝났음에도 x가 살아서 add_5(10)으로 y값을 받아 15를 출력하는 것을 볼 수 있다.
이는 변수가 하나라도 참조중이면 사라지지 않음을 알 수 있다. 이를 클로져라고 한다.

짤막상식

변수를 변경 불가능하게 감추기 위한 코딩 기법이 존재한다

def create_sq(x):
    def sq(y):
        return x ** y
    return sq

제곱2 = create_sq(2)    # x를 변경 불가능하게 감추기 위한 코딩 기법
제곱3 = create_sq(3)
제곱4 = create_sq(4)

제곱2(2), 제곱2(3), 제곱2(4)
제곱3(2), 제곱3(3), 제곱3(4)
제곱4(2), 제곱4(3), 제곱4(4)

위의 코드를 보면 x를 따로 집어넣지 않고 뒤에서 활용하고 있다.


재귀함수

  • 내가 나를 호출하는 함수
  • 정말 자신이 있는게 아니면 반복문 사용을 권장
  • 억지로 사용하지 말 것
  • 잘못 사용하면 비효율의 끝판왕
def 숫자출력(count):
    if count > 100:
        return
    print(count)
    return 숫자출력(count+1) #값을 1부터 반복횟수 까지의 값을 출력

숫자출력(1)

재귀함수로 구현 가능한 것은 대부분 for문으로 쉽게 구현이 가능하다.

# 권장
for i in range(1, 101):
    print(i)

재귀함수는 알고리즘 문제(분할 정복, 다이나믹 프로그래밍)에서 쓰인다고 한다.

# f(5) => 5 * 4 * 3 * 2 * 1 == 120

def f(n):
    if n <= 1:
        return n 
    return n * f(n-1)

f(5)	# 120

위의 코드보단 아래의 코드를 권장한다.

# 권장
result = 1
for i in range(1, 6):
    result *= i
result

몇가지 예시를 더 보고 마무리 하자.

def f(n):
    if n <= 1:
        return n
    return n * f(n-1)

# f(5)        5 * f(4) == 5 * 24 => 120
# f(4)        4 * f(3) == 4 * 6
# f(3)        3 * f(2) == 3 * 2
# f(2)        2 * f(1) == 2 * 1
# f(1)        1
s = ''
for i in 'hello':
    s = i + s
s	# 'olleh'

# s = 'h' + ''
# s = 'e' + 'h'
# s = 'l' + 'eh'
# s = 'l' + 'leh'
# s = 'o' + 'lleh'
def f(s):
    if len(s) <= 1:
        return s
    return f(s[1:]) + s[0]

f('hello')

# f('hello')      f('ello') + 'h' == 'olleh'
# f('ello')       f('llo') + 'e' == 'olle'
# f('llo')        f('lo') + 'l' == 'oll'
# f('lo')         f('o') + 'l' == 'ol'
# f('o')          'o'

연습문제

문제 1: 함수를 인자로 받는 고차함수

주어진 리스트와 함수를 인자로 받아, 리스트의 각 요소에 해당 함수를 적용한 결과를 반환하는 고차함수 apply_function을 작성해주세요.

# 첫번째 방법
def apply_function(lst, func):
    return [func(i) for i in lst]

# 예제 사용
result = apply_function([1, 2, 3, 4], lambda x: x * 2)
print(result)  # [2, 4, 6, 8]이 출력되어야 함
# 두번째 방법(권장)
def apply_function(lst, func):
    return list(map(func, lst))

# 예제 사용
result = apply_function([1, 2, 3, 4], lambda x: x * 2)
print(result)  # [2, 4, 6, 8]이 출력되어야 함

문제 2: 함수를 반환하는 고차함수

정수 n을 인자로 받아, 다른 정수 x에 n을 곱하는 함수를 반환하는 고차함수 multiply_by_n을 작성해주세요. 힌트를 드리자면 반환 값은 lambda여야 합니다.

def multiply_by_n(n):
    return lambda x: x * n

# 예제 사용
multiplier = multiply_by_n(5)
print(multiplier(3))  # 15가 출력되어야 함

피드백

어제 연습문제를 풀면서 어렵다라고 생각했는데 오늘 풀이를 보니까 다들 똑같이 생각한 것 같다 ㅋㅋㅋ. 그건 그렇고.. 오늘은 파이썬 심화과정으로 메서드 채이닝, 일급함수와 고차함수, 재귀함수에 대해 배워봤다. 메서드 채이닝은 이전에도 몇번씩 해서 괜찮았던 것 같고 일급함수와 고차함수라는 단어를 처음 들었는데 중요하다고 하셨던 것 치고는 생각보다 어렵진 않았어서 한번 싹 훑고 지나갔다. 재귀함수는 예전에 몇번 보긴 했는데 그때도 그렇지만 지금도 조금 헷갈리는 부분이 있는 것 같다. 이쯤되면 일부러 그렇게 만든걸수도?? for문으로 쓰는게 익숙해지기 전까지 좋다고 하시니까 그렇게 하도록 노력하겠다.
오늘 부로 책 집필 프로젝트(코팅 테스트)를 시작하게 되었는데 어차피 해야할 공부를 다같이 한다는게 좋은 것 같다. 혼자하면 중간에 어렵다고 그런 경우도 있으니까. 문제는 pyalgo 100제로 시작을 할 것 같고.. 일주일에 2번가량 모여서 설명해주고 책 페이지 채우고 이런식으로 진행할 것이다. 뭔가 혼자하면 안할 것 같아서 스터디고 뭐고 신청을 했는데 모두 포기하지 말고 끝까지 가서 좋은 결과가 있으면 좋겠다.

profile
익숙해지기 위해 기록합니다

0개의 댓글