Mit*_*ops 8 python algorithm recursion nested setattr
有很多很好的getattr()函数用于解析嵌套的字典结构,例如:
我想做一个并行的setattr().基本上,给定:
cmd = 'f[0].a'
val = 'whatever'
x = {"a":"stuff"}
Run Code Online (Sandbox Code Playgroud)
我想生成一个我可以分配的功能:
x['f'][0]['a'] = val
Run Code Online (Sandbox Code Playgroud)
或多或少,这将以与以下相同的方式工作:
setattr(x,'f[0].a',val)
Run Code Online (Sandbox Code Playgroud)
产量:
>>> x
{"a":"stuff","f":[{"a":"whatever"}]}
Run Code Online (Sandbox Code Playgroud)
我现在叫它setByDot():
setByDot(x,'f[0].a',val)
Run Code Online (Sandbox Code Playgroud)
这样做的一个问题是,如果中间的密钥不存在,则需要检查并生成中间密钥(如果不存在) - 即,对于上述情况:
>>> x = {"a":"stuff"}
>>> x['f'][0]['a'] = val
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'f'
Run Code Online (Sandbox Code Playgroud)
所以,你首先必须做:
>>> x['f']=[{}]
>>> x
{'a': 'stuff', 'f': [{}]}
>>> x['f'][0]['a']=val
>>> x
{'a': 'stuff', 'f': [{'a': 'whatever'}]}
Run Code Online (Sandbox Code Playgroud)
另一个是当下一个项目是一个字符串时,键入当下一个项目是一个字符串时,键入不同,即:
>>> x = {"a":"stuff"}
>>> x['f']=['']
>>> x['f'][0]['a']=val
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
Run Code Online (Sandbox Code Playgroud)
...失败,因为赋值是空字符串而不是空字典.对于dict中的每个非列表,null dict将是正确的赋值,直到最后一个 - 可能是列表或值.
@TokenMacGuy在下面的评论中指出的第二个问题是,当你必须创建一个不存在的列表时,你可能需要创建大量的空白值.所以,
setattr(x,'f[10].a',val)
Run Code Online (Sandbox Code Playgroud)
---可能意味着算法必须制作一个像:
>>> x['f']=[{},{},{},{},{},{},{},{},{},{},{}]
>>> x['f'][10]['a']=val
Run Code Online (Sandbox Code Playgroud)
屈服
>>> x
{"a":"stuff","f":[{},{},{},{},{},{},{},{},{},{},{"a":"whatever"}]}
Run Code Online (Sandbox Code Playgroud)
这就是与吸气剂相关的设定器......
>>> getByDot(x,"f[10].a")
"whatever"
Run Code Online (Sandbox Code Playgroud)
更重要的是,中间体应该/不/覆盖已经存在的值.
下面是我到目前为止的一个简单的想法 - 我可以识别列表与dicts和其他数据类型,并在它们不存在的地方创建它们.但是,我没有看到(a)在哪里进行递归调用,或者(b)如何在迭代列表时"构建"深层对象,以及(c)如何区分/探测/我是当我从/ setting /构造深对象时,我必须做到达到堆栈的末尾.
def setByDot(obj,ref,newval):
ref = ref.replace("[",".[")
cmd = ref.split('.')
numkeys = len(cmd)
count = 0
for c in cmd:
count = count+1
while count < numkeys:
if c.find("["):
idstart = c.find("[")
numend = c.find("]")
try:
deep = obj[int(idstart+1:numend-1)]
except:
obj[int(idstart+1:numend-1)] = []
deep = obj[int(idstart+1:numend-1)]
else:
try:
deep = obj[c]
except:
if obj[c] isinstance(dict):
obj[c] = {}
else:
obj[c] = ''
deep = obj[c]
setByDot(deep,c,newval)
Run Code Online (Sandbox Code Playgroud)
这看起来非常棘手,因为如果你正在制作占位符,你必须提前检查/ next/object的类型,并且你必须在后面建立一条路径.
UPDATE
我最近也回答了这个问题,这可能是相关或有帮助的.
我将其分为两个步骤。第一步,查询字符串被分解为一系列指令。这样问题就解耦了,我们可以在运行之前查看指令,并且不需要递归调用。
def build_instructions(obj, q):
"""
Breaks down a query string into a series of actionable instructions.
Each instruction is a (_type, arg) tuple.
arg -- The key used for the __getitem__ or __setitem__ call on
the current object.
_type -- Used to determine the data type for the value of
obj.__getitem__(arg)
If a key/index is missing, _type is used to initialize an empty value.
In this way _type provides the ability to
"""
arg = []
_type = None
instructions = []
for i, ch in enumerate(q):
if ch == "[":
# Begin list query
if _type is not None:
arg = "".join(arg)
if _type == list and arg.isalpha():
_type = dict
instructions.append((_type, arg))
_type, arg = None, []
_type = list
elif ch == ".":
# Begin dict query
if _type is not None:
arg = "".join(arg)
if _type == list and arg.isalpha():
_type = dict
instructions.append((_type, arg))
_type, arg = None, []
_type = dict
elif ch.isalnum():
if i == 0:
# Query begins with alphanum, assume dict access
_type = type(obj)
# Fill out args
arg.append(ch)
else:
TypeError("Unrecognized character: {}".format(ch))
if _type is not None:
# Finish up last query
instructions.append((_type, "".join(arg)))
return instructions
Run Code Online (Sandbox Code Playgroud)
对于你的例子
>>> x = {"a": "stuff"}
>>> print(build_instructions(x, "f[0].a"))
[(<type 'dict'>, 'f'), (<type 'list'>, '0'), (<type 'dict'>, 'a')]
Run Code Online (Sandbox Code Playgroud)
预期返回值只是_type指令中下一个元组的(第一项)。这非常重要,因为它允许我们正确初始化/重建丢失的键。
这意味着我们的第一条指令对 a 进行操作dict,设置或获取密钥'f',并且预计返回 a list。类似地,我们的第二条指令对 a 进行操作list,设置或获取索引0,并预计返回 a dict。
现在让我们创建我们的_setattr函数。这会获取正确的指令并遍历它们,根据需要创建键值对。最后,它还设置了val我们给它的值。
def _setattr(obj, query, val):
"""
This is a special setattr function that will take in a string query,
interpret it, add the appropriate data structure to obj, and set val.
We only define two actions that are available in our query string:
.x -- dict.__setitem__(x, ...)
[x] -- list.__setitem__(x, ...) OR dict.__setitem__(x, ...)
the calling context determines how this is interpreted.
"""
instructions = build_instructions(obj, query)
for i, (_, arg) in enumerate(instructions[:-1]):
_type = instructions[i + 1][0]
obj = _set(obj, _type, arg)
_type, arg = instructions[-1]
_set(obj, _type, arg, val)
def _set(obj, _type, arg, val=None):
"""
Helper function for calling obj.__setitem__(arg, val or _type()).
"""
if val is not None:
# Time to set our value
_type = type(val)
if isinstance(obj, dict):
if arg not in obj:
# If key isn't in obj, initialize it with _type()
# or set it with val
obj[arg] = (_type() if val is None else val)
obj = obj[arg]
elif isinstance(obj, list):
n = len(obj)
arg = int(arg)
if n > arg:
obj[arg] = (_type() if val is None else val)
else:
# Need to amplify our list, initialize empty values with _type()
obj.extend([_type() for x in range(arg - n + 1)])
obj = obj[arg]
return obj
Run Code Online (Sandbox Code Playgroud)
正因为我们可以,所以这里有一个_getattr函数。
def _getattr(obj, query):
"""
Very similar to _setattr. Instead of setting attributes they will be
returned. As expected, an error will be raised if a __getitem__ call
fails.
"""
instructions = build_instructions(obj, query)
for i, (_, arg) in enumerate(instructions[:-1]):
_type = instructions[i + 1][0]
obj = _get(obj, _type, arg)
_type, arg = instructions[-1]
return _get(obj, _type, arg)
def _get(obj, _type, arg):
"""
Helper function for calling obj.__getitem__(arg).
"""
if isinstance(obj, dict):
obj = obj[arg]
elif isinstance(obj, list):
arg = int(arg)
obj = obj[arg]
return obj
Run Code Online (Sandbox Code Playgroud)
行动中:
>>> x = {"a": "stuff"}
>>> _setattr(x, "f[0].a", "test")
>>> print x
{'a': 'stuff', 'f': [{'a': 'test'}]}
>>> print _getattr(x, "f[0].a")
"test"
>>> x = ["one", "two"]
>>> _setattr(x, "3[0].a", "test")
>>> print x
['one', 'two', [], [{'a': 'test'}]]
>>> print _getattr(x, "3[0].a")
"test"
Run Code Online (Sandbox Code Playgroud)
现在来一些很酷的东西。与 python 不同,我们的_setattr函数可以设置不可散列的dict键。
x = []
_setattr(x, "1.4", "asdf")
print x
[{}, {'4': 'asdf'}] # A list, which isn't hashable
>>> y = {"a": "stuff"}
>>> _setattr(y, "f[1.4]", "test") # We're indexing f with 1.4, which is a list!
>>> print y
{'a': 'stuff', 'f': [{}, {'4': 'test'}]}
>>> print _getattr(y, "f[1.4]") # Works for _getattr too
"test"
Run Code Online (Sandbox Code Playgroud)
我们并没有真正使用不可散列的dict键,但看起来我们使用的是我们的查询语言,所以谁在乎呢,对吧!
最后,您可以对同一个对象运行多个_setattr调用,您自己尝试一下即可。