प्रारूप मानक जोंस मॉड्यूल के साथ तैरता है


100

मैं झंडे की सूची को क्रमबद्ध करने के लिए अजगर 2.6 में मानक जेसन मॉड्यूल का उपयोग कर रहा हूं । हालाँकि, मुझे इस तरह के परिणाम मिल रहे हैं:

>>> import json
>>> json.dumps([23.67, 23.97, 23.87])
'[23.670000000000002, 23.969999999999999, 23.870000000000001]'

मैं चाहता हूं कि केवल दो दशमलव अंकों के साथ झांकियों का गठन किया जाए। आउटपुट इस तरह दिखना चाहिए:

>>> json.dumps([23.67, 23.97, 23.87])
'[23.67, 23.97, 23.87]'

मैंने अपने स्वयं के JSON एनकोडर वर्ग को परिभाषित करने की कोशिश की है:

class MyEncoder(json.JSONEncoder):
    def encode(self, obj):
        if isinstance(obj, float):
            return format(obj, '.2f')
        return json.JSONEncoder.encode(self, obj)

यह एकमात्र फ्लोट ऑब्जेक्ट के लिए काम करता है:

>>> json.dumps(23.67, cls=MyEncoder)
'23.67'

लेकिन नेस्टेड वस्तुओं के लिए विफल:

>>> json.dumps([23.67, 23.97, 23.87])
'[23.670000000000002, 23.969999999999999, 23.870000000000001]'

मैं बाहरी निर्भरता नहीं रखना चाहता, इसलिए मैं मानक json मॉड्यूल के साथ रहना पसंद करता हूं।

इसे कैसे प्राप्त किया जा सकता है?

जवाबों:


80

नोट: यह पायथन के किसी भी हाल के संस्करण में काम नहीं करता है ।

दुर्भाग्य से, मेरा मानना ​​है कि आपको यह बंदर-पेटिंग द्वारा करना होगा (जो, मेरी राय में, मानक पुस्तकालय jsonपैकेज में एक डिजाइन दोष दर्शाता है )। जैसे, यह कोड:

import json
from json import encoder
encoder.FLOAT_REPR = lambda o: format(o, '.2f')
    
print(json.dumps(23.67))
print(json.dumps([23.67, 23.97, 23.87]))

का उत्सर्जन करता है:

23.67
[23.67, 23.97, 23.87]

जैसी आपकी इच्छा। जाहिर है, ओवरराइड करने के लिए एक शानदार तरीका होना चाहिए FLOAT_REPRताकि अगर आप चाहते हैं कि यह एक फ्लोट का प्रतिनिधित्व आपके नियंत्रण में हो; लेकिन दुर्भाग्य से यह नहीं है कि jsonपैकेज कैसे डिजाइन किया गया था :-(


10
JSON एनकोडर के पायथन सी संस्करण का उपयोग करके पायथन 2.7 में यह समाधान काम नहीं करता है।
नेल्सन

25
हालाँकि आप ऐसा करते हैं,% .3g के बजाय% .15g या% .12g जैसे कुछ का उपयोग करें।
गिडो वैन रोसुम

23
मुझे यह स्निपेट एक जूनियर प्रोग्रामर के कोड में मिला। यह एक बहुत ही गंभीर लेकिन सूक्ष्म बग बनाया गया होता अगर इसे पकड़ा नहीं गया होता। क्या आप इस कोड पर इस बंदर के पेटिंग के वैश्विक निहितार्थ बताते हुए चेतावनी दे सकते हैं।
रोरी हार्ट

12
आपके द्वारा किए जाने पर इसे वापस सेट करने के लिए यह अच्छी स्वच्छता है: original_float_repr = encoder.FLOAT_REPR encoder.FLOAT_REPR = lambda o: format(o, '.2f') print json.dumps(1.0001) encoder.FLOAT_REPR = original_float_repr
जेफ कॉफमैन

6
जैसा कि दूसरों ने बताया है, यह अब कम से कम पायथन 3.6+ में काम नहीं कर रहा है। 23.67यह देखने के लिए कि कैसे .2fसम्मान नहीं किया जाता है, कुछ अंक जोड़ें ।
निको श्लोमर

57
import simplejson
    
class PrettyFloat(float):
    def __repr__(self):
        return '%.15g' % self
    
def pretty_floats(obj):
    if isinstance(obj, float):
        return PrettyFloat(obj)
    elif isinstance(obj, dict):
        return dict((k, pretty_floats(v)) for k, v in obj.items())
    elif isinstance(obj, (list, tuple)):
        return list(map(pretty_floats, obj))
    return obj
    
print(simplejson.dumps(pretty_floats([23.67, 23.97, 23.87])))

का उत्सर्जन करता है

[23.67, 23.97, 23.87]

कोई मंकीपैक जरूरी नहीं।


2
मुझे यह समाधान पसंद है; बेहतर एकीकरण, और 2.7 के साथ काम करता है। क्योंकि मैं वैसे भी डेटा का निर्माण कर रहा हूं, इसलिए मैंने pretty_floatsफ़ंक्शन को समाप्त कर दिया और बस इसे अपने अन्य कोड में एकीकृत कर दिया।
mikepurvis

1
Python3 में यह "मैप ऑब्जेक्ट जेएसएन सीरियल करने योग्य नहीं है" त्रुटि देता है, लेकिन आप मानचित्र को () के साथ सूची में परिवर्तित कर सकते हैंlist( map(pretty_floats, obj) )
गुगली

1
@ गुगली: ऐसा इसलिए है क्योंकि पायथन 3 mapरिटर्न list
इट्रेटर है

4
मेरे लिए काम नहीं करता है (Python 3.5.2, simplejson 3.16.0)। इसे% .6g और [23.671234556, 23.971234556, 23.871234556] के साथ आज़माया, यह अभी भी पूरी संख्या को छापता है।
szali

27

यदि आप पायथन 2.7 का उपयोग कर रहे हैं, तो एक सरल उपाय यह है कि आप अपनी तरंगों को स्पष्ट रूप से वांछित सटीकता के साथ गोल करें।

>>> sys.version
'2.7.1 (r271:86832, Nov 27 2010, 18:30:46) [MSC v.1500 32 bit (Intel)]'
>>> json.dumps(1.0/3.0)
'0.3333333333333333'
>>> json.dumps(round(1.0/3.0, 2))
'0.33'

यह काम करता है क्योंकि पायथन 2.7 ने फ्लोटिंग को अधिक सुसंगत बना दिया । दुर्भाग्य से यह पायथन 2.6 में काम नहीं करता है:

>>> sys.version
'2.6.6 (r266:84292, Dec 27 2010, 00:02:40) \n[GCC 4.4.5]'
>>> json.dumps(round(1.0/3.0, 2))
'0.33000000000000002'

ऊपर उल्लिखित समाधान 2.6 के लिए वर्कअराउंड हैं, लेकिन कोई भी पूरी तरह से पर्याप्त नहीं हैं। यदि आपका पायथन रनटाइम JSON मॉड्यूल के C संस्करण का उपयोग करता है तो बंदर पैचिंग json.encoder.FLOAT_REPR काम नहीं करता है। टॉम वॉटके के जवाब में प्रिटीफ्लोट वर्ग काम करता है, लेकिन केवल अगर% g एन्कोडिंग आपके आवेदन के लिए विश्व स्तर पर काम करता है। % .15g थोड़ा जादू है, यह काम करता है क्योंकि फ्लोट प्रिसिजन 17 महत्वपूर्ण अंक है और% g अनुगामी शून्य को प्रिंट नहीं करता है।

मैंने कुछ समय बिताने के लिए प्रिटीफ्लोट बनाने की कोशिश की, जिसमें प्रत्येक संख्या के लिए सटीक अनुकूलन की अनुमति हो। यानी, एक वाक्यविन्यास की तरह

>>> json.dumps(PrettyFloat(1.0 / 3.0, 4))
'0.3333'

यह अधिकार पाना आसान नहीं है। फ्लोट से इनहेरिट करना अजीब है। ऑब्जेक्ट से इनहेरिट करना और अपने स्वयं के डिफ़ॉल्ट () विधि के साथ एक JSONEncoder उपवर्ग का उपयोग करना चाहिए, सिवाय इसके कि json मॉड्यूल लगता है कि सभी कस्टम प्रकारों को स्ट्रिंग्स के रूप में क्रमबद्ध किया जाना चाहिए। यानी: आप आउटपुट में जावास्क्रिप्ट स्ट्रिंग "0.33" के साथ समाप्त होते हैं, न कि 0.33 नंबर पर। इस काम को करने का एक तरीका अभी तक हो सकता है, लेकिन यह दिखने में जितना कठिन है।


JSONEncoder.iterencode और पैटर्न मिलान का उपयोग करते हुए पायथन 2.6 के लिए एक और दृष्टिकोण github.com/migurski/LilJSON/blob/master/liljson.py
Nelson

उम्मीद है कि यह आपके फ्लोट्स के आसपास से गुजरता है और अधिक हल्का - मुझे पसंद है कि हम JSON वर्गों के साथ खिलवाड़ करने से कैसे बच सकते हैं।
लिंकन बी

20

वास्तव में दुर्भाग्यपूर्ण है जो dumpsआपको तैरने के लिए कुछ भी करने की अनुमति नहीं देता है। हालाँकि loadsकरता है। इसलिए यदि आपको अतिरिक्त सीपीयू लोड से ऐतराज नहीं है, तो आप इसे एनकोडर / डिकोडर / एनकोडर के माध्यम से फेंक सकते हैं और सही परिणाम प्राप्त कर सकते हैं:

>>> json.dumps(json.loads(json.dumps([.333333333333, .432432]), parse_float=lambda x: round(float(x), 3)))
'[0.333, 0.432]'

धन्यवाद, यह वास्तव में उपयोगी सुझाव है। मैं parse_floatkwarg के बारे में नहीं जानता था !
बेनामी

यहाँ सबसे सरल सुझाव जो 3.6 में भी काम करता है।
ब्रेंट फ़ॉस्ट

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

10

यहाँ एक समाधान है जो मेरे लिए पायथन 3 में काम करता है और उसे बंदर के पेटिंग की आवश्यकता नहीं है:

import json

def round_floats(o):
    if isinstance(o, float): return round(o, 2)
    if isinstance(o, dict): return {k: round_floats(v) for k, v in o.items()}
    if isinstance(o, (list, tuple)): return [round_floats(x) for x in o]
    return o


json.dumps(round_floats([23.63437, 23.93437, 23.842347]))

आउटपुट है:

[23.63, 23.93, 23.84]

यह डेटा को कॉपी करता है लेकिन राउंडेड फ्लोट्स के साथ।


9

यदि आप पायथन 2.5 या पुराने संस्करणों के साथ फंस गए हैं: सी-स्पीडअप स्थापित होने पर बंदर-पैच ट्रिक मूल सिम्पसन मॉड्यूल के साथ काम नहीं करता है:

$ python
Python 2.5.4 (r254:67916, Jan 20 2009, 11:06:13) 
[GCC 4.2.1 (SUSE Linux)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import simplejson
>>> simplejson.__version__
'2.0.9'
>>> simplejson._speedups
<module 'simplejson._speedups' from '/home/carlos/.python-eggs/simplejson-2.0.9-py2.5-linux-i686.egg-tmp/simplejson/_speedups.so'>
>>> simplejson.encoder.FLOAT_REPR = lambda f: ("%.2f" % f)
>>> simplejson.dumps([23.67, 23.97, 23.87])
'[23.670000000000002, 23.969999999999999, 23.870000000000001]'
>>> simplejson.encoder.c_make_encoder = None
>>> simplejson.dumps([23.67, 23.97, 23.87])
'[23.67, 23.97, 23.87]'
>>> 

7

आप वह कर सकते हैं जो आपको करने की आवश्यकता है, लेकिन यह प्रलेखित नहीं है:

>>> import json
>>> json.encoder.FLOAT_REPR = lambda f: ("%.2f" % f)
>>> json.dumps([23.67, 23.97, 23.87])
'[23.67, 23.97, 23.87]'

5
साफ-सुथरा दिखता है, लेकिन लगता है कि अजगर 3.6 पर काम नहीं कर रहा है। विशेष रूप से, मुझे मॉड्यूल FLOAT_REPRमें एक स्थिरांक दिखाई नहीं दिया json.encoder
टॉमस गैंडर

2

एलेक्स मार्टेली का समाधान एकल थ्रेडेड ऐप्स के लिए काम करेगा, लेकिन बहु-थ्रेडेड ऐप्स के लिए काम नहीं कर सकता है जिन्हें प्रति थ्रेड दशमलव स्थानों की संख्या को नियंत्रित करने की आवश्यकता होती है। यहाँ एक समाधान है जो बहु थ्रेडेड ऐप्स में काम करना चाहिए:

import threading
from json import encoder

def FLOAT_REPR(f):
    """
    Serialize a float to a string, with a given number of digits
    """
    decimal_places = getattr(encoder.thread_local, 'decimal_places', 0)
    format_str = '%%.%df' % decimal_places
    return format_str % f

encoder.thread_local = threading.local()
encoder.FLOAT_REPR = FLOAT_REPR     

#As an example, call like this:
import json

encoder.thread_local.decimal_places = 1
json.dumps([1.56, 1.54]) #Should result in '[1.6, 1.5]'

आप केवल इच्छित स्थानों की संख्या के लिए encoder.thread_local.decimal_places सेट कर सकते हैं, और उस धागे में json.dumps () के लिए अगला कॉल दशमलव स्थानों की संख्या का उपयोग करेगा


2

यदि आपको वैश्विक json.encoder.FLOAT_REPR को ओवरराइड किए बिना अजगर 2.7 में ऐसा करने की आवश्यकता है, तो यहां एक तरीका है।

import json
import math

class MyEncoder(json.JSONEncoder):
    "JSON encoder that renders floats to two decimal places"

    FLOAT_FRMT = '{0:.2f}'

    def floatstr(self, obj):
        return self.FLOAT_FRMT.format(obj)

    def _iterencode(self, obj, markers=None):
        # stl JSON lame override #1
        new_obj = obj
        if isinstance(obj, float):
            if not math.isnan(obj) and not math.isinf(obj):
                new_obj = self.floatstr(obj)
        return super(MyEncoder, self)._iterencode(new_obj, markers=markers)

    def _iterencode_dict(self, dct, markers=None):
        # stl JSON lame override #2
        new_dct = {}
        for key, value in dct.iteritems():
            if isinstance(key, float):
                if not math.isnan(key) and not math.isinf(key):
                    key = self.floatstr(key)
            new_dct[key] = value
        return super(MyEncoder, self)._iterencode_dict(new_dct, markers=markers)

फिर, अजगर 2.7 में:

>>> from tmp import MyEncoder
>>> enc = MyEncoder()
>>> enc.encode([23.67, 23.98, 23.87])
'[23.67, 23.98, 23.87]'

पाइथन 2.6 में, यह काफी काम नहीं करता है क्योंकि मैथ्यू स्चिनकेल नीचे बताते हैं:

>>> import MyEncoder
>>> enc = MyEncoder()  
>>> enc.encode([23.67, 23.97, 23.87])
'["23.67", "23.97", "23.87"]'

4
वे तार की तरह दिखते हैं, संख्या नहीं।
मैथ्यू Schinckel

1

पेशेवरों:

  • किसी भी JSON एनकोडर, या यहां तक ​​कि अजगर के साथ काम करता है।
  • लघु (ईश), काम करने लगता है।

विपक्ष:

  • बदसूरत regexp हैक, मुश्किल से परीक्षण किया गया।
  • द्विघात जटिलता।

    def fix_floats(json, decimals=2, quote='"'):
        pattern = r'^((?:(?:"(?:\\.|[^\\"])*?")|[^"])*?)(-?\d+\.\d{'+str(decimals)+'}\d+)'
        pattern = re.sub('"', quote, pattern) 
        fmt = "%%.%df" % decimals
        n = 1
        while n:
            json, n = re.subn(pattern, lambda m: m.group(1)+(fmt % float(m.group(2)).rstrip('0')), json)
        return json

1

मानक json मॉड्यूल आयात करते समय, यह डिफ़ॉल्ट एनकोडर FLOAT_REPR को बदलने के लिए पर्याप्त है। वास्तव में एनकोडर उदाहरणों को आयात करने या बनाने की आवश्यकता नहीं है।

import json
json.encoder.FLOAT_REPR = lambda o: format(o, '.2f')

json.dumps([23.67, 23.97, 23.87]) #returns  '[23.67, 23.97, 23.87]'

कभी-कभी आउटपुट के लिए भी बहुत उपयोगी होता है क्योंकि json सबसे अच्छा प्रतिनिधित्व करता है अजगर स्ट्र के साथ अनुमान लगा सकता है। यह सुनिश्चित करेगा कि हस्ताक्षरकर्ताओं के अंकों को नजरअंदाज नहीं किया जाएगा।

import json
json.dumps([23.67, 23.9779, 23.87489])
# output is'[23.670000000000002, 23.977900000000002, 23.874890000000001]'

json.encoder.FLOAT_REPR = str
json.dumps([23.67, 23.9779, 23.87489])
# output is '[23.67, 23.9779, 23.87489]'

1

मैं @ नेल्सन से सहमत हूं कि फ्लोट से विरासत में मिलना अजीब है, लेकिन शायद एक समाधान जो केवल __repr__फ़ंक्शन को छूता है वह क्षम्य हो सकता है। मैंने decimalजरूरत पड़ने पर सुधारक नावों को इसके लिए पैकेज का उपयोग करके समाप्त कर दिया । उल्टा यह है कि यह उन सभी संदर्भों में काम करता है जहां repr()कहा जा रहा है, इसलिए भी जब मुद्रण सूची केवल उदाहरण के लिए stdout करने के लिए। इसके अलावा, डेटा रन बनाए जाने के बाद सटीक रनटाइम कॉन्फ़िगर करने योग्य है। नकारात्मक पक्ष यह है कि आपके डेटा को इस विशेष फ्लोट वर्ग में परिवर्तित करने की आवश्यकता है (दुर्भाग्य से आप बंदर के पैच को नहीं देख सकते हैंfloat.__repr__ )। उसके लिए मैं एक संक्षिप्त रूपांतरण समारोह प्रदान करता हूं।

कोड:

import decimal
C = decimal.getcontext()

class decimal_formatted_float(float):
   def __repr__(self):
       s = str(C.create_decimal_from_float(self))
       if '.' in s: s = s.rstrip('0')
       return s

def convert_to_dff(elem):
    try:
        return elem.__class__(map(convert_to_dff, elem))
    except:
        if isinstance(elem, float):
            return decimal_formatted_float(elem)
        else:
            return elem

उपयोग उदाहरण:

>>> import json
>>> li = [(1.2345,),(7.890123,4.567,890,890.)]
>>>
>>> decimal.getcontext().prec = 15
>>> dff_li = convert_to_dff(li)
>>> dff_li
[(1.2345,), (7.890123, 4.567, 890, 890)]
>>> json.dumps(dff_li)
'[[1.2345], [7.890123, 4.567, 890, 890]]'
>>>
>>> decimal.getcontext().prec = 3
>>> dff_li = convert_to_dff(li)
>>> dff_li
[(1.23,), (7.89, 4.57, 890, 890)]
>>> json.dumps(dff_li)
'[[1.23], [7.89, 4.57, 890, 890]]'

यह अंतर्निहित Python3 json पैकेज के साथ काम नहीं करता है, जो __repr __ () का उपयोग नहीं करता है।
इयान गोल्डबी

0

सुन्न का उपयोग करना

यदि आप वास्तव में लंबे समय तक तैरते हैं तो आप उन्हें सुन्न के साथ सही ढंग से ऊपर / नीचे गोल कर सकते हैं:

import json 

import numpy as np

data = np.array([23.671234, 23.97432, 23.870123])

json.dumps(np.around(data, decimals=2).tolist())

'[23.67, 23.97, 23.87]'


-1

मैंने अभी इस समस्या को ठीक करने के लिए fjson , एक छोटा अजगर पुस्तकालय जारी किया। के साथ स्थापित करें

pip install fjson

और पैरामीटर jsonके अतिरिक्त के साथ उपयोग करें float_format:

import math
import fjson


data = {"a": 1, "b": math.pi}
print(fjson.dumps(data, float_format=".6e", indent=2))
{
  "a": 1,
  "b": 3.141593e+00
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.