Python의 파일 / 스트림에서 여러 JSON 값을 느리게 읽을 수있는 방법은 무엇입니까?


101

Python의 파일 / 스트림에서 한 번에 하나씩 여러 JSON 개체를 읽고 싶습니다. 불행하게도 json.load()단지 .read()파일의 마지막에이를 때까지에요; 단일 객체를 읽거나 객체를 느리게 반복하는 데 사용하는 방법이없는 것 같습니다.

이렇게 할 수있는 방법이 있습니까? 표준 라이브러리를 사용하는 것이 이상적이지만 타사 라이브러리가있는 경우 대신 사용합니다.

현재 각 개체를 별도의 줄에 배치하고를 사용 json.loads(f.readline())하고 있지만이 작업을 수행 할 필요는 없습니다.

사용 예

example.py

import my_json as json
import sys

for o in json.iterload(sys.stdin):
    print("Working on a", type(o))

in.txt

{"foo": ["bar", "baz"]} 1 2 [] 4 5 6

예제 세션

$ python3.2 example.py < in.txt
Working on a dict
Working on a int
Working on a int
Working on a list
Working on a int
Working on a int
Working on a int

중첩 된 개체에서 원하는 동작의 예를 추가해 주시겠습니까?
Tim McNamara

@TimMcNamara : 중첩 된 개체의 동작은 변경되지 않아야합니다. 그러나 첫 번째 최상위 객체 ( {"foo": ["bar", "baz"]}내 예에서는) 의 끝에 도달하면 다음 객체 ( ) yield로 계속 진행 해야 합니다 1.
Jeremy

1
왜 "json 라인"을 피합니까? 그것은이 더이 없도록 JSON으로 객체 직렬화하는 것이 가능 '\n'하기 때문에 그 JSON 표현의 (단일 개행 문자가 아닌 두 개의 문자)를 '\n'이스케이프해야합니다 JSON 문자열 내부에 따라서 '\n'예를 들어에만 서식을 위해 사용될 수있다, 저는 믿습니다 json.dumps()'아무튼 t는 '\n'기본적으로 소개 합니다. U + 0085와 같은 유니 코드 줄 바꿈은 json 문자열 내에서 이스케이프되지 않을 수 있습니다.
jfs 2014

2
이 경우 ijson 라이브러리가 유용 할 수 있습니다. pypi.python.org/pypi/ijson의 github.com/isagalaev/ijson
보리스 Chervenkov

1
제목은 " 파이썬의 파일 / 스트림에서 여러 JSON 을 어떻게 느리게 읽을 수 있습니까?" 가 아니어야합니다. 객체도 json int, string 등의 값이기 때문에 그 반대는 필요하지 않습니다.
hetepeperfan

답변:


20

여기 훨씬 더 간단한 해결책이 있습니다. 비밀은 올바르게 구문 분석하기 위해 예외의 정보를 시도하고, 실패하고, 사용하는 것입니다. 유일한 제한은 파일을 검색 할 수 있어야한다는 것입니다.

def stream_read_json(fn):
    import json
    start_pos = 0
    with open(fn, 'r') as f:
        while True:
            try:
                obj = json.load(f)
                yield obj
                return
            except json.JSONDecodeError as e:
                f.seek(start_pos)
                json_str = f.read(e.pos)
                obj = json.loads(json_str)
                start_pos += e.pos
                yield obj

편집 : 이것은 Python> = 3.5에서만 작동한다는 것을 알았습니다. 이전의 경우 실패는 ValueError를 반환하고 문자열에서 위치를 구문 분석해야합니다.

def stream_read_json(fn):
    import json
    import re
    start_pos = 0
    with open(fn, 'r') as f:
        while True:
            try:
                obj = json.load(f)
                yield obj
                return
            except ValueError as e:
                f.seek(start_pos)
                end_pos = int(re.match('Extra data: line \d+ column \d+ .*\(char (\d+).*\)',
                                    e.args[0]).groups()[0])
                json_str = f.read(end_pos)
                obj = json.loads(json_str)
                start_pos += end_pos
                yield obj

Stack Overflow에 오신 것을 환영합니다. 답변에 감사드립니다! 그것은 내가 찾고자하는 것에 훨씬 더 가깝습니다. 나는 그들이 직접 추구를 제공하지 않더라도 내가 생각하고 있던 케이스의 유형에 이것을 적용 할 수 있어야한다.
Jeremy는

그것은 re작동하지 않습니다 - 백 슬래시 탈출 필요가 없다. 원시 문자열을 고려하십시오 r'...'.
Tom Swirly

2
저는 제 작업을 위해 이것을 필요로했습니다. 그래서 저는 약간의 세부 사항과 함께 당신의 기술을 사용하여 이것을하기위한 작은 파이썬 라이브러리를 만들었습니다. 그리고 여기에 있습니다 : pypi.python.org/pypi/Streamy
Tom Swirly

2
당신이 사용하는 경우 ujson대신 json당신이 엄청난 속도 향상을 얻을 수거야
OddNorg

40

JSON은 일반적으로 이러한 종류의 점진적 사용에 적합하지 않습니다. 전체 로트를 구문 분석하지 않고 한 번에 하나씩 쉽게로드 할 수 있도록 여러 개체를 직렬화하는 표준 방법이 없습니다.

사용중인 라인 별 솔루션은 다른 곳에서도 볼 수 있습니다. Scrapy는이를 'JSON 라인'이라고 부릅니다.

약간 더 파이썬 적으로 할 수 있습니다.

for jsonline in f:
    yield json.loads(jsonline)   # or do the processing in this loop

이것이 최선의 방법이라고 생각합니다. 타사 라이브러리에 의존하지 않고 무슨 일이 일어나고 있는지 이해하기 쉽습니다. 나는 내 자신의 코드에서도 그것을 사용했습니다.


4
re : "표준 방법 없음": 문제가 보이지 않습니다. 구문은 한 문자 버퍼가있는 한 여러 연속 객체를 모호하지 않게 만드는 것 같습니다. 다른 사람들이 "JSON 라인"을 사용한다고 지적 해주셔서 감사합니다. 지금은 사용하는 것이 덜 나쁩니다.
Jeremy

31

조금 늦었을지 모르지만 정확한 문제가있었습니다 (글쎄요, 다소간). 이러한 문제에 대한 내 표준 솔루션은 일반적으로 잘 알려진 루트 개체에서 정규식 분할을 수행하는 것이지만 제 경우에는 불가능했습니다. 이를 일반적으로 수행 할 수있는 유일한 방법 은 적절한 토크 나이저를 구현하는 것 입니다.

충분히 일반적이고 합리적으로 성능이 좋은 솔루션을 찾지 못한 후에는 splitstream모듈을 작성하면서이 작업을 끝냈습니다 . JSON 및 XML을 이해하고 구문 분석을 위해 연속 스트림을 여러 청크로 분할하는 사전 토큰 화 프로그램입니다 (실제 구문 분석은 사용자에게 맡김). 어떤 종류의 성능을 얻기 위해 C 모듈로 작성됩니다.

예:

from splitstream import splitfile

for jsonstr in splitfile(sys.stdin, format="json")):
    yield json.loads(jsonstr)

굉장합니다. 공유해 주셔서 감사합니다.
Jeremy

이것이 확실한 해결책입니다. 계속 업데이트하시기 바랍니다.
Bartvds

그것은 단순히 작동합니다. 유용한 모듈을 제공해 주셔서 감사합니다.
Vinod Sharma

1
컴파일 된 .py 버전을 업로드 할 수 있습니까? 모듈을 빌드하고 설치하려고 시도했지만 ... 상수 재정의와 관련하여 많은 오류가 발생합니다.
SirJames

모듈은 C로 작성되었습니다. 순수 Python으로 이식하는 것은 작업을 수행하는 사람에게 연습으로 남겨집니다. 그러나 작성된 목적에 비해 너무 느릴 수 있습니다. 컴파일하는 데 문제가 있으면 python-dev packge를 설치해야합니다.
Krumelur

25

물론 할 수 있습니다. 당신은 raw_decode직접 가져 가야합니다 . 이 구현은 전체 파일을 메모리에로드하고 해당 문자열에서 작동합니다 (그렇게하는 것과 유사 json.load). 큰 파일이있는 경우 필요한만큼만 파일에서 읽도록 수정할 수 있습니다.

import json
from json.decoder import WHITESPACE

def iterload(string_or_fp, cls=json.JSONDecoder, **kwargs):
    if isinstance(string_or_fp, file):
        string = string_or_fp.read()
    else:
        string = str(string_or_fp)

    decoder = cls(**kwargs)
    idx = WHITESPACE.match(string, 0).end()
    while idx < len(string):
        obj, end = decoder.raw_decode(string, idx)
        yield obj
        idx = WHITESPACE.match(string, end).end()

사용법 : 요청하신대로 발전기입니다.


2
까다로운 부분은 스트리밍 읽기가 전체 개체를 디코딩 할 수있는 충분한 파일을 가져 오는지 확인하는 것 같습니다. 따라서 이것은 객체에 개행 문자가 없다고 가정하는 경우 작동하는 간단한 접근 방식입니다. 당신이 영업 이익은 피하려고하는 파일에 대한 추가 구조의 종류를 부과하지 않는 한, 당신이 @Benedict에서 같은 솔루션 필요할 것 같다
nealmcb

24

이것은 실제로 줄을 스트리밍해야하기 때문에 매우 불쾌한 문제이지만 중괄호에 대한 여러 줄의 패턴 일치뿐만 아니라 패턴 일치 json도 있습니다. 그것은 일종의 json-preparse와 json parse입니다. Json은 다른 형식에 비해 파싱이 쉽기 때문에 항상 파싱 라이브러리를 사용할 필요는 없지만 이러한 충돌 문제를 어떻게 해결해야할까요?

구조에 발전기!

이와 같은 문제에 대한 생성기의 장점은 게으름을 유지하면서 문제의 난이도를 점진적으로 추상화하여 서로 쌓을 수 있다는 것입니다. 또한 값을 제너레이터 (send ())로 다시 전달하는 메커니즘을 사용하는 것도 고려했지만 다행히도 사용할 필요가 없다는 것을 알았습니다.

첫 번째 문제를 해결하려면 re.finditer의 스트리밍 버전으로 일종의 streamingfinditer가 필요합니다. 아래에서 내 시도는 일치를 반환하는 동안 필요에 따라 줄을 가져옵니다 (디버그 문의 주석 처리를 제거). 그런 다음 실제로 일치하지 않는 줄과 일치 항목을 생성하도록 약간 수정했습니다 (항복 된 튜플의 첫 번째 부분에 0 또는 1로 표시됨).

import re

def streamingfinditer(pat,stream):
  for s in stream:
#    print "Read next line: " + s
    while 1:
      m = re.search(pat,s)
      if not m:
        yield (0,s)
        break
      yield (1,m.group())
      s = re.split(pat,s,1)[1]

이를 통해 중괄호까지 일치하고 중괄호가 균형을 이루는 지 매번 고려한 다음 단순 또는 복합 객체를 적절하게 반환 할 수 있습니다.

braces='{}[]'
whitespaceesc=' \t'
bracesesc='\\'+'\\'.join(braces)
balancemap=dict(zip(braces,[1,-1,1,-1]))
bracespat='['+bracesesc+']'
nobracespat='[^'+bracesesc+']*'
untilbracespat=nobracespat+bracespat

def simpleorcompoundobjects(stream):
  obj = ""
  unbalanced = 0
  for (c,m) in streamingfinditer(re.compile(untilbracespat),stream):
    if (c == 0): # remainder of line returned, nothing interesting
      if (unbalanced == 0):
        yield (0,m)
      else:
        obj += m
    if (c == 1): # match returned
      if (unbalanced == 0):
        yield (0,m[:-1])
        obj += m[-1]
      else:
        obj += m
      unbalanced += balancemap[m[-1]]
      if (unbalanced == 0):
        yield (1,obj)
        obj="" 

다음과 같이 튜플을 반환합니다.

(0,"String of simple non-braced objects easy to parse")
(1,"{ 'Compound' : 'objects' }")

기본적으로 그게 끔찍한 부분입니다. 이제 우리가 적합하다고 판단되는 최종 수준의 구문 분석을 수행하면됩니다. 예를 들어 Jeremy Roman의 iterload 함수 (Thanks!)를 사용하여 한 줄에 대한 구문 분석을 수행 할 수 있습니다.

def streamingiterload(stream):
  for c,o in simpleorcompoundobjects(stream):
    for x in iterload(o):
      yield x 

테스트 :

of = open("test.json","w") 
of.write("""[ "hello" ] { "goodbye" : 1 } 1 2 {
} 2
9 78
 4 5 { "animals" : [ "dog" , "lots of mice" ,
 "cat" ] }
""")
of.close()
// open & stream the json
f = open("test.json","r")
for o in streamingiterload(f.readlines()):
  print o
f.close()

이 결과를 얻습니다 (디버그 라인을 켜면 필요에 따라 라인이 당겨지는 것을 볼 수 있습니다).

[u'hello']
{u'goodbye': 1}
1
2
{}
2
9
78
4
5
{u'animals': [u'dog', u'lots of mice', u'cat']}

이것은 모든 상황에서 작동하지 않습니다. json라이브러리 의 구현으로 인해 파서를 직접 다시 구현하지 않고는 완전히 올바르게 작동 하는 것은 불가능 합니다.


8
이 작업을 올바르게 수행하려면 문자열 내의 중괄호와 대괄호도주의해야합니다. 그리고 이스케이프 된 따옴표를주의하십시오. 알기 전에 "준비 자"는 전체 JSON 구문 분석기만큼 복잡해질 것입니다.
Petr Viktorin 2011 년

감사합니다 Jeremy. 질문의 기쁜 도전이었습니다! 페트르 네 - 당신은 절대적 권리 : 물론입니다
베네딕토

1
잘하셨습니다. 문자 가 JSON 문자열 "}"과 같고 "]"발생 하면 올바르게 작동 합니까? 나는 이것이 정규식으로 구문 분석하는 일반적인 제한이라고 생각합니다.
Thomas K

2
주변을 살펴보면 주요 파싱 기능이 제대로 사용하기 가 불가능한 방식으로 구축되어 있다는 사실을 발견했습니다. 따라서 완전한 파서를 직접 구현하지 않으면 완벽한 결과를 얻을 수 없습니다. 이 답변은 몇 가지 유용한 관련 사항을 보여주고 간단한 경우를 멋지게 처리합니다.
Jeremy

3
이 대답은 끔찍하며 왜 그것이 upvoted인지 모르겠습니다. 저자는 실제로 모든 입력에 대해 작동하지 않으므로 정의상 정답도 아니며 계산 되는 복잡한 정규식을 사용 하므로 그것이 무엇인지 읽을 수도 없습니다. 때때로 올바른 결과를 제공하는 기능은 무슨 소용이 있습니까?
Tom Swirly

10

더 나은 방법은 상태 머신을 사용하는 것입니다. 아래는 아래 링크의 NodeJS 코드를 Python 3 으로 변환하여 작업 한 샘플 코드입니다 ( Python 3에서만 사용할 수있는 비 로컬 키워드 사용, Python 2에서는 코드가 작동하지 않음).

Edit-1 : Python 2와 호환되는 코드 업데이트 및 제작

편집 -2 : Python3 전용 버전도 업데이트 및 추가했습니다.

https://gist.github.com/creationix/5992451

Python 3 전용 버전

# A streaming byte oriented JSON parser.  Feed it a single byte at a time and
# it will emit complete objects as it comes across them.  Whitespace within and
# between objects is ignored.  This means it can parse newline delimited JSON.
import math


def json_machine(emit, next_func=None):
    def _value(byte_data):
        if not byte_data:
            return

        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _value  # Ignore whitespace

        if byte_data == 0x22:  # "
            return string_machine(on_value)

        if byte_data == 0x2d or (0x30 <= byte_data < 0x40):  # - or 0-9
            return number_machine(byte_data, on_number)

        if byte_data == 0x7b:  #:
            return object_machine(on_value)

        if byte_data == 0x5b:  # [
            return array_machine(on_value)

        if byte_data == 0x74:  # t
            return constant_machine(TRUE, True, on_value)

        if byte_data == 0x66:  # f
            return constant_machine(FALSE, False, on_value)

        if byte_data == 0x6e:  # n
            return constant_machine(NULL, None, on_value)

        if next_func == _value:
            raise Exception("Unexpected 0x" + str(byte_data))

        return next_func(byte_data)

    def on_value(value):
        emit(value)
        return next_func

    def on_number(number, byte):
        emit(number)
        return _value(byte)

    next_func = next_func or _value
    return _value


TRUE = [0x72, 0x75, 0x65]
FALSE = [0x61, 0x6c, 0x73, 0x65]
NULL = [0x75, 0x6c, 0x6c]


def constant_machine(bytes_data, value, emit):
    i = 0
    length = len(bytes_data)

    def _constant(byte_data):
        nonlocal i
        if byte_data != bytes_data[i]:
            i += 1
            raise Exception("Unexpected 0x" + str(byte_data))

        i += 1
        if i < length:
            return _constant
        return emit(value)

    return _constant


def string_machine(emit):
    string = ""

    def _string(byte_data):
        nonlocal string

        if byte_data == 0x22:  # "
            return emit(string)

        if byte_data == 0x5c:  # \
            return _escaped_string

        if byte_data & 0x80:  # UTF-8 handling
            return utf8_machine(byte_data, on_char_code)

        if byte_data < 0x20:  # ASCII control character
            raise Exception("Unexpected control character: 0x" + str(byte_data))

        string += chr(byte_data)
        return _string

    def _escaped_string(byte_data):
        nonlocal string

        if byte_data == 0x22 or byte_data == 0x5c or byte_data == 0x2f:  # " \ /
            string += chr(byte_data)
            return _string

        if byte_data == 0x62:  # b
            string += "\b"
            return _string

        if byte_data == 0x66:  # f
            string += "\f"
            return _string

        if byte_data == 0x6e:  # n
            string += "\n"
            return _string

        if byte_data == 0x72:  # r
            string += "\r"
            return _string

        if byte_data == 0x74:  # t
            string += "\t"
            return _string

        if byte_data == 0x75:  # u
            return hex_machine(on_char_code)

    def on_char_code(char_code):
        nonlocal string
        string += chr(char_code)
        return _string

    return _string


# Nestable state machine for UTF-8 Decoding.
def utf8_machine(byte_data, emit):
    left = 0
    num = 0

    def _utf8(byte_data):
        nonlocal num, left
        if (byte_data & 0xc0) != 0x80:
            raise Exception("Invalid byte in UTF-8 character: 0x" + byte_data.toString(16))

        left = left - 1

        num |= (byte_data & 0x3f) << (left * 6)
        if left:
            return _utf8
        return emit(num)

    if 0xc0 <= byte_data < 0xe0:  # 2-byte UTF-8 Character
        left = 1
        num = (byte_data & 0x1f) << 6
        return _utf8

    if 0xe0 <= byte_data < 0xf0:  # 3-byte UTF-8 Character
        left = 2
        num = (byte_data & 0xf) << 12
        return _utf8

    if 0xf0 <= byte_data < 0xf8:  # 4-byte UTF-8 Character
        left = 3
        num = (byte_data & 0x07) << 18
        return _utf8

    raise Exception("Invalid byte in UTF-8 string: 0x" + str(byte_data))


# Nestable state machine for hex escaped characters
def hex_machine(emit):
    left = 4
    num = 0

    def _hex(byte_data):
        nonlocal num, left

        if 0x30 <= byte_data < 0x40:
            i = byte_data - 0x30
        elif 0x61 <= byte_data <= 0x66:
            i = byte_data - 0x57
        elif 0x41 <= byte_data <= 0x46:
            i = byte_data - 0x37
        else:
            raise Exception("Expected hex char in string hex escape")

        left -= 1
        num |= i << (left * 4)

        if left:
            return _hex
        return emit(num)

    return _hex


def number_machine(byte_data, emit):
    sign = 1
    number = 0
    decimal = 0
    esign = 1
    exponent = 0

    def _mid(byte_data):
        if byte_data == 0x2e:  # .
            return _decimal

        return _later(byte_data)

    def _number(byte_data):
        nonlocal number
        if 0x30 <= byte_data < 0x40:
            number = number * 10 + (byte_data - 0x30)
            return _number

        return _mid(byte_data)

    def _start(byte_data):
        if byte_data == 0x30:
            return _mid

        if 0x30 < byte_data < 0x40:
            return _number(byte_data)

        raise Exception("Invalid number: 0x" + str(byte_data))

    if byte_data == 0x2d:  # -
        sign = -1
        return _start

    def _decimal(byte_data):
        nonlocal decimal
        if 0x30 <= byte_data < 0x40:
            decimal = (decimal + byte_data - 0x30) / 10
            return _decimal

        return _later(byte_data)

    def _later(byte_data):
        if byte_data == 0x45 or byte_data == 0x65:  # E e
            return _esign

        return _done(byte_data)

    def _esign(byte_data):
        nonlocal esign
        if byte_data == 0x2b:  # +
            return _exponent

        if byte_data == 0x2d:  # -
            esign = -1
            return _exponent

        return _exponent(byte_data)

    def _exponent(byte_data):
        nonlocal exponent
        if 0x30 <= byte_data < 0x40:
            exponent = exponent * 10 + (byte_data - 0x30)
            return _exponent

        return _done(byte_data)

    def _done(byte_data):
        value = sign * (number + decimal)
        if exponent:
            value *= math.pow(10, esign * exponent)

        return emit(value, byte_data)

    return _start(byte_data)


def array_machine(emit):
    array_data = []

    def _array(byte_data):
        if byte_data == 0x5d:  # ]
            return emit(array_data)

        return json_machine(on_value, _comma)(byte_data)

    def on_value(value):
        array_data.append(value)

    def _comma(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return json_machine(on_value, _comma)

        if byte_data == 0x5d:  # ]
            return emit(array_data)

        raise Exception("Unexpected byte: 0x" + str(byte_data) + " in array body")

    return _array


def object_machine(emit):
    object_data = {}
    key = None

    def _object(byte_data):
        if byte_data == 0x7d:  #
            return emit(object_data)

        return _key(byte_data)

    def _key(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _object  # Ignore whitespace

        if byte_data == 0x22:
            return string_machine(on_key)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    def on_key(result):
        nonlocal key
        key = result
        return _colon

    def _colon(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _colon  # Ignore whitespace

        if byte_data == 0x3a:  # :
            return json_machine(on_value, _comma)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    def on_value(value):
        object_data[key] = value

    def _comma(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return _key

        if byte_data == 0x7d:  #
            return emit(object_data)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    return _object

Python 2 호환 버전

# A streaming byte oriented JSON parser.  Feed it a single byte at a time and
# it will emit complete objects as it comes across them.  Whitespace within and
# between objects is ignored.  This means it can parse newline delimited JSON.
import math


def json_machine(emit, next_func=None):
    def _value(byte_data):
        if not byte_data:
            return

        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _value  # Ignore whitespace

        if byte_data == 0x22:  # "
            return string_machine(on_value)

        if byte_data == 0x2d or (0x30 <= byte_data < 0x40):  # - or 0-9
            return number_machine(byte_data, on_number)

        if byte_data == 0x7b:  #:
            return object_machine(on_value)

        if byte_data == 0x5b:  # [
            return array_machine(on_value)

        if byte_data == 0x74:  # t
            return constant_machine(TRUE, True, on_value)

        if byte_data == 0x66:  # f
            return constant_machine(FALSE, False, on_value)

        if byte_data == 0x6e:  # n
            return constant_machine(NULL, None, on_value)

        if next_func == _value:
            raise Exception("Unexpected 0x" + str(byte_data))

        return next_func(byte_data)

    def on_value(value):
        emit(value)
        return next_func

    def on_number(number, byte):
        emit(number)
        return _value(byte)

    next_func = next_func or _value
    return _value


TRUE = [0x72, 0x75, 0x65]
FALSE = [0x61, 0x6c, 0x73, 0x65]
NULL = [0x75, 0x6c, 0x6c]


def constant_machine(bytes_data, value, emit):
    local_data = {"i": 0, "length": len(bytes_data)}

    def _constant(byte_data):
        # nonlocal i, length
        if byte_data != bytes_data[local_data["i"]]:
            local_data["i"] += 1
            raise Exception("Unexpected 0x" + byte_data.toString(16))

        local_data["i"] += 1

        if local_data["i"] < local_data["length"]:
            return _constant
        return emit(value)

    return _constant


def string_machine(emit):
    local_data = {"string": ""}

    def _string(byte_data):
        # nonlocal string

        if byte_data == 0x22:  # "
            return emit(local_data["string"])

        if byte_data == 0x5c:  # \
            return _escaped_string

        if byte_data & 0x80:  # UTF-8 handling
            return utf8_machine(byte_data, on_char_code)

        if byte_data < 0x20:  # ASCII control character
            raise Exception("Unexpected control character: 0x" + byte_data.toString(16))

        local_data["string"] += chr(byte_data)
        return _string

    def _escaped_string(byte_data):
        # nonlocal string

        if byte_data == 0x22 or byte_data == 0x5c or byte_data == 0x2f:  # " \ /
            local_data["string"] += chr(byte_data)
            return _string

        if byte_data == 0x62:  # b
            local_data["string"] += "\b"
            return _string

        if byte_data == 0x66:  # f
            local_data["string"] += "\f"
            return _string

        if byte_data == 0x6e:  # n
            local_data["string"] += "\n"
            return _string

        if byte_data == 0x72:  # r
            local_data["string"] += "\r"
            return _string

        if byte_data == 0x74:  # t
            local_data["string"] += "\t"
            return _string

        if byte_data == 0x75:  # u
            return hex_machine(on_char_code)

    def on_char_code(char_code):
        # nonlocal string
        local_data["string"] += chr(char_code)
        return _string

    return _string


# Nestable state machine for UTF-8 Decoding.
def utf8_machine(byte_data, emit):
    local_data = {"left": 0, "num": 0}

    def _utf8(byte_data):
        # nonlocal num, left
        if (byte_data & 0xc0) != 0x80:
            raise Exception("Invalid byte in UTF-8 character: 0x" + byte_data.toString(16))

        local_data["left"] -= 1

        local_data["num"] |= (byte_data & 0x3f) << (local_data["left"] * 6)
        if local_data["left"]:
            return _utf8
        return emit(local_data["num"])

    if 0xc0 <= byte_data < 0xe0:  # 2-byte UTF-8 Character
        local_data["left"] = 1
        local_data["num"] = (byte_data & 0x1f) << 6
        return _utf8

    if 0xe0 <= byte_data < 0xf0:  # 3-byte UTF-8 Character
        local_data["left"] = 2
        local_data["num"] = (byte_data & 0xf) << 12
        return _utf8

    if 0xf0 <= byte_data < 0xf8:  # 4-byte UTF-8 Character
        local_data["left"] = 3
        local_data["num"] = (byte_data & 0x07) << 18
        return _utf8

    raise Exception("Invalid byte in UTF-8 string: 0x" + str(byte_data))


# Nestable state machine for hex escaped characters
def hex_machine(emit):
    local_data = {"left": 4, "num": 0}

    def _hex(byte_data):
        # nonlocal num, left
        i = 0  # Parse the hex byte
        if 0x30 <= byte_data < 0x40:
            i = byte_data - 0x30
        elif 0x61 <= byte_data <= 0x66:
            i = byte_data - 0x57
        elif 0x41 <= byte_data <= 0x46:
            i = byte_data - 0x37
        else:
            raise Exception("Expected hex char in string hex escape")

        local_data["left"] -= 1
        local_data["num"] |= i << (local_data["left"] * 4)

        if local_data["left"]:
            return _hex
        return emit(local_data["num"])

    return _hex


def number_machine(byte_data, emit):
    local_data = {"sign": 1, "number": 0, "decimal": 0, "esign": 1, "exponent": 0}

    def _mid(byte_data):
        if byte_data == 0x2e:  # .
            return _decimal

        return _later(byte_data)

    def _number(byte_data):
        # nonlocal number
        if 0x30 <= byte_data < 0x40:
            local_data["number"] = local_data["number"] * 10 + (byte_data - 0x30)
            return _number

        return _mid(byte_data)

    def _start(byte_data):
        if byte_data == 0x30:
            return _mid

        if 0x30 < byte_data < 0x40:
            return _number(byte_data)

        raise Exception("Invalid number: 0x" + byte_data.toString(16))

    if byte_data == 0x2d:  # -
        local_data["sign"] = -1
        return _start

    def _decimal(byte_data):
        # nonlocal decimal
        if 0x30 <= byte_data < 0x40:
            local_data["decimal"] = (local_data["decimal"] + byte_data - 0x30) / 10
            return _decimal

        return _later(byte_data)

    def _later(byte_data):
        if byte_data == 0x45 or byte_data == 0x65:  # E e
            return _esign

        return _done(byte_data)

    def _esign(byte_data):
        # nonlocal esign
        if byte_data == 0x2b:  # +
            return _exponent

        if byte_data == 0x2d:  # -
            local_data["esign"] = -1
            return _exponent

        return _exponent(byte_data)

    def _exponent(byte_data):
        # nonlocal exponent
        if 0x30 <= byte_data < 0x40:
            local_data["exponent"] = local_data["exponent"] * 10 + (byte_data - 0x30)
            return _exponent

        return _done(byte_data)

    def _done(byte_data):
        value = local_data["sign"] * (local_data["number"] + local_data["decimal"])
        if local_data["exponent"]:
            value *= math.pow(10, local_data["esign"] * local_data["exponent"])

        return emit(value, byte_data)

    return _start(byte_data)


def array_machine(emit):
    local_data = {"array_data": []}

    def _array(byte_data):
        if byte_data == 0x5d:  # ]
            return emit(local_data["array_data"])

        return json_machine(on_value, _comma)(byte_data)

    def on_value(value):
        # nonlocal array_data
        local_data["array_data"].append(value)

    def _comma(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return json_machine(on_value, _comma)

        if byte_data == 0x5d:  # ]
            return emit(local_data["array_data"])

        raise Exception("Unexpected byte: 0x" + str(byte_data) + " in array body")

    return _array


def object_machine(emit):
    local_data = {"object_data": {}, "key": ""}

    def _object(byte_data):
        # nonlocal object_data, key
        if byte_data == 0x7d:  #
            return emit(local_data["object_data"])

        return _key(byte_data)

    def _key(byte_data):
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _object  # Ignore whitespace

        if byte_data == 0x22:
            return string_machine(on_key)

        raise Exception("Unexpected byte: 0x" + byte_data.toString(16))

    def on_key(result):
        # nonlocal object_data, key
        local_data["key"] = result
        return _colon

    def _colon(byte_data):
        # nonlocal object_data, key
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _colon  # Ignore whitespace

        if byte_data == 0x3a:  # :
            return json_machine(on_value, _comma)

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    def on_value(value):
        # nonlocal object_data, key
        local_data["object_data"][local_data["key"]] = value

    def _comma(byte_data):
        # nonlocal object_data
        if byte_data == 0x09 or byte_data == 0x0a or byte_data == 0x0d or byte_data == 0x20:
            return _comma  # Ignore whitespace

        if byte_data == 0x2c:  # ,
            return _key

        if byte_data == 0x7d:  #
            return emit(local_data["object_data"])

        raise Exception("Unexpected byte: 0x" + str(byte_data))

    return _object

그것을 테스트

if __name__ == "__main__":
    test_json = """[1,2,"3"] {"name": 
    "tarun"} 1 2 
    3 [{"name":"a", 
    "data": [1,
    null,2]}]
"""
    def found_json(data):
        print(data)

    state = json_machine(found_json)

    for char in test_json:
        state = state(ord(char))

동일한 출력은

[1, 2, '3']
{'name': 'tarun'}
1
2
3
[{'name': 'a', 'data': [1, None, 2]}]

좋은 해결책! 나중에 자세히 살펴 보 겠지만 이것은 매우 유망합니다. 그러나 그만한 가치가 있기 때문에 저는 Python 3 전용 버전을 선호했습니다. 모든 지역 변수에 딕셔너리를 사용하는 것은 다소 어색하며 과거에 Python 2를 떠날 수있어서 기쁩니다. )
제레미

@JeremyBanks, 어떤 버전을 목표로 삼았는지 몰랐습니다. 지금은 아직 파이썬 2에있을 수 있습니다 다른 사람에 대한 대답도 Python3 전용 버전과 Py2 호환 하나를 추가 한
Tarun Lalwani

@JeremyBanks 만 일일는 현상금 왼쪽, 검토하고 답에 대한 피드백 제공 할 수 있기를 바랍니다
Tarun Lalwani을

실제로 문제를 이해 한 사람은 타룬 뿐인 것 같습니다. 구문 분석의 효율성은 입력에서 발생하는 패스 수에 따라 달라집니다. 대부분의 답변은 정규식을 사용하거나 사전에 한 줄을 읽거나 (위험 할 수도 있음) 더 나쁜 경우에는 알 수없는 횟수만큼 구문 분석에 실패합니다. 안타깝게도 이것은 Python의 일부가 아닙니다.
mschonaker

4

솔루션을 제공하고 싶습니다. 핵심은 디코딩을 "시도"하는 것입니다. 실패하면 더 많은 피드를 제공하고 그렇지 않으면 오프셋 정보를 사용하여 다음 디코딩을 준비합니다.

그러나 현재 json 모듈은 디코딩 할 문자열 헤드의 SPACE를 허용 할 수 없으므로 제거해야합니다.

import sys
import json

def iterload(file):
    buffer = ""
    dec = json.JSONDecoder()
    for line in file:         
        buffer = buffer.strip(" \n\r\t") + line.strip(" \n\r\t")
        while(True):
            try:
                r = dec.raw_decode(buffer)
            except:
                break
            yield r[0]
            buffer = buffer[r[1]:].strip(" \n\r\t")


for o in iterload(sys.stdin):
    print("Working on a", type(o),  o)

========================= 여러 txt 파일을 테스트했으며 제대로 작동합니다. (in1.txt)

{"foo": ["bar", "baz"]
}
 1 2 [
  ]  4
{"foo1": ["bar1", {"foo2":{"A":1, "B":3}, "DDD":4}]
}
 5   6

(in2.txt)

{"foo"
: ["bar",
  "baz"]
  } 
1 2 [
] 4 5 6

(in.txt, 이니셜)

{"foo": ["bar", "baz"]} 1 2 [] 4 5 6

(Benedict의 테스트 케이스에 대한 출력)

python test.py < in.txt
('Working on a', <type 'list'>, [u'hello'])
('Working on a', <type 'dict'>, {u'goodbye': 1})
('Working on a', <type 'int'>, 1)
('Working on a', <type 'int'>, 2)
('Working on a', <type 'dict'>, {})
('Working on a', <type 'int'>, 2)
('Working on a', <type 'int'>, 9)
('Working on a', <type 'int'>, 78)
('Working on a', <type 'int'>, 4)
('Working on a', <type 'int'>, 5)
('Working on a', <type 'dict'>, {u'animals': [u'dog', u'lots of mice', u'cat']})

3

여기 내 것 :

import simplejson as json
from simplejson import JSONDecodeError
class StreamJsonListLoader():
    """
    When you have a big JSON file containint a list, such as

    [{
        ...
    },
    {
        ...
    },
    {
        ...
    },
    ...
    ]

    And it's too big to be practically loaded into memory and parsed by json.load,
    This class comes to the rescue. It lets you lazy-load the large json list.
    """

    def __init__(self, filename_or_stream):
        if type(filename_or_stream) == str:
            self.stream = open(filename_or_stream)
        else:
            self.stream = filename_or_stream

        if not self.stream.read(1) == '[':
            raise NotImplementedError('Only JSON-streams of lists (that start with a [) are supported.')

    def __iter__(self):
        return self

    def next(self):
        read_buffer = self.stream.read(1)
        while True:
            try:
                json_obj = json.loads(read_buffer)

                if not self.stream.read(1) in [',',']']:
                    raise Exception('JSON seems to be malformed: object is not followed by comma (,) or end of list (]).')
                return json_obj
            except JSONDecodeError:
                next_char = self.stream.read(1)
                read_buffer += next_char
                while next_char != '}':
                    next_char = self.stream.read(1)
                    if next_char == '':
                        raise StopIteration
                    read_buffer += next_char

안녕하세요, 이것은 매우 유용하지만 클래스를 사용하여 json 파일을로드하는 방법을 보여줄 수 있습니까?
song0089

3

@wuilang의 우아한 솔루션을 사용했습니다. 간단한 접근 방식 (바이트 읽기, 디코딩 시도, 바이트 읽기, 디코딩 시도 등)은 작동했지만 안타깝게도 매우 느 렸습니다.

제 경우에는 파일에서 동일한 객체 유형의 "예쁘게 인쇄 된"JSON 객체를 읽으려고했습니다. 이를 통해 접근 방식을 최적화 할 수있었습니다. 파일을 한 줄씩 읽을 수 있었고 정확히 "}"가 포함 된 줄을 찾은 경우에만 디코딩 할 수있었습니다.

def iterload(stream):
    buf = ""
    dec = json.JSONDecoder()
    for line in stream:
        line = line.rstrip()
        buf = buf + line
        if line == "}":
            yield dec.raw_decode(buf)
            buf = ""

문자열 리터럴에서 줄 바꿈을 이스케이프 처리하는 한 줄에 하나씩 압축 된 JSON을 사용하는 경우이 접근 방식을 훨씬 더 안전하게 단순화 할 수 있습니다.

def iterload(stream):
    dec = json.JSONDecoder()
    for line in stream:
        yield dec.raw_decode(line)

분명히 이러한 간단한 접근 방식은 매우 특정한 종류의 JSON에만 적용됩니다. 그러나 이러한 가정이 유지된다면 이러한 솔루션은 정확하고 빠르게 작동합니다.


2

json.JSONDecoder 인스턴스를 사용하는 경우 raw_decode멤버 함수를 사용할 수 있습니다 . JSON 값의 파이썬 표현의 튜플과 구문 분석이 중지 된 인덱스를 반환합니다. 이렇게하면 나머지 JSON 값을 쉽게 분할 (또는 스트림 개체에서 검색) 할 수 있습니다. 입력에서 다른 JSON 값 사이의 공백을 건너 뛰는 추가 while 루프에 대해 너무 만족스럽지 않지만 내 의견으로는 작업이 완료됩니다.

import json

def yield_multiple_value(f):
    '''
    parses multiple JSON values from a file.
    '''
    vals_str = f.read()
    decoder = json.JSONDecoder()
    try:
        nread = 0
        while nread < len(vals_str):
            val, n = decoder.raw_decode(vals_str[nread:])
            nread += n
            # Skip over whitespace because of bug, below.
            while nread < len(vals_str) and vals_str[nread].isspace():
                nread += 1
            yield val
    except json.JSONDecodeError as e:
        pass
    return

다음 버전은 훨씬 더 짧고 이미 구문 분석 된 문자열 부분을 먹습니다. 어떤 이유로 문자열의 첫 번째 문자가 공백 일 때 두 번째 호출 json.JSONDecoder.raw_decode ()가 실패하는 것처럼 보이며, 이것이 위의 whileloop에서 공백을 건너 뛰는 이유이기도합니다.

def yield_multiple_value(f):
    '''
    parses multiple JSON values from a file.
    '''
    vals_str = f.read()
    decoder = json.JSONDecoder()
    while vals_str:
        val, n = decoder.raw_decode(vals_str)
        #remove the read characters from the start.
        vals_str = vals_str[n:]
        # remove leading white space because a second call to decoder.raw_decode()
        # fails when the string starts with whitespace, and
        # I don't understand why...
        vals_str = vals_str.lstrip()
        yield val
    return

json.JSONDecoder 클래스에 대한 문서에서 raw_decode https://docs.python.org/3/library/json.html#encoders-and-decoders 메서드 는 다음을 포함합니다.

이것은 끝에 불필요한 데이터가있을 수있는 문자열에서 JSON 문서를 디코딩하는 데 사용할 수 있습니다.

그리고이 불필요한 데이터는 쉽게 또 다른 JSON 값이 될 수 있습니다. 즉,이 목적을 염두에두고 메소드를 작성할 수 있습니다.

위쪽 함수를 사용하는 input.txt로 원래 질문에 제시된 예제 출력을 얻습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.