Python에 static variable을 만들어보자!

|

stackoverflow에서 발췌

다음과 같이 static int를 정의하고 쓰던 사람이 파이썬에 놀러와서 질문을 했다.

void foo()
{
    static int counter = 0;
    counter++;
    printf("counter is %d\n", counter);
}

“나 C 쓰던 사람인데 이런거 Python으로 어떻게 하니?”

그러자 어떤 현인이 다음과 같은 방식을 제안했다.

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter
foo.counter = 0

요런 식으로 foo.counter를 초기화 시켜주고 쓰면 돼!

근데 저러면 초기화 안시키면 망하니까 decorator를 쓰렴!

def static_vars(**kwargs):
    def decorate(func):
        for k in kwargs:
            setattr(func, k, kwargs[k])
        return func
    return decorate

요렇게 정의하구

@static_vars(counter=0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

요렇게 쓰면 대! 진짜 되나 실험해보았다.

>>> def static_vars(**kwargs):
...     def decorate(func):
...             for k in kwargs:
...                     print k
...                     print kwargs[k]
...                     setattr(func, k, kwargs[k])
...             return func
...     return decorate
... 
>>> @static_vars(counter=0)
... def foo():
...     foo.counter += 1
...     print "Counter is %d" % foo.counter
... 
counter
0
>>> foo()
Counter is 1
>>> foo()
Counter is 2
>>> foo()
Counter is 3

참고로 setattr()는 다음과 같은 형식이다.

setattr(object, name, value)

getattr()과 반대되는 녀석. 인자로는 object, string, value가 된다. 이 string은 존재하는 attribute 또는 새로운 attribute의 이름이 된다. 예시 > setattr(x, ‘foobar’, 123) == x.foobar = 123.

근데 같다고했는데 func.k = kwargs[k]하면 에러나네..

Iterator & generator

|

Iterator

예제를 보자.

>>> a = [0,1,2,3,4]
>>> b = iter(a)
>>> b
<listiterator object at 0x106984a50>
>>> b.next()
0
>>> b.next()
1
# ...
>>> b.next()
4
>>> b.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> 

a라는 공간에 list를 생성하였고, a에는 0,1,2,3,4가 순차적으로 저장되어있다. b는 iter() 함수의 인자로 a를 주었다. 이제 b는 메모리 주소 0x106984a50안에 위치한다.

  • iterator : b
  • iteration : iterator에서 순차적으로 요소를 가져오는 행위
  • iterable : iteration이 가능한 경우

listiterable한 객체이다. 이에 대해서 iter(b)로 해당 객체의 iterator를 얻어올 수 있다. 그 후 이 iterator의 next()를 호출해서 iteration이 가능하다. next()를 호출 시에 더이상 줄 값이 없다면 StopIteration Exception이 나게된다.

>>> a = [0,1,2,3,4]
>>> for i in a:
...     print i,
... 
0 1 2 3 4

위의 코드는 이렇게도 쓸 수 있다.

>>> a = [0,1,2,3,4]
>>> b = iter(a)
>>> 
>>> while True:
...     try:
...         i = b.next()
...     except StopIteration:
...         break
...     print i,
... 
0 1 2 3 4

Generator

이것도 먼저 예제를 보자!

>>> def num_generator(n):
...     print "Function Start"
...     while n < 6:
...             yield n
...             n += 1
...     print "Function End"
... 
>>> for i in num_generator(0):
...     print i
... 
Function Start
0
1
2
3
4
5
Function End

Generator는 함수의 형태를 가진다. 일반적으로 for문과 같이 사용된다. yield를 사용하는 함수 == generator라고 봐도 무방하다.

이거 언제쓰냐?

List Comprehension 구문은 python에서 자주 쓰이는 방법이다.

a = [0,1,2,3,4]
b = [i**2 for i in a]

for j in b:
    ...

위의 코드를 보면 a를 이용해 list b를 만들고 이를 이용하여 어떤 조작을 하려고 한다. a, b가 모두 메모리를 차지한다. 작은 리스트는 괜찮지만 a가 엄청 많은 데이터를 가지고있을 때 비슷한 사이즈의 메모리를 차지하는 것은 별로 효율적이지 못하다. 이 때 generator를 쓰면 list a만 유지하며 각 iteration에 맞는 값을 제공할 수 있다.

  • 장점
    • 한번 쓰고 버릴 리스트에 대한 메모리 절약
  • 단점?
    • b가 앞으로도 계속 쓰일 것이라면 어떤 것이 비용이 더 클까? (나중에 조사해서 쓰자)
>>> a = [0,1,2,3,4]
>>> 
>>> def genTest(l):
...     for i in a:
...             yield i**2
... 
>>> for j in genTest(a):
...     print j,
... 
0 1 4 9 16

yield?

python에서 yield란 return과 비슷하나 좀 다르다. return처럼 yield를 만나는 순간 값을 리턴하나, 함수가 종료되지 않고 다음 호출 시 거기서부터 시작된다!

이 예제 좋구만

>>> def generator(n):
...     print "Generator start!"
...     while n < 6:
...             print "Generator : I give control to the Main"
...             yield n
...             print "Generator : I received a control."
...             n += 1
...             print "Generator : n += 1"
...     print "Generator End!"
... 
>>> for i in generator(0):
...     print "Main: I received a control."
...     print i
...     print "Main: I give control to Generator"
... 
Generator start!
Generator : I give control to the Main
Main: I received a control.
0
Main: I give control to Generator
Generator : I received a control.
Generator : n += 1
Generator : I give control to the Main
Main: I received a control.
1
.........
5
Main: I give control to Generator
Generator : I received a control.
Generator : n += 1
Generator End!

for문을 맨 위에서 봤던 동치 while문으로 바꿔 생각하면 쉽게 이해할 수 있다.

while문에서 b.next()를 호출할 때 generator가 호출된다

Coroutine

|

Coroutine 정의
프로그램에서 대등한 관계로 서로 호출하는 것. 예를 들면, 게임 프로그램에서 각 플레이어의 루틴은 서로 코루틴된다.

종속적이지 않고 서로 대화하는 형식의 함수

다음 그래프는 동작 방식을 sequence로 설명한 그림이다.

coroutine

Coroutine을 직접 만들어보자!

# coding: utf-8

import random
import sys

reload(sys)
sys.setdefaultencoding('utf8')

def game(name):
    print "안녕 " + name
    print "조건 : 0 < num < 101"

    count = 0
    num = random.randrange(1,101)

    while True:
        OoC = (yield)
        count += 1

        if OoC < 0 or OoC > 100:
            print "잘못된 숫자!"
        elif OoC > num:
            print "더 작은 숫자야."
        elif OoC < num:
            print "더 큰 숫자야."
        else:
            print "%d번 만에 맞췄네!" %count

OoC = (yield) : 요걸로 입력을 받을 때 까지 대기한다.

>>> from coroutine import game
>>> c = game("Hulk")
>>> c.next()
안녕 Hulk
조건 : 0 < num < 101
>>> c.send(50)
 작은 숫자야.
>>> c.send(1)
  숫자야.
>>> c.send(20)
 작은 숫자야.
>>> c.send(10)
  숫자야.
>>> c.send(15)
  숫자야.
>>> c.send(17)
 작은 숫자야.
>>> c.send(16)
7 만에 맞췄네!
>>> c.close()

c.next() : 코루틴을 호출한 직후 next()를 사용하여 첫 yield까지 동작시켜야 한다.

c.send() : send로 데이터를 보내서 yield 뒤를 실행시켜야 함.

c.close() : 코루틴을 종료시키려면 close()함수를 사용한다.

call by ref? value?

|

Call By Reference!

** Python 함수에서 모든 argument는 call by reference로 불린다. **

다만 mutable object이냐, immutable object이냐에 따라서 call by value처럼 보이는 것 뿐이다.

Immutable type

def foo( a ):
    a = 2

b = 3
foo( b )
print b # 3

위의 코드처럼 immutable한 경우는 call by value처럼 동작한다.

|0x0001| 주소에 3이라는 값이 있었으면 처음에

b |0x0001|, a |0x0001|가 된다.

이제 a = 2를 하면 |0x0002|에 2라는 값이 생기고 a는 |0x0002|의 주소를 가리키게된다.

따라서 함수 밖에 나왔을 때 b에게는 영향이 없는 것이다.

다음 코드르 보면 확실히 알 수 있을 것이다.

def foo( a ):
	print "Before operation, id(a) is ", id( a )
	a = 3
	print "After operation, id(a) is ", id( a )

b = 2
print "Before calling foo, id(b) is ", id( b )
foo( b )
print "After calling foo, id(b) is ", id( b )

Mutable type

def foo( a ):
    a.append( 2 )

b = [ 3,2,1]
foo(b)
print b # [3, 2, 1, 2]

List의 경우 mutable한 녀석이며 위의 foo는 list에 append를 하기 때문에 a와 b의 주소가 바뀌지 않으며 따라서 call by reference로 볼 수 있다.

Conclusion!

결국, object의 reference를 바꾸지 않는 operation의 경우 call by reference이며, reference를 바꾸면 call by value!