我试图了解在 Tcl 中为字典编写几行代码时,后台发生了多少“工作”,特别是
dict with...{}
。我确信这是一个非常新手的问题,即使不是愚蠢的问题,我可能会在尝试在字典中表示对象并尝试获取对对象中嵌套属性的引用时感到困惑。
假设字典中有十个嵌套级别的键,其中一个本身就是一本字典,我想迭代它的键。
如果使用
dict with varName ... key_10
则无法迭代 key_10
中字典的键,因为它们现在是主体中的隐藏变量;并且,如果尝试这样做,则会引发错误,指出缺少密钥或 key_10 不存在。如果在 dict with
之上的级别使用 key_10
,则可以迭代 key_10
中保存的字典的键,但会为该级别的其他 9 个(或 10 个)键生成影子变量,而主体中没有代码相关。
我的问题是,当以这种方式使用时,字典嵌套路径的结构是否应该消除不需要的变量的遮蔽,例如让每个终止点以某种无用的名称结尾,这可能会导致单个变量被遮蔽?有某种推荐的最佳实践吗?
因此,在这个
key_10
示例中,key_10
中保存的字典将被移动到 key_10 data
,这样就可以使用 dict with varName ... key_10 { ... }
并且唯一的影子变量是 data
;然后可以迭代现在保存在 data
而不是 key_10
中的字典的键?
请原谅我这个非常没有创意的例子,但它说明了问题。
谢谢你。
set example [dict create a {b { c {d {key_0 {This is key_0}}}}}]
foreach key {key_1 key_2 key_3 key_4 key_5 key_6 key_7 key_8 key_9 key_10} {
dict set example a b c d $key "This is $key"
}
dict set example a b c d key_10 {firstName Joe lastName Doakes age 50 height 72 weight 195}
dict with example a b c d key_10 {
dict for {key value} $key_10 {
puts "$key : $value"
}
}
# can't read "key_10": no such variable
dict with example a b c d {
dict for {key value} $key_10 {
puts "$key : $value"
}
puts $key_0
puts $key_1
puts $key_2
puts $key_3
puts $key_4
puts $key_5
puts $key_6
puts $key_7
puts $key_8
puts $key_9
}
# firstName : Joe
# lastName : Doakes
# age : 50
# height : 72
# weight : 195
# This is key_0
# This is key_1
# This is key_2
# This is key_3
# This is key_4
# This is key_5
# This is key_6
# This is key_7
# This is key_8
# This is key_9
unset key_0
unset key_1
unset key_2
unset key_3
unset key_4
unset key_5
unset key_6
unset key_7
unset key_8
unset key_9
set example [dict create a {b { c {d {key_0 {Zero-index item}}}}}]
foreach key {key_1 key_2 key_3 key_4 key_5 key_6 key_7 key_8 key_9 key_10} {
dict set example a b c d $key "This is $key"
}
dict set example a b c d key_10 {data {firstName Joe lastName Doakes age 50 height 72 weight 195}}
dict with example a b c d key_10 {
dict for {key value} $data {
puts "$key : $value"
}
puts $key_0
puts $key_1
puts $key_2
puts $key_3
puts $key_4
puts $key_5
puts $key_6
puts $key_7
puts $key_8
puts $key_9
puts $key_10
}
# firstName : Joe
# lastName : Doakes
# age : 50
# height : 72
# weight : 195
# can't read "key_0": no such variable
这将是一个你喜欢哪种风格的问题,但在这种情况下我会避免
dict with
。你的问题的某些语言表明你在心理上将其建模为做一些与实际不同的事情(“影子”):
set d {foo dictfoo bar dictbar}
set foo outerfoo
dict with d {
puts $foo ;# prints "dictfoo"
}
puts $foo ;# prints "dictfoo", not "outerfoo"
puts $bar ;# prints "dictbar" - the variables persist
也就是说:
foo
范围内的变量dict with
不会“隐藏”之前的foo
变量,它会替换其内容,这种效果在dict with
范围之后仍然存在。因此 dict with
应该非常谨慎地使用,因为它改变程序状态(存在的变量集及其内容)的方式不受代码约束,而是受数据(字典的内容)约束。
如果该数据源自用户(例如 Web 后端中的表单参数),这很容易产生安全隐患。想象一下,在这个例子中,
form_dict
是一个命令,它返回一个字典,其中包含我们正在服务的HTTP请求中的表单数据(我们希望包含“名称”和“地址”):
set is_admin [check_user_is_admin]
set form [form_dict]
dict with form {
puts "name is: $name"
puts "address is: $address"
}
if {$is_admin} {
do_something_privileged
}
然后,攻击者可以制作类似
/form_handler?name=My%20name&address=xxx&is_admin=1
的请求,并且无论 do_something_privileged
返回什么,check_user_is_admin
都会运行。
如果您使用
dict with
解包的字典是来自 select * from foo
的数据库结果中的一行,并且稍后将新列添加到表 foo
中,则会发生类似的问题:这将导致变量在调用 dict with
的代码中设置新列的名称,更改 dict with
、及其后中代码的含义,而无需更改代码。
由于在您的示例中您只阅读字典的内容,所以我会这样写:
set example [dict create a {b { c {d {key_0 {This is key_0}}}}}]
foreach key {key_1 key_2 key_3 key_4 key_5 key_6 key_7 key_8 key_9 key_10} {
dict set example a b c d $key "This is $key"
}
dict set example a b c d key_10 {firstName Joe lastName Doakes age 50 height 72 weight 195}
dict for {key value} [dict get $example a b c d key_10] {
puts "$key : $value"
}
set nested [dict get $example a b c d]
dict for {key value} [dict get $nested key_10] {
puts "$key : $value"
}
puts [dict get $nested key_0]
puts [dict get $nested key_1]
puts [dict get $nested key_2]
puts [dict get $nested key_3]
puts [dict get $nested key_4]
puts [dict get $nested key_5]
puts [dict get $nested key_6]
puts [dict get $nested key_7]
puts [dict get $nested key_8]
puts [dict get $nested key_9]
就访问数据的“幕后”工作而言,我建议您测量用例的性能,如果您的 Tcl 版本足够新,请使用
timerate
,或者以字节为单位使用 time
-编码上下文(例如proc
正文)。由于 dict
位于核心并且是字节代码编译的,因此性能可能比您对其正在执行的操作的直觉更好:
set dict {
a "value of a"
b "value of b"
c "value of c"
d "value of d"
e "value of e"
f "value of f"
g "value of g"
h "value of h"
i "value of i"
j "value of j"
k "value of k"
l "value of l"
m "value of m"
n "value of n"
o "value of o"
p "value of p"
q "value of q"
r "value of r"
s "value of s"
t "value of t"
u "value of u"
v "value of v"
w "value of w"
x "value of x"
y "value of y"
z "value of z"
}
puts "using dict get: [timerate {
set foo "[dict get $dict a] [dict get $dict m] [dict get $dict z]"
}]"
puts "using dict with: [timerate {
dict with dict {
set foo "$a $m $z"
}
}]"
在我的具有 Tcl 8.7 优化版本的系统上,我得到:
using dict get: 0.190637 µs/# 5245581 # 5245581 #/sec 1000.000 net-ms
using dict with: 1.657568 µs/# 603294 # 603293 #/sec 1000.001 net-ms
这个结果可能令人惊讶,但考虑到
dict with
必须做很多工作:
dict with
范围内的代码持续时间内跟踪这些变量,或者安排将对这些变量的更改传播回字典中。而
dict get
只需要读取这些按键即可。