-
Notifications
You must be signed in to change notification settings - Fork 0
Python_1
jjin-choi edited this page Jul 15, 2020
·
2 revisions
- 강사명 : 김정인
- email : jikim@imguru.co.kr
- github : https://github.com/imguru-mooc/python_intermediate
- Ref : 파이썬 완벽 가이드 / Python Cookbook (데이비드 M. 비즐리)
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
- 클로저 내부에서 정의한 변수에 접근도 가능하다.
- nonlocal 선언으로 내부 변수를 수정하는 함수를 작성
- 접근 메소드를 클로저 함수에 붙여 마치 인스턴스 메소드인 것처럼 동작하는 것
- 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)