簡體   English   中英

當我需要一本自參考字典時我該怎么辦?

[英]What do I do when I need a self referential dictionary?

我是 Python 新手,有點驚訝我不能這樣做。

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

我想知道在我的腳本中正確執行此操作的 Pythonic 方法是什么,因為我覺得我不是唯一一個嘗試執行此操作的人。

編輯:有足夠多的人想知道我在做什么,所以這里有更多關於我的用例的細節。 假設我想保留字典對象來保存文件系統路徑。 路徑相對於字典中的其他值。 例如,這就是我的一本詞典的樣子。

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

重要的是,我可以隨時更改dictionary['user']並讓所有字典值反映更改。 同樣,這是我使用它的一個例子,所以我希望它傳達了我的目標。

根據我自己的研究,我認為我需要實現一個類來做到這一點。

不用擔心創建新類 - 您可以利用 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"]

最近我沒有做對象就出現了:

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

print dictionary['home']()
dictionary['user']='tony'
print dictionary['home']()
>>> dictionary = {
... 'a':'123'
... }
>>> dictionary['b'] = dictionary['a'] + '456'
>>> dictionary
{'a': '123', 'b': '123456'}

它工作正常,但是當您嘗試使用dictionary它尚未定義(因為它必須首先評估該文字字典)。

但要小心,因為這就賦予的關鍵'b'通過的關鍵參考值'a'在轉讓時,並不會每次都做查找。 如果這就是您正在尋找的,那是可能的,但需要做更多的工作。

您在編輯中描述的是 INI 配置文件的工作方式。 Python 確實有一個名為ConfigParser的內置庫,它應該適用於您所描述的內容。

這是一個有趣的問題。 看起來 Greg 有一個很好的解決方案 但這並不好玩;)

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'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 關於我為什么按照我的方式命名這些類的問題。

surrogate 這個詞通常與代表其他事物相關聯,所以它看起來很合適,因為這就是SurrogateDict類所做的:一個實例替換字典文字中的“自我”引用。 話雖如此,(除了有時只是直接愚蠢)命名可能是我編碼中最困難的事情之一。 如果您(或其他任何人)可以建議一個更好的名字,我會全力以赴。

我將提供一個簡短的解釋。 整個S指的是 SurrogateDict 的一個實例,而d是真正的字典。

  1. 引用S[key]觸發S.__getitem__SurrogateDictEntry(key)被放置在d

  2. 當構造S[key] = SurrogateDictEntry(key) ,它存儲key 這將是dkey用於表示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並解析所有引用。

這是一個粗略的草圖。 如果您還有其他問題,請隨時提問。

如果您像我一樣徘徊如何使@jsbueno 片段與 {} 樣式替換一起工作,下面是示例代碼(雖然可能效率不高):

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',它有由於評估順序,它自己引用了“用戶”)(** 擴展在實際格式調用之前完成,並且不像原始 % 版本那樣延遲)。

寫一個類,也許是一些有屬性的東西:

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 

作為@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__檢查它,但此時它變得復雜且冗長,足以讓首先為您的數據使用類可能會更容易。

這在惰性評估語言(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 在某些函數式語言中的工作方式。

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"])

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM