Каков был бы питонный способ предотвратить циклический цикл при написании JSON? - PullRequest
0 голосов
/ 25 февраля 2019

У меня есть два класса A и B, каждый из которых хранит ссылки на объекты другого класса в списках:

class A:
    def __init__(self,name):
        self.name = name
        self.my_Bs = []
    def registerB(self,b):
        self.my_Bs.append(b)

class B:
    def __init__(self,name):
        self.name = name
        self.my_As = []
    def registerA(self,a):
        self.my_As.append(a)

Теперь мое приложение создает два списка, один из объектовA, один из объектов B, имеющий перекрестные ссылки.

# a list of As, a list of Bs
list_of_As = [A('firstA'), A('secondA')]
list_of_Bs = [B('firstB'), B('secondB')]
# example of one cross-reference
list_of_As[0].registerB(list_of_Bs[1])
list_of_Bs[1].registerA(list_of_As[0])

Очевидно, что если я вызову json.dumps() для list_of_..., я получу ошибку циклической ссылки.

ЧтоЯ хочу сделать, чтобы обойти эту проблему, чтобы сбросить JSON со списком элементов name атрибутов вместо списков самих объектов :

# This is what I want to obtain for
# the JSON for list_of_As
[
    {'name' : 'firstA', 'my_Bs': ['secondB']},
    {'name' : 'secondA', 'my_Bs': []}
]

ЕдинственныйЯ могу думать о том, чтобы сохранить в каждом классе дополнительный список строк (соответственно my_Bs_names и my_As_names) и использовать JSONEncoder следующим образом:

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, 'A'):
            return { # filter out the list of B objects
                k: v for k, v in obj.__dict__.items() if k != 'my_Bs'
            }
        if isinstance(obj, 'B'):
            return { # filter out the list of A objects
                k: v for k, v in obj.__dict__.items() if k != 'my_As'
            }
        return super(MyEncoder, self).default(obj)

# Use the custom encoder to dump JSON for list_of_As
print json.dumps(list_of_As, cls=MyEncoder)

Если я не ошибаюсь,Я бы получил следующий результат:

# This is what I obtain for
# the JSON for list_of_As with the code above
[
    {'name' : 'firstA', 'my_Bs_names': ['secondB']},
    {'name' : 'secondA', 'my_Bs_names': []}
]

Есть ли более элегантный способ получить этот результат?Например, тот, который не требует никаких дополнительных списков строк?

Ответы [ 4 ]

0 голосов
/ 08 марта 2019

Как насчет этого?

  • Классы, такие как A и B, должны указывать только атрибут класса (_deep_fields), перечисляющий их атрибуты, которые могут привести к циклическим зависимостям (необходимо"мелкий" -сериализуется)
  • Они также должны наследоваться от ShallowSerializable, который просто игнорирует атрибуты в _deep_fields, если shallow равен True
  • Кодер кодирует все ключиобъект, но вызывает make_shallow для всех значений, чтобы убедиться, что shallow=True отправляется любым объектам, которые наследуются от ShallowSerializable
  • Решение является общим, в том смысле, что любой другой класс, который долженреализовать это поведение нужно только наследовать от ShallowSerializable и определить _deep_fields.
class ShallowSerializable(object):
     _deep_fields = set()
     def get_dict(self,  shallow=False):
         return {
             k: v
             for k, v in self.__dict__.items()
             if not shallow or k not in self._deep_fields
         }

class A(ShallowSerializable):
    _deep_fields = {'my_Bs'}

    def __init__(self,name):
        self.name = name
        self.my_Bs = []

     def registerB(self,b):
        self.my_Bs.append(b)

class B(ShallowSerializable):
    _deep_fields = {'my_As'}

    def __init__(self,name):
        self.name = name
        self.my_As = []

    def registerA(self,a):
        self.my_As.append(a)


class MyEncoder(json.JSONEncoder):
    def make_shallow(self, obj):
        if isinstance(obj, ShallowSerializable):
            return obj.get_dict(shallow=True)
        elif isinstance(obj, dict):
            return {k: self.make_shallow(v) for k, v in obj.items()}
        elif isinstance(obj, list):
            return [self.make_shallow(x) for x in obj]
        else:
            return obj

    def default(self, obj):
        return {
            k: self.make_shallow(v)
            for k, v in obj.__dict__.items()
        }

Использование:

list_of_As = [A('firstA'), A('secondA')]
list_of_Bs = [B('firstB'), B('secondB')]
# example of one cross-reference
list_of_As[0].registerB(list_of_Bs[1])
list_of_Bs[1].registerA(list_of_As[0])

json.dumps(list_of_As, cls=MyEncoder)
>>> '[{"my_Bs": [{"name": "secondB"}], "name": "firstA"}, {"my_Bs": [], "name": "secondA"}]'

json.dumps(list_of_Bs, cls=MyEncoder)
>>> '[{"my_As": [], "name": "firstB"}, {"my_As": [{"name": "firstA"}], "name": "secondB"}]'
0 голосов
/ 03 марта 2019

Наилучшим подходом является запись id() значений объектов, которые уже были видны при кодировании.id() значения уникальны для объектов с перекрывающимися временами жизни, и при кодировании вы можете рассчитывать на недолговечные объекты.Это работает для любого типа объекта и не требует, чтобы объекты были хэшируемыми.

Оба модуля copy и pickle используют эту технику вmemo словарь, который отображает значения id() в их объект для дальнейшего использования.

Вы можете использовать эту технику и здесь;вам действительно нужно сохранить набор идентификаторов, чтобы обнаружить, что вы можете вернуть атрибут .name.Использование набора делает тестирование на повторные ссылки быстрым и эффективным (тестирование членства занимает O (1) постоянное время, в отличие от списков, которые занимают O (N) линейное время):

class CircularEncoder(json.JSONEncoder):
    def __init__(self, *args, **kwargs):
        kwargs['check_circular'] = False  # no need to check anymore
        super(CircularEncoder, self).__init__(*args, **kwargs)
        self._memo = set()

    def default(self, obj):
        if isinstance(obj, (A, B)):
            d = id(obj)
            if d in self._memo:
                return obj.name
            self._memo.add(d)
            return vars(obj)
        return super(CircularEncoder, self).default(obj)

, затем используйте json.dumps() с этим классом:

json.dumps(list_of_As, cls=CircularEncoder)

Для вашего образца ввода это дает:

>>> print(json.dumps(list_of_As, cls=CircularEncoder, indent=2))
[
  {
    "name": "firstA",
    "my_Bs": [
      {
        "name": "secondB",
        "my_As": [
          "firstA"
        ]
      }
    ]
  },
  {
    "name": "secondA",
    "my_Bs": []
  }
]
0 голосов
/ 04 марта 2019

Вы можете сделать это, изменив, какое строковое представление объекта или, скажем, представление объекта python, созданное с помощью магического метода python, сколько библиотек, изменяющих свою консоль и строковое представление, заменили на использование гексов класса

Выполнить код здесь

import json
class A:
    def __init__(self,name):
        self.name = name
        self.my_Bs = []

    def registerB(self,b):
        self.my_Bs.append(b)

    def __str__(self):
        _storage = {
            "name" : self.name,
            "my_Bs": [obj.name for obj in self.my_Bs]
        }
        return json.dumps(_storage)

    __repr__ = __str__

class B:
    def __init__(self,name):
        self.name = name
        self.my_As = []

    def registerA(self,a):
        self.my_As.append(a)

    def __str__(self):
        _storage = {
            "name" : self.name,
            "my_Bs" : [obj.name for obj in self.my_As]
        }
        return json.dumps(_storage)

    __repr__ = __str__


# a list of As, a list of Bs
list_of_As = [A('firstA'), A('secondA')]
list_of_Bs = [B('firstB'), B('secondB')]
# example of one cross-reference
list_of_As[0].registerB(list_of_Bs[1])
list_of_Bs[1].registerA(list_of_As[0])
str(list_of_As) # will make it done without  more overhead

Теперь вы также можете оптимизировать свой код, потому что он просто меняет представление, которое он представлял, без дополнительного набора классов

0 голосов
/ 02 марта 2019

General JSONEncoder класс, который предотвращает ошибку циклической ссылки

Следующий класс кодировщика MyEncoder выполняет рекурсивное кодирование вложенных объектов до тех пор, пока не будет обнаружена циклическая ссылка, чей атрибут name возвращается вместосам объект.

import json
class MyEncoder(json.JSONEncoder):
    def __init__(self, *args, **argv):
        super().__init__(*args, **argv)
        self.proc_objs = []
    def default(self, obj):
        if isinstance(obj,(A,B)):
            if obj in self.proc_objs:
                return obj.name # short circle the object dumping
            self.proc_objs.append(obj)
            return obj.__dict__
        return obj

json.dumps(list_of_As, cls=MyEncoder, check_circular=False, indent=2)

Вывод:

[
  { "name": "firstA",
    "my_Bs": [
      { "name": "secondB",
        "my_As": [ "firstA" ]
      }
    ]
  },
  { "name": "secondA", "my_Bs": [] }
]

Использование пользовательского toJSON метода

Вы можете реализовать метод сериализатора в ваших классах.

class JSONable:
    def toJSON(self):
        d = dict()
        for k,v in self.__dict__.items():
            # save a list of "name"s of the objects in "my_As" or "my_Bs"
            d[k] = [o.name for o in v] if isinstance(v, list) else v
        return d

class A(JSONable):
    def __init__(self,name):
        self.name = name
        self.my_Bs = []
    def register(self,b):
        self.my_Bs.append(b)

class B(JSONable):
    def __init__(self,name):
        self.name = name
        self.my_As = []
    def register(self,a):
        self.my_As.append(a)

json.dumps(list_of_As, default=lambda x: x.toJSON(), indent=2)

Выход:

[
  { "name":  "firstA",  "my_Bs": [  "secondB" ] },
  { "name":  "secondA", "my_Bs": [] }
]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...