2013년 2월 28일 목요일

Python n-grouper

파이썬 zip 함수 설명을 보면 다음과 같은 문장이 있다.
This makes possible an idiom for clustering a data series into n-length groups using zip(*[iter(s)]*n).
주어진 리스트 s를 순서대로 n개씩 묶어서 반환하는 것을 zip(*[iter(s)]*n) 를 통해 할 수 있다는 의미다. 이는 다음과 같이 사용할 수 있다.
s = [1, 2, 3, 4, 5, 6]
print zip(*[iter(s)]*2)   # [(1, 2), (3, 4), (5, 6)]
이 짧은 n-grouper 코드가 흥미있는 파이썬 문법을 함축적으로 보여줄 수 있어 소개한다.

iter 함수

주어진 객체의 멤버를 순회할 수 있는 iterator 객체를 만들어 반환한다. 만약 리스트가 넘겨졌다면 순서대로 내부 멤버를 반환하는 iterator 객체를 만들어 반환한다. 이런 경우 간단히 iterator 객체는 첫 번째 멤버를 가리키는 index 변수가지고 iterator 객체가 참조할 때마다 현재 index 가 가리키는 값을 반환하며 동시에 index 가 증가한다고 볼 수 있다.
s = [1, 2, 3, 4, 5, 6]
for i in iter(s): 
  print i,   # 1 2 3 4 5 6

[...]*n

주어진 리스트를 n 번 반복해 붙여 반환한다. 만약 [1, 2, 3]*2 라면 [1, 2, 3, 1, 2, 3] 을 반환한다. 단 여기서 주의해야 할 점은 멤버가 반복될 때 얕은 복사가 (shallow copy) 수행된다는 점이다. 따라서 리스트 멤버가 객체 타입의 경우에는 반복되는 객체가 모두 동일한 객체임을 주의해야 한다.
a=[[1], [2]]*2
print a          # [[1], [2], [1], [2]]
a[0].append(3)
print a          # [[1, 3], [2], [1, 3], [2]]
이 경우 a 의 첫번째 값 뿐만 아니라 세번째 값도 변경되었음을 확인할 수 있다. 첫번째와 세번째가 같은 객체를 가리키기 때문이다.

따라서 [iter(s)]*2 로 나타내는 리스트 곱은 아래와 같이 수행된다. 여기서 zip 함수에 동일한 iterator 객체를 가리키는 i 가 두 개 넘겨졌음에 주의해야 한다.
i = iter(s)
print zip(*[i, i])

Unpacking argument *

파이썬의 함수 호출 문법의 재미있는 부분이다. *[...] 형태로 함수 인자를 넘겨주면 리스트 [...] 의 멤버가 펼쳐져 함수 개별 인자로 넘겨진다. zip(*[i, i]) 는 아래와 같이 실행된다.
print zip(i, i)   # == zip(*[i, i])

함수 인자의 평가 순서

여기서는 당장 중요하지 않지만 파이썬의 함수 인자 평가는 왼쪽에서 오른쪽으로 정해져 있다. 따라서 아래와 같은 예제도 결과를 예측할 수 있다.
f=lambda x, y: x.append(y) or x
p=lambda x, y: x + y
a=[]
print p(f(a, 1), f(a, 2))   # [1, 2, 1, 2]
이는 C 언어의 경우 함수 인자의 평가 순서가 정해져 있지 않은 것과 비교된다. 아래의 예제는 컴파일러 혹은 옵션에 따라 서로 다른 결과를 반환하다.
int i=0;
p(i++, i++);

zip 함수의 실행 중 인자 평가 순서

zip 함수가 실행하면서 넘겨 받은 인자를 참조하는데 (iterator 이므로 next() 메소드가 불리는 방식으로) 이 순서를 zip 함수가 보장해 준다. 왼쪽에서 오른쪽으로 평가하는 것으로 되어 있는데 때문에 zip(i, j) 는 아래와 같이 수행됨을 보장한다.
i = iter(s)
j = i
print zip(i, j)
# zip(i, j):
#   r = []
#   while end:
#     a = i.next()
#     b = j.next()
#     r.append((a, b))
#   return r
이렇게 zip의 동작 방식을 정해둔 것은 같은 객체를 여러 인자를 통해 넘겼을 때에도 결과를 일정하게 보장하기 위함으로 보인다. 때문에 zip(i, i) 와 같은 코드의 수행 결과가 항상 일정할 수 있음을 기대할 수 있다.

결론

zip 함수로 구현한 n-grouper 는 파이썬의 여러 문법적 요소를 함축적으로 담고 있어 흥미로운 코드 임에는 틀림없다. 다만 객체가 얕은 복사가 발생하는 것에 주의하면서 동시에 zip 함수의 수행 방식을 숙지하고 있지 않으면 이해가 잘 안되는 코드가 좋은 코드인지는 잘 모르겠다. 개인적으로는 아래와 같이 다소 장황해 보이지만 의도가 명확한 코드가 더 좋지 않을까 싶다.
s = [1, 2, 3, 4, 5, 6]
n = 2
print [s[i-n:i] for i in range(n, len(s)+1, n)]
# [(1, 2), (3, 4), (5, 6)]

댓글 1개:

giaonhanquocte :

Thanks for sharing, nice post! Post really provice useful information!

Giaonhan247 chuyên dịch vụ vận chuyển hàng đi mỹ cũng như dịch vụ ship hàng mỹ từ dịch vụ nhận mua hộ hàng mỹ từ trang ebay vn cùng với dịch vụ mua hàng amazon về VN uy tín, giá rẻ.