【问题标题】:Simple tool/library to visualize huge python dict简单的工具/库来可视化巨大的 python dict
【发布时间】:2013-02-22 11:38:41
【问题描述】:

我有一个像这样的巨大 dict 结构:

my_data = {
    'key1': {
        '_': 'value1': 'aaa'
    },
    'key2': {
        '_': 'value2': 'bbb',
        'key2.1': {
            '_': 'ccc',
            'key2.1.1': {
                '_': 'ddd'
            }
        }
        'key2.2': {
            '_': 'eee',
            'key2.2.1': {
                '_': 'fff'
            }
            'key2.2.2': {
                '_': 'ggg'
            }               
        }
    }
}

等等。

我想以一种树形表示形式向用户显示它,使用 GTK、TK 或任何能够浏览它的东西来折叠和展开分支,并可能搜索键和值。

可能我不需要手动开发这样的工具,并且已经有一些东西可以开箱即用地可视化这种数据?

【问题讨论】:

  • 如果您的 dict 只包含简单类型,将其编码为 JSON 并找到 JSON 查看器可能会更容易。
  • @PavelAnossov 同意。这是一种可能的解决方法。

标签: python user-interface dictionary gtk tkinter


【解决方案1】:

我不知道现成的工具,但您可以使用Traits UI 快速开发自己的工具

from enthought.traits.api \
    import HasTraits, Instance

from enthought.traits.ui.api \
    import View, VGroup, Item, ValueEditor

class DictEditor(HasTraits):
    Object = Instance( object )

    def __init__(self, obj, **traits):
        super(DictEditor, self).__init__(**traits)
        self.Object = obj

    def trait_view(self, name=None, view_elements=None):
        return View(
          VGroup(
            Item( 'Object',
                  label      = 'Debug',
                  id         = 'debug',
                  editor     = ValueEditor(),
                  style      = 'custom',
                  dock       = 'horizontal',
                  show_label = False
            ),
          ),
          title     = 'Dictionary Editor',
          width     = 800,
          height    = 600,
          resizable = True,
        )


def build_sample_data():
    my_data = dict(zip(range(10),range(10,20)))
    my_data[11] = dict(zip(range(10),range(10,20)))
    my_data[11][11] = dict(zip(range(10),range(10,20)))
    return my_data

# Test
if __name__ == '__main__':
    my_data = build_sample_data()
    b = DictEditor(my_data)
    b.configure_traits()

就是这样。你将有一个类似的 GUI:

Traits UI 使用模型-视图-控制器方法来创建 GUI,而无需以编程方式创建每个小部件。在这里,我使用预定义的 ValueEditor 来显示任意类型。您现在可以扩展它以支持搜索、过滤等...

编辑

支持过滤的简单扩展:

# -*- coding: utf-8 -*-
"""
Created on Fri Feb 22 12:52:28 2013

@author: kranzth
"""
from enthought.traits.api \
    import HasTraits, Instance, Str, on_trait_change

from enthought.traits.ui.api \
    import View, VGroup, Item, ValueEditor, TextEditor

from copy import deepcopy

class DictEditor(HasTraits):
    SearchTerm = Str()
    Object = Instance( object )

    def __init__(self, obj, **traits):
        super(DictEditor, self).__init__(**traits)
        self._original_object = obj
        self.Object = self._filter(obj)

    def trait_view(self, name=None, view_elements=None):
        return View(
          VGroup(
            Item( 'SearchTerm',
                  label      = 'Search:',
                  id         = 'search',
                  editor     = TextEditor(),
                  #style      = 'custom',
                  dock       = 'horizontal',
                  show_label = True
            ),
            Item( 'Object',
                  label      = 'Debug',
                  id         = 'debug',
                  editor     = ValueEditor(),
                  style      = 'custom',
                  dock       = 'horizontal',
                  show_label = False
            ),
          ),
          title     = 'Dictionary Editor',
          width     = 800,
          height    = 600,
          resizable = True,
        )

    @on_trait_change("SearchTerm")
    def search(self):
        self.Object = self._filter(self._original_object, self.SearchTerm)

    def _filter(self, object_, search_term=None):
        def has_matching_leaf(obj):
            if isinstance(obj, list):
                return any(
                        map(has_matching_leaf, obj))
            if isinstance(obj, dict):
                return any(
                        map(has_matching_leaf, obj.values()))
            else:
                try:
                    if not str(obj) == search_term:
                        return False
                    return True
                except ValueError:
                    False

        obj = deepcopy(object_)
        if search_term is None:
            return obj

        if isinstance(obj, dict):
            for k in obj.keys():
                if not has_matching_leaf(obj[k]):
                    del obj[k]

            for k in obj.keys():
                if isinstance(obj, dict):
                    obj[k] = self._filter(obj[k], search_term)
                elif isinstance(obj, list):
                    filter(has_matching_leaf,obj[k])

        return obj



def build_sample_data():
    def make_one_level_dict():
        return dict(zip(range(100),
                        range(100,150) + map(str,range(150,200))))

    my_data = make_one_level_dict()
    my_data[11] = make_one_level_dict()
    my_data[11][11] = make_one_level_dict()
    return my_data

# Test
if __name__ == '__main__':
    my_data = build_sample_data()
    b = DictEditor(my_data)
    b.configure_traits()

会给你一个带有“filter-as-you-type”的文本框。搜索并非对所有情况都完全正确,但您可以弄清楚这个想法。

请注意,在此示例中,dict 中的数据部分为整数,部分为字符串,两种类型都会找到。

【讨论】:

  • 不错的一个。会试一试
  • 对不起。不能将此标记为简单。半小时后,我仍然收到来自已安装模块的NotImplemented 错误。 PyPi 和官方 github 版本都不起作用。 enthought.traits.ui 包很可能有效,但我无法使其工作。
  • 您使用的是哪个操作系统?在 Linux 上安装很简单,在 Windows 上我推荐 Python(x,y)
  • 我在 Linux 上。我很高兴看到有效的安装指南。
  • 我在 Ubuntu 上试过了,那里的模块有不同的名称。写from traits.api ...from traitsui.api ...
【解决方案2】:

这里已经有一些很好的答案,但我相信这个符合“简单”的条件(它只使用 python 内置库 tkinter 和 uuid)。

它基于 John Gaines Jr. 在 another question 中的回答,由 Will Ware 修改以支持列表,由我修改以支持元组(在 python 3 上运行)。

我还对其进行了重新组织,以便您可以使用像tk_tree_view(data) 这样简单的东西来调用查看器,并传入字典(如最后的示例所示)。

import uuid
import tkinter as tk
from tkinter import ttk


def j_tree(tree, parent, dic):
    for key in sorted(dic.keys()):
        uid = uuid.uuid4()
        if isinstance(dic[key], dict):
            tree.insert(parent, 'end', uid, text=key)
            j_tree(tree, uid, dic[key])
        elif isinstance(dic[key], tuple):
            tree.insert(parent, 'end', uid, text=str(key) + '()')
            j_tree(tree, uid,
                   dict([(i, x) for i, x in enumerate(dic[key])]))
        elif isinstance(dic[key], list):
            tree.insert(parent, 'end', uid, text=str(key) + '[]')
            j_tree(tree, uid,
                   dict([(i, x) for i, x in enumerate(dic[key])]))
        else:
            value = dic[key]
            if isinstance(value, str):
                value = value.replace(' ', '_')
            tree.insert(parent, 'end', uid, text=key, value=value)


def tk_tree_view(data):
    # Setup the root UI
    root = tk.Tk()
    root.title("tk_tree_view")
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)

    # Setup the Frames
    tree_frame = ttk.Frame(root, padding="3")
    tree_frame.grid(row=0, column=0, sticky=tk.NSEW)

    # Setup the Tree
    tree = ttk.Treeview(tree_frame, columns=('Values'))
    tree.column('Values', width=100, anchor='center')
    tree.heading('Values', text='Values')
    j_tree(tree, '', data)
    tree.pack(fill=tk.BOTH, expand=1)

    # Limit windows minimum dimensions
    root.update_idletasks()
    root.minsize(root.winfo_reqwidth(), root.winfo_reqheight())
    root.mainloop()


if __name__ == "__main__":
    # Setup some test data
    data = {
        "firstName": "John",
        "lastName": "Smith",
        "gender": "male",
        "age": 32,
        "address": {
            "streetAddress": "21 2nd Street",
            "city": "New York",
            "state": "NY",
            "postalCode": "10021"},
        "phoneNumbers": [
            {"type": "home", "number": "212 555-1234"},
            {"type": "fax",
             "number": "646 555-4567",
             "alphabet": [
                 "abc",
                 "def",
                 "ghi"]
             }
        ]}

    # call it with
    tk_tree_view(data)

看起来像这样:

【讨论】:

  • 如果您想保留键的顺序(如果您使用的是有序字典),请将 for key in sorted(dic.keys()) 更改为 for key in dic.keys()
  • 如何在原地编辑这个 dict/json 并将其保存在要写入文件的变量中?
  • sorted(dic.keys()) 不适用于str 类型。我认为根本不应该使用sorted,因为通常人们使用字符串作为键。
【解决方案3】:

我终于按照@PavelAnossov 的建议将我的数据转换为json 并使用d3 Tree Layout

【讨论】:

  • 您是否符合您包含的 d3 链接中显示的“名称”“儿童”json 格式?如果是这样,您能否分享更多完整的解决方案?我很难将类似的 dict 结构转换为该 json 格式。
  • 那是我同事在工作中编写的一堆难看的硬代码。我认为我不能(也不想)在任何地方分享这段专有代码。
  • 我完全明白!感谢您的回复-我希望有人解决了这个问题:)
【解决方案4】:

我在上面修改了 Lucas 的答案,以便它接受具有混合键类型(如整数和字符串)的字典。我这样做的方式是使用 json 编码和解码,所以所有的键都是字符串。

import uuid
import json
import tkinter as tk
from tkinter import ttk


def j_tree(tree, parent, dic):
    for key in sorted(dic.keys()):
        uid = uuid.uuid4()
        if isinstance(dic[key], dict):
            tree.insert(parent, 'end', uid, text=key)
            j_tree(tree, uid, dic[key])
        elif isinstance(dic[key], tuple):
            tree.insert(parent, 'end', uid, text=str(key) + '()')
            j_tree(tree, uid,
                   dict([(i, x) for i, x in enumerate(dic[key])]))
        elif isinstance(dic[key], list):
            tree.insert(parent, 'end', uid, text=str(key) + '[]')
            j_tree(tree, uid,
                   dict([(i, x) for i, x in enumerate(dic[key])]))
        else:
            value = dic[key]
            if isinstance(value, str):
                value = value.replace(' ', '_')
            tree.insert(parent, 'end', uid, text=key, value=value)


def tk_tree_view(data):
    # Setup the root UI
    data = json.dumps(data)
    data = json.loads(data) 
    root = tk.Tk()
    root.title("tk_tree_view")
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)

    # Setup the Frames
    tree_frame = ttk.Frame(root, padding="3")
    tree_frame.grid(row=0, column=0, sticky=tk.NSEW)

    # Setup the Tree
    tree = ttk.Treeview(tree_frame, columns=('Values'))
    tree.column('Values', width=100, anchor='center')
    tree.heading('Values', text='Values')
    j_tree(tree, '', data)
    tree.pack(fill=tk.BOTH, expand=1)

    # Limit windows minimum dimensions
    root.update_idletasks()
    root.minsize(root.winfo_reqwidth(), root.winfo_reqheight())
    root.mainloop()


if __name__ == "__main__":
    # Setup some test data
    data = {
        "firstName": "John",
        "lastName": "Smith",
        "gender": "male",
        "age": 32,
        "address": {
            "streetAddress": "21 2nd Street",
            "city": "New York",
            "state": "NY",
            "postalCode": "10021"},
        "phoneNumbers": [
            {"type": "home", "number": "212 555-1234"},
            {"type": "fax",
             "number": "646 555-4567",
             "alphabet": [
                 "abc",
                 "def",
                 "ghi"]
             }
        ]}

我发现我可以将此代码粘贴到 ipython 中。
然后将数据定义为我的字典

data = [paste the big dict here]

然后用

调用它
tk_tree_view(data)

这种例子确实展示了 tk 的简单性。

【讨论】:

    【解决方案5】:

    如果您使用带有 JSONViewer 扩展的浏览器,这可能对您有用:

    import json
    import tempfile
    import os
    import subprocess
    def view_obj(obj):
      (f, filepath)= tempfile.mkstemp()
      os.close(f)
      with open(filepath, 'w') as f:
        json.dump(obj, f)
        subprocess.call(["google-chrome", filepath])
    
    view_obj({'key':'value'})  # Opens Chrome and renders JSON nicely
    

    【讨论】:

      【解决方案6】:

      如果您使用的是 IDE,请在字典初始化后设置断点并拥有您想要探索的数据,然后在调试模式下运行。在调试模式下应该有一个“变量”视图,您可以在其中展开和折叠您提到的字典。

      【讨论】:

        【解决方案7】:

        只需补充@Thorsten 的答案。包traits 很久以前就被重构了。运行Thorsten的代码的正确方法是:

        1. 安装特征模块:sudo apt-get install python-traitsui
        2. 将他代码中的模块导入行更改为:

          from traits.api \
          import HasTraits, Instance, Str, on_trait_change
          
          from traitsui.api \
          import View, VGroup, Item, ValueEditor, TextEditor
          

        【讨论】:

        • 有没有办法使用 pip 安装它?因为我有一段时间不使用 Ubuntu。我希望能够在 Fedora 和 Windows 上安装它。
        • 我认为您可以使用“pip install tr​​aitsui”,因为它已在 pypi 网站上注册:pypi.python.org/pypi/traitsui
        • 只是让大家知道,这在 2018 年仍然有效。我从 Christoph Gohlke 的页面和 pip install traitsui 安装 pyqt4。然后这些对 Thorsten 的回答的 mod 效果很好。
        【解决方案8】:

        这个简单的函数以表格形式打印字典。它还可以处理嵌套字典。

        def visualise_dict(d,lvl=0):
        
            # go through the dictionary alphabetically 
            for k in sorted(d):
        
                # print the table header if we're at the beginning
                if lvl == 0 and k == sorted(d)[0]:
                    print('{:<25} {:<15} {:<10}'.format('KEY','LEVEL','TYPE'))
                    print('-'*79)
        
                indent = '  '*lvl # indent the table to visualise hierarchy
                t = str(type(d[k]))
        
                # print details of each entry
                print("{:<25} {:<15} {:<10}".format(indent+str(k),lvl,t))
        
                # if the entry is a dictionary
                if type(d[k])==dict:
                    # visualise THAT dictionary with +1 indent
                    visualise_dict(d[k],lvl+1)
        

        带有示例字典:

        d = {}
        d.update({1:{},2:{}})
        d[1]['foo'] = {}
        d[1]['foo']['bar'] = 1
        d[2]['bar'] = 5.2
        
        visualise_dict(d)
        

        返回

        In [1]: visualise_dict(d)
        KEY                       LEVEL           TYPE      
        -------------------------------------------------------------------------------
        1                         0               <class 'dict'>
          foo                     1               <class 'dict'>
            bar                   2               <class 'int'>
        2                         0               <class 'dict'>
          bar                     1               <class 'float'>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-11-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-18
          • 2020-07-30
          • 2012-02-15
          • 2021-09-26
          相关资源
          最近更新 更多