Python 심화 - 1

lambda function

  • 익명 함수

  • 이름이 없다.

  • 함수를 재사용하지 않고 몇번 정도만 쓸때

    • 아래와 같이 일반적인 함수는 객체를 만듦

    • 재사용을 위해 함수 이름(메모리)를 할당

      1
      2
      3
      4
      5
      def func(a, b):
      return a + b

      func
      >> <function __main__.func(a, b)>
    • 아래와 같이 람다 함수를 쓸 경우, 변수에 할당해놓지 않으면 메모리에서 사라짐

    • 람다는 익명 함수이기 때문에 한번 쓰임

    • 다음 줄로 넘어가면 힙(heap) 메모리 영역에서 증발된다.

      1
      2
      3
      f = lambda a,b : a + b
      f(1, 2)
      >> 3
    • 파이썬의 함수 = 식(expression)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      # 이항식
      # ex) a+b
      a = 10
      b = 20
      a + b
      >> 30

      # 단항식
      # ex) 10
      # '10' 자체도 식이다.
      10
      >> 10

      # 문자열도 문자열 자체를 반환하기 때문에 식이다.
      "I am your father!"
      >> 'I am your father!'

      # return 값이 없어도 None을 반환하기 때문에 식이다.
      def func(a, b):
      a + b

      print(func(10, 20))
      >> None

      # print()도 None을 반환하기 때문에 식이다.
      print(print(func(10, 20)))
      >> None
      None
    • lambda는 return을 쓰지 않아도 무조건 값을 반환하기 때문에 return이 필요없다.

    • 맨 뒷자리에 무조건 식이 와야 한다.

      1
      2
      3
      f2 = lambda a, b: a + b
      f2(1, 2)
      >> 3
    • 아래와 같이 함수를 잠깐 쓰고 넘어갈 때 람다를 쓴다.

    • 이렇게 하면 함수가 메모리에 남지 않는다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      li = [5, 2, 3, 1, 7, 10]

      li.sort(key = lambda x: x % 2, reverse = True)
      li
      >> [5, 3, 1, 7, 2, 10]

      # key 값에 'x % 2' 조건이 있다.
      # 짝수는 0이 되고 홀수는 1이 된다.
      # li = [5, 2, 3, 1, 7, 10]이 [1, 0, 1, 1, 1, 0]이 된다.
      # sort를 쓰면 오름차순이지만, reverse를 썼기 때문에 1, 0 순으로 정렬된다.
      # 정렬 후에도 작은 숫자대로 정렬이 되지 않았는데, 이 것을 stable sort라고 한다.
  • filter, map, reduce

    • lazy evaluation(게으른 연산)

    • python에서도 filter, map, reduce 함수가 있다.

    • filter, map은 바로 쓸 수 있으나, reduce는 functools 모듈을 추가해주어야 한다.

      1
      2
      3
      4
      from functools import reduce

      reduce
      >> <function _functools.reduce>
    • filter

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      # lambda 함수 조건 : 양수만 출력
      # filter 맨 마지막 인자는 iterable 객체를 넣어준다.(순회 가능한 객체 ex) list 등)
      # filter 함수의 판단은 boolean이다.(True, False)

      li = [-3, 5, 1, 2, -5, -4, 14]

      f = filter(lambda e: e > 0, li)
      f
      >> <filter at 0x1db910>

      # next()를 통해 결과값을 순차적으로 호출할 수 있다.
      next(f)
      >> 5
      next(f)
      >> 1
      next(f)
      >> 2
      next(f)
      >> 14

      # 조건에 만족하는 수가 끝나면 StopIteration 에러가 발생한다.
      next(f)
      >> StopIteration

      # for문을 사용해서 조건에 맞는 수를 모두 출력할 수 있다.
      f = filter(lambda e: e > 0, li)

      for e in f:
      print(e, end = ' ')
      >> 5 1 2 14

      # list 형변환을 통해서 list로 받을 수 있다.
      without_neg = list(filter(lambda e: e > 0, li))
      without_neg
      >> [5, 1, 2, 14]
    • map, filter, reduce, generator 쓰는 이유 = lazy evaluation(게으른 연산)

    • 함수의 실행 시기는 내가 결정한다!

    • 내가 원할 때만 결과값을 가져온다!

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      li = [-3, 5, 1, 2, -5, -4, 14]

      def func(x):
      print("func executed")
      return x > 0

      f = filter(func, li)
      next(f)
      >> func executed
      func executed
      5

      next(f)
      >> func executed
      1

      result = []
      for elem in li:
      if elem > 0:
      result.append(elem)
      result
      >> [5, 1, 2, 14]

      f = filter(func, li)
      print("다른 작업") # 다른 작업을 나타냄
      >> 다른 작업

      next(f)
      >> func executed
      func executed
      5

      print("another job") # 다른 작업을 나타냄
      >> another job

      next(f)
      >> func executed
      1

      # 위 결과처럼 for문 안에 함수가 있으면 반복이 끝날 때까지 실행해야 한다.
      # 그러나 filter 함수는 내가 원하는 시점에서 부를 수 있다.(lazy evaluation)
    • map

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      li = [2, 3, 5, 7]

      # map = 맵핑한다.
      m = map(lambda x: x**2, li)

      # m은 map 객체
      # filter 객체와 map 객체 모두 generator 객체이다.
      # generator 객체는 iterator 객체이다.
      # filter 객체와 map 객체 모두 iterable 성질이 있다.
      # iterable 성질이 있다면 for문이 적용 가능하다.

      next(m)
      >> 4
      next(m)
      >> 9
      next(m)
      >> 25
      next(m)
      >> 49

      # map 또한 조건에 만족하는 수가 끝나면 StopIteration 에러가 발생한다.
      next(m)
      >> StopIteration
    • 양수를 골라내서 제곱한 값을 리스트로 만들기

      1
      2
      3
      4
      5
      6
      7
      8
      9
      li = [2, 3, -5, 6, -2, 1, -10]

      result_li = list(map(lambda x: x**2, filter(lambda e: e > 0, li)))

      print(result_li)
      >> [4, 9, 36, 1]

      # map 함수를 먼저 쓰게 되면 제곱을 할 경우, 음수도 양수가 되버린다.
      # filter, map 객체 모두 iterable 성질이 있어서 함수에 바로 적용 가능하다.
    • reduce

    • 자료구조(list, tuple)를 연산을 통해서 단 하나의 값으로 만드는 함수

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      from functools import reduce

      help(reduce)
      >> Help on built-in function reduce in module _functools:

      reduce(...)
      reduce(function, sequence[, initial]) -> value

      Apply a function of two arguments cumulatively to the items of a sequence,
      from left to right, so as to reduce the sequence to a single value.
      For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
      ((((1+2)+3)+4)+5). If initial is present, it is placed before the items
      of the sequence in the calculation, and serves as a default when the
      sequence is empty.
    • reduce 함수 설명을 보면 처음 인자(function)는 람다식

    • 두 번째 인자(sequence)는 순회할 수 있는 객체(iterable)

    • 반환값은 하나의 value로 나오는 것을 알 수 있음

      1
      2
      3
      4
      5
      6
      7
      8
      9
      li = [2, 3, -5, 6, -2, 1, -10]

      result = reduce(lambda a, b: a + b, li, 100)

      # 끝에 100을 넣어주면 처음 a에 2가 들어가는 것이 아니라 100이 들어간다.
      # 100과 같이 초기값이 없다면 a에 2가 들어가고 b에 3이 들어간다.

      result
      >> 95
    • 최소값 구하기

      1
      2
      3
      4
      5
      6
      7
      li = [3, 6, 8, -10, 2, 1, 100, 50, 46, -47]

      result = reduce(lambda a, b: a if a < b else b, li)
      result
      >> -47

      # 최대값을 구하려면 a > b하면 된다.¶
    • 문자수 세기

    • 결과값은 dictionary로 표시

    • python 함수는 식이므로 무조건 값을 반환

    • 논리 연산을 할 때 마지막으로 참조한 값을 반환

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      dic = {'a' : 1, 'b' : 2}

      # a가 존재하면 a의 value 값을, a가 존재하지 않으면 0을 나타낸다.
      dic.get('a', 0)
      >> 1

      # update()의 반환값은 None이다.
      print(dic.update({'a' : 5}))
      >> None

      li = ['a', 'b', 'a', 'b', 'b', 'a', 'c', 'a']

      result = reduce(lambda dic, ch: dic.update({ch : dic.get(ch, 0)+1}) or dic, li, {})
      result
      >> {'a': 4, 'b': 3, 'c': 1}

      # update()는 None을 반환하기 때문에 or를 이용해서 dic 객체를 반환한다.
      # 논리 연산에서는 마지막 참조한 값을 반환한다.
      # 예를 들어 or 앞의 값이 False이면 뒤의 값까지 보고 판단한다.
      # 따라서 앞이 False라면 뒤의 값(마지막 값)을 참조하게 된다.
      # dic.get(ch, 0)은 해당 키 값이 있다면 키 값을 반환해주고 없다면 0을 반환해준다.
      # 처음 초기값 {}(빈 딕셔너리)가 dic에 들어가고 ch에 'a'가 들어간다.
      # 그 다음 or 연산을 통해 update()를 실행한 후, 결과값이 dic에 들어가게 된다.

Decorator(데코레이터)

  • 쉽게 기능을 추가할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 처음 함수를 정의할 때 쓰는 *의 의미는 가변인자를 사용한다는 의미이다.
    # 함수를 호출할 때 쓰는 *의 의미는 unpacking을 의미한다.(tuple, dictionary)

    def outer(org_func):
    def inner(*args, **kwargs):
    # 추가되는 기능
    print("여기에 기능 추가")
    return org_func(*args, **kwargs)
    return inner

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

    func.__name__
    >> 'func'
    • 일반적인 기능 추가

      1
      2
      3
      4
      5
      6
      7
      func = outer(func) # inner
      func.__name__
      >> 'inner'

      func(4, 6)
      >> 여기에 기능 추가
      10
    • 데코레이터를 사용한 기능 추가

      1
      2
      3
      4
      5
      6
      7
      @outer
      def func(a, b):
      return a + b

      func(4, 6)
      >> 여기에 기능 추가
      10
  • 데코레이터를 사용하여 경과 시간 측정 함수 기능 추가

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    from functools import wraps
    import time

    def benchmarker(org_func):
    @wraps(org_func)
    def inner(*args, **kwargs):
    start = time.time()
    result = org_func(*args, **kwargs)
    elapsed = time.time() - start
    print(f'elapsed time : {elapsed:.2f}')
    return result
    return inner

    @benchmarker
    def something(a, b):
    time.sleep(2) # C언어에서는 ms이지만 python에서는 sec이다.
    return a + b

    something(1, 2)
    >> elapsed time : 2.00
    3

    something.__name__
    >> 'something'

    # wraps()를 사용하면 원래 함수의 이름을 가질 수 있다.
    # wraps()를 사용하지 않으면 something의 이름은 inner가 된다.
  • 데코레이터를 사용하여 callcounter 함수 기능 추가

    • 어떤 함수를 호출한 횟수를 보여주는 함수

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      g_call_num = 0

      def callcounter(org_func):
      @wraps(org_func)
      def inner(*args, **kwargs):
      global g_call_num
      g_call_num += 1
      print(f'{g_call_num}번 호출되었습니다.')
      return org_func(*args, **kwargs)
      return inner

      @callcounter
      def func(a, b):
      return a + b

      for _ in range(10):
      print(func(10, 5))
      >> 1번 호출되었습니다.
      15
      2번 호출되었습니다.
      15
      3번 호출되었습니다.
      15
      4번 호출되었습니다.
      15
      5번 호출되었습니다.
      15

      #경과 시간 측정 함수까지 기능 추가
      @benchmarker
      @callcounter
      def another_func(a, b):
      return a + b

      another_func.__name__
      >> 'another_func'

      another_func(10, 2)
      >> 6번 호출되었습니다.
      elapsed time : 0.00
      12
Share