2017-08-02 54 views
2

我正在尋找一種有效的方法來以相反的順序對python列表進行序列化。Python高效的反向列表JSON序列化

我試圖做json.dumps(reversed(mylist))但顯然json.dumps不接受迭代器。

我也可以做json.dumps(list(reversed(mylist)))但是這對於非常大的列表來說非常低效,而且我不需要創建臨時列表,我希望能夠動態地將列表序列化,而不是創建臨時列表。

我想我可以使用json.JSONEncoder,但我真的不明白我應該從default函數返回什麼。

我也必須堅持使用標準庫,因爲我沒有安裝其他軟件包的自由。

到目前爲止,我試着提出的兩種解決方案,並在這裏是測試輸出:

>>> timeit.timeit('li.reverse(); json.dumps(li)', number=1, globals=globals()) 
2.5034537549945526 
>>> timeit.timeit('"[{}]".format(",".join(map(json.dumps,reversed(li))))', number=1, globals=globals()) 
41.076039729989134 

我仍然認爲實現我自己的JSONEncoder效率會比較高,但我還是不知道到底該怎麼辦它。爲了避免複製

+0

是否使用'mylist.reverse()'(避免複製)首先反轉列表 - 執行序列化,然後在需要時再次反轉它? –

+0

這比創建一個新列表要好,但它仍然會創建一個不需要的中間步驟。但感謝提示。 :) – silentnights

+1

看完了json庫後 - 它看起來並不那麼簡單。 JSONDecoder.default有一點說*例如,爲了支持任意迭代器,你可以* ...但是這表明你從那個可迭代的列表中返回一個列表,這對於子列表是有意義的(例如,如果你有'{test:range (10)}'擴大了...但是不是整個數據的'reverse',它的一些層次由C實現處理,其他位由嵌套'_functions'的'_functions'處理。 。爲了簡單,我堅持使用'list.reverse' :) –

回答

3

一種方法是就地反轉列表,如:

mylist.reverse() 
json_string = json.dumps(mylist) 

然後mylist.reverse()回來如果需要的話。

0

我們發瘋之前,看是否有下面的滿足您的性能需求:

mylist.reverse(); json.dumps(mylist); mylist.reverse() 
json.dumps(mylist[::-1]) 
json.dumps(tuple(reversed(mylist))) 

你提到定義自己的JSONEncoder默認的功能,這是相當簡單的事情(在最下方,例如*) ,但我不認爲它在這裏工作,因爲json.JSONEncoder需要默認功能將對象轉換爲以下之一:

None, True, False, str, int, float, list, tuple, dict 

轉換一個迭代器列表或元組將創建一個大對象,這是我們試圖避免的。

你可能需要修改你的json庫或猴子補丁。

以下是json.encoder的CPython源代碼。 PyPy,Jython和其他Python實現可能爲json模塊使用相同的代碼。

https://github.com/python/cpython/blob/master/Lib/json/encoder.py#L204

def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, 
    _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, 
    ## HACK: hand-optimized bytecode; turn globals into locals 
    ValueError=ValueError, 
    dict=dict, 
    float=float, 
    id=id, 
    int=int, 
    isinstance=isinstance, 
    list=list, 
    str=str, 
    tuple=tuple, 
    _intstr=int.__str__, 
    ... 
    def _iterencode(o, _current_indent_level): 
     if isinstance(o, str): 
      yield _encoder(o) 
     ... 
     elif isinstance(o, (list, tuple)): 
      yield from _iterencode_list(o, _current_indent_level) 

     # Add support for processing iterators 
     elif isinstance(o, iterator_types): 
      # Side-effect: this will consume the iterator. 
      # This is probably why it's not included in the official json module 
      # We could use itertools.tee to be able to iterate over 
      # the original iterator while still having an unconsumed iterator 
      # but this would require updating all references to the original 
      # iterator with the new unconsumed iterator. 
      # The side effect may be unavoidable. 
      yield from _iterencode_list(o, _current_index_level) 

出於性能考慮,你要定義的迭代器類型的功能之外,並把它作爲一個地方。

str_iterator = type(iter(str() )) 
list_iterator = type(iter(list() )) 
tuple_iterator = type(iter(tuple() )) 
range_iterator = type(iter(range(0))) 
list_reverseiterator = type(reversed(list() )) 
reverseiterator  = type(reversed(tuple())) #same as <class 'reversed'> 

# Add any other iterator classes that you need here, plus any container data types that json doesn't support (sets, frozensets, bytes, bytearray, array.array, numpy.array) 
iterator_types = (str_iterator, list_iterator, tuple_iterator, range_iterator, 
        list_reverseiterator, reversed) 

如果你想去猴子修補路線,你需要重新定義json.encoder。_make_iterencode功能,具有isinstance(X, (list, tuple)+iterator_types)

import json 
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, 
     _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot, 
     iterable_types=_get_iterable_types(), 
     ... 
    ): 
    ... 

json.encoder._make_iterencode = _make_iterencode 

這些變化更換的isinstance(X, (list, tuple))所有出現是這個樣子:https://github.com/python/cpython/pull/3034/files

*正如所承諾的,如何定義自己的默認功能,雖然不是有用沒有複製傾銷迭代器迭代器首先進入列表或元組。

class JSONEncoderThatSupportsIterators(json.JSONEncoder): 
    def default(self, o): 
     try: 
      iterable = iter(o) 
     except TypeError: 
      pass 
     else: 
      return list(iterable) 
     # Let the base class default method raise the TypeError 
     return json.JSONEncoder.default(self, o) 

li = range(10000000) # or xrange if Python 2 
dumped = JSONEncoderThatSupportsIterators().encode(reversed(li)) 
assert dumped.startswith('[999999, 999998, 999997, ') 
assert dumped.endswith('6, 5, 4, 3, 2, 1, 0]') 

或者,而不是繼承json.JSONEncoder,您可以定義default(self, o)功能,並將其作爲參數傳遞給json.dumps(default=default)