我试着将martineau solution in this post应用到一个稍微不同的情况下,但似乎由于某些模糊的原因(至少对我来说),自定义编码器并没有从json. dump()方法调用.

from collections.abc import MutableMapping
import json
import numpy as np

class JSONSerializer(json.JSONEncoder):
    def encode(self, obj):
        # Convert dictionary keys that are tuples into strings.
        if isinstance(obj, MutableMapping):
            for key in list(obj.keys()):
                if isinstance(key, tuple):
                    strkey = "%d:%d" % key
                    obj[strkey] = obj.pop(key)

        return super().encode(obj)

class Agent(object):
    def __init__(self, states, alpha=0.15, random_factor=0.2):
        self.state_history = [((0, 0), 0)] # state, reward
        self.alpha = alpha
        self.random_factor = random_factor
        
        # start the rewards table
        self.G = {}
        self.init_reward(states)


    def init_reward(self, states):
        for i, row in enumerate(states):
            for j, col in enumerate(row):
                self.G[(j,i)] = np.random.uniform(high=1.0, low=0.1)
    
    def memorize(self):
        with open("memory.json", "w") as w:
            json.dump(self.G, w, cls=JSONSerializer)

if __name__ == "__main__":
    robot = Agent(states=np.zeros((6, 6)), alpha=0.1, random_factor=0.25)
    print(robot.G)
    robot.memorize()

当测试字符串编码时,它似乎返回了我所期望的,但是当调用json.dump时,定制编码器似乎根本没有被调用.你知道为什么吗?

谢谢

推荐答案

这可能应该被认为是json包的实现细节,也可能是Python版本的东西.在任何情况下,它对您的实施至关重要:

  • dump()函数在内部调用编码器上的iterencode()方法(参见第169和176行in the actual source code).
  • 然而,dumps()函数在内部调用encode()(参见第231和238行).

您可以通过调整encode()并覆盖JSONSerializer中的iterencode()来验证这一点,如下所示:

class JSONSerializer(json.JSONEncoder):
    def encode(self, obj):
        print("encode called")
        ...  # Your previous code here
        return super().encode(obj)

    def iterencode(self, *args, **kwargs):
        print("iterencode called")
        return super().iterencode(*args, **kwargs)

...你会看到只有"iterencode called"会与你的测试代码一起打印,而不是"encode called".

你在问题中链接的另一个堆栈溢出问题似乎也有同样的问题,至少在使用一个相当新版本的Python时(我目前正在编写这篇文章)——参见我的comment相应答案.

我有两个解决方案:

  1. Either在你的Agent.memorize()方法中使用dumps(),例如:
    def memorize(self):
        with open("memory.json", "w") as w:
            w.write(json.dumps(self.G, cls=JSONSerializer))
    
  2. Or将您自己的实现从encode()移动到iterencode(),例如:
    class JSONSerializer(json.JSONEncoder):
    
        def iterencode(self, obj, *args, **kwargs):
            if isinstance(obj, MutableMapping):
                for key in list(obj.keys()):
                    if isinstance(key, tuple):
                        strkey = "%d:%d" % key
                        obj[strkey] = obj.pop(key)
            yield from super().iterencode(obj, *args, **kwargs)
    
    这个第二种解决方案似乎有一个好处,即它可以与dump()dumps()一起工作(见下面的注释).

一个补充说明:后来的dumps()函数似乎也会导致调用iterencode()(我没有跟踪源代码,以了解这到底发生在哪里,但从我添加的打印机来看,它肯定会发生).这有以下效果:(1)在第一个建议的解决方案中,由于首先调用encode(),我们可以进行所有的调整以使我们的数据可在那里串行化,在稍后的时间点,调用iterencode()将不会导致错误.(2)在第二个建议的解决方案中,当我们重新实现iterencode()时,我们的数据将在这一点上被设置为JSON可串行化.

Update:实际上还有第三种解决方案,感谢@ AbdulAzizBarkat的 comments :我们可以覆盖default()方法.然而,我们必须确保我们交出一个对象类型来进行序列化,而不是常规编码器处理,否则我们将永远无法使用它到达default()方法.在给定的代码中,我们可以将Agent实例本身传递给dump()dumps(),而不是它的字典字段G.所以我们要做两个调整:

  1. memorize()调整为self,而不是self.G,例如:
    def memorize(self):
         with open("memory.json", "w") as w:
             json.dump(self, w, cls=JSONSerializer)
    
  2. 调整JSONSerializer.default()以处理Agent实例,例如,如下所示(我们不再需要encode()iterencode()):
    class JSONSerializer(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, Agent):
                new_obj = {}
                for key in obj.G.keys():
                    new_key = ("%d:%d" % key) if isinstance(key, tuple) else key
                    new_obj[new_key] = obj.G[key]
                return new_obj  # Return the adjusted dictionary
            return super().default(obj)
    

最后一点:在前两个解决方案中,原始词典的键被更改了.你可能不希望有这个,因为实际上你的实例不应该仅仅因为你转储了它的副本而改变.所以你可能更希望用改变后的键和原始值创建一个新的字典.我在第三个解决方案中解决了这个问题.

Python相关问答推荐

使用LineConnection动画1D数据

我从带有langchain的mongoDB中的vector serch获得一个空数组

DataFrame groupby函数从列返回数组而不是值

点到面的Y距离

将整组数组拆分为最小值与最大值之和的子数组

删除最后一个pip安装的包

非常奇怪:tzLocal.get_Localzone()基于python3别名的不同输出?

如何使用LangChain和AzureOpenAI在Python中解决AttribeHelp和BadPressMessage错误?

大小为M的第N位_计数(或人口计数)的公式

LocaleError:模块keras._' tf_keras. keras没有属性__internal_'''

干燥化与列姆化的比较

循环浏览每个客户记录,以获取他们来自的第一个/最后一个渠道

Cython无法识别Numpy类型

查看pandas字符列是否在字符串列中

如何使用Azure Function将xlsb转换为xlsx?

如何使用pytest在traceback中找到特定的异常

SpaCy:Regex模式在基于规则的匹配器中不起作用

时长超过24小时如何从Excel导入时长数据

通过对列的其余部分进行采样,在Polars DataFrame中填充_null`?

try 使用RegEx解析由标识多行文本数据的3行头组成的日志(log)文件