Python'da bir dosya / akıştan birden çok JSON değerini nasıl yavaşça okuyabilirim?


101

Python'daki bir dosya / akıştan teker teker birden çok JSON nesnesi okumak istiyorum. Ne yazık ki json.load()sadece .read()dosyanın sonuna kadar; Tek bir nesneyi okumak veya nesneler üzerinde tembel olarak yinelemek için onu kullanmanın bir yolu yok gibi görünüyor.

Bunu yapmanın bir yolu var mı? Standart kitaplığı kullanmak ideal olacaktır, ancak üçüncü taraf bir kitaplık varsa bunun yerine onu kullanırdım.

Şu anda her bir nesneyi ayrı bir çizgiye koyuyorum ve kullanıyorum json.loads(f.readline()), ancak bunu yapmamayı gerçekten tercih ederim.

Örnek Kullanım

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

örnek oturum

$ 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

İç içe geçmiş nesnelerden olmasını istediğiniz davranışa bir örnek ekleyebilir misiniz lütfen?
Tim McNamara

@TimMcNamara: İç içe nesnenin davranışı değişmemelidir. Bununla birlikte, birinci üst düzey nesnenin sonuna ulaştığımızda ( {"foo": ["bar", "baz"]}benim örneğimde), bu olmalı yieldve sonra bir sonrakine ( 1) devam etmelidir .
Jeremy

1
"json satırlarından" neden kaçının? O hiçbir vardır öyle ki json içine bir nesne seri hale getirmek her zaman mümkündür '\n'nedeniyle json temsilinde (tek bir yeni satır değil, iki karakter) '\n'şekilde çıkmalıdır bir json dize içinde ve bu nedenle '\n'örneğin yalnızca biçimlendirme için kullanılabilir, inanıyorum json.dumps()'doesn t '\n'Varsayılan olarak tanıtın. U + 0085 gibi Unicode satırsonlarının json dizeleri içinde çıkış karaktersiz olabileceğine dikkat edin.
jfs

2
İjson kütüphane bu durumda yararlı olabilir. pypi.python.org/pypi/ijson github.com/isagalaev/ijson
Boris Chervenkov

1
Başlığın " Python'daki bir dosya / akıştan birden çok JSON değerini nasıl yavaşça okuyabilirim ?" Olması gerekmez mi? Bir nesne json int, string vb. Olduğu gibi bir değer olduğundan, tersi gerekli değil mi?
hetepeperfan

Yanıtlar:


20

İşte çok daha basit bir çözüm. İşin sırrı, istisnadaki bilgileri denemek, başarısız olmak ve doğru şekilde ayrıştırmak için kullanmaktır. Tek sınırlama, dosyanın aranabilir olmasıdır.

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

Düzenleme: bunun yalnızca Python> = 3.5 için çalışacağını fark ettim. Daha önce, hatalar bir ValueError döndürür ve dizeden konumu ayrıştırmanız gerekir, örn.

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'a hoş geldiniz ve cevabınız için teşekkürler! Bulmayı umduğuma çok daha yakın. Doğrudan arama sağlamasalar bile, bunu düşündüğüm vaka türlerine uyarlayabilmeliyim.
Jeremy

Bu reişe yaramaz - ters eğik çizgilerin kaçması gerekir. Ham bir dizge düşünün r'...'.
Tom Swirly

2
Buna kendi çalışmam için ihtiyacım vardı, bu yüzden bunu yapmak için küçük bir python kitaplığı oluşturdum, tekniğinizi bazı ayrıntılarla aşağı yukarı kullanarak ve işte burada: pypi.python.org/pypi/Streamy
Tom Swirly

2
Onun ujsonyerine kullanırsanız, jsonbüyük bir hızlanma elde
edeceksiniz

40

JSON genellikle bu tür artımlı kullanım için pek iyi değildir; Birden fazla nesneyi seri hale getirmenin standart bir yolu yoktur, böylece tüm partiyi ayrıştırmadan teker teker kolayca yüklenebilirler.

Kullandığınız satır başına nesne çözümü başka yerlerde de görülür. Scrapy buna 'JSON hatları' diyor:

Bunu biraz daha Pythonically yapabilirsiniz:

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

Bunun en iyi yolla ilgili olduğunu düşünüyorum - herhangi bir üçüncü taraf kitaplığına bağlı değil ve neler olduğunu anlamak kolay. Ben de kendi kodumun bir kısmında kullandım.


4
re: "standart bir yol yok": Sorunu görmüyorum, sözdizimi, tek karakterlik bir arabelleğiniz olduğu sürece birden fazla ardışık nesneyi belirsiz hale getiriyor gibi görünüyor. Başkalarının "JSON hatları" kullandığına işaret ettiğiniz için teşekkürler, şimdilik onu kullanmak konusunda kendimi daha az kötü hissediyorum.
Jeremy

31

Belki biraz geç, ama tam olarak bu problemi yaşadım (aşağı yukarı). Bu problemler için standart çözümüm, genellikle iyi bilinen bazı kök nesnelerde normal ifade ayırma yapmaktır, ancak benim durumumda bu imkansızdı. Bunu jenerik olarak yapmanın tek uygulanabilir yolu, uygun bir jetonlaştırıcı uygulamaktır .

Yeterince genel ve makul derecede iyi performans gösteren bir çözüm bulamayınca, bunu kendim yapmayı bırakıp splitstreammodülü yazdım . JSON ve XML'i anlayan ve sürekli bir akışı ayrıştırma için birden fazla parçaya bölen bir ön belirteçleştiricidir (yine de asıl ayrıştırmayı size bırakmaktadır). Bundan bir çeşit performans elde etmek için C modülü olarak yazılmıştır.

Misal:

from splitstream import splitfile

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

Bu harika. Paylaştığınız için teşekkürler
Jeremy

Kesin çözüm budur. Umarım güncellemeye devam edersiniz.
Bartvds

Basitçe çalışıyor. Böylesine kullanışlı bir modül sağladığınız için teşekkürler.
Vinod Sharma

1
Derlenmiş bir .py sürümü yükleyebilir misiniz? Modülü oluşturmaya ve kurmaya çalıştım ama ... sabitleri ve benzerlerini yeniden tanımlamayla ilgili bir dizi hata üretiyor.
SirJames

Modül C'de yazılmıştır. Onu saf Python'a taşımak, görev için kim varsa ona bir egzersiz olarak bırakılmıştır :). Yine de yazıldığı amaç için muhtemelen çok yavaş olacaktır. Derlemede sorun yaşıyorsanız muhtemelen python-dev paketini kurmanız gerekir.
Krumelur

25

Tabii bunu yapabilirsin. Sadece raw_decodedoğrudan almalısın. Bu uygulama tüm dosyayı belleğe yükler ve bu dizge üzerinde çalışır (olduğu json.loadgibi); Eğer büyük dosyalarınız varsa, onu sadece dosyadan gerektiği kadar zorlanmadan okuyacak şekilde değiştirebilirsiniz.

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()

Kullanım: tam istediğiniz gibi, bir jeneratördür.


2
İşin zor kısmı, akış okumalarının, çözmeniz gereken tüm bir nesneye sahip olmanız için yeterince dosya getirmesini sağlamak gibi görünüyor. Yani bu, örneğin nesnelerin içinde hiçbir zaman satırsonu olmadığını varsayarsanız işe yarayan basit bir yaklaşımdır. Ancak, OP'nin kaçınmaya çalıştığı dosyaya bu tür bir ek yapı empoze etmedikçe, @Benedict
nealmcb

24

Bu aslında oldukça çirkin bir sorundur, çünkü satırlar halinde akış yapmak zorundasınız, ancak parantezlere karşı birden çok satırda desen eşleşiyor, aynı zamanda desen eşleştirme json. Bir tür json hazırlığı ve ardından json ayrıştırması. Json, diğer formatlara kıyasla, ayrıştırılması kolaydır, bu nedenle bir ayrıştırma kitaplığına gitmek her zaman gerekli değildir, yine de, bu çelişkili sorunları nasıl çözmeliyiz?

Kurtarma için jeneratörler!

Böyle bir problem için jeneratörlerin güzelliği, tembelliği korurken, problemin zorluğunu kademeli olarak soyutlayarak onları üst üste yığabilmenizdir. Ayrıca, değerleri bir üreteç'e (send ()) geri göndermek için mekanizmayı kullanmayı da düşündüm, ancak neyse ki bunu kullanmam gerekmediğini fark ettim.

Sorunlardan ilkini çözmek için, re.finditer'ın akış sürümü olarak bir tür akış bulucuya ihtiyacınız vardır. Aşağıdaki bu denemem gerektiği gibi satırları çeker (görmek için hata ayıklama ifadesini kaldırın) hala eşleşmeleri döndürürken. Aslında daha sonra eşleşmeyen satırların yanı sıra eşleşmeleri (verilen dizinin ilk bölümünde 0 veya 1 olarak işaretlenmiş) elde etmek için biraz değiştirdim.

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]

Bununla, küme ayraçlarına kadar eşleştirmek, her seferinde küme ayraçlarının dengeli olup olmadığını hesaplamak ve ardından uygun şekilde basit veya bileşik nesneleri döndürmek mümkündür.

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="" 

Bu, tupl'leri aşağıdaki gibi döndürür:

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

Temelde yapılan kötü kısım bu. Şimdi, uygun gördüğümüz şekilde son ayrıştırma düzeyini yapmalıyız. Örneğin, tek bir satırı ayrıştırmak için Jeremy Roman'ın yineleme işlevini (Teşekkürler!) Kullanabiliriz:

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

Dene:

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()

Bu sonuçları alıyorum (ve bu hata ayıklama satırını açarsanız, gerektiğinde satırları çektiğini göreceksiniz):

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

Bu her durumda işe yaramayacaktır. jsonKitaplığın uygulanmasından dolayı , ayrıştırıcıyı kendiniz yeniden uygulamadan tamamen doğru şekilde çalışmak imkansızdır .


8
Bunu doğru bir şekilde yapmak istiyorsanız, dizelerdeki parantez ve parantezlere de dikkat etmeniz gerekir. Ve sonra kaçan alıntılara da dikkat edin. Siz farkına varmadan, "preparser" neredeyse tam bir JSON ayrıştırıcısı kadar karmaşık hale gelecektir.
Petr Viktorin

Teşekkürler Jeremy. Bir soruya meydan okumak güzeldi! Evet Petr - kesinlikle haklısınız :)
Benedict

1
Güzel yapılmış. Karakterler JSON dizelerini beğenir "}"ve "]"içinde oluşursa bu doğru şekilde davranır mı? Bunun normal ifade ile ayrıştırmanın genel bir sınırlaması olduğunu düşünüyorum.
Thomas K

2
Etrafa bakarken, ana ayrıştırma işlevinin, onu tembel olarak doğru şekilde kullanmanın imkansız olduğu bir şekilde inşa edildiğini gördüm , bu nedenle, kendi başınıza tam bir ayrıştırıcı uygulamadan mükemmel bir sonuç elde edemezsiniz. Bu cevap, birkaç yararlı konuyla ilgili şeyi gösterir ve basit vakaları güzelce ele alır.
Jeremy

3
Bu cevap korkunç ve neden oy verildiğine dair hiçbir fikrim yok. Yazar, aslında tüm girdiler için işe yaramadığını kabul ediyor, bu nedenle tanım gereği doğru bir cevap bile değil ve hesaplanan karmaşık bir düzenli ifade kullanıyor , bu yüzden ne olduğunu bile okuyamıyoruz. Bazen doğru sonucu veren bir işlev ne işe yarar?
Tom Swirly

10

Bunu yapmanın daha iyi bir yolunun bir durum makinesi kullanmak olduğuna inanıyorum. Aşağıda, aşağıdaki bağlantıdaki bir NodeJS kodunu Python 3'e dönüştürerek oluşturduğum örnek bir kod bulunmaktadır (yerel olmayan anahtar kelime yalnızca Python 3'te mevcuttur, kod Python 2'de çalışmaz)

Düzenleme-1: Güncellenmiş ve Python 2 ile uyumlu hale getirilmiş kod

Düzenleme-2: Güncellenmiş ve yalnızca Python3 sürümü eklendi

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

Yalnızca Python 3 sürümü

# 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 uyumlu sürüm

# 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

Test ediyorum

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))

Aynısının çıktısı

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

Güzel çözüm! Daha sonra daha yakından bakacağım ama bu çok umut verici. Ama değeri ne olursa olsun, sadece Python 3 sürümünü tercih ettim. Tüm yerel değişkenleriniz için dicts kullanmak biraz tuhaftır ve ben Python 2'yi geçmişte bıraktığım için mutluyum. ;)
Jeremy

@JeremyBanks, elbette hangi sürümü hedeflediğinizi bilmiyordum. Şimdi hala Python 2'de olabilecek başka birinin cevabına yalnızca Python3 ve Py2 uyumlu bir sürüm ekledim
Tarun Lalwani

@JeremyBanks, ödülle sadece 1 gün kaldı, umarım yanıtı inceleyip geri bildirimde bulunabilirsiniz
Tarun Lalwani

Görünüşe göre sorunu gerçekten anlayan tek kişi Tarun'du. Ayrıştırmanın verimliliği, girişte gerçekleşen geçişlerin sayısına bağlıdır. Yanıtların çoğu normal ifade kullanır veya önceden bir satırı okur (bu da tehlikeli olabilir) veya daha kötüsü, bilinmeyen sayıda ayrıştırmada başarısız olur. Ne yazık ki bu Python'un bir parçası değil.
mschonaker

4

Bir çözüm sunmak istiyorum. Temel düşünce, kodu çözmeyi "denemektir": başarısız olursa, ona daha fazla besleme verin, aksi takdirde bir sonraki kod çözmeyi hazırlamak için ofset bilgisini kullanın.

Ancak şu anki json modülü, dizginin başındaki SPACE'in kodunun çözülmesine tahammül edemiyor, bu yüzden onları çıkarmam gerekiyor.

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)

========================= Birkaç txt dosyasını test ettim ve sorunsuz çalışıyor. (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, baş harfiniz)

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

(Benedict'in test çantası için çıktı)

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

Benimki burada:

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

Merhaba, bu çok kullanışlı ama json dosyasını yüklemek için sınıfı nasıl kullanabileceğimi gösterebilir misin?
song0089

3

@ Wuilang'ın zarif çözümünü kullandım. Basit yaklaşım - bir bayt oku, kodunu çözmeyi dene, bir bayt oku, kodunu çözmeyi dene, ... - işe yaradı, ama maalesef çok yavaştı.

Benim durumumda, bir dosyadan aynı nesne türündeki "güzel yazdırılmış" JSON nesnelerini okumaya çalışıyordum. Bu, yaklaşımı optimize etmeme izin verdi; Dosyayı satır satır okuyabiliyordum, ancak tam olarak "}" içeren bir satır bulduğumda kodunu çözebiliyordum:

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 = ""

Dize değişmezlerinde yeni satırlardan kaçan satır başına bir kompakt JSON ile çalışıyorsanız, bu yaklaşımı daha da güvenli bir şekilde basitleştirebilirsiniz:

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

Açıktır ki, bu basit yaklaşımlar yalnızca çok özel JSON türleri için işe yarar. Ancak, bu varsayımlar geçerli olursa, bu çözümler doğru ve hızlı bir şekilde çalışır.


2

Bir json.JSONDecoder örneği kullanıyorsanız, raw_decodeüye işlevini kullanabilirsiniz . JSON değerinin python temsilinin bir demetini ve ayrıştırmanın durduğu bir dizini döndürür. Bu, kalan JSON değerlerini dilimlemeyi (veya bir akış nesnesinde aramayı) kolaylaştırır. Girişteki farklı JSON değerleri arasındaki beyaz boşluğu atlamak için fazladan while döngüsünden pek memnun değilim ama bence işi hallediyor.

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

Bir sonraki sürüm çok daha kısadır ve dizenin halihazırda ayrıştırılmış olan kısmını yer. Görünüşe göre, dizedeki ilk karakter bir boşluk olduğunda, ikinci bir json.JSONDecoder.raw_decode () çağrısı başarısız görünüyor, bu da yukarıdaki boşlukta boşlukları atlamamın nedenidir ...

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 sınıfıyla ilgili belgelerde, raw_decode https://docs.python.org/3/library/json.html#encoders-and-decoders yöntemi şunları içerir:

Bu, sonunda gereksiz veriler olabilecek bir dizeden JSON belgesinin kodunu çözmek için kullanılabilir.

Ve bu gereksiz veriler kolaylıkla başka bir JSON değeri olabilir. Başka bir deyişle yöntem bu amaçla yazılabilir.

Üst işlevi kullanan input.txt ile, orijinal soruda sunulan örnek çıktıyı elde ederim.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.