मैं पायथन में एक फ़ाइल / स्ट्रीम से कई JSON मानों को कैसे आलसी रूप से पढ़ सकता हूं?


101

मैं पायथन में एक फ़ाइल / स्ट्रीम से कई 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

क्या आप उन वस्तुओं का उदाहरण जोड़ सकते हैं जिन्हें आप कृपया नेस्टेड ऑब्जेक्ट्स से चाहते हैं?
टिम मैकनामारा

@TimMcNamara: नेस्टेड ऑब्जेक्ट का व्यवहार नहीं बदलना चाहिए। हालांकि, एक बार जब हम पहले शीर्ष-स्तरीय ऑब्जेक्ट ( {"foo": ["bar", "baz"]}मेरे उदाहरण में) के अंत तक पहुँच गए हैं , तो इसे इसे करना चाहिए yieldऔर फिर अगले एक ( 1) पर जारी रखना चाहिए ।
जेरेमी

1
क्यों "json लाइनों" से बचें? किसी वस्तु को हमेशा json में क्रमबद्ध करना संभव है, जैसे कि '\n'इसके json प्रतिनिधित्व में कोई (एक नई पंक्ति, दो वर्ण नहीं) है क्योंकि json स्ट्रिंग के अंदर '\n'बच जाना चाहिए और इसलिए '\n'इसका उपयोग केवल स्वरूपण के लिए किया जा सकता है, मुझे विश्वास है कि json.dumps()' t '\n'डिफ़ॉल्ट रूप से परिचय । खबरदार कि यू + 0085 जैसी यूनिकोड की नई किस्में जिंग स्ट्रिंग्स के अंदर मौजूद हो सकती हैं।
2:17

2
Ijson पुस्तकालय इस मामले में उपयोगी हो सकता है। pypi.python.org/pypi/ijson github.com/isagalaev/ijson
Boris 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

संपादित करें: अभी देखा कि यह केवल पायथन> = 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

ढेर अतिप्रवाह में आपका स्वागत है और उत्तर के लिए धन्यवाद! मैं जो पाने की उम्मीद कर रहा था, उसके बहुत करीब है। मुझे उन मामलों के प्रकारों के लिए इसे अनुकूलित करने में सक्षम होना चाहिए जिनके बारे में मैं सोच रहा था, भले ही वे सीधे मांग न प्रदान करें।
जेरेमी

वह reकाम नहीं करेगा - बैकस्लैश को भागने की जरूरत है। एक कच्चे तार पर विचार करें r'...'
टॉम स्विरली

2
मुझे अपने काम के लिए इसकी आवश्यकता थी, इसलिए II ने ऐसा करने के लिए थोड़ी जानकारी के साथ अपनी तकनीक का उपयोग करते हुए, थोड़ा अजगर पुस्तकालय बनाया, और यह यहाँ है: pypi.python.org/pypi/Streamy
Tom

2
यदि आप ujsonइसके बजाय का उपयोग करते हैं jsonतो आपको बहुत बड़ा स्पीडअप मिलने वाला है
OddNorg

40

JSON आम तौर पर वृद्धिशील उपयोग के इस प्रकार के लिए बहुत अच्छा नहीं है; कई वस्तुओं को क्रमबद्ध करने का कोई मानक तरीका नहीं है ताकि वे एक बार में पूरी तरह से लोड किए बिना आसानी से लोड हो सकें।

आपके द्वारा उपयोग किए जा रहे लाइन प्रति ऑब्जेक्ट को अन्यत्र भी देखा जाता है। स्क्रेपी ने इसे 'JSON लाइन्स' कहा:

आप इसे थोड़ा और पाइथोनिक रूप से कर सकते हैं:

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

मुझे लगता है कि यह सबसे अच्छा तरीका है - यह किसी भी तीसरे पक्ष के पुस्तकालयों पर भरोसा नहीं करता है, और यह समझना आसान है कि क्या हो रहा है। मैंने इसे अपने कुछ कोड में भी उपयोग किया है।


4
पुन: "कोई मानक तरीका नहीं": मुझे समस्या नहीं दिख रही है, वाक्य-विन्यास को लगता है कि जब तक आपके पास एक-वर्ण बफ़र है, तब तक कई लगातार ऑब्जेक्ट्स को अस्पष्ट बना देता है। यह इंगित करने के लिए धन्यवाद कि अन्य लोग "JSON लाइनों" का उपयोग करते हैं, मुझे अब इसके लिए उपयोग करने के बारे में कम बुरा लगता है।
जेरेमी

31

थोड़ी देर हो सकती है, लेकिन मुझे यह सटीक समस्या थी (अच्छी तरह से, कम या ज्यादा)। इन समस्याओं के लिए मेरा मानक समाधान आमतौर पर किसी प्रसिद्ध रूट ऑब्जेक्ट पर एक रेगेक्स विभाजन करना है, लेकिन मेरे मामले में यह असंभव था। ऐसा करने का एकमात्र व्यावहारिक तरीका एक उचित टोकन लागू करना है

जेनेरिक-पर्याप्त और यथोचित रूप से अच्छा प्रदर्शन करने वाला समाधान नहीं मिलने के बाद, मैंने splitstreamमॉड्यूल को लिखते हुए, इसे स्वयं करना समाप्त कर दिया । यह एक पूर्व-टोकन है जो जेन्सन और एक्सएमएल को समझता है और पार्सिंग के लिए कई खंडों में एक सतत स्ट्रीम को विभाजित करता है (यह वास्तविक पार्सिंग को आपके ऊपर छोड़ देता है)। इसमें से किसी प्रकार का प्रदर्शन प्राप्त करने के लिए, इसे C मॉड्यूल के रूप में लिखा जाता है।

उदाहरण:

from splitstream import splitfile

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

वह तो कमाल है। इसे शेयर करने के लिए धन्यवाद।
जेरेमी

यह निश्चित समाधान है। मुझे उम्मीद है कि आप इसे अपडेट करते रहेंगे।
बार्टवेड्स

यह बस काम करता है। इस तरह के एक उपयोगी मॉड्यूल प्रदान करने के लिए धन्यवाद।
विनोद शर्मा

1
क्या आप एक संकलित .py संस्करण अपलोड कर सकते हैं? मैंने मॉड्यूल को बनाने और स्थापित करने की कोशिश की है लेकिन ... यह निरंतरता को पुनर्परिभाषित करने और इस तरह की त्रुटियों के बारे में बताता है।
सरजाम्स

मॉड्यूल सी में लिखा गया है। इसे शुद्ध पायथन में पोर्ट करना एक अभ्यास के रूप में छोड़ दिया गया है जो कोई भी कार्य के लिए है :)। यह संभवतः उस उद्देश्य के लिए बहुत धीमा होगा जिसके लिए इसे लिखा गया था। यदि आपको परेशान करने में दिक्कत होती है, तो संभवतः आपको अजगर-देव पैग स्थापित करने की आवश्यकता है।
क्रुमेलुर

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-parse है, इसके बाद json parse होता है। जोंस, अन्य प्रारूपों की तुलना में, पार्स करना आसान है, इसलिए हमेशा एक पार्सिंग लाइब्रेरी के लिए जाना आवश्यक नहीं है, फिर भी, हमें इन परस्पर विरोधी मुद्दों को कैसे हल करना चाहिए?

बचाव के लिए जनरेटर!

इस तरह की समस्या के लिए जनरेटर की सुंदरता यह है कि आप उन्हें एक-दूसरे के ऊपर ढेर कर सकते हैं धीरे-धीरे आलस्य को बनाए रखते हुए समस्या की कठिनाई को दूर कर सकते हैं। मैंने एक जनरेटर (भेजने ()) में मूल्यों को वापस लाने के लिए तंत्र का उपयोग करने पर भी विचार किया, लेकिन सौभाग्य से पाया गया कि मुझे इसका उपयोग करने की आवश्यकता नहीं है।

समस्याओं के पहले को हल करने के लिए आपको किसी प्रकार के स्ट्रीमिंगफ्रंट की आवश्यकता होती है, जो कि re.finditer के स्ट्रीमिंग संस्करण के रूप में है। इसके नीचे मेरा प्रयास आवश्यकतानुसार लाइनों में खींचता है (डिबग स्टेटमेंट को देखने के लिए) अभी भी मैच लौट रहा है। मैंने वास्तव में इसे गैर-मिलान वाली रेखाओं के साथ-साथ मैचों (उपज वाले टुपल के पहले भाग में 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="" 

यह इस प्रकार tuples देता है:

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

मूल रूप से यह बुरा हिस्सा है। अब हमें बस अंतिम स्तर की पार्सिंग करनी है जैसा हम फिट देखते हैं। उदाहरण के लिए, हम जेरेमी रोमन के पुनरावृत्ति फ़ंक्शन का उपयोग कर सकते हैं (धन्यवाद!) एक पंक्ति के लिए पार्सिंग करने के लिए:

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 पार्सर के रूप में लगभग जटिल हो जाएगा।
पेट्र विक्टोरिन

धन्यवाद जेरेमी। यह एक सवाल की अच्छी चुनौती थी! हाँ पेट्र - आप बिल्कुल सही हैं :)
बेनेडिक्ट

1
अच्छी तरह से किया। क्या यह सही ढंग से व्यवहार करेगा यदि JSON तार के अंदर वर्ण पसंद "}"और "]"घटित हों? मुझे लगता है कि यह रेगेक्स के साथ पार्सिंग की एक सामान्य सीमा है।
थॉमस के

2
जब मैंने चारों ओर झाँका तो पाया कि मुख्य पार्सिंग फंक्शन इस तरह से बनाया गया है कि इसे आसानी से आलसी तरीके से उपयोग करना असंभव है, इसलिए आप अपने द्वारा पूर्ण पार्सर लागू किए बिना एक आदर्श परिणाम प्राप्त नहीं करने जा रहे हैं। यह उत्तर कई उपयोगी प्रासंगिक चीजों को प्रदर्शित करता है, और साधारण मामलों को अच्छी तरह से संभालता है।
जेरेमी

3
यह उत्तर भयावह है और मुझे नहीं पता कि इसे क्यों उखाड़ा जाता है। लेखक स्वीकार करता है कि यह वास्तव में सभी इनपुट के लिए काम नहीं करता है इसलिए परिभाषा के अनुसार यह एक सही उत्तर भी नहीं है, और यह एक जटिल नियमित अभिव्यक्ति का उपयोग करता है जो कि गणना की जाती है , इसलिए हम यह भी नहीं पढ़ सकते हैं कि यह क्या है। एक अच्छा कार्य क्या है जो कभी-कभी सही परिणाम देता है?
टॉम स्विरली

10

मेरा मानना ​​है कि ऐसा करने का एक बेहतर तरीका राज्य मशीन का उपयोग करना होगा। नीचे एक नमूना कोड है जिसे मैंने पायथन 3 के लिंक के नीचे एक नोड कोड (NodeJS कोड) में परिवर्तित करके काम किया है (केवल पायथन 3 में उपलब्ध गैर-कीवर्ड का उपयोग किया है, कोड पायथन 2 पर काम नहीं करेगा)

एडिट -1: पाइथन 2 के साथ संगत और अद्यतित कोड

एडिट -2: अपडेट किया गया और साथ ही केवल पायथन 3 का संस्करण जोड़ा गया

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

पायथन 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

अजगर 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]}]

अच्छा समाधान! मैं बाद में करीब से देखूंगा लेकिन यह बहुत आशाजनक है। लेकिन इसके लायक क्या है, मैंने पायथन 3-केवल संस्करण को प्राथमिकता दी। अपने सभी स्थानीय चरों के लिए डाइक का उपयोग करना अजीब है, और मैं अतीत में पायथन 2 को छोड़कर खुश हूं। ;)
जेरेमी

@JeremyBanks, निश्चित रूप से मुझे नहीं पता था कि आपने किस संस्करण को लक्षित किया है। अब मैंने एक Python3 केवल संस्करण और Py2 को एक संगत में जोड़ दिया है जो किसी और के उत्तर में है जो अभी भी Python 2 पर हो सकता है
तरुण लालवानी

@JeremyBanks, इनाम के साथ केवल 1 दिन बचा है, आशा है कि आप समीक्षा कर सकते हैं और जवाब पर प्रतिक्रिया प्रदान कर सकते हैं
तरुण लालवानी

यह केवल एक ही लग रहा है जो वास्तव में समस्या को समझा तरुण था। पार्सिंग की दक्षता इनपुट पर होने वाले पास की संख्या पर निर्भर करती है। अधिकांश उत्तर रेगेक्स का उपयोग करते हैं या पहले से एक पंक्ति पढ़ते हैं (यह खतरनाक भी हो सकता है) या, इससे भी बदतर, एक अज्ञात संख्या में पार्स करने में विफल रहता है। बहुत बुरा यह अजगर का हिस्सा नहीं है।
फरवरी को mschonaker

4

मैं एक समाधान प्रदान करना चाहूंगा। मुख्य विचार डिकोड करने के लिए "प्रयास" करना है: यदि यह विफल रहता है, तो इसे अधिक फ़ीड दें, अन्यथा अगले डिकोडिंग को तैयार करने के लिए ऑफसेट जानकारी का उपयोग करें।

हालांकि वर्तमान जसन मॉड्यूल स्ट्रिंग के सिर में स्पेस को बर्दाश्त नहीं कर सकता है, इसलिए मुझे उन्हें उतारना होगा।

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

(inxt, अपने प्रारंभिक)

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

(बेनेडिक्ट के टेस्टकेस के लिए आउटपुट)

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 फ़ाइल को लोड करने के लिए कक्षा का उपयोग कैसे कर सकता हूं?
गीत ००

3

मैंने @ वुइलांग के सुरुचिपूर्ण समाधान का उपयोग किया। सरल दृष्टिकोण - एक बाइट पढ़ें, डिकोड करने का प्रयास करें, एक बाइट पढ़ें, डिकोड करने का प्रयास करें, ... - काम किया, लेकिन दुर्भाग्य से यह बहुत धीमा था।

मेरे मामले में, मैं एक फ़ाइल से एक ही ऑब्जेक्ट प्रकार के "सुंदर-मुद्रित" 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 मूल्यों के बीच सफेद स्थान को छोड़ने के लिए लूप के अतिरिक्त के बारे में बहुत खुश नहीं हूं, लेकिन यह मेरी राय में काम करता है।

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 () तब विफल होती है जब स्ट्रिंग में पहला वर्ण एक व्हाट्सएप है, यही कारण है कि मैं व्हाट्सएप के ऊपर व्हाट्सएप पर छोड़ देता हूं ...

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 मान हो सकता है। दूसरे शब्दों में, इस उद्देश्य को ध्यान में रखते हुए विधि लिखी जा सकती है।

मूल फ़ंक्शन में प्रस्तुत उदाहरण के रूप में ऊपरी फ़ंक्शन का उपयोग कर इनपुट के साथ।


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.