Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot use the dict() function to convert an easydict object to a regular dictionary #48

Open
WaterLoran opened this issue Apr 30, 2024 · 10 comments

Comments

@WaterLoran
Copy link

    good = {
        'node': {
            'type': 'activity',
            'id': 'nG6CFOqb4dQq',
            'diagramId': '4wwTVGk0mPhGtfDI',
            'renderKey': 'activity',
            'x': 180,
            'y': 200,
            'width': 100,
            'height': 60,
            'angle': 0,
            'attrs': {
                'body': {
                    'fill': 'none',
                    'stroke': 'none',
                    'refWidth': '100%',
                    'refHeight': '100%'
                },
                'fo': {
                    'refWidth': '100%',
                    'refHeight': '100%'
                },
                'label': {
                    'fontSize': 14,
                    'fill': '#333',
                    'refX': '50%',
                    'refY': '50%',
                    'textAnchor': 'middle',
                    'textVerticalAnchor': 'middle'
                },
                'shapeText': {
                    'verticalAlign': 'middle',
                    'textAlign': 'center',
                    'fill': '#000000',
                    'fontFamily': '宋体',
                    'fontSize': 12,
                    'lineHeight': 1.2,
                    'text': '活动',
                    'fontWeight': 'normal'
                },
                'shapeStyle': {
                    'strokeWidth': 1,
                    'fill': {
                        'startColor': '#89BCFF',
                        'endColor': '#C0DCFF',
                        'direction': 'to bottom'
                    },
                    'stroke': '#000000',
                    'strokeDasharray': '',
                    '3dEffect': False
                }
            },
            'extraProps': {},
            'ports': {
                'items': [{
                    'group': 'top',
                    'id': 'rrbr7WGHuRHq'
                }, {
                    'group': 'right',
                    'id': 'WCfPQPY3qVVs'
                }, {
                    'group': 'bottom',
                    'id': 'XxGbrm8p3ywp'
                }, {
                    'group': 'left',
                    'id': 'iVxkmHyURPG9'
                }],
                'groups': {
                    'top': {
                        'position': {
                            'name': 'top'
                        },
                        'zIndex': 10
                    },
                    'right': {
                        'position': {
                            'name': 'right'
                        },
                        'zIndex': 10
                    },
                    'bottom': {
                        'position': {
                            'name': 'bottom'
                        },
                        'zIndex': 10
                    },
                    'left': {
                        'position': {
                            'name': 'left'
                        },
                        'zIndex': 10
                    }
                }
            },
            'data': {
                'number': None,
                'desc': None,
                'category': None,
                'executionRole': {
                    'roleId': '2XfVV5q3IPik',
                    'roleText': '角色'
                },
                'keyActivity': None,
                'input': [{
                    'id': 3056,
                    'name': 'file_a.docx',
                    'nameEn': None,
                    'number': None,
                    'parentId': 3055,
                    'sortId': 0,
                    'type': 'STD_FILE',
                    'path': '3054-3055-3056-',
                    'namePath': '架构_batch_upload_files_process_001/流程图_batch_upload_files_process_001/file_a.docx',
                    'parentName': None,
                    'approvalStatus': None,
                    'isModified': None,
                    'hasChildren': None,
                    'isPublished': False,
                    'recordStatus': 0,
                    'level': 2,
                    'viewSort': '016100000000',
                    'isPublic': 0,
                    'extraProps': {
                        'relatedFileId': 3055
                    },
                    'processId': None,
                    'supportRiskDirId': None,
                    'institutionId': None,
                    'modifiedTime': None,
                    'supportFileDirId': 3055,
                    'deptId': None,
                    'pubTime': None
                }, {
                    'id': 3057,
                    'name': 'file_b.docx',
                    'nameEn': None,
                    'number': None,
                    'parentId': 3055,
                    'sortId': 1,
                    'type': 'STD_FILE',
                    'path': '3054-3055-3057-',
                    'namePath': '架构_batch_upload_files_process_001/流程图_batch_upload_files_process_001/file_b.docx',
                    'parentName': None,
                    'approvalStatus': None,
                    'isModified': None,
                    'hasChildren': None,
                    'isPublished': False,
                    'recordStatus': 0,
                    'level': 2,
                    'viewSort': '016100000001',
                    'isPublic': 0,
                    'extraProps': {
                        'relatedFileId': 3055
                    },
                    'processId': None,
                    'supportRiskDirId': None,
                    'institutionId': None,
                    'modifiedTime': None,
                    'supportFileDirId': 3055,
                    'deptId': None,
                    'pubTime': None
                }, {
                    'id': 3058,
                    'name': 'file_c.docx',
                    'nameEn': None,
                    'number': None,
                    'parentId': 3055,
                    'sortId': 2,
                    'type': 'STD_FILE',
                    'path': '3054-3055-3058-',
                    'namePath': '架构_batch_upload_files_process_001/流程图_batch_upload_files_process_001/file_c.docx',
                    'parentName': None,
                    'approvalStatus': None,
                    'isModified': None,
                    'hasChildren': None,
                    'isPublished': False,
                    'recordStatus': 0,
                    'level': 2,
                    'viewSort': '016100000002',
                    'isPublic': 0,
                    'extraProps': {
                        'relatedFileId': 3055
                    },
                    'processId': None,
                    'supportRiskDirId': None,
                    'institutionId': None,
                    'modifiedTime': None,
                    'supportFileDirId': 3055,
                    'deptId': None,
                    'pubTime': None
                }],
                'output': None,
                'sopList': [],
                'controlPoints': [],
                'standards': [],
                'indicators': [],
                'itSystems': [],
                'processingTimeLimit': None
            },
            'zIndex': 5
        }
    }
    print("kwargs: " + json.dumps(good))
    from easydict import EasyDict
    a = EasyDict(good)
    a = dict(a)
    print("kwargs: " + json.dumps(a))

#############################################
I think it will be a problem that needs to be solved because it is not possible to revert back to a regular dict, which may result in certain scenarios where other third-party libraries cannot be used. Because of the code design, sometimes I may need to first convert it to a regular dictionary, process it, and then revert back

@WaterLoran
Copy link
Author

If we use this easydict related data to send the function to the send function of the request function, it may also cause problems

@WaterLoran
Copy link
Author

Alternatively, I would expect to provide a method for this class, such as XX. change_to_dict(), and then help me switch back to a regular dictionary

@WaterLoran
Copy link
Author

image
大概是处理不了这种列表类型的

@WaterLoran
Copy link
Author

I can't help it, I was too eager to use it, so I wrote the method for rebuilding the dictionary myself.

Can you also let me join this project so that I can provide the relevant code

The code is as follows

def rebuild_dict(self):
    def iter_node(data):
        if isinstance(data, self.__class__):
            new_dict = {}
            for key, value in data.__dict__.items():
                if key == "rebuild_dict":
                    pass  # 类内置函数, 不做递归处理
                else:
                    if isinstance(value, self.__class__):
                        new_dict[key] = iter_node(value)
                    elif isinstance(value, dict):
                        new_dict[key] = iter_node(value)
                    elif isinstance(value, list):  # 列表里面为列表的情况:
                        new_dict[key] = iter_node(value)
                    else:
                        new_dict[key] = value
            return new_dict
        elif isinstance(data, dict):
            new_dict = {}
            for key, value in data.items():
                if key == "rebuild_dict":
                    pass
                else:
                    if isinstance(value, self.__class__):
                        new_dict[key] = iter_node(value)
                    elif isinstance(value, dict):  # 列表里面为字典的情况
                        new_dict[key] = iter_node(value)
                    elif isinstance(value, list):  # 列表里面为列表的情况:
                        new_dict[key] = iter_node(value)
                    else:
                        new_dict[key] = value
            return new_dict
        elif isinstance(data, list):
            new_list = []
            for item in data:
                if isinstance(item, self.__class__):
                    new_list.append(iter_node(item))
                elif isinstance(item, dict):  # 列表里面为字典的情况
                    new_list.append(iter_node(item))
                elif isinstance(item, list):  # 列表里面为列表的情况:
                    new_list.append(iter_node(item))
                else:
                    new_list.append(item)
            return new_list
        else:
            raise
    return iter_node(self)

@WaterLoran
Copy link
Author

class EasyDict(dict):
"""
Get attributes

>>> d = EasyDict({'foo':3})
>>> d['foo']
3
>>> d.foo
3
>>> d.bar
Traceback (most recent call last):
...
AttributeError: 'EasyDict' object has no attribute 'bar'

Works recursively

>>> d = EasyDict({'foo':3, 'bar':{'x':1, 'y':2}})
>>> isinstance(d.bar, dict)
True
>>> d.bar.x
1

Bullet-proof

>>> EasyDict({})
{}
>>> EasyDict(d={})
{}
>>> EasyDict(None)
{}
>>> d = {'a': 1}
>>> EasyDict(**d)
{'a': 1}
>>> EasyDict((('a', 1), ('b', 2)))
{'a': 1, 'b': 2}

Set attributes

>>> d = EasyDict()
>>> d.foo = 3
>>> d.foo
3
>>> d.bar = {'prop': 'value'}
>>> d.bar.prop
'value'
>>> d
{'foo': 3, 'bar': {'prop': 'value'}}
>>> d.bar.prop = 'newer'
>>> d.bar.prop
'newer'


Values extraction

>>> d = EasyDict({'foo':0, 'bar':[{'x':1, 'y':2}, {'x':3, 'y':4}]})
>>> isinstance(d.bar, list)
True
>>> from operator import attrgetter
>>> list(map(attrgetter('x'), d.bar))
[1, 3]
>>> list(map(attrgetter('y'), d.bar))
[2, 4]
>>> d = EasyDict()
>>> list(d.keys())
[]
>>> d = EasyDict(foo=3, bar=dict(x=1, y=2))
>>> d.foo
3
>>> d.bar.x
1

Still like a dict though

>>> o = EasyDict({'clean':True})
>>> list(o.items())
[('clean', True)]

And like a class

>>> class Flower(EasyDict):
...     power = 1
...
>>> f = Flower()
>>> f.power
1
>>> f = Flower({'height': 12})
>>> f.height
12
>>> f['power']
1
>>> sorted(f.keys())
['height', 'power']

update and pop items
>>> d = EasyDict(a=1, b='2')
>>> e = EasyDict(c=3.0, a=9.0)
>>> d.update(e)
>>> d.c
3.0
>>> d['c']
3.0
>>> d.get('c')
3.0
>>> d.update(a=4, b=4)
>>> d.b
4
>>> d.pop('a')
4
>>> d.a
Traceback (most recent call last):
...
AttributeError: 'EasyDict' object has no attribute 'a'
"""

def __init__(self, d=None, **kwargs):
    if d is None:
        d = {}
    else:
        d = dict(d)
    if kwargs:
        d.update(**kwargs)
    for k, v in d.items():
        setattr(self, k, v)
    # Class attributes
    for k in self.__class__.__dict__.keys():
        if not (k.startswith('__') and k.endswith('__')) and not k in ('update', 'pop', 'rebuild_dict'):
            setattr(self, k, getattr(self, k))

def __setattr__(self, name, value):
    if isinstance(value, (list, tuple)):
        value = [self.__class__(x)
                 if isinstance(x, dict) else x for x in value]
    elif isinstance(value, dict) and not isinstance(value, self.__class__):
        value = self.__class__(value)
    super(EasyDict, self).__setattr__(name, value)
    super(EasyDict, self).__setitem__(name, value)

__setitem__ = __setattr__

def update(self, e=None, **f):
    d = e or dict()
    d.update(f)
    for k in d:
        setattr(self, k, d[k])

def pop(self, k, d=None):
    delattr(self, k)
    return super(EasyDict, self).pop(k, d)

def rebuild_dict(self):
    def iter_node(data):
        if isinstance(data, self.__class__):
            new_dict = {}
            for key, value in data.__dict__.items():
                if isinstance(value, self.__class__):
                    new_dict[key] = iter_node(value)
                elif isinstance(value, dict):
                    new_dict[key] = iter_node(value)
                elif isinstance(value, list):  # 列表里面为列表的情况:
                    new_dict[key] = iter_node(value)
                else:
                    new_dict[key] = value
            return new_dict
        elif isinstance(data, dict):
            new_dict = {}
            for key, value in data.items():
                if isinstance(value, self.__class__):
                    new_dict[key] = iter_node(value)
                elif isinstance(value, dict):  # 列表里面为字典的情况
                    new_dict[key] = iter_node(value)
                elif isinstance(value, list):  # 列表里面为列表的情况:
                    new_dict[key] = iter_node(value)
                else:
                    new_dict[key] = value
            return new_dict
        elif isinstance(data, list):
            new_list = []
            for item in data:
                if isinstance(item, self.__class__):
                    new_list.append(iter_node(item))
                elif isinstance(item, dict):  # 列表里面为字典的情况
                    new_list.append(iter_node(item))
                elif isinstance(item, list):  # 列表里面为列表的情况:
                    new_list.append(iter_node(item))
                else:
                    new_list.append(item)
            return new_list
        else:
            raise

    return iter_node(self)

if name == "main":
import doctest

doctest.testmod()

@finemap
Copy link

finemap commented May 26, 2024

You can use deepcopy to recursively revert EasyDict to built-in dict

deepcopy is able to use dict to rebuild EasyDict instance recursively no matter where and how deep it exists in the instance to be copied, including in tuples, lists, dicts, and of course in EasyDicts.

first override __reduce__ method to tell deepcopy to use dict to build a deepcopy.

def __reduce__(self):
    return dict, (self.__dict__,)

and you can now

ezdict = EasyDict(items=[EasyDict(items='something')])

from copy import deepcopy
ezdict = deepcopy(ezdict)

and get a copied instance with every EasyDict node reverted to dict.

In your implementation, you define a new instance method rebuild_dict(), this will lead to side effect that a key named rebuild_dict is to be added in __dict__ of EasyDIct instances and contaminate the original content in EasyDict instances and may cause infinite recursion when binding this method to instance.

To avoid such side effect, you may handle it in a magic method way

def __reduce__(self):
    return dict, (self.__dict__,)

def __asdict__(self, recurse=True):
    if recurse:
        from copy import deepcopy
        return deepcopy(self.__dict__)
    else:
        return dict(self.__dict__)

and use it like:

dct = ezdict.__asdict__()

for type reversion.

Note: overriding __reduce__ will change pickle behavior.

If pickle compatible feature should be kept, an implementation could be:

def __asdict__(self, recurse=True):
    if recurse:
        _r = self.__class__.__reduce__
        self.__class__.__reduce__ = lambda obj: (dict, (obj.__dict__,),)
        from copy import deepcopy
        _c = deepcopy(self.__dict__)
        self.__class__.__reduce__ = _r
        return _c
    else:
        return dict(self.__dict__)

@WaterLoran
Copy link
Author

It looks very good code. Thank you very much for your patient guidance. I will read this code a few more times

@WaterLoran
Copy link
Author

I feel very happy being answered on GitHub. Thank you

@WaterLoran
Copy link
Author

I just discovered a problem, which is that if the value of a key in this data object is an io_buffer, an error will occur,
The error is as follows:
err:cannot pickle '_io.BufferedReader' object,uri:csvc-file/files/chunk-upload
@finemap

@WaterLoran
Copy link
Author

Emmm, the solution is probably to make a targeted judgment. You can't use dict directly. Let me see how to change it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants