Python 当我需要一本自指词典时,我该怎么办?
我是Python新手,有点惊讶我不能这么做Python 当我需要一本自指词典时,我该怎么办?,python,dictionary,Python,Dictionary,我是Python新手,有点惊讶我不能这么做 dictionary = { 'a' : '123', 'b' : dictionary['a'] + '456' } 我想知道在我的脚本中,用什么样的Pythonic方法才能正确地做到这一点,因为我觉得我不是唯一一个尝试这么做的人 >>> dictionary = { ... 'a':'123' ... } >>> dictionary['b'] = dictionary['a'] + '456'
dictionary = {
'a' : '123',
'b' : dictionary['a'] + '456'
}
我想知道在我的脚本中,用什么样的Pythonic方法才能正确地做到这一点,因为我觉得我不是唯一一个尝试这么做的人
>>> dictionary = {
... 'a':'123'
... }
>>> dictionary['b'] = dictionary['a'] + '456'
>>> dictionary
{'a': '123', 'b': '123456'}
编辑:有足够多的人想知道我在用这个做什么,所以这里有我的用例的更多细节。假设我想保留dictionary对象来保存文件系统路径。路径是相对于字典中的其他值的。例如,这就是我的一本字典的样子
dictionary = {
'user': 'sholsapp',
'home': '/home/' + dictionary['user']
}
重要的是,我可以随时更改字典['user']
,并让所有字典值反映更改。同样,这是我使用它的一个例子,所以我希望它能传达我的目标
根据我自己的研究,我认为我需要实现一个类来实现这一点
>>> dictionary = {
... 'a':'123'
... }
>>> dictionary['b'] = dictionary['a'] + '456'
>>> dictionary
{'a': '123', 'b': '123456'}
它工作得很好,但是当您试图使用字典时,它还没有被定义(因为它必须首先计算该文本字典)
但是要小心,因为这会在赋值时将'a'
键引用的值赋值给'b'
键,并且不会每次都进行查找。如果这就是你想要的,那是可能的,但需要更多的工作。最近的我没有做任何事情:
dictionary = {
'user' : 'gnucom',
'home' : lambda:'/home/'+dictionary['user']
}
print dictionary['home']()
dictionary['user']='tony'
print dictionary['home']()
不必担心创建新类-
您可以利用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"]
编写一个类,可能是具有属性的类:
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
您在编辑中描述的是INI配置文件的工作方式。Python确实有一个名为的内置库,它应该适用于您所描述的内容。这是一个有趣的问题。格雷格似乎有一个好主意。但这并不有趣;)
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的问题时,我问我为什么要这样命名这些类
“代理”一词通常与代表其他内容相关,因此它似乎是合适的,因为subscratedict
类就是这样做的:实例替换字典文本中的“self”引用。话虽如此,(除了有时直截了当的愚蠢之外)命名对我来说可能是编码中最困难的事情之一。如果你(或其他任何人)能建议一个更好的名字,我洗耳恭听
我将提供一个简短的解释。贯穿始终,S
是指代理ICT的一个实例,d
是真正的字典
参考S[key]
会触发S.\uu getitem\uu
和代理ICTentry(key)
以放置在d
中
当构造S[key]=subscratedictentry(key)
时,它存储key
。这将是进入d
的键,该subrogatedictEntry
项作为其替代项的值
返回S[key]
后,它要么输入d
,要么对其执行一些操作。如果对其执行操作,它将触发相对的\uuuuuuuuuuuuuuuuuuuuuuu
方法,该方法简单地存储执行操作的值和操作的名称,然后返回自身。我们无法实际解决该操作,因为尚未构造d
在构造d
之后,它被传递给S.resolve
。此方法通过d
循环查找subscratedICTentry
的任何实例,并将其替换为对实例调用resolve
方法的结果
subscratedictentry.resolve
方法接收现在构造的d
作为参数,并可以使用它在构造时存储的key
的值来获取它作为代理的值。如果在创建后对其执行了操作,则已使用所执行操作的名称设置了op
属性。如果该类有一个\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
方法,那么它有一个\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。因此,现在我们有了逻辑(self.op_uuresolve)和所有必要的值(self.value,self.storaged_uvalue),最终得到d[key]
的真实值。因此,我们返回步骤4在字典中的位置
最后,subscratedict.resolve
方法返回d
,并解析所有引用
那是一幅草图。如果您还有任何问题,请随时提问。作为的扩展版本,您可以构建一个字典子类,如果其值是可调用的,则调用其值:
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添加一些属性,并在调用dict中检查它。但是在这一点上,它变得越来越复杂和冗长,以至于可能更容易首先为数据使用类。,就像我在思考如何使用{}样式的替换一样,下面是示例代码(可能效率不高):
我试着用.format(**self)
简单地替换%self
,使它工作起来,但结果是不起作用
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
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"])