Python: sorted, key 의 작동 원리.

다시 파기 시작한 Python. 이번엔 좀 제대로된 구멍을 뚫을 수 있기를.

늘, 다시 봐도 새로운 lambda 를 이해하려 하다 보니, 예제로 sorted 의 key 인수가 나왔는데, 여기서 막혀버렸다. 람다는 커녕, sorted 도 이해가 되지 않는다.

가지고 있는 책들도 뒤지고, 인터넷도 살펴보고 하면서 뭔가 감이 잡힐 듯 하던 차에, 이거 혹시 예전에도 같은 문제로 고민하지 않았었을까? 하는 생각이 들었다.

이 블로그를 뒤져보니 없었다. 그렇다면 예전에는..?

흠! 역시! 한번 샌 바가지는 절대로 막히지 않는군.

전문을 옮겨올까 하다가, 괜히 더 머리만 복잡해질 듯 하여 핵심 문구만 다시 기억해보기로 한다.

key=yyy 구문이 들어간다면, 여기에는 단순한 값이 아니고 함수가 넘어가는 것이며, key 함수의 인수는 sort 함수의 첫번째 인수(위에서 보면 xx)의 각 구성원이 ‘순차적’으로 하나씩 들어가서 최종값이 반환되어 나오는 것이라는 것을 명심하자!!


이게 뭔 소린고 하면..
이런 리스트가 있다고 하자.

student_tuples
Out[3]: [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

리스트엔 튜플이 들어 있고, 각 튜플은 성(姓), 성적(成績), 나이로 이뤄져있다.

이걸 정렬하고 싶어서 sorted 를 사용했다.

sorted(student_tuples)
Out[4]: [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

각 튜플의 첫번째 요소인, ‘성’으로 정렬이 됐다.
헌데, 내가 원하는 건 기준이 성이 아니고 ‘나이’다. 즉, 각 튜플의 세번째 요소로 정렬을 하고 싶다.. 이거다.

이를 위해 key 가 필요하다. key 는, 스프레드시트에서 정렬 기준을 정해주는 역할을 한다. 여기선, “리스트 안 튜플의 세번째 요소(또는 마지막 요소)를 정렬 기준으로 삼으라”로 얘기해주면 된다.

문제는, ‘리스트 안 튜플의 마지막 요소를 정렬 기준으로 삼으라’고 어떻게 말을 해야 하느냐에 있다. 엑셀이나 리브레 캘크같으면 쉬운데~ 엉엉!!

무식하게 이렇게 해봤더니..

sorted(student_tuples, key=student_tuples[0][-1])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-b783a9cce172> in <module>
----> 1 sorted(student_tuples, key=student_tuples[0][-1])

TypeError: 'int' object is not callable

당연히 오류가 난다. student_tuples[0][-1] 의 값은 10 인데, 10은 ‘not callable’ 이라는 불친절한 설명만 돌아왔을 뿐, 왜 이런 현상이 나타났는지는…


사실, not callable 로 모든 게 설명이 된다. 이 글 초반에 옮겨적어놓은 글도 같은 맥락. key 에는 ‘값’이 들어가는게 아니고, 호출 가능한 함수가 들어가야 한다.

왜?
왜냐고?
위에서 봤듯, student_tuples[0][-1] 값 ’10’ 하나를 넘긴다고 정렬이 되지 않기 때문이다.
정렬을 하려면, student_tuples[0][-1], student_tuples[1][-1], student_tuples[2][-1] 가 모두 필요한데, 내가 넘긴 값은 달랑 1개이므로, sorted 는 제대로 처리를 할 수가 없다.

따라서, key 에는 저 값들을 도출해낼 수 있는 ‘함수’를 넣어줘야만 한다. 이렇게, 후처리가 필요할 때 부르는 함수를 callback 함수라고 한다는데.. (사실 이 개념도 확실하게 이해가 되지는 않는다는게 문제지?)

key 는 앞 인수의 값(iterable)들을 인자로 받고, 처리할 함수로 넘긴다.
아마 이런 식이 되리라 생각한다.

# key 입장에서..
for item in student_tuples:
    func(item)

따라서, key 에는 저 인수들(item)을 받을 수 있는 함수를 넣어줘야 한다.

이걸 간단하게 람다로 구현하면 이렇게 된다.

sorted(student_tuples, key=lambda student: student[-1])
Out[8]: [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

lambda student: 는, student_tuples 의 각 인자를 하나씩 인수로 받게 되고, student[-1] 을 통해, 각 튜플의 제일 마지막 인자를 반환하게 된다.

그리고 그 값들을 토대로 정렬이 이뤄진다.

….

음… 5년 만에 다시 정리를 한 셈인데, 이번에는 죽을 때까지 잊지 않을 수 있으려나??


작년 11월에 쓰고, 현재 1월 14일.
다시 읽어보니 뭔 말인지 알겠다. 그러나, 이 글의 제목만 봤었을 때 당시는…

역시 내용을 잘 기억하지 못했다. 이래서 반복 학습이 중요한 거야.

Author: 아무도안

안녕하세요. 글 남겨주셔서 고맙습니다.