挺有意思的问题,可以分成两部分解答:
1. 为什么直接运行ToExpression["a"] = 456会报错
简单来说,因为赋值语句Set具有HoldFirst属性,因此左侧的ToExpression["a"]不会自动计算为a,而ToExpression["a"]本身并不是一个可被赋值的表达式,类似的例子包括(x+3)=5、Length[y]=3等等。如果对C++中的左值、右值概念有一定了解的话,这里理解起来会更容易一些。
知道了问题产生的原因,解决的思路也就有了,在Set运行之前使ToExpression["a"]计算即可,Evaluate、Activate+Inactive、ReleaseHold+Hold等都可以达成目标
Clear["a"];
Evaluate[ToExpression["a"]] = 3;
Clear["a"];
Activate[Inactive[Set][ToExpression["a"],3]]
Clear["a"];
ReleaseHold[Hold[Set][ToExpression["a"],3]]
需要注意的是,如果a本身已经被赋值,ToExpression["a"]得到的数值也很可能是不可被赋值的表达式,因此需要先运行Clear
另,对于这种字符串转变量名的操作,个人建议用Symbol["a"]替代ToExpression["a"]
2. 为什么Unprotect[ToExpression]后上述语句可以运行,但得到的结果不符合预期
因为Unprotect[ToExpression]后,ToExpression["a"]=3类似于f["a"]=3,赋值改变的是ToExpression的DownValue,而非a的OwnValue
换句话说,运行这一语句后,ToExpression["a"]和变量a就完全没有关系了
最后补充一下,我个人非常不建议在MMA中使用这种字符串直接到变量名的赋值方式,使用DownValue或Association都是更好的选择