# exec的坑

# 概述

在 Python 中,eval()​ 函数用于将字符串作为 Python 表达式来求值并返回结果。可以将一个字符串解析为一个表达式,并将表达式的结果返回。该函数常用于从外部获取一些动态的表达式并计算其结果。

eval()​ 的函数签名如下:

exec(source, globals=None, locals=None, /)
    Execute the given source in the context of globals and locals.

    The source may be a string representing one or more Python statements
    or a code object as returned by compile().
    The globals must be a dictionary and locals can be any mapping,
    defaulting to the current globals and locals.
    If only globals is given, locals defaults to it.

>>> dis.dis('exec("a=1")') 
  1           0 LOAD_NAME                0 (exec)
              2 LOAD_CONST               0 ('a=1')
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

其中,expression​ 表示待求值的字符串表达式,globals​ 和 locals​ 分别表示全局命名空间和局部命名空间,如果不传入这两个参数,则默认使用当前的全局和局部命名空间。

下面是一个使用 eval()​ 函数的简单示例:

# 计算字符串表达式的值
expr = "2 + 3 * 4"
result = eval(expr)
print(result)  # 输出:14

# 在指定命名空间下计算表达式
x = 1
y = 2
expr = "x + y"
result = eval(expr, {'x': 3, 'y': 4})
print(result)  # 输出:7

# 在指定命名空间下计算包含函数调用的表达式
def add(x, y):
    return x + y

expr = "add(a, b)"
result = eval(expr, {'a': 1, 'b': 2, 'add': add})
print(result)  # 输出:3

需要注意的是,eval()​ 函数对于不安全的字符串表达式(例如来自用户输入的字符串)存在安全隐患,可能会导致代码注入攻击等问题,因此在使用时需要格外小心,避免执行恶意代码。

# eval的坑

注意eval的执行结果在局部环境中可能并不会生效,使用时需要注意。

## GLOBAL - APPLIED
g = 1
print(globals())    # {'g': 1}
exec('g = 2')
exec('g2 = 1')
print(globals())    # {'g': 2, 'g2': 1}
print(g)            # 2
print(g2)           # 1
globals()
print(globals())    # {'g': 2, 'g2': 1}
print(g)            # 2
print(g2)           # 1


print('func1 ##################################')
## Local - UNAPPLIED
def func1():
    l1 = 1
    print(locals()) # {'l1': 1}
    exec('l1 = 2')
    exec('ll1 = 1')
    print(locals()) # {'l1': 1}

func1()


print('func2 ##################################')
## Local - APPLIED
def func2():
    l2 = 1
    loc = locals()
    print(loc)      # {'l2': 1}
    exec('l2 = 2')
    # exec('ll2  = 1')
    print(loc)      # {'l2': 1, 'loc': {...}, 'll2': 1} 
    print(locals()) # {'l2': 1, 'loc': {...}, 'll2': 1}
    # print(ll2)      # NameError: name 'll2' is not defined
    print(locals().get('ll2'))  # 1

func2()


print('func3 ##################################')
## LOCAL - UNAPPLIED
def func3():
    x = 0
    loc = locals()
    print(loc)          # {'x': 0}
    exec('x += 1')      # {'x': 1, 'loc': {...}}
    print(loc)
    locals()            # {'x': 0, 'loc': {...}}
    print(loc)

func3()



print('func4 ##################################')
## Local - UNAPPLIED
def func4():
    l2 = 1
    loc = locals()
    print(loc)      # {'l2': 1}
    exec('l2 = 2')
    print(loc)      # {'l2': 2, 'loc': {...}}
    print(locals()) # {'l2': 1, 'loc': {...}}
    print(l2)       # 1

func4()

"""
>>> dis.dis('exec("a=1")') 
  1           0 LOAD_NAME                0 (exec)
              2 LOAD_CONST               0 ('a=1')
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

exec(source, globals=None, locals=None, /)
    Execute the given source in the context of globals and locals.

    The source may be a string representing one or more Python statements
    or a code object as returned by compile().
    The globals must be a dictionary and locals can be any mapping,
    defaulting to the current globals and locals.
    If only globals is given, locals defaults to it.
"""