정규표현식을 모르고 있는 사람은 아래 포스팅을 읽고 오자

2021.04.17 - [정보보안-이론] - 정규표현식에 대하여

 

정규표현식에 대하여

정규표현식은 여러 목적으로 사용된다. 정규표현식 자체는 어렵지 않다. 정규식(正規式)은 특정한 규칙을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어이다. < 정규표현식의 정의 : 출

tutoreducto.tistory.com

필자도 정규표현식을 많이 안 써봤지만. Python도 정규표현식을 당연히 지원한다. re(Regular Expression)이라는 모듈을 알아보자.


Python에서 re사용법 

POSIX와 거의 동일하다. 아래는 Python에서 패턴문자가 의미하는 것이다.

패턴문자 설명
. New line을 제외한 모든 문자, 플래그중에 re.DOTALL이 있는데, 이게 설정되면 new line을 포함한 모든 문자가 된다.
^ 문자열의 처음에 일치하는 패턴, re.MULTILINE이 설정되면, 각라인마다 첫 패턴을 검색한다.
$ 문자열의 끝에 일치하는 패턴, re.MULTILINE이 설정되면, 각라인마다 첫 패턴을 검색한다.
* 해당하는 패턴의 0번이상 반복을 탐색 
+ 해당하는 패턴의 1번이상 반복을 탐색 
? 해당하는 패턴의 0번 혹은 1번 매칭을 탐색 
{m} 해당하는 패턴의 m번의 반복을 탐색
{m,n} 해당하는 패턴의 m번 이상 n번 이하 반복을 탐색
{m,n}? 해당하는 패턴의 m번 이상 n번 이하 반복중 최소길이 값을 탐색
\ 이스케이프 지시자
[] 대괄호안에들어간 문자중 하나를 선택
| 파이프기호, 좌변과 우변중하나를 선택

 

re객체의 사용

Python에서 정규표현식을 사용하기 위해서는 우선 정규표현식객체를 생성해야 된다. compile 명령을 다음과 같이 사용한다.

>>> import re
>>> re_object = re.compile("a{3}.*c$")

### re.compile(pattern)으로 사용한다.
### 위 패턴은 3번의 a반복과 0번이상의 아무문자 c로 끝나는 패턴들이다.

이렇게 생성이 된 re객체는 다음과 같은 식으로 사용될 수 있다.

 

match

>>> pat = re_object.match("aaaddc")
>>> pat
<re.Match object; span=(0, 6), match='aaaddc'>
### 문자열 aaaddc는 re_object의 패턴과 정합하니 pat을 생성한다.

>>> pat = re_object.match("a4aaddc")
>>> pat
None
### 문자열 a4aaddc는 re_object의 패턴과 정합하지 않으니, pat이 None이다.

 

match로 생성된 match객체는 다음과 같은 method를 가진다.

 match.group() : 정합된 문자열을 반환한다.
 match.start() : 정합된 문자열의 시작위치를 반환한다.
 match.end() : 정합된 문자열의 마지막위치를 반환한다.
 match.span() : 정합된 문자열 위치의 (시작, 끝)을 튜플로 반환한다.

 

* match객체를 아래처럼 바로 생성할 수 있다.

>>> re_object = re.match("(a{3}.)c$", "aaabbc")

 

search

>>> pat = re_object.search("123 aaaddc")
>>> pat
<re.Match object; span=(4, 10), match='aaaddc'>
### search는 패턴에 정합하는 문자열의 위치를 span(튜플임당)구조로 알려준다

 

findall

>>> re_object = re.compile("a{3}.c")
>>> pat = re_object.findall("aaabbc 123 aaaeec 123")
>>> print(pat)
["aaabbc", "aaaeec"]

### findall은 패턴에 부합되는 모든 결과를 list로 반환한다.
### 좋은 설명을 위해서 re_object를 다시 생성하였다.

 

finditer

>>> re_object = re.compile("a{3}.c")
>>> pat = re_object.finditer("aaabbc 123 aaaeec 123")
>>> pat
<callable_iterator object at 0x000001E22720F5E0>

### findall과 같은데, iterable한 객체를 반환한다. 객체는 하나하나가 match로 yield된다.

 

 

정규식 생성의 옵션

다음과 같은 옵션들이 있다. 

1. VERBOSE, X : 정규식을 여러 줄로 적고, 주석을 달아 세부적인 설명이 가능토록 해준다.

2. MULTILINE, M : 시작검색(^)이나 끝 검색($)이 여러 줄에(new line으로 구분되는) 각 줄에 해당되게 해 준다.

3. IGNORECASE, I : 대소문자 구별 없이 검색을 하게 해 준다.

4. DOTALL, S : 점(.)이 New Line을 포함해서 검색할 수 있게 해 준다.

 

 

* 출처 : Python API docs.python.org/3/library/re.html

 

re — Regular expression operations — Python 3.9.4 documentation

This module provides regular expression matching operations similar to those found in Perl. Both patterns and strings to be searched can be Unicode strings (str) as well as 8-bit strings (bytes). However, Unicode strings and 8-bit strings cannot be mixed:

docs.python.org


re모듈의 사용법에 대해 알아보았다. 정규표현식을 잘 사용하시는 분들의 것을 보면 무슨 암호문 같다. 사용법을 알고 능숙하게는 사용하지는 못하더라도, 읽을 수 있는 정도는 알아두자.

오랜만에 돌아온 모듈 탐구 시간이다. 이번 시간에는 일반 문자열 연산을 조금 더 편리하게 도와주는 string 모듈에 대해서 알아보자

 


제공하는 문자열

1. string.ascii_letters : 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'

2. string.ascii_lowercase : 'abcdefghijklmnopqrstuvwxyz'

3. string.ascii_uppercase : 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

4. string.digits : '0123456789'

5. string.hexdigits : '0123456789abcdefABCDEF'

6. string.octdigits :'01234567'

7. string.punctuation : '!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~'

8. string.printable : 출력 가능한 모든 문자열의 조합(ascii_letter + punctuation + digits)

9. string.whitespace : 공백으로 취급되는 모든 문자열(스페이스, 탭, 줄 바꿈, 캐리지 리턴, 세로 탭 및 폼 피드)

 

 

Formatter 클래스

Formatter은 문자열을 리터럴과 치환 필드(중괄호 {})로 표시를 한다. Formatter클래스는 str.format()과 유사하게 사용된다.

 

format 가장 기본적인 사용법은 다음과 같다.

 

>>> "tell me your {}".format("money")
tell me your money

 

 

중괄호 안에는 다음과 같은 문법을 써넣을 수 있으며, 의미는 옆 주석을 확인하면 된다.

"I love {}" ### 일반적인 치환문자{} "I love {0}"와 같은 의미이다.
"I love {} and {}" ### "I lovev {0} and {1}"와 같은 의미이다.
>>> 순서를 지정해줄 수 도 있고, 생략할 수도 있다.

"I have my name {name}" ### 키워드 name으로 지정값을 대입하게 할 수 있다.
"List number 1 is {my_list[0]}" ### my_list의 첫번째 원소값을 대입하게 한다
"Show me your money {0.my_money}" ### 0번째의 원소의 속성값 my_money를 출력하게 한다.
>>> 키워드를 통한 사용도 가능하며 나아가 자료구조의 속성이나 원소를 넣어줄 수 있다.

"I have much fun with {0!s}" ### 0번째 객체의 str()메소드를 호출한다.
"I have much fun with {0!r}" ### 0번째 객체의 repr()메소드를 호출한다.
"I have much fun with {0!a}" ### 0번째 객체의 ascii()메소드를 호출한다.
>>> 객체의 내장함수를 호출할 수 있다.

 

다양한 트릭을 다음처럼 사용할 수 있다.

### Trick 1. 텍스트 정렬
>>> "{:<30}".format("좌로밀착!")
'좌로밀착!                         '

>>> "{:>30}".format("우로밀착!")
'                         우로밀착!'

>>> "{:&^30}".format("가운데정렬!")
'&&&&&&&&&&&&가운데정렬!&&&&&&&&&&&&'


### Trick 2. 진법
>>> my_height = 185

>>> print("키가 어떻게 되세요?")
키가 어떻게 되세요?

>>> "10진수로 : {0:d} 16진수로 : {0:x} 8진수로 : {0:o} 2진수로 {0:b}".format(my_height)
'10진수로 : 185 16진수로 : b9 8진수로 : 271 2진수로 10111001'


### Trick 3. 구분자
>>> money = 100000000
>>> rate = 0.35

>>> "{0:,} {1:.2%}".format(money, rate)
'100,000,000 35.00%'

 

 

Template 클래스

Template 클래스는 대체 문자열 하나를 인자로 생성된다. 사용법이 직관적이니 다음의 코드를 확인하자.

### Template의 사용 ###
>>> my_template=string.Template("$abc is my $hobby")
>>> my_dict = {"abc":"riducto","hobby":"python"}
>>> your_dict = {"abc":"riducto"}

>>> my_template.substitute(my_dict)
'riducto is my python'
>>> my_template.substitute(your_dict)
Traceback (most recent call last):
...
KeyError: 'hobby'
### subsutitube는 템플릿에 지정된 $문자를 키로한 딕셔너리로 치환한다. key가 없을 경우 KeyError Exception

>>> my_template.safe_substitute(your_dict)
'riducto is my $hobby'
### 그런당신을 위해 safe_substitute는 지정문자를 치환에 실패한 경우 내비둔다.

 

* 출처 : docs.python.org/ko/3/library/string.html

 

string — 일반적인 문자열 연산 — Python 3.9.4 문서

string — 일반적인 문자열 연산 소스 코드: Lib/string.py 문자열 상수 이 모듈에 정의된 상수는 다음과 같습니다: string.ascii_letters 아래에 나오는 ascii_lowercase와 ascii_uppercase 상수를 이어붙인 것입니다

docs.python.org


이 정도면 string 모듈에 대해서는 다 설명한 거 같다. 다음 모듈 탐구 시간에 만나도록 하자!

sys모듈은 Python 인터프리터의 환경설정 등에 접근한다. 흔히 사용하는 sys.modules 나 sys.path 뿐만이 아니라 더욱 폭넓은 기능과 정보들을 가지고 있으니, 이번 시간을 통해서 자세히 알아보도록 하자


sys에서 제공하는 정보

1. sys.argv : Python 스크립트에 전달된 명령 줄의 인자를 LIST형태로 전달한다. 스크립트 이름은 sys.argv[0]를 가지고 뒤에 전달이 되면 순서대로 sys.argv [1], [2]가 된다. 만약 실행형 옵션인 -c를 설정하면 sys.argv [0]는 "-c"가 된다.

2. sys.byteorder : 현재의 바이트표기법이 little인지 big인지를 표시한다.

3. sys.builtin_module_names : 현재 Python 인터프리터로 가져온 모든 모듈의 이름을 제공하는 문자열을 튜플로 반환한다.

4. sys.copyright : Python Interpreter의 저작권을 문자열로 반환한다.

5. sys.dont_write_bytecode : 이게 True면 모듈 import 시. pyc를 만들지 않는다.

6. sys.pycache_prefix : 이게 설정되면(디렉터리 문자열로 설정)  __pycache__에 pyc가 생성되는 것이 아니라, 같은 디렉터리 문자열에 pyc가 생성이 된다.

7. sys.float_info : 실수는 운영환경에 따라 표현의 깊이 달라진다. 그 표현 깊이를 반환한다.

8. sys.hash_info : 해시 알고리즘을 계산하는 머신의 정보를 튜플로 반환한다.

9. sys.int_info : 정수 표현의 수준을 튜플로 반환한다.

10. sys.maxunicode : 가장 큰 유니코드의 코드 값을 반환한다.

11. sys.modules : 이미 로드된 모듈을 저장한 Dictionary이다. API에서는 modules Dictionary를 직접 바꾸는 것은 추천하지 않는다.

12. sys.path : 모듈이 탐색될 수 있는 경로를 가진 LIST이다. sys.path에 경로가 추가되면, 그 위치에서 모듈을 절대 경로를 생략하고 import 할 수 있다.

13. sys.platform : Python이 실행되는 플랫폼을 문자열로 반환한다.

14. sys.thread_info : 스레드 구현에 관련된 정보를 담은 Tuple

15. sys.version : Python 인터프리터의 버전 번호와 빌드 번호 등을 포함한 문자열, 유사한 기능으로 sys.api_version, sys.version_info, sys.winver

 

 

sys에서 제공하는 함수

1. sys.exit([args]) : SystemExit이라는 Exception을 발생시켜 Python에서 빠져나온다. 따라서 이를 try - except으로 예외 처리하면 Python은 종료되지 않는다.

2. sys.getallocatedblocks() : 현재 Pythond이 메모리에서 차지한 공간을 출력한다. 메모리 Leak 등을 검사하는 데 사용된다.

3. sys.getdefaultencoding() : 현재 기본 문자열 인코딩 타입을 반환한다.

4. sys.getrecursionlimit() : 스택의 재귀 깊이를 반환한다. setrecursionlimit(int)로 설정할 수도 있다.

5. sys.getsizeof(object) : 객체의 크기를 바이트 단위로 반환한다. 객체가 참조하는 다른 객체의 크기를 포함하지 느 않는다.

6. sys.intern(String) : String을 메모리 사전에 등록한다. 이는 성능 최적화를 위한 것으로, 만약 'ABC"라는 문자열이 intern으로 등록되면, a="ABC', b="ABC"라고할 때 a와 b는 같은 메모리 위치를 가진다.

7. sys.is_finalizing() : Python이 종료 중이면 True, 아니면 False를 반환한다.

 

 

표준 스트림

1. sys.stdin : 표준 입력 스트림(객체다)

2. sys.stdout : 표준 출력 스트림(객체다)

3. sys.stderr : 표준 에러 스트림(객체다)

 

* 출처 : docs.python.org/ko/3/library/sys.html

 

sys — 시스템 특정 파라미터와 함수 — Python 3.9.4 문서

sys — 시스템 특정 파라미터와 함수 이 모듈은 인터프리터에 의해 사용되거나 유지되는 일부 변수와 인터프리터와 강하게 상호 작용하는 함수에 대한 액세스를 제공합니다. 항상 사용 가능합니

docs.python.org


이번 시간에는 Python인터프리터와 실행환경에 관련된 sys에 관하여 조금 깊게 알아보았다. 위에 기술된 정보나 함수 외에도 저수준의 제공 기능(함수의 breakpoint, hook, audit 등)을 또한 제공하니, API를 읽어보는 것을 추천한다.

와 글이 날아갔다. 다시 쓴다. os는 Python에 내장되어있는 모듈 중 하나로, 저수준의 파일과 디렉터리 작업을 지원한다.(고수준의 기능과 확장성, 이식성이 필요하다면 shutil이라는 더 적합한 모듈이 있다.)

옛날 Latte에는 DOS OS를 이용한 window를 사용을 했다.(그 때의 Window는 이런 이쁘장한 화면 없었다~ 이 말이야)

검은 화면에 글자로 상호작용하는, 지금으로 치면 cmd명령프롬포트가 사용자와의 인터페이스 역할을 해주었다. os는 python수준에서 컴퓨터의 파일 시스템과 파일에 접근 / 처리를 지시할 수 있는 Python수준의 기능을 제공한다. 그러면 바로 만나보자



* 이 모듈탐구시간에는 WINDOW에서 사용되는 os모듈 함수들만을 다룬다 *
* 파일디스크립터 함수는 나도 잘 모르는 것이라 제대로 된 이해를 하기 전까지 정리하지 않을 것이다 *



환경변수 관련 함수

1. os.environ() : 문자열로 환경변수를 반환한다.
2. os.environb() : 바이트로 환경변수를 반환한다.
3. os.getenv(key) : key에 해당하는 환경변수 value를 반환한다. 두 번째 파라미터로 default=None을 넣을 수 있는데, KeyError시 default값을 반환한다.
4. os.getenvb(key) : key에 해당하는 환경변수 value를 바이트로 반환한다. 역시 두 번째 파라미터로 default=None을 넣을 수 있다.
5. os.putenv(key,value) : key에 해당하는 환경변수 value를 추가한다.
* 환경변수란 시스템에 전역으로 정의된 변수를 의미한다. 예를 들면 명령프롬포트에 notepad.exe(System32 폴더 아래)를 쳐서 현재 디렉터리에 없는 notepad.exe를 실행할 수 있는 이유는 기본적으로 탐색할 디렉터리인 PATH라는 환경변수에 C:\Windows\System32가 추가되어있기 때문이다.

 

 


호스트 정보 알아내기

1. os.getlogin() : 현재 로그인한 사용자의 이름을 반환한다.
2. os.cpu_count() : 시스템의 CPU수를 반환한다. 없으면 None을 반환한다.(응? 없을 수 있나?)

 

 


디렉터리 탐색

1. os.getcwd() : 현재 작업경로를 절대 경로로 반환한다.
2. os.getcwdb() : 현재 작업경로를 절대 경로로 바이트 반환한다.
3. os.chdir(path) : 현재 작업경로를 path로 설정한다.(상대 경로도 인정한다)
4. os.listdir(path) : path 디렉토리에 있는 항목들의 이름을 담고 있는 LIST를 반환한다. path가 생략되면 현재 디렉터리를 탐색
5. os.scandir(path) : path 디렉토리에 있는 항목들의 이름을 담고 있는 순서 없는 Iterator를 반환한다. path가 생략되면 현재 디렉터리이며, 현재 경로인 ".", 상위 경로인 ".."은 포함하지 않고, scandir.close()로 종료된다.
5. os.lstat(filename) : 주어진 경로에 대해 lstat 명령어를 실행한다(파일의 속성을 가져오는 명령어 이다)
6. os.readlink(path) : path인 심볼릭 링크가 가리키는 경로를 문자열로 반환한다.

* scandir로 생성된 Iterator는 DirEntry라는 객체로 반환되는데, name, path, inode 등 파일 속성에 대한 정보를 가지고 있다.

 


파일, 디렉터리 조작

1. os.mkdir(path) : path라는 디렉터리를 생성한다.
2. os.makedirs(name) : 재귀적으로 name의 디렉터리를 생성한다.(ex : 상위 폴더가 없으면 그것도 만들고, 위에 것도 만든다.)
3. os.remove(path) : 파일 path를 제거한다. path가 디렉터리인 경우 Exception이 발생된다.
4. os.rmdir(path) : 디렉터리 path를 제거한다. 디렉터리가 존재하지 않거나 비어있지 않으면 Exception이 발생된다.
5. os.removedirs(name) : 재귀적으로 name의 디렉터리를 제거한다.(ex : 상위 폴더도 없애고, 그 위 것도 없애고)
6. os.rename(src, dst) : src를 dst로 이름을 바꾼다 이미 dst가 존재하는 경우 Exception이 발생된다.
7. os.renames(old, new) : old를 new로 재귀적으로 이름을 바꾼다.(만약 new에 없는 디렉터리 경로가 있으면 생성한다.)
8. os.symlink(src, dst) : dst를 가리키는 소프트 링크 src를 생성한다
9. os.truncate(path, length) : 강제로 path파일을 length 크기에 맞춘다.(이거 무슨 침대 있지 않았나?) 자르는 것이다.

 


프로세스 조작

1. os.abort() : 현재 실행 중인 프로세스에 SIGABRT 시그널을 보낸다.
2. os.add_dll_directory(path) : DLL탐색경로에 path를 추가한다.(안 써봐서 이건 모르겠다.)
3. os.execl(path, args...) : 현재 프로세스를 대체해서 새로운 프로세스를 생성한다. 이는 Python에 결과를 반환하지 않는다. (ex : os.execl("notepad.exe")는 현재 Python이 종료되고, 메모장이 실행된다.
4. os._exit(n) : 종료 코드 n으로 프로세스를 종료시킨다.
5. os.kill(pid, sig) : 프로세스 pid에 시그널 sig를 전송한다. 그룹 단위로 전송하는 killpg도 있다.
6. os.popen(cmd) : 이거 할 말 많은 함수인데, 명령 프롬포트와 파이프라인을 연결한다. Python에서 다른 명령줄(cmd)을 실행할 때 사용된다. subprocess모듈과 깊은 관련이 있고, 관련 내용은 모듈 탐구가 아닌, 다른 포스팅으로 자세하게 소개하겠다.
7. os.startfile(path) : 파일 탐색기에서 path를 두 번 클릭하는 것과 같은 결과이다. 따라서 os.startfile()은 시작하자마자 끝난다.
8. os.system(command) : 프로그램이 별도의 명령 프롬포트(cmd)(사실 여기선 SHELL이 맞는 말이다.)을 실행하여 command를 실행한다. 이 또한 위 popen처럼 별도의 포스팅에서 서술한다.
9. os.times() : 모든 프로세스 시간을 반환한다.
10. os.waitpid(pid, 0) : 지정된 pid의 프로세스가 종료될 때까지 기다린다.

 

 


* 출처 : https://docs.python.org/ko/3/library/os.html

 

os — 기타 운영 체제 인터페이스 — Python 3.9.4 문서

os — 기타 운영 체제 인터페이스 소스 코드: Lib/os.py 이 모듈은 운영 체제 종속 기능을 사용하는 이식성 있는 방법을 제공합니다. 파일을 읽거나 쓰고 싶으면 open()을 보세요, 경로를 조작하려면 o

docs.python.org



한 번씩 써 본 함수만 적은 건데 엄청나게 많은 함수가 있고 포스팅에 적힌 os모듈은 반의반도 기술되지 않은 것이기에 놀라울 따름이다. 공식 홈페이지 문서는 꼭 찾아볼 것!

컴퓨터 세계에서 자주 사용되는 json이라는 표준이다. json은 Python을 자주 사용하는 사람이라면 아는 dictionary이라는 자료구조와 유사하게 생겼는데, 광범위하게 데이터 오브젝트를 인간이 읽을 수 있는 상태에서 전달하기 위한 표준이다. 다음은 json의 예시이다.

{
    "file_format_version" : "1.0.0",
    "ICD": {
        "library_path": ".\\XXX.dll",
        "api_version": "1.0.3"
    }
}

// json파일의 예시



json처럼 객체를 저장하고 나중에 재구성할 수 있는 포맷으로 표기하는것을 직렬화(Seriallization), 그 데이터를 다시 불러오는 것을 역직렬화(Deseriallization)이라고 한다. Python은 json처럼 인간 읽을 수 있는 방식 말고 pickle이라는 모듈을 이용해서 바이트 수준의 직렬화를 제공한다.



pickle을 이용한 데이터의 저장


다음은 students LIST객체를 my_list.dump에 저장하는 예시이다. byte연산을 수행하는 만큼 파일모드를 wb로 열었다.

>>> import pickle
>>> students = ["CHEEL","GLAADOS","STEENLY"]
>>> with open("my_list.dump","wb") as f:
	pickle.dump(students,f)

>>> b_students = pickle.dumps(students)
>>> print(b_students)
b'\x80\x04\x95!\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x05CHEEL\x94\x8c\x07GLAADOS\x94\x8c\x07STEENLY\x94e.'


* b_students처럼 저장하는 대신 pickle.dumps(students)로 바이트 반환받을 수 있다.

 



pickle을 이용한 데이터의 불러오기


다음은 my_list.dump에 저장된 바이트스트림을 students에 다시 할당하는 코드이다. 역시 rb로 열었다.

>>> import pickle
>>> with open("my_list.dump","rb") as f:
	new_students = pickle.load(f)

>>> print(new_students)
['CHEEL', 'GLAADOS', 'STEENLY']
>>> new_b_students = pickle.loads(b_students)
>>> print(new_b_students)
['CHEEL', 'GLAADOS', 'STEENLY']


* 역시 pickle.loads(datas)로 파일연산없이 직접 받을 수 있다.



* pickle불가한 객체를 pickle 하면 PicklingError라는 Exception이 발생한다. 가능한 객체 목록은 API를 참조하자
* 클래스 인스턴스를 pickle하는 방법이 API에 나와있다. __init__()이 호출되는 것이 아닌, 기존의 attribute를 저장했다가 load 하는 방식인데, 사용자가 재정의할 수 있다는 점에서 재미있는 이야기이니, API를 한 번씩 읽어보기를 바란다.
* 과거에는 pickle과 비슷한 marshal이라는 모듈이 있었다고한다.(안써봤다.) API에서는 여러 가지 이유로 marshal대신 pickle을 쓰기를 권장한다.

 

* 출처 : docs.python.org/ko/3/library/pickle.html

 

pickle — 파이썬 객체 직렬화 — Python 3.9.4 문서

pickle — 파이썬 객체 직렬화 소스 코드: Lib/pickle.py pickle 모듈은 파이썬 객체 구조의 직렬화와 역 직렬화를 위한 바이너리 프로토콜을 구현합니다. 《피클링(pickling)》은 파이썬 객체 계층 구조가

docs.python.org



이번시간에는 Python의 직렬화 / 역직렬화 방식인 pickle에 대해서 알아보았다. 거대한 데이터를 저장하거나, 변수의 프로그램 수명에서 재사용할 시 채용할 수 있는 기법이니, 익혀두면 도움이 될 것이다.

혹시 이 글을 읽는 독자는 LIST의 복사를 시도해보았는가? 아마 a=[1,2,3]과 같은 LIST를 바로 b=a로 복사부터 시도했을 가능성이 크다(우리 Python은 직관적이어서 이거도 돼야 되는 거 아닌가?) 웃긴 건 이때 a [0]=5를 하면 b도 [5,2,3]이 된다는 점이다. 이는 복사의 깊이가 달라서 생기는 문제이다.

 


복사의 구분

복사는 수준에 따라 다음과 같이 구분된다.

① 얕은 복사(Shallow copy) : 메모리 주소만 같은 곳을 가리키게 복사되는것, 값이 복사되는 것은 아니다.
② 깊은 복사(Deep copy) : 실제 값까지 모두 복사되는 것

 

Python은 모듈 copy를 통해서 이 복사행위를 다룬다. 다음은 두 가지는 copy모듈이 제공하는 함수이다.

1. copy.copy(x) : x의 얕은 복사결과를 반환

2. copy.deepcopy(x) : x의 깊은 복사결과를 반환

 

다음은 예시이다.

>>> import copy
>>> a=[[1,2,3,4,5],[6,7,8,9]]
>>> b = copy.copy(a)
>>> a[1].append(10)
>>> print(b)
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
>>> b = copy.deepcopy(a)
>>> a[1].append(11)
>>> print(b)
[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]

다음과 같이 얕은복사로 이루어진 copy.copy는 원본의 추가 연산이 복사본에도 영향이 가고, 깊은 복사가 실행된 이후에는 원본의 변화가 복사본에 영향을 미치지 않는 것을 확인할 수 있다.


이번 모듈탐구 시간에는 복사를 용이하게 도와주는 copy모듈에 대해서 알아보았다. 비록 list를 예시로 들었지만, 다른 자료구조(Dict, tuple 등)이나 객체에도 해당되는 이야기이니, 유용하게 사용하기를 바란다.

+ Recent posts