Ходить / перебирать вложенный словарь произвольной глубины (словарь представляет собой дерево каталогов) - PullRequest
7 голосов
/ 12 октября 2011

Я почти уверен, что есть простое решение для этого, но я потратил часы, читая и перечитывая тот же набор связанных результатов, которые не вполне отвечают моей проблеме.

Контекстэтот вопрос (включен для завершения, но вы можете его пропустить)

Этот вопрос возник, потому что я хочу, чтобы пользователь мог выбрать группу файлов в каталоге (а также в любом подкаталоге),и, к сожалению, по умолчанию Tkinter не может выбрать несколько файлов в диалоговом окне файлов в Windows 7 (http://bugs.python.org/issue8010).

). Таким образом, я пытаюсь представить структуру каталогов альтернативным методом (все еще использующим Tkinter): создание факсимильного сообщенияструктуры каталогов, состоящей из помеченных и с отступом флажков (организованных в виде дерева). Таким образом, каталог вроде этого:

\SomeRootDirectory
    \foo.txt
    \bar.txt
    \Stories
        \Horror
            \scary.txt
            \Trash
                \notscary.txt
        \Cyberpunk
    \Poems
        \doyoureadme.txt

будет выглядеть примерно так (где # представляет флажок):

SomeRootDirectory
    # foo.txt
    # bar.txt
    Stories
        Horror
            # scary.txt
            Trash
                # notscary.txt
        Cyberpunk
    Poems
        # doyoureadme.txt

Создать оригинальный словарь из структуры каталогов легко, используя определенный рецепт I fв ActiveState (см. ниже), но я попадаю в стену, когда пытаюсь перебрать красиво вложенный словарь, с которым мне осталось.И я думаю, что мне нужно перебрать его, чтобы заполнить фрейм Tkinter довольно сетчатым представлением дерева.Затем я надеюсь загрузить различные текстовые файлы, выбранные пользователем, интерпретируя, какие флажки были истинными или ложными.Все кажется довольно простым, за исключением перебора словаря без без фиксации глубины.

В более абстрактных терминах

Для создания этих вложенных словарей я используюрецепт ActiveState - http://code.activestate.com/recipes/577879/. Он реализует os.walk для создания таких словарей:

a={
    'SomeRootDirectory': {
        'foo.txt': None,
        'bar.txt': None,
        'Stories': {
            'Horror': {
                'horror.txt' : None,
                'Trash' : {
                    'notscary.txt' : None,
                    },
                },
            'Cyberpunk' : None
            },
        'Poems' : {
            'doyoureadme.txt' : None
        }
    }
}

После чего я в тупике.Я новичок в Python на момент написания

Решение, адаптированное на основе ответов Spicavigo

#distinguish between directory and file
dirtab = "/==="
filetab = "|---"

Parents={-1:"Root"}
def add_dir(level, parent, index, k):
    print (dirtab*level)+k
def add_file(level, parent, index, k):
    #Currently an empty folder gets passed to add_file; here's a quick treatment.
    if "." in k:
        print (filetab*level)+k
    else:
        print (dirtab*level)+k
def addemup(level=0, parent=-1, index=0, di={}):
    for k in di:
        index +=1
        if di[k]:
            Parents[index]=k
            add_dir(level, parent, index, k)
            addemup(level+1, index, index, di[k])
        else:
            add_file(level, parent, index, k)

addemup(di=a) #dictionary from above

Это дает то, что, я думаю, будет очень легко преобразовать в Tkinterпредставление:

SomeRootDirectory
/===Poems
|---|---doyoureadme.txt
/===Stories
/===/===Horror
|---|---|---rickscott.txt
/===/===/===Trash
|---|---|---|---notscary.txt
/===/===Cyberpunk
|---foo.txt
|---bar.txt

Спасибо, это сообщество невероятно.

Ответы [ 5 ]

9 голосов
/ 12 октября 2011

Вот функция, которая печатает все ваши имена файлов.Он проходит по всем ключам в словаре, и если они сопоставляются с вещами, которые не являются словарями (в вашем случае, именем файла), мы выводим имя.В противном случае мы вызываем функцию в словаре, которая сопоставлена.

def print_all_files(directory):

    for filename in directory.keys():
        if not isinstance(directory[filename], dict):
            print filename
        else:
            print_all_files(directory[filename])

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

Ключевым моментом, который необходимо понять, является то, что каждый раз, когда вызывается print_all_files, он не знает, насколько глубоко он находится в дереве.Он просто смотрит на файлы, которые прямо там, и печатает имена.Если есть директора, они просто запускаются на них.

4 голосов
/ 12 октября 2011

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

2 голосов
/ 06 января 2013

Я понимаю, что это старый вопрос, но я просто искал простой, чистый способ обходить вложенные диктовки, и это самое близкое, что придумал мой ограниченный поиск.Ответ oadams не достаточно полезен, если вам нужно больше, чем просто имена файлов, а ответ spicavigo выглядит сложным.

В итоге я просто свернул свой собственный, который действует подобно тому, как os.walk обрабатывает каталоги, за исключением того, что он возвращает всеключ / значение информации.

Возвращает итератор, и для каждого каталога в «дереве» вложенных диктов итератор возвращает (путь, поддикты, значения), где:

  • путь - это путьк dict
  • sub-dicts - это набор пар (ключ, dict) для каждого подчиненного в этом dict
  • значения - это набор пар (ключ, значение) для каждой (не продиктованный) элемент в этом диктате


def walk(d):
    '''
    Walk a tree (nested dicts).

    For each 'path', or dict, in the tree, returns a 3-tuple containing:
    (path, sub-dicts, values)

    where:
    * path is the path to the dict
    * sub-dicts is a tuple of (key,dict) pairs for each sub-dict in this dict
    * values is a tuple of (key,value) pairs for each (non-dict) item in this dict
    '''
    # nested dict keys
    nested_keys = tuple(k for k in d.keys() if isinstance(d[k],dict))
    # key/value pairs for non-dicts
    items = tuple((k,d[k]) for k in d.keys() if k not in nested_keys)

    # return path, key/sub-dict pairs, and key/value pairs
    yield ('/', [(k,d[k]) for k in nested_keys], items)

    # recurse each subdict
    for k in nested_keys:
        for res in walk(d[k]):
            # for each result, stick key in path and pass on
            res = ('/%s' % k + res[0], res[1], res[2])
            yield res

Вот код, который я использовал для его тестирования, хотя у него есть пара других не связанных (но аккуратных)вещи в нем:

import simplejson as json
from collections import defaultdict

# see https://gist.github.com/2012250
tree = lambda: defaultdict(tree)

def walk(d):
    '''
    Walk a tree (nested dicts).

    For each 'path', or dict, in the tree, returns a 3-tuple containing:
    (path, sub-dicts, values)

    where:
    * path is the path to the dict
    * sub-dicts is a tuple of (key,dict) pairs for each sub-dict in this dict
    * values is a tuple of (key,value) pairs for each (non-dict) item in this dict
    '''
    # nested dict keys
    nested_keys = tuple(k for k in d.keys() if isinstance(d[k],dict))
    # key/value pairs for non-dicts
    items = tuple((k,d[k]) for k in d.keys() if k not in nested_keys)

    # return path, key/sub-dict pairs, and key/value pairs
    yield ('/', [(k,d[k]) for k in nested_keys], items)

    # recurse each subdict
    for k in nested_keys:
        for res in walk(d[k]):
            # for each result, stick key in path and pass on
            res = ('/%s' % k + res[0], res[1], res[2])
            yield res

# use fancy tree to store arbitrary nested paths/values
mem = tree()

root = mem['SomeRootDirectory']
root['foo.txt'] = None
root['bar.txt'] = None
root['Stories']['Horror']['rickscott.txt'] = None
root['Stories']['Horror']['Trash']['notscary.txt'] = None
root['Stories']['Cyberpunk']
root['Poems']['doyoureadme.txt'] = None

# convert to json string
s = json.dumps(mem, indent=2)

#print mem
print s
print

# json.loads converts to nested dicts, need to walk them
for (path, dicts, items) in walk(json.loads(s)):
    # this will print every path
    print '[%s]' % path
    for key,val in items:
        # this will print every key,value pair (skips empty paths)
        print '%s = %s' % (path+key,val)
    print

Вывод выглядит так:

{
  "SomeRootDirectory": {
    "foo.txt": null,
    "Stories": {
      "Horror": {
        "rickscott.txt": null,
        "Trash": {
          "notscary.txt": null
        }
      },
      "Cyberpunk": {}
    },
    "Poems": {
      "doyoureadme.txt": null
    },
    "bar.txt": null
  }
}

[/]

[/SomeRootDirectory/]
/SomeRootDirectory/foo.txt = None
/SomeRootDirectory/bar.txt = None

[/SomeRootDirectory/Stories/]

[/SomeRootDirectory/Stories/Horror/]
/SomeRootDirectory/Stories/Horror/rickscott.txt = None

[/SomeRootDirectory/Stories/Horror/Trash/]
/SomeRootDirectory/Stories/Horror/Trash/notscary.txt = None

[/SomeRootDirectory/Stories/Cyberpunk/]

[/SomeRootDirectory/Poems/]
/SomeRootDirectory/Poems/doyoureadme.txt = None
0 голосов
/ 30 мая 2015
a={
    'SomeRootDirectory': {
        'foo.txt': None,
        'bar.txt': None,
        'Stories': {
            'Horror': {
                'rickscott.txt' : None,
                'Trash' : {
                    'notscary.txt' : None,
                    },
                },
            'Cyberpunk' : None
            },
        'Poems' : {
            'doyoureadme.txt' : None
        }
    }
}

def dict_paths(dictionary, level=0, parents=[], paths=[]):
  for key in dictionary:
    parents = parents[0:level]
    paths.append(parents + [key])
    if dictionary[key]:
      parents.append(key)
      dict_paths(dictionary[key], level+1, parents, paths)
  return paths

dp = dict_paths(a)
for p in dp:
    print '/'.join(p)
0 голосов
/ 02 апреля 2015

Вы можете просматривать вложенный словарь, используя рекурсию

def walk_dict(dictionary):
    for key in dictionary:
        if isinstance(dictionary[key], dict):
           walk_dict(dictionary[key])
        else:
           #do something with dictionary[k]
           pass

Надеюсь, это поможет:)

...