[英]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
是真正的字典。
引用S[key]
觸發S.__getitem__
和SurrogateDictEntry(key)
被放置在d
。
當構造S[key] = SurrogateDictEntry(key)
,它存儲key
。 這將是d
的key
用於表示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
並解析所有引用。
這是一個粗略的草圖。 如果您還有其他問題,請隨時提問。
如果您像我一樣徘徊如何使@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()
方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.