March 29, 2021
파이썬에서 문자열을 특정 구분자로 자를 수 있게 해주는 builtin 함수로 str.split
, str.rsplit
, str.partition
, str.rpartition
이 있다.
그리고 str.split
함수에는 최대 몇번 자를 지 지정해줄 수 있는 maxsplit
파라미터가 있는데, 어떤 상황에서 사용하면 좋을 지 알아보자!
가장 많이 사용되는 str.split
이다. 구분자로 전체 문자열을 잘라서 배열로 저장한다.
In [3]: s = "hi_hello_1_2_3_4_5"
In [4]: s.split("_")
Out[4]: ['hi', 'hello', '1', '2', '3', '4', '5']
여기서 maxsplit
를 적용하면 한 번만 자르고 그 뒤에는 그대로 돌려준다.
In [3]: s = "hi_hello_1_2_3_4_5"
In [4]: s.split("_", maxsplit=1)
Out[4]: ['hi', 'hello_1_2_3_4_5']
무슨 장점이 있을까?
먼저 메모리 측면에서 장점이 있다. 거의 두 배 차이..잘라지는 요소가 많아지면 많아질수록 더 큰 효과를 볼 수 있다.
문자열보다 배열이 큰 이유는 알잘딱깔센 찾아보자!
In [7]: getsizeof(s.split("_"))
Out[7]: 152
In [8]: getsizeof(s.split("_", maxsplit=1))
Out[8]: 72
두번째로는 속도 측면에서 장점이 있다. 요것도 잘라지는 요소가 많아지면 많아질수록 효과가 크다.
In [16]: timeit.timeit("'hi_hello_1_2_3_4_5_6_7_8_9_10'.split('_', maxsplit=1)")
Out[16]: 0.20580519600002845
In [17]: timeit.timeit("'hi_hello_1_2_3_4_5_6_7_8_9_10'.split('_')")
Out[17]: 0.2410702570000467
여기서 흥미로운 점 하나 추가! positional argument로 전달하는 것이 속도 측면에서 어느정도 이점이 있는데, 이유는 keyword argument를 해석하는 과정에서 오버로드가 발생하기 때문이다. 물론 이 차이는 문자열의 길이에 비례하지 않기 때문에 코드를 읽는 사람을 생각해서 keyword argument를 사용하는 것도 나쁘지 않다.
In [15]: timeit.timeit("'hi_hello_1_2_3_4_5_6_7_8_9_10'.split('_', 1)")
Out[15]: 0.17547050000001718
In [16]: timeit.timeit("'hi_hello_1_2_3_4_5_6_7_8_9_10'.split('_', maxsplit=1)")
Out[16]: 0.20580519600002845
다음은 str.rsplit
이다. 요건 보통 maxsplit이랑 같이 사용되는데, str.split
문자열의 왼쪽부터 잘라내는데 그러면 맨 오른쪽에 있는 값을 찾기 위해서는 항상 문자열 전체를 잘라내야 하기 때문에 오른쪽에 있는 값을 빠르게 가져오기 위해서 str.rsplit
을 사용한다.
아까와 다르게 5가 따로 잘린 것을 확인할 수 있다. maxsplit
을 사용하는 이유는 str.split
에서 설명한 것과 동일하다.
In [18]: s.rsplit("_", maxsplit=1)
Out[18]: ['hi_hello_1_2_3_4', '5']
흠 그러면 split 함수가 있는데 str.partition
은 왜 있는 걸까? 그냥 str.split
으로 지지고 볶고 하면 될 거 같은데..🤔
라는 생각이 들 수 있지만 str.partition
은 첫번째 요소를 가져오는데 특화된 녀석이다.
In [19]: s.partition("_")
Out[19]: ('hi', '_', 'hello_1_2_3_4_5')
str.rpartition
은? r
이 붙어있는 걸 보니 str.rsplit
과 같이 오른쪽에서 잘라오겠지요?
In [20]: s.rpartition("_")
Out[20]: ('hi_hello_1_2_3_4', '_', '5')
하나 가져오기용인건 알겠는데 무슨 장점있냐! 속도 측면에서 차이가 있다.
In [21]: timeit.timeit("'hi_hello_1_2_3_4_5_6_7_8_9_10'.partition('_')")
Out[21]: 0.13420031200007543
In [22]: timeit.timeit("'hi_hello_1_2_3_4_5_6_7_8_9_10'.split('_', 1)")
Out[22]: 0.20099570999991556
In [23]: timeit.timeit("'hi_hello_1_2_3_4_5_6_7_8_9_10'.split('_', 1)")
Out[23]: 0.16223106200004622
와우..positional argument로 던진 것보다 빠른 것을 확인할 수 있다.
그렇다면 요녀석은 대체 왜 빠른 것일까..
이런 건..CPython 코드를 까보는게 가장 빠르다.
먼저, str.split
부터 살펴보자.. C언어를 잘 몰라서 코드를 기가맥히게 풀이는 할 수 없당 ㅠ..그래도 대충 보면 sep_len
이 구분자의 길이인 거 같고..길이가 1일때는 split_char
함수를 수행하는 것을 알 수 있다.
STRINGLIB(split)(PyObject* str_obj
...
if (sep_len == 0) {
PyErr_SetString(PyExc_ValueError, "empty separator");
return NULL;
}
else if (sep_len == 1)
return STRINGLIB(split_char)(str_obj, str, str_len, sep[0], maxcount);
split_char
를 보면..
STRINGLIB(split_char)(PyObject* str_obj,
...
while ((j < str_len) && (maxcount-- > 0)) {
for(; j < str_len; j++) {
/* I found that using memchr makes no difference */
if (str[j] == ch) {
SPLIT_ADD(str, i, j);
i = j = j + 1;
break;
}
}
}
전체 문자열을 돌면서 구분자와 일치하는 문자일때 SPLIT_ADD
하는 것을 볼 수 있다. 대충 여기까지만 보고, str.partition
을 살펴보자.
STRINGLIB(partition)(PyObject* str_obj,
if (sep_len == 0) {
PyErr_SetString(PyExc_ValueError, "empty separator");
return NULL;
}
out = PyTuple_New(3);
if (!out)
return NULL;
pos = FASTSEARCH(str, str_len, sep, sep_len, -1, FAST_SEARCH);
out
이 3개의 요소를 가진 tuple이니까 아까 봤던 Out[19]: ('hi', '_', 'hello_1_2_3_4_5')
요값인 거 같은데, split_char
와는 다르게 pos
라는 변수가 있다!
...
PyTuple_SET_ITEM(out, 0, STRINGLIB_NEW(str, pos));
Py_INCREF(sep_obj);
PyTuple_SET_ITEM(out, 1, sep_obj);
pos += sep_len;
PyTuple_SET_ITEM(out, 2, STRINGLIB_NEW(str + pos, str_len - pos));
요 변수로 문자열을 잘라내서 out 변수에 값을 할당하는 걸 알 수 있는데, pos
값을 구하는데 FASTSEARCH
라는 함수를 사용하고 있다.. 아무래도 요 녀석이 속도 차이의 핵심인듯한데, 어떤 함수길래 속도 차이를 내고 있는 것일까?
요기서 구현 코드를 볼 수 있기는 한데.. 다른 코드에 비해서 너무 길어서 내가 분석하기에는 좀 귀찮다 ㅋ.. 그래서 킹갓오버플로우에서 찾아보니 Boyer-Moore 알고리즘을 구현한 거라고 한다.(FYI. https://stackoverflow.com/a/12244891) 옛날 옛적에 구현해본 적이 있는데 지금 보니 역시나 잘 모르겠다 ㅎㅋ..암튼 빠르니까 좋은 거겟지 뭐 하하하하하
글이 좀 길어졌는데 이번 글은 여기서 마쳐야겠다.(절대 BM 알고리즘을 분석하기 싫어서 그런 거 아닙니다 6ㅇㅅㅇ..)