代码之家  ›  专栏  ›  技术社区  ›  sholsapp

当我需要一本自引用字典时,我该怎么办?

  •  35
  • sholsapp  · 技术社区  · 14 年前

    我对python不太熟悉,我有点惊讶我不能这么做。

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

    我在想,在我的剧本中,怎样才能正确地完成这个动作,因为我觉得我不是唯一一个尝试过这样做的人。

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

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

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

    根据我自己的研究,我认为我需要实现一个类来实现这一点。

    8 回复  |  直到 9 年前
        1
  •  45
  •   aaronasterling    14 年前

    不用担心创建新的类- 您可以利用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
  •  13
  •   Tony Veijalainen    14 年前

    最近的一次我没有做任何事情:

    dictionary = {
        'user' : 'gnucom',
        'home' : lambda:'/home/'+dictionary['user'] 
    }
    
    print dictionary['home']()
    dictionary['user']='tony'
    print dictionary['home']()
    
        3
  •  8
  •   Daniel DiPaolo    14 年前
    >>> dictionary = {
    ... 'a':'123'
    ... }
    >>> dictionary['b'] = dictionary['a'] + '456'
    >>> dictionary
    {'a': '123', 'b': '123456'}
    

    它很好用,但是当你想用的时候 dictionary 它还没有被定义(因为它必须首先评估这个字面字典)。

    但要小心,因为这把钥匙 'b' 键引用的值 'a' 派遣时 而且不会每次都进行查找。如果这就是你想要的,那是有可能的,但需要更多的工作。

        4
  •  5
  •   Greg    14 年前

    您在编辑中描述的是ini配置文件的工作方式。python确实有一个内置的库,名为 ConfigParser 这对你描述的内容很有用。

        5
  •  5
  •   Community CDub    7 年前

    这是一个有趣的问题。好像格雷格有一个 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 引用Surrogateddict的实例和 d 是真正的字典。

    1. 参考文献 S[key] 触发器 S.__getitem__ SurrogateDictEntry(key) 放置在 D .

    2. 什么时候? S[key] = SurrogateDictEntry(key) 是建造的,它存储 key . 这将是 钥匙 进入之内 D 对于此项的值 SurrogateDictEntry 是的代理。

    3. S [键] 如果返回,则输入 D 或对其执行了某些操作。如果对其执行操作,它将触发 __op__ 方法,该方法简单地存储执行操作的值和操作的名称,然后返回自身。我们不能真正解决这个问题,因为 D 还没有建造。

    4. D 被构造,传递给 S.resolve . 这个方法循环 D 查找的任何实例 代理字典项 并将其替换为调用 resolve 实例上的方法。

    5. 这个 SurrogateDictEntry.resolve 方法接收现在构造的 D 作为参数,可以使用 钥匙 它在构建时存储以获取作为其代理项的值。如果在创建后对其执行了操作,则 op 属性将设置为已执行操作的名称。如果班级有 α-π 方法,则它具有 __op__resolve__ 方法的实际逻辑通常位于 α-π 方法。所以现在我们有了逻辑(自我。 行动决心 )以及所有必要的值(self.value,self.stored_value),最终得到 d[key] . 所以我们把第四步放在字典里的内容返回。

    6. 最后是 SurrogateDict.resolve 方法返回 D 解决了所有引用。

    那是一个草图。如果你还有什么问题,可以问。

        6
  •  3
  •   Community CDub    7 年前

    如果你,就像我在徘徊如何使 @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”,而home本身引用的是“user”),因为计算顺序(**expansion是在实际格式调用之前完成的,并且不会像在原始%版本中那样延迟)。

        7
  •  2
  •   Jochen Ritzel    14 年前

    编写一个类,可能是一些具有属性的内容:

    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
  •  1
  •   Community CDub    7 年前

    作为的扩展版本 @Tony's 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
    

    当然,只有当您实际上不打算将可调用文件存储为值时,这才是可用的。如果需要这样做,可以将lambda声明包装在一个函数中,该函数向生成的lambda添加一些属性,然后在 CallingDict.__getitem__ 但是在那一点上,它变得越来越复杂,越来越冗长,以至于一开始就可以更容易地为数据使用类。