Skip to content

Python_1

jjin-choi edited this page Jul 15, 2020 · 2 revisions

§ Intro

§ 함수와 함수형 프로그래밍

1. 파이썬 함수의 특징

  3 def add(x, y):
  4     return x+y
  5  
  6 a= add(3, 4)
  7 print (a)
  8 print (type(add))
  9 print (add)
  • output
    • line 7 ) 7
    • line 8 ) <class 'function'>
    • line 9 ) <function add at 0x7f682f067e18>
 11 import sys         
 12 sys.getrefcount(add) 
 13     
 14 add1 = add         
 15 a = add1(3, 4)     
 16     
 17 print(type(add1))  
 18 print (add1)       
 19 sys.getrefcount(add)  
  • add1 과 add 의 주소값이 같다 ↔ 같은 객체를 가리키고 있음
  • sys.getrefcount 는 뭘까?
  • 파이썬에서는 함수가 객체이다. (symbol로 가리킬 수 있는 모든 type이 모두 객체이다.)
  • 함수는 refcount = 0 일때 완전히 제거된다.

2. default parameter

  • 인자는 함수를 호출했을 때 정해지지만, default parameter는 함수 정의 시에 정해진다.
  1 a = 10
  2  
  3 def foo(x=a):
  4     return x
  5  
  6 a = 5
  7 print (foo())  
  • default parameter는 정의 시 정해지며, runtime 상에서는 사라지지 않는다.
 10 def foo(x, items=[]):
 11     items.append(x)
 12     return items
 13  
 14 print(foo(1))
 15 print(foo(2))
 16 print(foo(3))
  • 따라서 처음 정의시에는 None 이라고 넣고, None 일 때 생성하도록 수정
 18 def foo(x, items=None):
 19     if items is None:
 20         items = []
 21     items.append(x)
 22     return items
 23  
 24 print(foo(1))   
 25 print(foo(2))
 26 print(foo(3))

3. 가변 인자

  • *가 붙은건, python 형태의 가변인자 (pointer가 아님)

  • 인자에 *을 사용하면 남아 있는 모든 인수가 __튜플__로서 packing되어 args 변수에 저장된다.

  • 튜플은 값을 바꿀 수 없음

  • 키워드 인자 : foo(x=3, y=22, w='hello', z=[1,2]) 라고 넣어도 x, y, w, z 가 key 역할을 하여 실제 function foo 에서의 key 값을 찾아 값을 대입한다.

    • 기본 인자랑 키워드 인자를 섞어서 넣을 수 있음
    • 하지만 기본 인자는 순서대로 들어가기 때문에 뒤에 남은 키워드 인자는 앞에서의 기본 인자를 다시 설정해줄 수 없다.
  • **가 붙으면, 가변 키워드 인수라고 한다.

  • 가변 키워드 인자는 __딕셔너리__로서 저장한다.

    • %> parms.pop("fgcolor", "black") : fgcolor 의 default는 black 이고 값이 들어오면 그 값으로.
  1 def make_table(data, **params):     
  2     fgcolor = params.pop("fgcolor", "black")
  3     bgcolor = params.pop("bgcolor", "white")
  4     width = params.pop("width", None)
  5  
  6     print (fgcolor)
  7     print (bgcolor)
  8     print (width)
  9  
 10     if params:
 11         raise TypeError (f"Unsupported configuration options {list(params)}")
 12  
 13 items = [1, 2, 3]
 14 make_table(items, fgcolor="red", bgcolor="black", border=1)

4. 키워드 매개변수만 받는 함수

  1 def recv(maxsize, *, block=True):
  2     print (maxsize, block)
  3  
  4 recv(8192, block=False)     # Work
  5  
  6 try:
  7     recv(8192, False)       # Fail
  8 except TypeError as e:
  9     print (e)
 10  
 11  
 12 def minimum(*values, clip=None):
 13     m = min(values)
 14  
 15     if clip is not None:
 16         m = clip if clip > m else m
 17     return m
 18  
 19 print (minimum(1, 5, 2, -5, 10))
 20 print (minimum(1, 5, 2, -5, 10, clip=0)) 
  • python 에서는 call by reference 이므로 함수 내에서 값 변경 시 원래 객체 값에도 반영된다.
  • ※ m = clip if clip > m else m
  • ※ for I, x in enumerate(items)
  • 여러 값을 return 하면 튜플에 넣어서 1개의 값으로 return 된다.

5. 유효 범위 (scope)

  • 전역 변수 a가 있고 함수 내부a 가 있을 때 함수 내부의 a는 함수에 속한 지역 변수
  • 함수 내에서 global a을 사용하면, 전역 네임스페이스 a를 의미한다.
  1 a = 42
  2 b = 37
  3  
  4 def foo():
  5     global a
  6     a = 13
  7     b = 0
  8  
  9 foo()
 10 print (f"a = {a}, b = {b}")
  • nested 함수 (중첩 함수) 안의 변수의 이름은 어휘 유효 범위에 따라 찾아 진다.
    • 즉 중첩 함수 내에서 변수가 없으면 한칸 밖으로 나가서 변수 를 찾는다.
    • 하지만 바깥쪽 함수에서 정의된 지역 변수 값을 안쪽 함수에서 다시 할당할 수는 없다.
    • unboundLocalError : local variable 'n' referenced before assignment
    • 그런데 ! 바깥쪽 함수에서 정의된 지역 변수 값을 안쪽 함수에서 다시 할당하는 방법이 있다.
    • 바로 nonlocal 을 사용하면 된다.
    • nonlocal 은 자기 scope 밖의 가장 가까운 바깥쪽 함수에서 정의된 지역 변수를 묶는다.
 12 #---------------------------------------- 
 13 def countdown(start):
 14     n = start
 15     def display():
 16         print (f'T-minus {n}')
 17          
 18     while n > 0:
 19         display()
 20         n -= 1
 21          
 22 countdown(3)
 23 #---------------------------------------- 
 24          
 25 def countdown(start):
 26     n = start
 27     def display():
 28         print (f'T-minus {n}')
 29          
 30     def decrement():
 31         n -= 1
 32          
 33     while n > 0:
 34         display()
 35         decrement() # Error
 36          
 37 countdown(3)
 38 #---------------------------------------- 
 39 def countdown(start):
 40     n = start
 41     def display():
 42         print (f'T-minus {n}')
 43          
 44     def decrement():
 45         nonlocal n
 46         n -= 1
 47          
 48     while n > 0:
 49         display()
 50         decrement() # Error
 51          
 52 countdown(3)

6.lambda 함수

  • lambda 는 1줄 함수이다. 다른 프로그래밍 언어에서는 익명 함수라고도 부른다.
  • 프로그램 내에서 함수를 재사용 하지 않으려는 경우 lambda 함수를 쓸 수 있다.
  • 일반 함수와 형태도 비슷하고 비슷하게 작동.
    • format : lambda (인자) : (return)
  1 add = lambda x, y : x + y
  2 # 아래 함수와 동일 
  3 # def add(x, y):
  4 #    return x + y 
  • 언제 쓰면 편할까?
    • sorting 할 때 사용하면 편하다.
  8 a = [(1, 2), (4, 1), (9, 10), (13, -3)]
  9 a.sort(key=lambda x: x[1])
 10 print (a)
 11 # sort by second value 
  • 주의사항
    • 람다에서 사용한 x 값이 실행 시간에 달라지는 변수이다.
    • default parameter가 아니라 전역 변수일 뿐이다.
    • 함수를 정의할 때 특정 값을 고정하고 싶으면, 디폴트 parameter로 지정하면 된다.
 14 x = 10
 15 a = lambda y : x + y
 16 x = 20
 17 b = lambda y : x + y
 18  
 19 print ("\nlambda function1")
 20 print (a(10)) # output : 30
 21 print (b(10)) # output : 30
 22  
 23 #----------------------------------------
 24 x = 10
 25 a = lambda y, x=x : x + y
 26 x = 20
 27 b = lambda y, x=x : x + y
 28  
 29 print ("\nlambda function2")  
 30 print (a(10)) # output : 30
 31 print (b(10)) # output : 30
  • list comprehension

    • ex) func = [lambda x: x+n for n in range(5)]
    • → func = [lambda x, n=n : x+n for n in range(5)]
    • n 값이 변할 것이라고 기대 하지만 람다 함수는 가장 마지막 값을 사용한다.
    • n 값을 함수를 정의하는 시점의 값으로 고정해놓고 사용한다.
  • 함수 인자에 meta data 넣기

    • ex) def add(x:int, y:int) -> int:
    • help(add) 혹은 add.annotations 이용해서 볼 수 있음
    • 인자에 정보를 추가해서 다른 사람이 함수를 어떻게 사용하는지 알 수 있도록 한다.
    • 함수의 주석은 함수의 __annotations__에 저장된다.

7.closure

  • closure
    • closure 란?
    • 함수가 데이터로 취급될 때는 함수가 정의된 곳의 주변 환경 정보가 함께 저장된다.
    • 함수가 return 할 때 자기 내부에 있는 함수를 호출하는 경우
    • 함수를 구성하는 문장과 실행 환경을 함께 묶은 것을 closure 라고 한다.
    • 이 예시에서는 mul_add 가 closure, 울타리 안에 있는 값 이라는 의미
    • 함수를 한 번 호출하면 그 안의 closure 가 계속 유지되며 다시 호출 시 또 다른 closure 가 생성된다.
    • closure 내부에 closure 에 free variable (지역 변수) 이 저장되어 있다.
  1 def calc():
  2     a = 3
  3     b = 5
  4  
  5     def mul_add(x):
  6         return a * x + b
  7     return mul_add
  8  
  9 c = calc()
 10 print (c(1), c(2), c(3), c(4))
 11  
 12  
 13 print (c.__closure__)
 14 print (c.__closure__[0].cell_contents)
 15 print (c.__closure__[1].cell_contents)  
  • 구현이 간단한 수식인 경우 람다를 이용하여 클로저를 사용할 수 있다. 내부 함수를 직접 호출할 일이 없으므로 이름이 없어도 된다.
 18 def calc():
 19     a = 3
 20     b = 5
 21      
 22     return lambda x: a * x + b 
 23      
 24 c = calc()
 25 print (c(1), c(2), c(3), c(4))
 26      
 27      
 28 print (c.__closure__)
 29 print (c.__closure__[0].cell_contents)
 30 print (c.__closure__[1].cell_contents)
 31      
  • 클로저 내부에서 정의한 변수에 접근도 가능하다.
    1. nonlocal 선언으로 내부 변수를 수정하는 함수를 작성
    2. 접근 메소드를 클로저 함수에 붙여 마치 인스턴스 메소드인 것처럼 동작하는 것
    • n의 값은 closure에 들어있고, property의 함수들은 dict 형태로 저장되어 있다.
 33 def sample():
 34     n = 0
 35  
 36     def func():
 37         print ('n = ', n)
 38  
 39     def get_n():
 40         return n
 41  
 42     def set_n(value):
 43         nonlocal n
 44         n = value
 45  
 46     func.get_n = get_n
 47     func.set_n = set_n
 48     return func
 49  
 50  
 51 if __name__ == '__main__':
 52     f = sample()
 53     f()
 54  
 55     n = 0
 56     f.set_n(10)
 57  
 58     f()
 59     print(f.get_n()) 

8. decorator

  • 이 예시에서는 func 이 지역변수, 즉 closure의 환경이 된다.
    • make_func_alarm 에서 func 이라는 인자가 들어오는데, 이 함수 호출이 끝나면 func 은 사라지지만 closure 에는 남아있다.
    • 따라서 함수를 인자로 받아서, 함수 전후에 새로운 기능을 추가한 함수를 만들 수도 있다. !
    • make_func_alarm 이 decorator 가 된다.
  1 def big_number(n):                   
  2     return n ** n ** n
  3  
  4 def make_func_alarm(func):
  5  
  6     def new_func(*args, **kwargs):
  7         print (" function start ! ")
  8         result = func(*args, **kwargs)
  9         print (" function end ! ")
 10         return result
 11  
 12     return new_func
 13  
 14 new_func = make_func_alarm(big_number)
 15 new_func(6)
  • 여기서 line 14 와 line 15 가 번거롭다.
    • 안에 있는 closure를 받아서 다시 한번 호출해줘야 하는 번거로움이 있어, python 에서는 이를 자동으로 해주는 macro 가 있다.
  • @를 사용하자 !
    • @make_time_checker 문법은 decorator
    • make_time_checker 라는 함수에다가 big_number를 넘긴 형태
    • 즉 어떤 함수 위쪽에 @__ 를 써준다는 것은, 그 함수를 decorator에 넘겨주겠다는 의미.
    • 실제로는 위 코드와 동일한 역할을 해준다.
  1 import time       
  2                   
  3 def make_func_checker(func):
  4                   
  5     def new_func(*args, **kwargs):
  6         start_time  = time.perf_counter()
  7         result      = func(*args, **kwargs)
  8         end_time    = time.perf_counter() 
  9         print ("runtime : ", end_time - start_time)
 10         return result
 11     return new_func
 12                   
 13 @make_func_checker 
 14 def big_number(n):
 15     return n ** n ** n
 16 # big_number = make_time_checker(big_number)                  
 17 big_number(7) 
  • Tracer tool (어떤 함수가 호출되었는지 다 로그로 남기고 싶을 때)
    • enable_tracing 을 True / False 로 해두어, tracing 을 할지 말지를 결정할 수 있다.
    • 지금은 사용법 정도만.. !
  1 enable_tracing = True
  2              
  3 if enable_tracing:
  4     debug_log = open("debug.log", "w")
  5              
  6 def trace(func):
  7     if enable_tracing:
  8         def callf(*args, **kwargs):
  9             debug_log.write("\n\n--------------------------------------------")
 10             debug_log.write(f"\nCalling {func.__name__} : {args} {kwargs}\n")
 11             r = func(*args, **kwargs)
 12             debug_log.write(f"{func.__name__} returned {r}\n")
 13             debug_log.write("--------------------------------------------\n\n")
 14              
 15             return r
 16         return callf
 17     else:    
 18         return func
 19              
 20 @trace       
 21 def square(x):
 22     return x * x
 23              
 24 square(100) 

9. Iterator

  • 반복자 : 순환 가능한 아이템에 접근할 때 for 순환문을 사용하고 싶지 않다면 !? iterator 사용하면 된다.
    • with : context manager (나중에 공부하기로..)
    • next는 iterator 를 사용하였음.
    • 이 예제에서는 더 이상 next를 할 수 없을 때 예외가 발생하게 되고 이 때 StopIteration
  1 with open("debug.log") as f : # f = open("passwd")  
  2     try:
  3         while True:
  4             line = next(f)
  5             print(line)
  6     except StopIteration:
  7         pass
  • 함수 iter (iterator의 약자)
    • it 는 python 에서는 reference (c에서는 pointer) 가 된다.
    • iter은 list 이나 file 이든 어떤 container가 오든지 sequential 하면 순차적으로 이동하는 reference 가 되며 이게 가능한 container를 iterable container 라고 부르며 실제로 이동하는 이 reference를 iterator 라고 부른다.
 10 items = [1, 2, 3]  # list 
 11 it = iter(items)   # print (it) : <list_iterator object at ...>
 12 print (next(it))
 13 print (next(it))
 14 print (next(it))

10. Delegating 순환

  • [func] : special function
  • 아래 예시에서 root Node 의 _children list에 각각 child1 child2 를 append 한다.
    • python 에서는 tree 라는 구조체가 없기 때문에 이를 list 로 가지고 만들어 준 것
    • for ch in root 이란, 아래 주석처리된 while문과 동일하다
    • 또한 print (객체) 이렇게 해주면, 객체의 address가 나오므로 Node 함수 내에는 repr 함수를 만들어, 값을 표시해주는 함수를 만들어주었다.
  1 class Node:
  2     def __init__(self, value):
  3         self._value = value
  4         self._children = []
  5          
  6     def __repr__(self):
  7         return f'Node({self._value})'
  8          
  9     def add_child(self, node):
 10         self._children.append(node)
 11          
 12     def __iter__(self):
 13         # iter(n1) == n1.__iter__()
 14         return iter(self._children) 
 15          
 16          
 17 if __name__ == '__main__':
 18     root = Node(0)
 19     child1 = Node(1)
 20     child2 = Node(2)
 21          
 22     root.add_child(child1)
 23     root.add_child(child2)
 24          
 25     for ch in root:
 26         print (ch)  # to print out _value, Node has __repr__ function
 27          
 28     # --- for loop ---
 29     # it = iter(root)
 30     # while True:
 31     #    ch = next(it)
 32     #    print (ch)

11. Generator

  • 함수 객체의 yield (양보하다) 라는 예약어가 하나라도 있다면 generator 이다.
    • x의 값을 던져주면서 양보한다..
    • yield 는 return 과 같지만 함수가 끝난 것은 아니다. 함수의 제어권을 호출자에게 양보한다.
    • yield 가 있는 함수는 일반 함수가 아니라 generator 이기 때문에 c = countdown(3) 이라고 하면 generator 객체를 넘겨주는 것.
    • next 를 하게 되면 yield 구문까지 수행되고, 이 때 yield 값 n을 넘겨주면서 멈춘다.
    • 그 다음 next 가 올때 까지 yield 에 멈춰있다가 next가 또 들어오는 순간 yield 다음 행부터 수행된다.
    • iterator 와 동일한 동작을 한다. (다른 점은, 이미 있는 container인 list 같은걸 순회하는게 아니라 그 때 그 때 생성되는 값을 가지고 제어 조건을 하나씩 양보하면서 함수 밖과 안을 하나하나씩 처리하는 병행 처리를 만들 수 있다.
def countdown(n): 
    print('Starting to count from',n)
    while n > 0:
        yield n
        n -= 1
    print('Done!')

12. iterator & generator

  • python 에서 tree 구조를 순회하려면 재귀함수를 호출해야 하나 싶지만,
    • 맨 아래에 for ch in root.depth_first(): 에서는 generator를 꺼내는 것이고 (왜냐면 depth_first 함수에 yield 가 있으므로)
    • depth_first 안에서 for c in self: 에서는 self 에 있는 list의 iterator 가 호출되는 것.
    • yield from [A] 은 [A] 에 있는 iterator 을 이용해서 나에게 양보하라는 의미
    • root - left - right 로 순회하는 pre order traverse 알고리즘이 된다. (root 부터 방문하는 것)
  1 class Node:
  2     def __init__(self, value):
  3         self._value = value
  4         self._children = []
  5          
  6     def __repr__(self):
  7         return f' :: Node({self._value}) ::'
  8          
  9     def add_child(self, node):
 10         self._children.append(node)
 11          
 12     def __iter__(self):
 13         return iter(self._children)
 14          
 15     def depth_first(self):
 16         yield self    
 17         for c in self:
 18             yield from c.depth_first()
 19          
 20          
 21 if __name__ == '__main__':
 22     root = Node(0)
 23     child1 = Node(1)
 24     child2 = Node(2)
 25          
 26     root.add_child(child1)
 27     root.add_child(child2)
 28          
 29     child1.add_child(Node(3))
 30     child1.add_child(Node(4))
 31     child2.add_child(Node(5))
 32          
 33     for ch in root.depth_first():
 34         print (ch)
 35          
 36     # g = root.depth_first()
 37     # while True : 
 38     #    ch = next(g)
 39     #    print (ch, end = '')
 40     #    
 41     #    it = iter(self)
 42     #    while True: 
 43     #        ch = next(g)

13. reverse 순환 및 상태를 가진 generator

  • deque 는 list와 비슷하지만 왼쪽 오른쪽에서 삽입 가능 (stack, queue 로도 사용가능)
  • 객체 자체에 for .. in .. 을 사용하면 객체 내부의 iter 함수를 타게 된다. (없으면 기본 iter 함수 동작)
  • generator 상태는 현재 line 수, max 를 가지고 있게 된다.
 30 from collections import deque
 31                  
 32 class linehistory:
 33     def __init__(self, lines, histlen=3):
 34         self.lines = lines
 35         self.history = deque(maxlen=histlen)
 36                  
 37     def __iter__(self):
 38         for lineno, line in enumerate(self.lines, 1):
 39             self.history.append((lineno, line))
 40             yield line
 41                  
 42     def clear(self):
 43         self.history.clear()
 44                  
 45 with open('debug.log') as f:
 46     lines = linehistory(f)
 47                  
 48     for line in lines:
 49         if 'python' in line:
 50             for lineno, hline in lines.history:
 51                 print (f'{lineno} : {hline}') 

14. iterator & generator

  • list 인 경우 slice 가능하지만 generator 는 [:] 가 되지 않음 (TypeError : 'generator' object is not subscriptable)
    • 이를 지원하는게 itertools !
  1 import itertools
  2      
  3 for x in itertools.islice(c, 10, 20):
  4     print (x) 
  • 순환 객체 첫 번째 부분 건너뛰기
  1 from itertools import dropwhile
  2 with open("debug.log") as f:
  3     for line in dropwhile(lambda line: line.startswith('#'), f):
  4         print (line)
  • enumerate(list) 사용하면 (index, value) 를 return 해줌

    • enumerate(list, 1) 이라고 하면 1부터 index 시작
  • zip (x, y) 라고 하면 x 와 y의 똑같은 index를 동시 순환

    • x와 y의 개수가 맞지 않는 경우에는 항상 짧은 쪽으로 순회한다.
    • 만약 긴 쪽으로 순회하기를 원한다면, zip_longest 를 사용하면 된다.
  6 a = [1, 2, 3]
  7 b = ['w', 'x', 'y', 'z']
  8      
  9 from itertools import zip_longest
 10 for i in zip(a, b):
 11     print (i)
 12      
 13 for i in zip_longest(a, b):
 14     print (i)     

15. coroutine

  • generator 중 하나
  • generator 에서 yield는 왼쪽에 아무것도 없어서, 함수가 이 지점에 도착하면 n 이라는 것을 반대쪽에 양보하고 제어권이 멈추었는데, 코루틴에서는 즉시 제어권을 양보 yield의 방향이 R value로 가있음.
    • 위에서의 yield 는 data를 넘겨주는 구조였다면, 지금의 yield 는 data를 받아주는 구조.
    • 끝내기 위해서는 close 라는 함수가 있음
  1 def receiver():
  2     print ("Ready to receive")
  3  
  4     while True:
  5         n = yield
  6         print (f"Got {n}")
  7  
  8 r = receiver()
  9 print (r)
 10  
 11 next(r) 
 12 r.send(1)
 13 r.send(2)
 14 r.send("Hello")
  • coroutine 과 decorator (next를 자동으로 해 주는 역할) 를 같이 써주면 좋다.
    • close 이후에도 send 를 하면 generator 가 이미 stop 조건을 만난 뒤이기 때문에 StopIteration 에러 발생한다.
 16 def coroutine(func):
 17     def start (*args, **kwargs):
 18         g = func(*args, **kwargs)
 19         next (g)
 20         return g
 21     return start
 22      
 23 @coroutine
 24 def receiver():
 25     print ("\nReady to receive")        
 26     while True:
 27         n = yield
 28         print (f"Got {n}")
 29      
 30 receiver()
 31 r.send("coroutine with decorator")
 32 r.close()
  • generator 와 coroutine 을 함께 쓰면 pipe 역할을 하게 된다.
    • ps -ef | grep ls | wc -l 이런 pipe !
    • code 는 reference 에서 참고하기 (1.16)
Clone this wiki locally