파이썬 코드 읽어보기 - json/decoder.py, scanner.py

json - JSON encoder and decoder

파이썬 코드 읽어보기 첫 번째 시리즈는 json입니다.

import json

개발 중 흔하게 만나던 json의 내부는 어떻게 되어 있는지 같이 확인해봅시다.

저는 cpython repository에서 코드를 확인해봤습니다. 파이썬 코드는 ./Lib/ 디렉토리 아래에서 확인할 수 있습니다.

cpython/Lib/json/ 디렉토리안의 내용입니다.

  • __init__.py
  • decoder.py
  • encoder.py
  • scanner.py
  • tool.py

본 글에서는 decoder.pyscanner.py 를 확인해보겠습니다.

다음과 같은 메소드와 클래스에 대해 자세히 살펴보려합니다.

  • JSONDecoder - cls
  • JSONDecodeError - cls
  • _decode_uXXXX - method
  • py_scanstring - method
  • JSONObject - method
  • JSONArray - method

class JSONDecoder 부터 시작합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class JSONDecoder(object):
def __init__(self, *, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, strict=True,
object_pairs_hook=None):
self.object_hook = object_hook
self.parse_float = parse_float or float
self.parse_int = parse_int or int
self.parse_constant = parse_constant or _CONSTANTS.__getitem__
self.strict = strict
self.object_pairs_hook = object_pairs_hook
self.parse_object = JSONObject
self.parse_array = JSONArray
self.parse_string = scanstring
self.memo = {}
self.scan_once = scanner.make_scanner(self)

def decode(self, s, _w=WHITESPACE.match):
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
end = _w(s, end).end()
if end != len(s):
raise JSONDecodeError("Extra data", s, end)
return obj

def raw_decode(self, s, idx=0):
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
raise JSONDecodeError("Expecting value", s, err.value) from None
return obj, end

생성자의 각 파라미터에 대한 설명은 생략하겠습니다.

decode() - JSON 문서를 포함하고 있는 스트링 객체를 파이썬 오브젝트로 반환합니다.

1
2
3
4
5
6
7
>>> from json import JSONDecoder
>>> decoder = JSONDecoder()
>>> a = decoder.decode('{"a" : 1}')
>>> a
{'a': 1}
>>> type(a)
<class 'dict'>

raw_decode(self, s, idx=0) - JSON 문서를 디코딩해서 파이썬 객체와 파라미터 s가 끝나는 지점의 인덱스를 tuple의 형태로 반환합니다.

return (object, len(s)) 대략 이런 느낌입니다.

1
2
>>> decoder.raw_decode('{"a": 123}')
({'a': 123}, 10)
1
2
3
4
5
6
def raw_decode(self, s, idx=0):
try:
obj, end = self.scan_once(s, idx)
except StopIteration as err:
raise JSONDecodeError("Expecting value", s, err.value) from None
return obj, end

어떤 경우에 StopIteration 이 발생하는지 확인해보겠습니다. self.scan_once(s, idx) 메소드는 생성자에서 scanner로 부터 주입받은 것임을 알 수 있습니다. self.scan_once = scanner.make_scanner(self)

1
2
3
# scanner.py

make_scanner = c_make_scanner or py_make_scanner

c 는 제가 잘 몰라서 py_make_scanner 중심으로 어떤 일들이 일어나고 있는지 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# scanner.py

def py_make_scanner(context):
parse_object = context.parse_object
parse_array = context.parse_array
parse_string = context.parse_string
match_number = NUMBER_RE.match
strict = context.strict
parse_float = context.parse_float
parse_int = context.parse_int
parse_constant = context.parse_constant
object_hook = context.object_hook
object_pairs_hook = context.object_pairs_hook
memo = context.memo

def _scan_once(string, idx):
try:
nextchar = string[idx]
except IndexError:
raise StopIteration(idx)

if nextchar == '"':
return parse_string(string, idx + 1, strict)
elif nextchar == '{':
return parse_object((string, idx + 1), strict,
_scan_once, object_hook, object_pairs_hook, memo)
elif nextchar == '[':
return parse_array((string, idx + 1), _scan_once)
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
return None, idx + 4
elif nextchar == 't' and string[idx:idx + 4] == 'true':
return True, idx + 4
elif nextchar == 'f' and string[idx:idx + 5] == 'false':
return False, idx + 5

m = match_number(string, idx)
if m is not None:
integer, frac, exp = m.groups()
if frac or exp:
res = parse_float(integer + (frac or '') + (exp or ''))
else:
res = parse_int(integer)
return res, m.end()
elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
return parse_constant('NaN'), idx + 3
elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
return parse_constant('Infinity'), idx + 8
elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
return parse_constant('-Infinity'), idx + 9
else:
raise StopIteration(idx)

def scan_once(string, idx):
try:
return _scan_once(string, idx)
finally:
memo.clear()

return scan_once

py_make_scanner.scan_once() 를 실행하면 내부에 정의된 메소드 scan_once()_scan_once() 프라이빗 메소드를 부른 후 finally 구문을 통해 memo.clear()를 실행합니다.

memo 변수는 memo = context.memo 를 통해 생성된 변수입니다. 그리고 contextJSONDecoder 클래스의 인스턴스입니다. 따라서 context.memodict 타입의 변수 입니다.

이상으로 decoder.pyscanner.py 를 간략하게 살펴 보았습니다.

다음으로는 json/tool.py 의 사용법과 코드를 보도록 하겠습니다.

감사합니다.