大量操作 dict 内元素时有什么能省略 dict 名字的语法糖?

230 天前
 xuegy

标题可能描述的不够具体。大致操作是从 json 读入数据,经过一通计算,用 python-docx-replace 替换模版快速生成文档。代码举例(省略数字格式化字符串部分):

dict = json.load(xxx)
dict[A]=dict[B]+dict[C]
dict[D]=dict[E]-dict[F]
dict[G]=dict[H]*dict[I]
dict[J]=dict[K]/dict[L]
...
docx_replace(doc, **dict)

因为实际代码是很长的数学公式,个人觉得写这么多dict[]可读性实在太差,于是采用了如下写法(我知道不妥,但是想不出更好的),被 LD 批评很不 Pythonic 。

dict = json.load(xxx)
locals().update(dict)
A=B+C
D=E-F
G=H*I
J=K/L
...
dict.update({key: value for (key, value) in locals().items() if type(value) == int or type(value) == float})
docx_replace(doc, **dict)

求助各位高人有没有更合理的写法?

2294 次点击
所在节点    Python
34 条回复
thinkershare
230 天前
没有办法,python 不支持这种类似 js 的 with 临时作用域附加(js 的 with 大多数时候也被认为是一个不好的东西),因为 python 字典的 key 很可能不是一个合法的变量名称,而且它的类型也不一定是一个字符串。
不管使用 local()或者 global 都是一个糟糕的实现方式。
xuegy
230 天前
@thinkershare 有个前提是,我的 json 文件可以保证所有的 key 都是合法的变量名称
hitmanx
230 天前
想到一个更 hack 的方法,把计算放到一个 function 里,定义一个类似 C/C++里面"preprocess"的 decorator 加在函数上。

在这个 decorator 的实现:通过 inspect.getsource(func)去拿到 source 。然后每一行里把 dict 里的存在的 token 替换成 dict[token],最后调用 exec()去执行替换完的字符串。

相当于你自己实现了一个 preprocessor
Ricardoo
230 天前
这种我一般会转成类
my_json = type('MyJson', (object,), my_json)
my_json.A = my_json.B + my_json.C

只比 dict 方式强一丢丢
thinkershare
230 天前
@xuegy 没有办法,因为变量在现代编程语言中都是很特殊的存在。任何尝试动态解构变量的做法都会导致性能的下降,因为编译器会尝试在今日函数前对函数做优化。确定要给整个函数分配的堆栈空间大小,如果搞动态变量注入到局部,那么函数将无法优化,函数的机器码也无法被缓存重用,这些都是实实在在的性能问题。javascript 当初对 with 的支持就是一个错误。python 已经有很多语法都是因为容易写但性能差而被滥用了,你看看现在 python 写的很多程序的性能为什么如此差就明白了,python 现在支持的语法已经非常容易让人写出性能极差,时间复杂度极高的代码了。可读性和性能有时候就是相互矛盾的。
我们目前一般是这么做的 d=EasyDict(json.load(txt), d.a+d.b 这样。
stein42
230 天前
exec 函数了解一下
```
env = {'a': 1, 'b': 2}
exec('c = a + b', None, env)
print(env)
```
FYFX
230 天前
你字符串转换成运行时的变量名, 不管怎么实现都是一种不安全的操作了
xuegy
230 天前
@thinkershare 这个 easydict 看起来是一个可以接受的折中方案
wuwukai007
230 天前
BingoXuan
230 天前
def some_cal(a=None,b=None,c=None):
# return anything serializable, like namedtuple

data = json.loads(json_str)
try:
res=some_cal(**data)
except:
...# handle exception
else:
json.dumps(res)
Tanix2
230 天前
如果计算都是示例那样两元素的加减乘除,那么可以使用如下代码

import re

d = {
'B': 2,
'C': 3,
}


def dict_calc(d: dict, text: str):
for line in text.splitlines(keepends=False):
sp = re.split(r'([=+\-*/])', line)
if len(sp) == 5:
sp = map(str.strip, sp)
a, eq, b, op, c = sp
if eq == '=' and op in '+-*/':
d[a] = eval(f'{d[b]}{op}{d[c]}')


dict_calc(d, '''
A = B + C
D = A - C
E = A * D
F = E / D
''')

print(d)
# Output:
# {'B': 2, 'C': 3, 'A': 5, 'D': 2, 'E': 10, 'F': 5.0}
xuegy
230 天前
@Tanix2 是很长很长的数学公式,什么都有,甚至还要用一下 numpy
Leviathann
230 天前
你这个本质上是在造 dsl
mylifcc
230 天前
我想说 如果是我会写成
dict[A]=dict.get("B", 0) + dict.get("C", 0)
......
liuhai233
230 天前
看起来你这个 json 结构是固定的,为什么不考虑 parse 成 pydantic 对象呢,感觉很多人写 python 就不考虑创建 model 类了
Tanix2
230 天前
@xuegy 如果你能找到 A 、B 、C 这样的名称的规律,可以用正则表达式把它们都找出来(只找等号右侧),然后再 eval ,不过这样写是没有代码提示的,也存在安全性问题。
iOCZ
230 天前
没必要
LandCruiser
230 天前
没有人说第二种可读性太差吗
xuegy
230 天前
@LandCruiser 你告诉我,像这种东西:
PL = PA * C3 * FR * k/(k-1.0) * N * C4 * (np.power(P0 / float(PA), (k-1.0) / (k*N)) - 1.0) / (EA * EM)
全写成 dict 还能看吗?
yesterdaysun
230 天前
要不用解构的方式导出本地变量计算, 算完再导回 dict

A, B, C, D, E, F, G, H, I, J, K, L = dict.values()

A = B + C
D = E - F
G = H * I
J = K / L

dict = {"A": A, "B": B, "C": C, "D": D, "E": E, "F": F, "G": G, "H": H, "I": I, "J": J, "K": K, "L": L}

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/975773

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX