Python有时需要动态的创造Python代码,然后将其作为语句执行 或 作为表达式计算。
exec用于执行存储在字符串中的Python代码。
1、 语句与表达式的区别:表达式是 某事,语句是 做某事(即告诉计算机做什么)。
比如2*2是4,而print 2*2是打印4。上述两句代码在交互式解释器中执行的结果是一样的,是因为解释器总是把所有表达式的值打印出来而已。而在程序中编写类似2*2这样的表达式并不会打印显示什么,编写print 2*2则会打印4。
语句与表达式的区别在赋值时更明显,因为语句不是表达式,所以没有值。如在交互式解释器中输入 x=2则不会打印任何东西,立刻出现新的提示符。虽然什么也没显现,但是有些东西已经发生变化如x的值现在变为3.这也是语句特性的一般定义:它们改变了事物。比如,赋值语句改变了变量,print语句改变了屏幕显示的内容。
2、 命名空间(作用域) 全局变量和局部变量
除了全局作用域外,每个函数会都会创建一个新的作用域。变量分为全局变量和局部变量,函数内的变量称为局部变量只在局部命名空间中起作用。
在函数内部读取全局变量一般来说不是问题,直接访问即可。但是,如果局部变量名或者参数的名字与全局变量名相同的话,就不能直接访问了,因为全局变量被局部变量给屏蔽了。如果确实需要的话,可以使用globals函数获取被屏蔽的全局变量值。(globals返回全局变量的字典,locals返回局部变量的值)。例如:有一个名为parameter的全局变量,那么在combine(parameter)函数内部访问全局变量时,因为与参数重名,必须使用globals()['parameter']获取。代码如下:
def combine(parameter): print parameter+globals()['parameter'] #函数调用 parameter="hello" combine("berry")
上面讲的是再函数内部读取全局变量的方法,不包括修改。如果要在函数内部修改全局变量,需要告知修改的值是全局变量,因为在函数内部将值赋予一个变量那么变量自动成为局部变量。通过global关键字来告诉Python函数内一个需要修改的变量是一个全局变量。代码如下:
x=1 def change_global(n): global x x=x+1
3、执行字符串的语句 exec
如输入exec "print 'hello'"会打印出hello。(注意:Python 3.0中,exec是一个函数不是一个语句了,因此使用exec('字符串语句')的方式来调用)。exec执行字符串语句存在安全风险,因为exec可能会干扰命名空间,即改变不应该变的变量。例如:
从上面的例子可以看出,exec干扰了命名空间,改变了sqrt的值,使其不是一个函数而变成1了。由此可见,如果对exec不加限制就会存在安全风险。下面是改进措施。
措施:通过增加 in <scope>来实现,其中的<scope>是一个字典,该字典起到放置代码字符串命名空间的作用。这样exec执行的代码只会在<scope>代表的命名空间中起作用。如:
从上面代码中可以看到,exec语句在scope命名空间中执行,不会影响到现在命名空间的sqrt。scope虽然充当命名空间的作用,但实质仍是一个字典,所以如果想知道scope命名空间中有多少变量时,可通过len(scope)获得,可通过scope.key()获得scope命名空间的所有变量。
4、eval 会计算字符串形式的Python表达式,并返回结果的值。
exec语句不会返回任何对象。而eval会返回表达式的值。下面的代码可以创建一个Python计算器:
#Python计算器 print eval(raw_input("Please input an arithmetic expression:"))
上面代码解释,上面代码中eval内部现在还不是字符串,首先执行raw_input()函数,raw_input()返回你输入的求值字符串,现在eval函数内部就是求值字符串了,就可以用eval进行字符串的求值了。如输入:4*5+6,那么raw_input就会返回“4*5+6”,eval求值后为26.
要注意上面代码与下面代码的区别:
print eval('raw_input("Please input an arithmetic expression:")')
在这个代码中,与Python计算器代码不同的是,eval函数内直接就是字符串,那么直接对字符串求值,但是字符串中是raw_input表达式,raw_input表达式将用户的输入转换为字符串,所以如果输入4+5的话会返回"4+5"。注意:raw_input('xxxxx')是一个表达式,表达式的值就是用户输入。 可能疑惑的是代码:exec('raw_input("Please input an arithmetic expression:")')不会报错,因为ecec也可以用于表达式,只是什么效果也没达到而已(既不返回值,也没干事情)。
跟exec一样,eval也可以使用命名空间。因为尽管表达式一般不会给变量重新赋值,但是表达式可以通过调用函数来达到给全局变量赋值的目的。例如执行下面代码后,全局变量x的值会被重新赋值为2:
x=1 def inc_x(): global x x=x+1 eval("inc_x()") print x
从上面的代码可以看出eval函数也是不安全的,必须使用命名空间。事实上,可以为eval提供两个命名空间,一个是全局的,另一个是局部的。全局的必须是字典,局部的可以是任何形式的映射。
exec和eval的命名空间使用代码(命名空间可以不是空的字典,可以提前为命名空间提供一些值):
scope={} scope['x']=1 scope['y']=2 print eval('x+y',scope)
scope={} exec "x=2" in scope eval("x*x",scope)