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

时间:2010-09-17 19:30:14

标签: python dictionary

我是Python新手,有点惊讶我不能这样做。

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

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

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

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

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

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

10 个答案:

答案 0 :(得分:46)

不用担心创建新课程 - 您可以利用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"]

答案 1 :(得分:13)

最近我没有做对象而出现了:

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

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

答案 2 :(得分:8)

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

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

但要小心,因为这会分配'b' 'a' 的密钥在分配时指定的值,并且不会进行查找每次。如果这正是您所寻找的,那么它可能会有更多的工作。

答案 3 :(得分:5)

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

答案 4 :(得分:5)

这是一个有趣的问题。格雷格似乎有一个good solution。但那没什么好玩的;)

jsbueno为very elegant solution,但仅适用于字符串(如您所要求的那样)。

'通用'自引用字典的技巧是使用代理对象。它需要一些(低估)代码来实现,但用法与你想要的一致:

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'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

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

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类的作用:实例替换字典文字中的'self'引用。话虽如此,(除了有时直接愚蠢)命名可能是我编码最难的事情之一。如果你(或其他任何人)可以建议一个更好的名字,我会全力以赴。

我将提供一个简短的解释。整个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并解析所有引用。

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

答案 5 :(得分:3)

如果你像我一样徘徊如何让@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 ',它有自己对'用户'的引用)因为评估顺序(**扩展在实际格式调用之前完成,并且没有像原始%版本那样延迟)。

答案 6 :(得分:2)

写一个类,也许是属性的东西:

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 

答案 7 :(得分:1)

作为@Tony's answer的扩展版本,你可以构建一个字典子类,如果它们是callables则调用它的值:

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

当然,只有当你不打算将callable存储为值时,这才有用。如果你需要能够做到这一点,你可以将lambda声明包装在一个函数中,该函数将一些属性添加到生成的lambda中,并在CallingDict.__getitem__中检查它,但此时它变得复杂,并且啰嗦,足以让你首先使用一个类来获取数据。

答案 8 :(得分:1)

这在懒惰的评估语言(haskell)中非常容易。

由于严格评估了Python,因此我们可以做一些技巧来使事情变得懒惰:

Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))

d1 = lambda self: lambda: {
  'a': lambda: 3,
  'b': lambda: self()['a']()
}

# fix the d1, and evaluate it
d2 = Y(d1)()

# to get a
d2['a']() # 3

# to get b
d2['b']() # 3

从语法角度来看,这不是很好。这是因为我们需要使用lambda: ...显式构造惰性表达式,并使用...()显式评估惰性表达式。在需要严格注释的惰性语言中,这是相反的问题,在Python中,我们最终需要惰性注释。

我认为通过更多的元编程和更多的技巧,可以使以上内容更易于使用。

请注意,这基本上是let-rec在某些功能语言中的工作方式。

答案 9 :(得分:0)

Python 3中的jsbueno答案:

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

dictionary = MyDict({
    'user' : 'gnucom',
    'home' : '/home/{0[user]}',
    'bin' : '{0[home]}/bin' 
})

print(dictionary["home"])
print(dictionary["bin"])

她的母羊使用带花括号{}.format()方法的python 3字符串格式。

文档:https://docs.python.org/3/library/string.html

相关问题