[英]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.