Introducing Python(처음 시작하는 파이썬)
Chapter 7. 데이터 주무르기
* Python Built-In Data Type
1) Strings
- Unicode 문자의 Sequence
- 텍스트 데이터 표현에 사용된다.
2) Bytes & Bytearrays
- 8-bit Integer Sequence
- Binary Data 표현에 사용된다.
7.1 Text Strings (텍스트 문자열)
※ Python 3의 String은 Byte의 Array가 아닌, Unicode String이다.
- Python 3에서는 일반적인 Byte Array와 Unicode 문자가 구별된다.
Unicode (유니코드) (URL)
- 전 세계 언어의 문자를 정의하기 위한 국제 표준 코드이다.
- 플랫폼, 프로그램, 언어에 상관없이 문자마다 고유한 코드값이 부여되어 있다.
- 수학 및 기타 분야의 기호들도 포함하고 있다.
- 유니코드 문자들은 Unicode Plane(유니코드 평면)이라 불리는, Logical Block으로 나뉜다.
- 유니코드에서 각 문자들은 Unicode ID 혹은 해당 문자의 Name을 통해 구분된다.
Basic Multilingual Plane | \u(4 hex numbers) | - 유니코드 기본 평면 256개 중 하나의 문자를 지정한다. - 앞의 두 16진수는 Plane Number를 의미하며, 뒤의 두 16진수는 해당 Plane에 있는 문자의 Index이다. |
Higher Plane | \U(8 hex numbers) | - 보다 높은 Plane에 위치한 문자를 지정한다. |
Standard Name | \N(standard name) | - 유니코드의 모든 문자는 표준 이름으로 지정할 수도 있다. |
* Python \(\texttt{unicodedata}\) Module
Function | Description |
\(\texttt{lookup( char_name )}\) | - 대소문자를 구분하지 않는 인자를 취하고, 유니코드 문자를 리턴한다. |
\(\texttt{name( unicode_char )}\) | - 인자로 유니코드 문자를 취하고, 대문자 이름을 반환한다. |
Example.
def unicode_test(value):
import unicodedata
name = unicodedata.name(value)
value2 = unicodedata.lookup(name)
print('value="%s", name="%s", value2="%s' % (value, name, value2))
Execution:
# ASCII Letter 'A'
>>> unicode_test('A')
value="A", name="LATIN CAPITAL LETTER A", value2="A"
# ASCII Punctuation '$'
>>> unicode_test('$')
value="$", name="DOLLAR SIGN", value2="$"
# Unicode Currency Character
>>> unicode_test('\u00a2')
value="¢", name="CENT SIGN", value2="¢"
# Unicode Currency Character (another version)
>>> unicode_test('\u20ac')
value="€", name="EURO SIGN", value2="€"
Example.
>>> place = 'caf\u00e9' # \u00e9 = é
>>> place
'café'
>>> place = 'caf\N{LATIN SMALL LETTER E WITH ACUTE}'
>>> place
'café'
UTF-8 Encoding and Decoding
* Encoding: String → Byte Code
* Decoding: Byte Code → String
- UTF-8은 Python, Linux, HTML에서 채택한 표준 Text Encoding 방식이다.
- UTF-8 방식에서는, Unicode 한 문자를 1Byte~4Byte로 Dynamic Encode한다.
(즉, UTF-8은 가변 길이 인코딩 방식이다.)
\(\texttt{encode()}\) Function & \(\texttt{decode()}\) Function (\(\texttt{encode()}\) 함수 & \(\texttt{decode()}\) 함수)
str.encode( [encoding='utf-8'], [errors='strict'] )
bytes.decode( [encoding='utf-8'], [errors='strict'] )
Return:
- String을 Unicode로 Encode한 값을 리턴한다. (\(\texttt{encode()}\) Function)
- Byte Code를 String으로 Decode한 값을 리턴한다. (\(\texttt{decode()}\) Function)
Arguments:
1) \(\texttt{encoding}\) (Optional)
\(\texttt{encoding}\) Values | Description |
\(\texttt{'uft-8'}\) (Default) |
- 8-bit 가변 길이 Encoding 형식 (거의 대부분의 문자를 지원한다.) |
\(\texttt{'ascii'}\) | - 7-bit ASCII Code |
\(\texttt{'latin-1'}\) | - ISO 8859-1 |
\(\texttt{'cp-1252'}\) | - Windows Encoding 형식 |
\(\texttt{'unicode-escape'}\) | - Python Unicode Literal 형식 - \uxxxx 또는 \Uxxxxxxxx |
2) \(\texttt{errors}\) (Optional)
\(\texttt{errors}\) Values | Description |
\(\texttt{'strict'}\) (Default) |
- 인코딩할 수 없는 문자가 감지되면, UnicodeEncodeError를 발생시킨다. |
\(\texttt{'backslashreplace'}\) | - 인코딩할 수 없는 문자를 backslash(\)로 대체한다. |
\(\texttt{'ignore'}\) | - 인코딩할 수 없는 문자를 무시한다. |
\(\texttt{'namereplace'}\) | - 인코딩할 수 없는 문자를 해당 문자의 설명으로 대체한다. |
\(\texttt{'replace'}\) | - 인코딩할 수 없는 문자를 questionmark(?)로 대체한다. |
\(\texttt{'xmlcharrefreplace'}\) | - 인코딩할 수 없는 문자를 XML 문자로 대체한다. |
Format (포맷)
- Formatting을 통해 데이터값을 문자열에 정갈하게 Interpolate(끼워넣는)할 수 있다.
- Formatting Style에는 %를 사용한 구버전과 {} 혹은 \(\texttt{format}\) 키워드를 사용한 신버전이 있으며,
Python 3에서는 두 스타일 모두를 지원하고 있다.
Old Style Formatting: %
string % data
- String안에 끼워넣을 데이터를 표시하는 Interpolation Sequence 형식을 사용한다.
* Interpolation Sequences
Type Specifier | Description |
%s | - 문자열 |
%d | - 10진 정수 |
%x | - 16진 정수 |
%o | - 8진 정수 |
%f | - 10진 부동소수점수 |
%e | - 지수로 나타낸 부동소수점수 |
%g | - 10진 부동소수점수 혹은 지수로 나타낸 부동소수점수 |
%% | - 리터럴 % (퍼센트 기호 그 자체를 의미) |
Example. Old Style Formmating
>>> '%s' % 42
'42'
>>> '%d' % 42
'42'
>>> '%x' % 42
'2a'
>>> '%o' % 42
'52'
>>> '%s' % 7.03
'7.03'
>>> '%f' % 7.03
'7.030000'
>>> '%e' % 7.03
'7.030000e+00'
>>> '%g' % 7.03
'7.03'
>>> '%d%%' % 100
'100%'
>>> actor = 'Richard Gere'
>>> cat = 'Chester'
>>> weight = 28
>>> "My wife's favorite actor is %s" % actor
"My wife's favorite actor is Richard Gere"
>>> "Our cat %s weighs %s pounds" % (cat, weight)
'Our cat Chester weighs 28 pounds'
※ \(\texttt{%}\)기호 뒤에 여러 데이터를 지시할 경우, 반드시 Tuple로 묶어서 지시해야 한다.
Example. Old Style Formmating
>>> n = 42
>>> f = 7.03
>>> s = 'string cheese'
>>> '%d %f %s' % (n, f, s)
'42 7.030000 string cheese'
>>> '%10d %10f %10s' % (n, f, s)
' 42 7.030000 string cheese'
>>> '%-10d %-10f %-10s' % (n, f, s)
'42 7.030000 string cheese'
>>> '%10.4d %10.4f %10.4s' % (n, f, s)
' 0042 7.0300 stri'
>>> '%.4d %.4f %.4s' % (n, f, s)
'0042 7.0300 stri'
>>> '%*.*d %*.*f %*.*s' % (10, 4, n, 10, 4, f, 10, 4, s)
' 0042 7.0300 stri'
New Style Formatting: {}, \(\texttt{format}\)
- New Style Formatting에서는 Type Specifier앞에 : (Colon)을 붙인다.
(Old Style에서는 %을 붙였었다.)
Example. New Style Formmating
>>> n = 42
>>> f = 7.03
>>> s = 'string cheese'
>>> '{} {} {}'.format(n, f, s)
'42 7.03 string cheese'
>>> '{2} {0} {1}'.format(f, s, n)
'42 7.03 string cheese'
# {0}은 첫 번째 인자인 f, {1}은 s를, {2}는 n을 의미한다.
>>> '{n} {f} {s}'.format(n=42, f=7.03, s='string cheese')
'42 7.03 string cheese'
Example. New Style Formatting
>>> n = 42
>>> f = 7.03
>>> s = 'string cheese'
>>> d = {'n': 42, 'f': 7.03, 's': 'string cheese'}
>>> '{0[n]} {0[f]} {0[s]} {1}'.format(d, 'other')
'42 7.03 string cheese other'
# {0}은 Dictionary d 전체를 의미한다.
# {1}은 d 다음에 오는 문자열 'other'을 의미한다.
>>> '{0:d} {1:f} {2:s}'.format(n, f, s)
'42 7.030000 string cheese'
>>> '{n:d} {f:f} {s:s}'.format(n=42, f=7.03, s='string cheese')
'42 7.030000 string cheese'
# New Style에서는 Type Specifier앞에 : (Colon)을 붙인다.
# 즉, :d = %d
>>> '{0:10d} {1:10f} {2:10s}'.format(n, f, s)
' 42 7.030000 string cheese'
>>> '{0:>10d} {1:>10f} {2:>10s}'.format(n, f, s)
' 42 7.030000 string cheese'
# > 기호는 오른쪽 정렬을 지시한다.
>>> '{0:<10d} {1:<10f} {2:<10s}'.format(n, f, s)
'42 7.030000 string cheese'
# < 기호는 왼쪽 정렬을 지시한다.
>>> '{0:^10d} {1:^10f} {2:^10s}'.format(n, f, s)
' 42 7.030000 string cheese'
# ^ 기호는 가운데 정렬을 지시한다.
>>> '{0:>10.4d} {1:>10.4f} {2:10.4s}'.format(n, f, s)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Precision not allowed in integer format specifier
>>> '{0:10d} {1:10.4f} {2:10.4s}'.format(n, f, s)
' 42 7.0300 stri'
# New style에서는 소수점으로 표현하는 Precision 지정자를 정수(:d)에 사용할 수 없다.
>>> '{0:>10d} {1:>10.4f} {2:10.4s}'.format(n, f, s)
' 42 7.0300 stri '
# 정수에 Precision 지정자를 설정하지만 않으면 문제없이 작동한다.
>>> '{0:!^20s}'.format('BIG SALE')
'!!!!!!BIG SALE!!!!!!'
# format 키워드안에 자유롭게 문자열을 입력할 수도 있다.
Regular Expression in Python (Python에서의 정규표현식)
* Regular Expression (정규표현식) (URL)
- Python에서는 Standard Module \(\texttt{re}\)에서 정규표현식에 관한 기능들을 제공하고 있다.
* Methods in \(\texttt{re}\)
Method | Description |
re.match( pattern, source ) | - source가 pattern으로 시작하는지의 여부를 리턴한다. |
re.compile( pattern ) | - 패턴매칭을 빠르게 하기 위해, pattern을 미리 컴파일하여 리턴한다. |
re.search( pattern, source ) | - source에서 pattern과 첫 번째로 일치하는 부분을 리턴한다. |
re.findall( pattern, source ) | - source에서 일치하는 모든 pattern들을 리턴한다. |
re.split( pattern, source ) | - pattern을 기준으로 source를 분할한 후, 분할된 String 조각들의 List를 리턴한다. |
re.sub( pattern, substitute, source ) | - source에서 pattern과 일치하는 모든 부분을 substitute로 변경한다. |
group() |
- 매칭 결과를 리턴한다. |
groups() |
- 매칭 결과를 리턴한다. |
Example. Precompiled Pattern
youpattern = re.compile('You')
result = youpattern.match('Young Frankenstein')
* Regular Expression Pattern Specifier
Pattern Specifier | Description |
. | - 임의의 1회의 문자 (단, 개행문자 \n은 여기에 포함되지 않는다.) |
* | - 임의의 0회 이상의 문자 |
? | - 임의의 0회 또는 1회의 문자 |
a | b | - a 또는 b |
^ | - Source 문자열의 시작 (Anchor) |
$ | - Source 문자열의 끝 (Anchor) |
prev ? | - 0 또는 1회의 prev |
prev* | - 0회 이상의 최대 prev |
prev*? | - 0회 이상의 최소 prev |
prev+ | - 1회 이상의 최대 prev |
prev+? | - 1회 이상의 최소 prev |
prev {m} | - m회의 prev |
prev {m, n} | - m에서 n회의 최대 prev |
prev {m, n}? | - m에서 n회의 최소 prev |
[abc] | - a 또는 b 또는 c (a|b|c와 같음) |
[^abc] | - (a 또는 b 또는 c) 가 아님 |
prev (?=next) | - 뒤에 next가 오면 prev |
prev (?!next) | - 뒤에 next가 오지 않으면 prev |
(?<=prev) next | - 전에 prev가 오면 next |
(?<!prev) next | - 전에 prev가 오지 않으면 next |
\d | - 숫자 |
\D | - 비숫자 |
\w | - 알파벳 문자 |
\W | - 비알파벳 문자 |
\s | - 공백 문자 |
\S | - 비공백 문자 |
\b | - 단어 경계 (\w와 \W 또는 \W와 \w 사이의 경계) |
\B | - 비단어 경계 |
Example. Special Character
>>> import string
>>> printable = string.printable
>>> len(printable)
100
>>> printable[0:50]
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN'
>>> printable[50:]
'OPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
# string Module의 printable Array에는 출력 가능한 문자들이 저장되어 있다.
>>> re.findall('\d', printable)
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
>>> re.findall('\w', printable)
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '_']
>>> re.findall('\s', printable)
[' ', '\t', '\n', '\r', '\x0b', '\x0c']
※ 정규표현식의 Pattern 앞에 \(\texttt{r}\) (Raw String)을 입력하면, Python의 Escape Sequence를 잠시 사용할 수 없게 되어, 이스케이프 문자와 정규표현식 패턴이 충돌하는 일을 방지할 수 있다.
>>> source = '''I wish I may, I wish I might
... Have a dish of fish tonight.'''
>>> re.findall('\bfish', source)
[]
>>> re.findall(r'\bfish', source)
['fish']
# 정규표현식에서, \b는 단어의 경계를 의미하는 기호이다.
7.2 Binary Data (이진 데이터)
Data Type: \(\texttt{bytes}\)
- Byte의 Tuple로, Immutable하다.
Data Type: \(\texttt{bytearray}\)
- Byte의 List로, Mutable하다.
Example. \(\texttt{bytes}\) and \(\texttt{bytearray}\)
>> blist = [1, 2, 3, 255]
>>> the_bytes = bytes(blist)
>>> the_bytes
b'\x01\x02\x03\xff'
>>> the_byte_array = bytearray(blist)
>>> the_byte_array
bytearray(b'\x01\x02\x03\xff')
>>> the_bytes[1] = 127 # bytes is IMMUTABLE!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
>>> the_byte_array[1] = 127 # bytearray is MUTABLE!
>>> the_byte_array
bytearray(b'\x01\x7f\x03\xff')
※ Python에서 Byte 값은 \(\texttt{b}\)로 시작하며, 값은 Single Quote로 둘러쌓인 채로 표현된다.
ex) \(\texttt{b'\x61'}\)
* Bit Operation
- 예시에서, a = 5 (0b01010), b = 1 (0b0001)으로 초기화되어 있다 가정한다.
Operator | Description | Example | Decimal Result | Binary Result |
& | AND | a & b | 1 | 0b0001 |
| | OR | a | b | 5 | 0b0101 |
^ | exclusive or (XOR) |
a ^ b | 4 | 0b0100 |
~ | Flip Bits | ~a | -6 | Bianry representation depends on \(\texttt{int}\) size |
<< | Left Shift | a << 1 | 10 | 0b1010 |
>> | Right Shift | a >> 1 | 2 | 0b0010 |
Module: \(\texttt{struct}\)
- Binary Data를 Python 데이터 구조로 바꾸거나, 그 역을 수행할 때 사용하기 용이한 도구들을 포함하고 있는 Module이다.
Method: \(\texttt{struct.unpack( format, buffer )}\) (URL)
- \(\texttt{struct}\) Module에 포함되어 있다.
- \(\texttt{format}\)에서 지시한대로, \(\texttt{buffer}\)의 내용을 Unpack한다.
- Unpack의 결과물을 Tuple로 리턴한다.
Method: \(\texttt{struct.pack( format, v1, v2, ... )}\) (URL)
- \(\texttt{struct}\) Module에 포함되어 있다.
- 데이터 \(\texttt{v1, v2, ...}\)를 \(\texttt{bytes}\) Object 형태로 리턴한다.
- 데이터들을 \(\texttt{format}\)에서 지시한대로 Packing한다.
* Format String
Character | Byte Order | Size | Alignment |
@ | Native | Native | Native |
= | Native | Standard | None |
< | Little-Endian | Standard | None |
> | Big-Endian | Standard | None |
! | Network (=Big-Endian) |
Standard | None |
- 즉 Example Code의 \(\texttt{>LL}\)은 두 개의 Unsigned Long Integer(L)가 Big-Endian 형식으로 저장되어 있음을 의미한다.
* Format Specifier
Specifier | Description | Bytes |
x | skip a byte | 1 |
b | signed byte | 1 |
B | unsigned byte | 1 |
h | signed short integer | 2 |
H | unsigned short integer | 2 |
i | signed integer | 4 |
I | unsigned integer | 4 |
l | signed long integer | 4 |
L | unsigned long integer | 4 |
Q | unsigned long long integer | 8 |
f | single precision float | 4 |
d | double precision float | 8 |
p | count and characters | 1 + count |
s | characters | count |
- 몇몇 Specifier는 문자의 개수를 가리키는 숫자가 Prefix로 올 수 있다.
ex) \(5B \iff BBBBB\)
Example. \(\texttt{pack() and unpack()}\) Method Usage
>>> import struct
>>> struct.pack('>L', 154)
b'\x00\x00\x00\x9a'
>>> struct.pack('>L', 141)
b'\x00\x00\x00\x8d'
>>> struct.unpack('>16x2L6x', data)
(154, 141)
# > : Big-Endian 정수 형식으로 Unpack
# 16x : 16Byte를 건너뜀
# 2L : 2개의 Unsigned Long Integer를 읽어들임 (총 8Byte)
# 6x : 6Byte를 건너뜀
Example. PNG 파일의 가로, 세로 길이값을 출력하는 Python 프로그램
>>> import struct
>>> valid_png_header = b'\x89PNG\r\n\x1a\n'
# 변수 valid_png_header는 PNG 파일의 첫 8Byte를 저장한다. (첫 8Byte는 PNG파일의 Header부분이다.)
>>> data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR' + \
... b'\x00\x00\x00\x9a\x00\x00\x00\x8d\x08\x02\x00\x00\x00\xc0'
# PNG 파일은 가로, 세로 길이에 대한 정보가 첫 24Byte내에 저장된다.
# 변수 data는 PNG 파일의 첫 30Byte를 저장하는 변수이다.
>>> if data[:8] == valid_png_header:
... width, height = struct.unpack('>LL', data[16:24])
# width에는 16~20Byte, height에는 21~24Byte가 저장된다.
... print('Valid PNG, width', width, 'height', height)
... else:
... print('Not a valid PNG')
...
Valid PNG, width 154 height 141
* Third-Party Open Source Packages
- bitstring (URL)
- construct (URL)
- hachoir (URL)
- binio (URL)
Example. \(\texttt{construct}\) Package를 이용하여 PNG 파일의 크기를 추출하는 프로그램
>>> pip install construct
# construct 패키지 설치
>>> from construct import Struct, Magic, UBInt32, Const, String
>>> # adapted from code at https://github.com/construct
>>> fmt = Struct('png',
... Magic(b'\x89PNG\r\n\x1a\n'),
... UBInt32('length'),
... Const(String('type', 4), b'IHDR'),
... UBInt32('width'),
... UBInt32('height')
... )
>>> data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR' + \
... b'\x00\x00\x00\x9a\x00\x00\x00\x8d\x08\x02\x00\x00\x00\xc0'
>>> result = fmt.parse(data)
>>> print(result)
Container:
length = 13
type = b'IHDR'
width = 154
height = 141
>>> print(result.width, result.height)
154, 141
Module: \(\texttt{binascii}\)
- Binary Data와 다양한 문자열 표현을 서로 변환할 수 있는 함수들을 제공한다.
Example. \(\texttt{binascii}\) Usage
>>> import binascii
>>> valid_png_header = b'\x89PNG\r\n\x1a\n'
>>> print(binascii.hexlify(valid_png_header))
b'89504e470d0a1a0a'
# 16진수 Sequence로 이루어진 8Byte의 PNG 파일의 Header를 출력
>>> print(binascii.unhexlify(b'89504e470d0a1a0a'))
b'\x89PNG\r\n\x1a\n'
# 위 프로그램의 역을 수행
Reference: Introducing Python(처음 시작하는 파이썬) (Bill Lubanovic 저, O'Reilly, 2015)