如何清除具有特定键的动态 Python 字典?

5

这个问题有些复杂,没有典型的答案浮出水面。

我正在开发的工具的性质决定我们使用MongoDB来存储大约25个不同工具的“设置”。每个工具都有自己的设置模式,因此每个文档都不同,但它们都存储在同一个集合中,并在由json模式绘制的相同编辑页面上进行编辑。

不知道字典的模式,我很难弄清楚如何迭代和清理数据,特别是删除密码。

鉴于以下字典,并知道其他字典可能具有不同的模式,如何遍历字典中的每个项并创建一个副本,与任何key ==“password”的项不同?

所以:

{
  "_enabled": true,
  "instances": [
    {
      "isdefault": true,
      "name": "dev",
      "password": "abc123",
      "url": "http://dev.example.com",
      "user": "buffy"
    },
    {
      "isdefault": false,
      "name": "prod",
      "password": "xxxxx",
      "url": "http://prod.example.com",
      "user": "spike"
    },
    {
      "isdefault": false,
      "name": "qa",
      "password": "dasddf",
      "url": "http://prod.example.com",
      "user": "willow"
    }
  ],
  "label": "MyServers"
}

应该得到以下结果:

{
  "_enabled": true,
  "instances": [
    {
      "isdefault": true,
      "name": "dev",
      "url": "http://dev.example.com",
      "user": "buffy"
    },
    {
      "isdefault": false,
      "name": "prod",
      "url": "http://prod.example.com",
      "user": "spike"
    },
    {
      "isdefault": false,
      "name": "qa",
      "url": "http://prod.example.com",
      "user": "willow"
    }
  ],
  "label": "MyServers"
}

3
你卡在哪里了?你尝试了什么?看起来你只需要循环遍历实例中的字典并删除密码键/值对... - aschmid00
等一下,这是Python字典吗?它看起来像JSON或其他什么东西... - Zizouz212
是的,对我来说它看起来像JSON。 - aschmid00
1
这很令人困惑,因为你在谈论动态模式。如果每个字典的结构都如此不同,以至于你永远不知道会遇到什么,无论我们讨论什么方法或语言,你都将无法开发算法。然而,我怀疑aschmid有答案:对于原始字典中的每个子字典,如果它包含'password',则删除'subdict['password']'。 - Two-Bit Alchemist
4个回答

5

首先深拷贝字典,然后捕获所有的字典并移除密码键:

from copy import deepcopy

def remove_pass(v):
    if isinstance(v, dict):
        if "password" in v:
            del v["password"]
        for ele in v.values():
            remove_pass(ele)
    elif isinstance(v, Iterable) and not isinstance(v, basestring):
        for ele in v:
            remove_pass(ele)


from pprint import pprint as pp
d = deepcopy(d)
for v in d.values():
   remove_pass(v)

输入:

{'_enabled': 'true',
 'foo': {'isdefault': 'false',
         'name': 'qa',
         'nested': {'password': 'nested'},
         'password': 'dasddf',
         'url': 'http://prod.example.com',
         'user': 'willow'},
 'instances': [{'isdefault': 'true',
                'name': 'dev',
                'password': 'abc123',
                'url': 'http://dev.example.com',
                'user': 'buffy'},
               {'isdefault': 'false',
                'name': 'prod',
                nested': {'more_nesting': {'even_more_nesting': ({'password': 'foobar'},
                                       {'password': 'foob'}),
                                        'password': 'bar'},
                'password': 'xxxxx',
                'url': 'http://prod.example.com',
                'user': 'spike'},
               {'isdefault': 'false',
                'name': 'qa',
                'password': 'dasddf',
                'url': 'http://prod.example.com',
                'user': 'willow'}],
 'label': 'MyServers'}

输出:

{'_enabled': 'true',
 'foo': {'isdefault': 'false',
         'name': 'qa',
         'nested': {},
         'url': 'http://prod.example.com',
         'user': 'willow'},
 'instances': [{'isdefault': 'true',
                'name': 'dev',
                'url': 'http://dev.example.com',
                'user': 'buffy'},
               {'isdefault': 'false',
                'name': 'prod',
                'nested': {'more_nesting': {'even_more_nesting': ({}, {})}},
                'url': 'http://prod.example.com',
                'user': 'spike'},
               {'isdefault': 'false',
                'name': 'qa',
                'url': 'http://prod.example.com',
                'user': 'willow'}],
 'label': 'MyServers'}

比起查看字典中是否存在'password'并删除该键/值对,使用d.pop('password', None)更加简单。 - bgporter
@bgporter,查找是0(1),不是真正的问题。 - Padraic Cunningham
不考虑执行时间,只注重代码的清晰易懂。这可能只是个人口味吧。 - bgporter
@bgporter,在这种情况下,我更担心获取具有灵活性和可用性的代码,而不是其他任何事情。 - Padraic Cunningham
1
感谢您的额外努力,并展示了更多嵌套的工作。我稍微修改了一下,只是将密码字段的值设置为"",而不是删除它(所以弹出窗口也无法工作),但这正是我所需要的,而且非常容易,让我感到有些惭愧。(有时我们倾向于过度复杂化事情!)谢谢! - hikaru

1
假设您只想检查作为列表或字典的容器,并从具有键值对中的字典条目中删除 key = "password"
#first copy the structure
new_data = copy.deepcopy(data)

#this is a recursive function. 
#Heavily nested structures may fail due to recursion limit
def clean_hierarchy(ele):
    #lists may contain dictionaries, so clean theses entries
    if isinstance(ele,list):
        for val in ele:
            clean_hierarchy(val)
    if isinstance(ele,dict):
        #remove possible password entry
        if "password" in ele:
            ele.pop("password",None)
        #dictionary may contain more dictionaries. Rinse and repeat!
        for val in ele.values():
            clean_hierarchy(val)

clean_hierarchy(new_data)

这篇回答比已接受的回答更清晰,因为它没有使用Iterable和basestring,我喜欢它。 - c8999c 3f964f64

0
如果您知道每个数据的结构(即在数组/字典的哪个深度上可以找到“密码”键),那么这将很简单。您只需要循环遍历列表项和字典以查找“密码”键。
如果每个设置字典的结构真的是不可预测的,那么您将不得不拼凑出一个解决方案。在这种情况下,我所做的是将我的JSON转储为字符串,使用正则表达式来删除/隔离我感兴趣的数据,然后将字符串加载回结构化的JSON。
类似于这样:
import json,re
raw_data = """{
  "_enabled": true,
  "instances": [
    {
      "isdefault": true,
      "name": "dev",
      "password": "abc123",
      "url": "http://dev.example.com",
      "user": "buffy"
    },
    {
      "isdefault": false,
      "name": "prod",
      "password": "xxxxx",
      "url": "http://prod.example.com",
      "user": "spike"
    },
    {
      "isdefault": false,
      "name": "qa",
      "password": "dasddf",
      "url": "http://prod.example.com",
      "user": "willow"
    }
  ],
  "label": "MyServers"
}"""

# I load and then dump my raw_data just to iron out any inconsistencies 
# in formatting before applying regex. i.e., inconsistent use of " instead of '
structured_data = json.loads(raw_data)
dumped_data = json.dumps(structured_data)

scrubbed = re.sub(r'"password": ".*?",', '', dumped_data)
structured_scrubbed = json.loads(scrubbed)

结果为:

structured_scrubbed = {'_enabled': True,
 'instances': [{'isdefault': True,
                'name': 'dev',
                'url': 'http://dev.example.com',
                'user': 'buffy'},
               {'isdefault': False,
                'name': 'prod',
                'url': 'http://prod.example.com',
                'user': 'spike'},
               {'isdefault': False,
                'name': 'qa',
                'url': 'http://prod.example.com',
                'user': 'willow'}],
 'label': 'MyServers'}

0
我使用一个通用函数getPaths来查找嵌套字典中特定键的路径。您可以使用它来查找到所有指向"password"键的路径,然后进行更改或删除。这适用于JSON字符串的所有格式/模式。
def getPaths(dictionary, searchKey):
    '''
    generator to get all paths for the key in the nested dictionary

    '''
    for k, v in dictionary.items():
        if k == searchKey :
            yield []
        elif isinstance(v, dict):
            # if the value if dict, go in recursively and yield the path
            for subkey in getPaths(v, searchKey):
                yield [k]+subkey
        elif isinstance(v, list):
            # if value is a list, for each element in the list, go in recursively and yield the path
            for i, item in enumerate(v):
                if isinstance(item, dict):
                    for subkey in getPaths(item, searchKey):
                        yield [k]+[i]+subkey


jsonstring = """{
  "_enabled": true,
  "instances": [
    {
      "isdefault": true,
      "name": "dev",
      "password": "abc123",
      "url": "http://dev.example.com",
      "user": "buffy"
    },
    {
      "isdefault": false,
      "name": "prod",
      "password": "xxxxx",
      "url": "http://prod.example.com",
      "user": "spike"
    },
    {
        "instance2": {
                      "isdefault": false,
                      "name": "qa",
                      "password": "dasddf",
                      "url": "http://prod.example.com",
                      "user": "willow"
                      }
    }
  ],
  "label": "MyServers"
}"""
import json
jsonObj = json.loads(jsonstring)


paths = getPaths(jsonObj , "password")
for path in paths:
    print('path:', path)

结果:

>>> path: ['instances', 0]
>>> path: ['instances', 1]
>>> path: ['instances', 2, 'instance2']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接