当我需要自我引用词典时,我该怎么办?

sho*_*app 40 python dictionary

我是Python的新手,有点惊讶我无法做到这一点.

dictionary = {
    'a' : '123',
    'b' : dictionary['a'] + '456'
}
Run Code Online (Sandbox Code Playgroud)

我想知道Pythonic在我的脚本中正确执行此操作的方法是什么,因为我觉得我不是唯一一个试图这样做的人.

编辑:足够的人想知道我在做什么,所以这里有更多我的用例的细节.让我们说我想保持字典对象来保存文件系统路径.路径相对于字典中的其他值.例如,这就是我的一本词典.

dictionary = {
    'user': 'sholsapp',
    'home': '/home/' + dictionary['user']
}
Run Code Online (Sandbox Code Playgroud)

重要的是,在任何时候我都可以改变dictionary['user']并让所有词典值反映出变化.再一次,这是我正在使用它的一个例子,所以我希望它能传达我的目标.

根据我自己的研究,我认为我需要实现一个类来完成这项工作.

jsb*_*eno 47

No fear of creating new classes - You can take advantage of Python's string formating capabilities and simply do:

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"]
Run Code Online (Sandbox Code Playgroud)

  • 自从这是创建一个新类以来,这是最严格的,尽管它是一个派生类……不过,它非常聪明,恕我直言,并且在大多数方面几乎是透明的(另一件事)。 (2认同)
  • jsbueno:很高兴你这么喜欢你的回答。`;¬)` 为了强调我之前关于它可能非常像电子表格的评论,这里有一个 [recipe](http://code.activestate.com/recipes/355045-spreadsheet/) 更完整地实现了这个想法(由 Python 的核心开发人员之一编写)。 (2认同)

Ton*_*nen 14

我最近没有做对象:

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

print dictionary['home']()
dictionary['user']='tony'
print dictionary['home']()
Run Code Online (Sandbox Code Playgroud)


Dan*_*olo 8

>>> dictionary = {
... 'a':'123'
... }
>>> dictionary['b'] = dictionary['a'] + '456'
>>> dictionary
{'a': '123', 'b': '123456'}
Run Code Online (Sandbox Code Playgroud)

它工作正常但是当你尝试使用dictionary它时尚未定义(因为它必须首先评估该文字字典).

但要小心,因为这种分配给关键'b'的关键参考值'a' 在分配的时间并不会每次都做查找.如果这是你正在寻找的,那么它可能会有更多的工作.

  • @gnucom:python 2.4相当陈旧,目前的版本是2.7,所有真正的努力都集中在3.你应该认真尝试升级. (4认同)
  • 你可以使用`dictionary.update({'b':dictionary ['a'] +'456'})`并传入你想要的大字典,但听起来像gddc有你想要的方法,是真正的自我引用和动态查找,而不仅仅是"分配时"查找. (3认同)

aar*_*ing 6

这是一个有趣的问题.看起来格雷格有一个很好的解决方案.但那没什么好玩的;)

jsbueno是一个非常优雅的解决方案,但只适用于字符串(如您所要求的).

"通用"自引用字典的技巧是使用代理对象.它需要一些(低估的)代码行来完成,但用法是你想要的:

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

实现这一目标的代码并不是那么短暂.它分为三类:

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's 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
Run Code Online (Sandbox Code Playgroud)

接下来是具体课程.很简单.

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
Run Code Online (Sandbox Code Playgroud)

这是最后一堂课

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
Run Code Online (Sandbox Code Playgroud)

回应gnucoms关于为什么我按照我的方式命名类的问题.

单词代理通常与代替其他东西相关联,因此它似乎是合适的,因为这就是SurrogateDict类所做的:一个实例替换字典文字中的'self'引用.话虽如此,(除了有时直接愚蠢)命名可能是我编码最难的事情之一.如果你(或其他任何人)可以建议一个更好的名字,我会全力以赴.

我将提供一个简短的解释.Fromout S指的是SurrogateDict的一个实例,d是真正的字典.

  1. 引用S[key]触发S.__getitem__SurrogateDictEntry(key)放入d.

  2. 什么时候S[key] = SurrogateDictEntry(key)构建,它存储key.这将是key进入d该的此项对应的值SurrogateDictEntry是充当替代.

  3. S[key]返回,它要么进入d,或上面进行了一些操作(一个或多个).如果对它执行了操作,它将触发相对__op__方法,该方法简单地存储执行操作的值和操作的名称,然后返回自身.我们实际上无法解决操作,因为d尚未构建.

  4. 之后d构造,它被传递给S.resolve.此方法循环d 查找任何实例SurrogateDictEntry并使用resolve在实例上调用方法的结果替换它们.

  5. SurrogateDictEntry.resolve方法接收现在构造d为参数的方法,并且可以使用key它在构造时存储的值来获得它作为代理的值.如果在创建后op对其执行了操作,则将使用所执行操作的名称设置该属性.如果类有一个__op__方法,那么它有一个__op__resolve__方法,其中包含通常在方法中的实际逻辑__op__.所以现在我们有了逻辑(self.op_resolve)和所有必要的值(self.value,self.stored_value)来最终得到真正的价值d[key].所以我们返回第4步放在字典中的那个.

  6. 最后,该SurrogateDict.resolve方法返回d并解析所有引用.

那是一个粗略的草图.如果您还有其他问题,请随时提出.


Gre*_*reg 5

您在编辑中描述的是INI配置文件的工作原理.Python确实有一个名为ConfigParser的内置库,它应该适用于您所描述的内容.