如何从 Python 中的字典列表中获取所有一个键?

How do I get all of one key from a list of dicts in Python?

我有一个很大的 JSON 文件,如下所示:

{
  "data" : [
    {"album": "I Look to You", "writer": "Leon Russell", "artist": "Whitney Houston", "year": "2009", "title": "\"A Song for You\""},
    {"album": "Michael Zager Band", "writer": "Michael Zager", "artist": "Whitney Houston", "year": "1983", "title": "\"Life's a Party\""},
    {"album": "Paul Jabara & Friends", "writer": "Paul Jabara", "artist": "Whitney Houston", "year": "1978", "title": "\"Eternal Love\""},
    ...

...我正在尝试制作一个非常简单的 API 来获取不同的值。现在我可以相当容易地获得 localhost/data/1/title 例如获得第一个标题值,但我想通过 localhost/titles 或其他方式获得 all 的标题。我将如何修改此处的 do_GET 方法以添加此类功能?

def do_GET(self):
    self.send_response(200)
    self.send_header('Content-type', 'application/json')
    self.end_headers()

    path = self.path[1:]
    components = string.split(path, '/')

    node = content
    for component in components:
        if len(component) == 0 or component == "favicon.ico":
            continue

        if type(node) == dict:
            node = node[component]

        elif type(node) == list:
            node = node[int(component)]

    self.wfile.write(json.dumps(node))

    return

这是一个非常模糊的想法,但我希望这个概念能被理解。

在此示例中,如果 url 不以 'data' 开头,它将使用 url 中指定的组件映射数据集合(如 'titles' ).

def do_GET(self):
    self.send_response(200)
    self.send_header('Content-type', 'application/json')
    self.end_headers()

    path = self.path[1:]
    components = string.split(path, '/')

    if components and components[0] != 'data':
        node = map(lambda x: x.get(components[0]), content)
    else:
        node = content
        for component in components:
            if len(component) == 0 or component == "favicon.ico":
                continue

            if type(node) == dict:
                node = node[component]

            elif type(node) == list:
                node = node[int(component)]

    self.wfile.write(json.dumps(node))

    return

这里的答案将遵循您当前的动态 URL 模式,而不会发生重大架构更改或要求。

在这里,我使用 "all" 替换 url 模式中的给定数字索引,因为我觉得这更好地代表了 data/[item(s)]/[attribute][=17= 的范例]

以下是一些 URLs 和示例输出:

  1. /data/1/album => "Michael Zager Band"
  2. /data/0/title => "A Song for You"
  3. /data/all/title => ["A Song for You", "Life's a Party", "Eternal Love"]
  4. /data/all/year => ["2009", "1983", "1978"]
  5. /data/1 => {"album": "Michael Zager Band", "title": "Life's a Party", "writer": "Michael Zager", "year": "1983", "artist": "Whitney Houston"}

PS - 我稍微改变了架构,使用递归,我认为这更好地遵循你想要做的事情。

def do_GET(self):
    self.send_response(200)
    self.send_header('Content-type', 'application/json')
    self.end_headers()

    path = self.path[1:]
    components = string.split(path, '/')

    node = parse_node(content, components)

    self.wfile.write(json.dumps(node))

    return

def parse_node(node, components):
    # For a valid node and component list:
    if node and len(components) and components[0] != "favicon.ico":
        # Dicts will return parse_node of the top-level node component found, 
        # reducing the component list by 1
        if type(node) == dict:
            return parse_node(node.get(components[0], None), components[1:])

        elif type(node) == list:
            # A list with an "all" argument will return a full list of sub-nodes matching the rest of the URL criteria
            if components[0] == "all":
                return [parse_node(n, components[1:]) for n in node]
            # A normal list node request will work as it did previously
            else:
                return parse_node(node[int(components[0])], components[1:])
    else:
        return node

    # Handle bad URL
    return None

我认为您 运行 遇到了麻烦,因为您正试图遍历路径组件以确定要做什么。这是一个有点复杂的解决问题的方法。

我会首先定义您希望 API 支持的 "routes" 或 "actions",然后编写代码来处理每个。这就是大多数网络框架的运作方式(例如 django's URL patterns or flask's routes)。在您的代码中使用相同的模式非常简单。

因此,根据您的描述,您似乎想要两条路线:

/data/{id}/{attr} - look up the value of `attr` for the given `id`
/{attr} - search all items for `attr`

我还将简化 "title" 与 "titles" 的对比,并且只使用单数形式,因为复数形式可能会带来更多麻烦,而不是它的价值。但如果你真的想这样做,有些图书馆可以提供帮助(例如 this one)。

一旦我们决定 URL 将遵循这两种模式,就很容易检查组件是否与它们匹配。请注意,我在这里简化了您的代码以获取它 运行 因为我不确定 do_GET 是如何调用的,或者 self 是什么:

import json

JSON = {
    "data" : [
        {"album": "I Look to You", "writer": "Leon Russell", "artist": "Whitney Houston", "year": "2009", "title": "\"A Song for You\""},
        {"album": "Michael Zager Band", "writer": "Michael Zager", "artist": "Whitney Houston", "year": "1983", "title": "\"Life's a Party\""},
        {"album": "Paul Jabara & Friends", "writer": "Paul Jabara", "artist": "Whitney Houston", "year": "1978", "title": "\"Eternal Love\""},
    ]
}

def do_GET(path):
    path = path[1:]
    components = path.split('/')

    if components[0] == 'favicon.ico':
        return "favicon response"
    elif len(components) == 0 or not path:
        return "error response"
    elif len(components) == 3 and components[0] == "data":
        #/data/{id}/{attr} - look up the value of `attr` for the given `id`
        key, item_id, attr = components
        item_id = int(item_id)
        return json.dumps(JSON[key][item_id][attr])
    elif len(components) == 1:
        #/{attr} - search all items for `attr`
        attr = components[0]
        out = []
        for k in JSON:
            for d in JSON[k]:
                if attr in d:
                    out.append(d[attr])
        return json.dumps(out)
    else:
        return "unknown response"

    return json.dumps(node)

if __name__ == "__main__":
    urls = [
        "/data/1/title",
        "/title",
        "/some_missing_attr",
        "/favicon.ico",
        "/",
    ]
    for u in urls:
        print u, "->", do_GET(u)

输出:

/data/1/title -> "\"Life's a Party\""
/title -> ["\"A Song for You\"", "\"Life's a Party\"", "\"Eternal Love\""]
/some_missing_attr -> []
/favicon.ico -> favicon response
/ -> error response

除非您真的想在任意 JSON 中进行任意嵌套查找,否则这应该会很好地工作。如果是这种情况,那么我认为您建议的 URL 不会起作用,您怎么知道“/titles”应该搜索所有元素而“/data”会查找一个元素?如果您真的想这样做,我会在 google 中搜索 "JSON query language",看看您可以重复使用哪些项目或从中获得灵感。