ফর্ম্যাট স্ট্যান্ডার্ড জেসন মডিউল সহ ভাসমান


104

আমি ফ্লোটের একটি তালিকা সিরিয়াল করতে পাইথন ২.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]'

আমি নিজের জেএসএন এনকোডার শ্রেণি সংজ্ঞায়নের চেষ্টা করেছি:

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]'

আমি বাহ্যিক নির্ভরতা রাখতে চাই না, তাই আমি স্ট্যান্ডার্ড জেসন মডিউলটির সাথে থাকা পছন্দ করি।

আমি কীভাবে এটি অর্জন করতে পারি?

উত্তর:


81

দ্রষ্টব্য: পাইথনের কোনও সাম্প্রতিক সংস্করণে এটি কাজ করে না

দুর্ভাগ্যক্রমে, আমি বিশ্বাস করি যে আপনাকে বানর-প্যাচিংয়ের মাধ্যমে এটি করতে হবে (যা আমার মতে, স্ট্যান্ডার্ড লাইব্রেরি 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 এনকোডারটির পাইথনের সি সংস্করণ ব্যবহার করে পাইথন ২.7 এ কাজ করে না।
নেলসন

25
তবে আপনি এটি করেন তবে% .3f এর পরিবর্তে% .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
অন্যরা যেমন উল্লেখ করেছে, এটি কমপক্ষে পাইথন ৩.6++ এ আর কাজ করবে না। সম্মান দেওয়া হয় না 23.67তা দেখতে কয়েকটি অঙ্ক যুক্ত করুন .2f
নিকো Schlömer

58
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]

কোন বাঁদিকের প্রয়োজনীয়তা নেই।


4
আমি এই সমাধান পছন্দ করি; আরও ভাল সংহতকরণ, এবং 2.7 এর সাথে কাজ করে। যেহেতু আমি যাইহোক ডেটা নিজেই তৈরি করছি, তাই আমি pretty_floatsফাংশনটি সরিয়ে ফেলেছি এবং কেবল আমার অন্যান্য কোডে এটি সংহত করেছি।
মাইকপুরভিস

4
পাইথন 3 এ এটি "মানচিত্র অবজেক্টটি জাসন সিরিয়ালাইজযোগ্য নয়" ত্রুটি দেয় তবে আপনি মানচিত্রটি () এর সাথে একটি তালিকায় রূপান্তর করতে পারেনlist( map(pretty_floats, obj) )
গুগলি

4
@ গুগলি: এর কারণ পাইথন 3 mapফেরত list
পুনরুদ্ধারকারী

5
আমার জন্য কাজ করে না (পাইথন 3.5.3.2, সিম্পজসন 3.16.0)। এটি% .6g এবং [23.671234556, 23.971234556, 23.871234556] দিয়ে চেষ্টা করেছেন, এটি এখনও পুরো সংখ্যাটি মুদ্রণ করে।
szali

27

আপনি যদি পাইথন ২.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'

এটি কাজ করে কারণ পাইথন ২.7 ফ্লোটের বৃত্তাকারকে আরও সুসংগত করে তুলেছে । দুর্ভাগ্যক্রমে এটি পাইথন ২.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'

উপরে উল্লিখিত সমাধানগুলি ২.6 এর জন্য কাজের ক্ষেত্র, তবে কোনওটিই পুরোপুরি পর্যাপ্ত নয়। আপনার পাইথন রানটাইম JSON মডিউলটির সি সংস্করণ ব্যবহার করে তবে বানর প্যাচিং json.encoder.FLOAT_REPR কাজ করে না। টম ওটকের উত্তরের প্রেটিফ্লোট ক্লাসটি কাজ করে তবে কেবল যদি% g এনকোডিংটি আপনার আবেদনের জন্য বিশ্বব্যাপী কাজ করে। % .15g বিট যাদু, এটি কাজ করে কারণ ভাসমান নির্ভুলতা 17 টি উল্লেখযোগ্য সংখ্যা এবং% g পিছনের শূন্যগুলি মুদ্রণ করে না।

আমি একটি প্রেটিফ্লোট তৈরির চেষ্টা করে কিছু সময় ব্যয় করেছি যা প্রতিটি সংখ্যার জন্য নির্ভুলতার স্বনির্ধারণের অনুমতি দেয়। যেমন একটি সিনট্যাক্স

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

এই অধিকারটি পাওয়া সহজ নয়। ভাসা থেকে উত্তোলন বিশ্রী হয়। অবজেক্ট থেকে ইনহেরিটিং এবং তার নিজস্ব ডিফল্ট () পদ্ধতির সাথে একটি JSONEncoder সাবক্লাস ব্যবহার করা কাজ করা উচিত, জসন মডিউলটি মনে না করে সমস্ত কাস্টম ধরণের স্ট্রিং হিসাবে সিরিয়াল করা উচিত। উদাহরণস্বরূপ: আপনি আউটপুটে জাভাস্ক্রিপ্ট স্ট্রিং "0.33" দিয়ে শেষ করেছেন, 0.33 নম্বরটি নয়। এই কাজটি করার এখনও একটি উপায় থাকতে পারে তবে এটি দেখতে এটির চেয়ে শক্ত।


JSONEncoder.iterencode এবং প্যাটার্ন ম্যাচিং ব্যবহার করে পাইথন ২.6 এর আরেকটি উপায় github.com/migurski/LilJSON/blob/master/liljson.py এ দেখা যাবে
নেলসন

আশা করা যায় এটি আপনার ভাসমানদেরকে আরও হালকা ওজনে ঘুরে বেড়াবে - আমি পছন্দ করি যে আমরা কীভাবে JSON ক্লাসগুলিতে স্তন্যপান করতে পারি যা চুষতে পারে।
লিংকন বি

22

সত্যিই দুর্ভাগ্য যে dumpsআপনাকে ভাসমান কিছু করার অনুমতি দেয় না। যাইহোক loads। সুতরাং অতিরিক্ত সিপিইউ লোডটি যদি আপনি আপত্তি না করেন তবে আপনি এটি এনকোডার / ডিকোডার / এনকোডার দিয়ে ফেলে দিতে পারেন এবং সঠিক ফলাফল পেতে পারেন:

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

ধন্যবাদ, এটি সত্যিই সহায়ক পরামর্শ। আমি জানতাম না parse_floatকোয়ার্গের কথা!
বেনামে

এখানে সবচেয়ে সহজ পরামর্শ যা 3.6 তেও কাজ করে।
ব্রেন্ট ফাউস্ট

"অতিরিক্ত সিপিইউ বোঝা মনে করবেন না" বাক্যাংশটি নোট করুন। আপনার কাছে সিরিয়ালাইজ করার জন্য প্রচুর ডেটা থাকলে অবশ্যই এই সমাধানটি ব্যবহার করবেন না। আমার জন্য, এটি একা যুক্ত করার ফলে একটি ত্রুটিবিহীন গণনা করে একটি প্রোগ্রাম তৈরি করা 3X বেশি সময় নেয়।
শনিব

12

পাইথন 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]'

6
ঝরঝরে দেখায় তবে পাইথন ৩.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]'

আপনি যে দশমিক জায়গাগুলি চান সেটির জন্য আপনি কেবল এনকোডার.থ্রেড_লোকাল.ডেসিমাল_প্লেস সেট করতে পারেন এবং সেই থ্রেডে json.dumps () এর পরের কলটি সেই সংখ্যার দশমিক স্থান ব্যবহার করবে


2

গ্লোবাল json.encoder.FLOAT_REPR ওভাররাইড না করে যদি অজগর ২.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)

তারপরে, অজগর ২.7 এ:

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

অজগর ২.6-তে, ম্যাথু শিনকেল নীচে উল্লেখ করেছেন বলে এটি যথেষ্ট কাজ করে না:

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

4
এগুলি স্ট্রিংয়ের মতো দেখাচ্ছে, সংখ্যা নয়।
ম্যাথু শিনকেল

1

পেশাদাররা:

  • যে কোনও JSON এনকোডার, বা অজগরটির repr এর সাথে কাজ করে।
  • সংক্ষিপ্ত (ইশ) কাজ করে বলে মনে হচ্ছে।

কনস:

  • কুৎসিত 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

স্ট্যান্ডার্ড জসন মডিউলটি আমদানি করার সময়, এটি ডিফল্ট এনকোডার 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]'

কখনও কখনও জসন হিসাবে আউটপুট করতে খুব দরকারী, সেরা উপস্থাপনা পাইথন স্ট্র সঙ্গে অনুমান করতে পারেন। এটি নিশ্চিত করবে যে উল্লেখযোগ্য অঙ্কগুলি উপেক্ষা করা হয়নি।

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()ডাকা হচ্ছে সেখানে এটি কাজ করে , ঠিক যেমন স্ট্রাউডের তালিকাগুলি কেবল মুদ্রণ করার সময়ও। এছাড়াও, ডেটা তৈরির পরে নির্ভুলতা রানটাইম কনফিগারযোগ্য। ডাউনসাইড অবশ্যই আপনার ডেটাটিকে এই বিশেষ ভাসমান শ্রেণিতে রূপান্তরিত করা দরকার (কারণ দুর্ভাগ্যক্রমে আপনি বানর প্যাচ হিসাবে মনে করতে পারেন না 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]]'

এটি অন্তর্নির্মিত পাইথন 3 জেসন প্যাকেজটির সাথে কাজ করে না, যা __repr __ () ব্যবহার করে না।
আয়ান গোল্ডবি

0

এই সমস্যাটি সমাধানের জন্য আমি সম্প্রতি একটি ছোট পাইথন গ্রন্থাগার এফজেসন প্রকাশ করেছি । সাথে ইনস্টল করুন

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
}

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]'

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.