literal-dict¶
Motivation¶
{"foo": foo, "bar": bar }
dict(foo=foo, bar=bar)
看不惯重复写键和值了?想像 JavaScript 对象字面量一样简写?
{ foo, bar, ... }
literal-dict 一定程度上做到了这一点
用法¶
name = "Muspi Merol"
age = 20
user = d[name, age, "active": True]
print(user) # Output: {'name': 'Muspi Merol', 'age': 20, 'active': True}
其中 d 是一个 DictBuilder 实例。创建 DictBuilder 时会默认以 dict 作为 factory,使每个 d[a, b, c] 的结果都是一个 dict。你可以传入别的 Callable[[dict], T] 作为一个 T 的 factory,常见的有 defaultdict、SimpleNamespace 等
实现¶
核心代码就这么简单:
class DictBuilder[D]:
def __init__(self, constructor: Callable[[dict], D] = dict):
self.constructor = constructor
def __getitem__(self, args: Union[slice, T, Sequence[Union[slice, T]]]) -> D:
if not isinstance(args, tuple):
args = (args,)
caller_frame = currentframe().f_back
obj = {}
for arg in cast(Sequence[Union[slice, T]], args):
if isinstance(arg, slice):
assert isinstance(arg.start, str), "Key must be a string"
obj[arg.start] = arg.stop
else:
for name, var in caller_frame.f_locals.items():
if var is arg and name not in obj:
obj[name] = arg
break
else:
for name, var in caller_frame.f_globals.items():
if var is arg and name not in obj:
obj[name] = arg
break
else:
for name, var in caller_frame.f_builtins.items():
if var is arg and name not in obj:
obj[name] = arg
break
return self.constructor(obj) if self.constructor is not dict else obj
其中 assert isinstance(arg.start, str), "Key must be a string" 这句是为了允许这样的用法:
>>> a, b = 1, 2
>>> d[a, b: 123]
# Output: {"a": 1, 2: 123}
其中 b 被解析为值而非键,这一点和 JavaScript 中不太一样,但我觉得这是更符合 Python 直觉的。
这个项目后续也没改过了,但是时不时会用到一下,尤其是在参数很多的地方,比如我 实习 的时候就在写 API / 数据库相关的代码中用到过。
不过它有个 bug,就是当多个键指向同一个值的时候,就没法通过我这种方法找到对于的变量名了
可能通过更 hacky 的 ast 方法可以解决,但我还没研究。得学学 sorcery