오늘은 오전에 lms 강의를 들었고, 오후에는 파이썬 심화과정이 진행됐습니다.
lms 듣는 것보다는 강의를 듣는게 훨씬 좋지만 어제는 약속으로 인해 오전을 lms 듣고
시간을 보내는게 컨디션 관리로 보면 좋았습니다.
오전시간에 어제 못한 과제도 하고 lms 강의도 들었지만 사실 정신이 없었습니다.^^
그래도 점심에 잠깐 자고 일어났더니 오후에 집중이 잘되었습니다.
오늘 배운 것들 정리해보겠습니다.


메서드 체이닝

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

'Hello World'.replace('Hello', 'hi') -> 'hi world'
'hi world'.lower() -> 'hi world'
다음과 같은 순서대로 진행됩니다.

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

result = calc.add(2).subtract(1).multiply(3).get_value() 뒤에 붙일 수 있는 함수가 위의 클래스에 있을까요?
없습니다. 마지막 get_value()의 리턴 값이 객체의 value 인데 add(), subtract(), multiply() 모두 객체를 받는
함수입니다.
예를 들어 'hello world'.replace('hello', 'hi').upper().append() 이 코드도 에러가 납니다.
이유는 append() 는 list에 사용하는 함수라서 문자열을 리턴하는 upper() 다음에 쓸 수가 없습니다.


일급함수와 고차함수

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

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

일급함수는 함수를 값으로 취급하는 것을 얘기합니다.
위에 greet() 함수를 정의하고, 아래에서 변수 say_hello 에 함수명 greet 를 할당했습니다.
아래 print문에서 greet('World') 으로 함수를 호출하지 않고, say_hello("World")
같은 함수를 호출했습니다.

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칙연산 모두 계산

결과
5
5
5
-1
6

우리가 실제 코딩하는 것은 여러 클래스 또는 인스턴스의 조합으로 이뤄져 있다고 합니다.
Django만 하더라도 대부분의 것들이 class로 구현되어 있고, 인스턴스나 클래스에 직접 접근하지 않고
내가 만든 변수로서 관리 할 수 있습니다.

# 별표 1개 => 데코레이터를 이해하기 위해 중요한 개념입니다.
# 1번 
def say_hello(name):
    return f'Hello, {name}'

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

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


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

def greet(func):
    return func

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

1번과 2번이 똑같은 결과를 나타냅니다. greet() 함수에서 리턴을 함수명으로 하는지 함수 그자체 객체로 하는지
보는게 중요합니다.

# 핵심은 함수를 리턴할 수 있다.! (데코레이터에 핵심 개념)
# 중요한 개념: x라는 변수가 함수가 끝났는데도 불구하고 살아있다? 클로져!
def create_adder(x):
    def adder(y):
        return x + y
    return adder

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

핵심은 함수를 리턴할 수 있다는 것입니다. 그리고, 기본적으로 함수내 변수는 함수가 끝나면 사라집니다.
하지만 위처럼 함수내 함수에서 x가 사용되니 사라지지 않고, 다시 쓰입니다.
중요한 개념이니 꼭 알아두라고 하시네요^^

# 나를 감싸고 있는 곳에서 부터 변수의 값을 찾는다.
x = 100
def one():
    def two():
        def three():
            print(x)
        three()
    two()

one()

함수 three() 내에 x가 없다고 바로 전역변수에서 값을 찾지 않습니다.
three() 밖으로 나와서 two() 내에서 x가 있는지 찾고, 없으면 나와서 one() 내에서 찾습니다.
함수내에 값이 없으니 그제서야 전역변수의 x값을 인지하고, 출력합니다.

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)

다른 예제입니다. 위처럼 함수를 리턴해서 사용할 수 있습니다.


재귀함수

# 이런 코드를 연습해봄으로 재귀에 친숙해질 수 있습니다.
# 분할 정복, 다이나믹 프로그래밍
# f(5) => 5 * 4 * 3 * 2 * 1 == 120 == 5! (수학 공식으로는 5! 로 표현합니다.)

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

f(5)    

for i in range(1, 6):
    i *= i

result

재귀함수는 내가 나를 호출하는 함수입니다.
정말 자신있지 않다면 반복문을 추천해주셨습니다.
잘못 사용하면 비효율의 끝판왕이 되기 때문에 억지로 사용하려 하지 않는게 좋다고 합니다.
위의 함수는 재귀함수의 기본이죠^^ 하나씩 써보면서 이해하는게 필요합니다.

s = ''
for i in 'hello':
    s = i + s

s

# s = 'h' + ''
# s = 'e' + 'h'
# s = 'l' + 'eh'
# s = 'l' + 'leh'
# s = 'o' + 'lleh'
# s = 'olleh'

다음과 같은 코드를 재귀함수로 만들 수 있습니다.
사실 위의 코드보고 결과가 왜 이렇게 나오지 순간 당황했습니다.
하나씩 해보는게 정말 중요한 것 같습니다. s = i + ss = s + i 의 결과값은 완전히 다릅니다.

def f(s):
    if len(s) <= 1:
        return s
    else:
        #return s[0] + f(s[1:])
        return f(s[1:]) + s[0]

# 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'

f('hello')

재귀함수로 만들어도 마찬가집니다. return f(s[1:]) + s[0]return s[0] + f(s[1:])
의 결과는 반대입니다.


재밌는 이야기도 많이 해주셔서 즐겁게 강의를 듣고 있습니다.
조금씩 과제도 어려워지고 있어서 걱정은 되지만 끝까지 화이팅해야죠.
내일 다시 오겠습니다^^