Подтвердить что ты не робот

Что мне делать, когда мне нужен самореляционный словарь?

Я новичок в Python, и я удивлен, что не могу этого сделать.

dictionary = {
    'a' : '123',
    'b' : dictionary['a'] + '456'
}

Мне интересно, как Pythonic правильно это сделать в моем script, потому что я чувствую, что я не единственный, кто пытался это сделать.

РЕДАКТИРОВАТЬ: Достаточно людей задавались вопросом, что я делаю с этим, так что вот подробности для моих случаев использования. Допустим, я хочу, чтобы словарные объекты сохраняли пути файловой системы. Пути относятся к другим значениям в словаре. Например, это может выглядеть один из моих словарей.

dictionary = {
    'user': 'sholsapp',
    'home': '/home/' + dictionary['user']
}

Важно, что в любой момент времени я могу изменить dictionary['user'], и все значения словарей отражают изменение. Опять же, это пример того, для чего я его использую, поэтому я надеюсь, что он передает мою цель.

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

4b9b3361

Ответ 1

Не бойтесь создавать новые классы - Вы можете использовать возможности форматирования строк Python и просто выполните:

class MyDict(dict):
   def __getitem__(self, item):
       return dict.__getitem__(self, item) % self

dictionary = MyDict({

    'user' : 'gnucom',
    'home' : '/home/%(user)s',
    'bin' : '%(home)s/bin' 
})


print dictionary["home"]
print dictionary["bin"]

Ответ 2

Ближайший я подошел без объекта:

dictionary = {
    'user' : 'gnucom',
    'home' : lambda:'/home/'+dictionary['user'] 
}

print dictionary['home']()
dictionary['user']='tony'
print dictionary['home']()

Ответ 3

>>> dictionary = {
... 'a':'123'
... }
>>> dictionary['b'] = dictionary['a'] + '456'
>>> dictionary
{'a': '123', 'b': '123456'}

Он отлично работает, но когда вы пытаетесь использовать dictionary, он еще не определен (потому что он сначала должен оценить этот литерал буквального значения).

Но будьте осторожны, потому что это присваивает ключу 'b' значение, на которое ссылается ключ 'a' во время присвоения, и не собирается делать поиск каждый раз. Если это то, что вы ищете, это возможно, но с большим количеством работы.

Ответ 4

Что вы описываете в своем редактировании, так это то, как работает файл конфигурации INI. У Python есть встроенная библиотека под названием ConfigParser, которая должна работать для того, что вы описываете.

Ответ 5

Это интересная проблема. Похоже, что у Грега есть хорошее решение. Но это не забавно;)

jsbueno как очень изящное решение, но это относится только к строкам (по вашему запросу).

Трюк с "общим" самореляционным словарем заключается в использовании суррогатного объекта. Для снятия кода требуется несколько (преуменьшение) строк кода, но использование - это то, что вы хотите:

S = SurrogateDict(AdditionSurrogateDictEntry)
d = S.resolve({'user': 'gnucom',
               'home': '/home/' + S['user'],
               'config': [S['home'] + '/.emacs', S['home'] + '/.bashrc']})

Код, чтобы сделать это, не так короток. Он живет в трех классах:

import abc

class SurrogateDictEntry(object):
    __metaclass__ = abc.ABCMeta
    def __init__(self, key):
        """record the key on the real dictionary that this will resolve to a 
           value for
        """
        self.key = key

    def resolve(self, d):
        """ return the actual value"""
        if hasattr(self, 'op'):
            # any operation done on self will store it name in self.op. 
            # if this is set, resolve it by calling the appropriate method 
            # now that we can get self.value out of d
            self.value = d[self.key]
            return getattr(self, self.op + 'resolve__')()
        else:
            return d[self.key]

    @staticmethod
    def make_op(opname):
        """A convience class. This will be the form of all op hooks for subclasses
           The actual logic for the op is in __op__resolve__ (e.g. __add__resolve__)
        """
        def op(self, other):
            self.stored_value = other
            self.op = opname
            return self
        op.__name__ = opname
        return op

Далее идет конкретный класс. достаточно просто.

class AdditionSurrogateDictEntry(SurrogateDictEntry):

    __add__ = SurrogateDictEntry.make_op('__add__')
    __radd__ = SurrogateDictEntry.make_op('__radd__')

    def __add__resolve__(self):
        return self.value + self.stored_value 

    def __radd__resolve__(self):
        return self.stored_value + self.value

Здесь конечный класс

class SurrogateDict(object):
    def __init__(self, EntryClass):
        self.EntryClass = EntryClass

    def __getitem__(self, key):
        """record the key and return""" 
        return self.EntryClass(key)

    @staticmethod
    def resolve(d):
        """I eat generators resolve self references"""
        stack = [d]
        while stack:
            cur = stack.pop()
            # This just tries to set it to an appropriate iterable
            it = xrange(len(cur)) if not hasattr(cur, 'keys') else cur.keys()
            for key in it:
                # sorry for being a duche. Just register your class with
                # SurrogateDictEntry and you can pass whatever.
                while isinstance(cur[key], SurrogateDictEntry):
                    cur[key] = cur[key].resolve(d)
                # I'm just going to check for iter but you can add other
                # checks here for items that we should loop over. 
                if hasattr(cur[key], '__iter__'):
                    stack.append(cur[key])
        return d

В ответ на вопрос gnucoms о том, почему я назвал классы так, как я это сделал.

Слово суррогат обычно связано с тем, чтобы стоять за чем-то другим, поэтому он казался подходящим, потому что то, что делает класс SurrogateDict: экземпляр заменяет ссылки "я" в словаре-литерале. Это, как говорится, (за исключением просто быть глупым иногда), называние, вероятно, является одной из самых сложных вещей для меня о кодировании. Если вы (или кто-либо еще) можете предложить лучшее имя, я все уши.

Я приведу краткое объяснение. На протяжении S ссылается на экземпляр SurrogateDict, а d - настоящий словарь.

  • Ссылка S[key] запускает S.__getitem__ и SurrogateDictEntry(key) для размещения в d.

  • При построении S[key] = SurrogateDictEntry(key) он хранит key. Это будет key в d для значения, которое эта запись SurrogateDictEntry действует как суррогат для.

  • После возврата S[key] он либо вводится в d, либо выполняет некоторые операции (операции) на нем. Если над ним выполняется операция, он запускает относительный метод __op__, который просто сохраняет значение, в котором выполняется операция, и имя операции, а затем возвращает себя. Мы не можем фактически разрешить операцию, потому что d еще не сконструирован.

  • Построено d, оно передается в S.resolve. Этот метод проходит через d, обнаруживая любые экземпляры SurrogateDictEntry и заменяя их результатом вызова метода resolve в экземпляре.

  • Метод SurrogateDictEntry.resolve получает построенный в настоящее время d аргумент и может использовать значение key, которое оно хранило во время построения, чтобы получить значение, которое оно выполняет как суррогат. Если после создания была выполнена операция после этого, атрибут op будет установлен с именем выполняемой операции. Если класс имеет метод __op__, то он имеет метод __op__resolve__ с фактической логикой, которая обычно находится в методе __op__. Итак, теперь у нас есть логика (self. op__resolve) и все необходимые значения (self.value, self.stored_value), чтобы получить реальное значение d[key]. Итак, мы возвращаем тот шаг 4 места в словаре.

  • наконец, метод SurrogateDict.resolve возвращает d со всеми разрешенными ссылками.

Это грубый эскиз. Если у вас есть еще вопросы, не стесняйтесь спрашивать.

Ответ 6

Если вы, как и я, блуждаете, как сделать @jsbueno snippet, работаете с {} заменой стиля, ниже приведен пример кода (который, вероятно, не очень эффективен, хотя ):

import string

class MyDict(dict):
    def __init__(self, *args, **kw):
        super(MyDict,self).__init__(*args, **kw)
        self.itemlist = super(MyDict,self).keys()
        self.fmt = string.Formatter() 

    def __getitem__(self, item):
        return self.fmt.vformat(dict.__getitem__(self, item), {}, self)


xs = MyDict({
    'user' : 'gnucom',
    'home' : '/home/{user}',
    'bin' : '{home}/bin'
})


>>> xs["home"]
'/home/gnucom'
>>> xs["bin"]
'/home/gnucom/bin'

Я попытался заставить его работать с простой заменой % self на .format(**self), но, оказывается, он не будет работать для вложенных выражений (например, "bin" в приведенном выше списке, в котором ссылаются "home", который имеет он имеет ссылку на "пользователь" ) из-за порядка оценки (** расширение выполняется до вызова фактического формата и не задерживается, как в исходной версии%).

Ответ 7

Напишите класс, возможно, что-то со свойствами:

class PathInfo(object):
    def __init__(self, user):
        self.user = user

    @property
    def home(self):
        return '/home/' + self.user

p = PathInfo('thc')
print p.home # /home/thc 

Ответ 8

В качестве расширенной версии @Tony answer вы можете создать подкласс словаря, который вызывает его значения, если они являются вызываемыми:

class CallingDict(dict):
    """Returns the result rather than the value of referenced callables.

    >>> cd = CallingDict({1: "One", 2: "Two", 'fsh': "Fish",
    ...                   "rhyme": lambda d: ' '.join((d[1], d['fsh'],
    ...                                                d[2], d['fsh']))})
    >>> cd["rhyme"]
    'One Fish Two Fish'
    >>> cd[1] = 'Red'
    >>> cd[2] = 'Blue'
    >>> cd["rhyme"]
    'Red Fish Blue Fish'
    """
    def __getitem__(self, item):
        it = super(CallingDict, self).__getitem__(item)
        if callable(it):
            return it(self)
        else:
            return it

Конечно, это можно было бы использовать только в том случае, если вы на самом деле не собираетесь хранить вызовы в качестве значений. Если вам нужно это сделать, вы можете обернуть объявление лямбда в функцию, которая добавляет некоторый атрибут к полученной лямбда, и проверить ее в CallingDict.__getitem__, но в этот момент она становится сложной и долговязанной, достаточно, чтобы проще было использовать класс для ваших данных.