【问题标题】:Loops and list-comprehension cannot find varibales when they are created using exec循环和列表理解在使用 exec 创建时找不到变量
【发布时间】:2022-06-22 22:10:17
【问题描述】:

在我的(简化的)脚本中,我有以下内容:

def predict(new_data):
 .
 .
 .
 model_list = fetch_needed_models() # ["f1","f2","f3"]

 for m in model_list:
      with open(f"./{m}.pkl","rb") as file:
          exec(f"{m}=pickle.load(file)")

 print(dir()) # [...,'f1','f2','f3',...]

 # Try with list-comprehension
 f1_pred = [f1.predict(x) for x in new_data] # NameError: name 'f1' does not exists
 
 # Try with loop
 f1_pred = []
 for x in new_data:
    f1_pred.append(f1.predict(x)) # NameError: name 'f1' does not exists

如果我在函数外部运行这些行,即仅逐行运行(在 VScode 中,交互式窗口中)它工作正常,但是当我运行像 python main.py 这样的函数时,我得到一个 NameError: name 'f1' is not defined 错误。我认为可能exec 没有正确执行,但是print(dir())exec 命令显示之后,变量确实存在。

有人知道为什么吗?

编辑

一个简单的测试

def test():
    exec("f= []")
    f.append(2)
    print(f)

if __name__=="__main__":
 test()
> python test.py
>      "NameError: name "f" is not defined

也失败了

【问题讨论】:

  • 你为什么要使用exec呢?更好的做法是拥有inputs = {},然后是input[m] = pickle.load(file);动态生成的变量会破坏静态分析工具,通常是代码异味。
  • 如果你这样做,你可以使用input["f1"].predict(x)
  • 请注意,如果您有任意数量的模型,为什么您只想引用f1 有点不清楚。做类似for model in input.values(): pred = [model.predict(x) for x in new_data] 这样的事情不是更有意义吗?
  • exec(f"f2=[]") f2.append(5) print(f2) 有效。你能提供一个更完整的例子吗?
  • @CharlesDuffy 因为模型在脚本中被进一步使用(并且这些模型在model_list 中通过它们的名称引用,例如pred_f1 = f1.predict())。

标签: python


【解决方案1】:

如果你真的想使用exec(你不应该,你应该重构你的代码以避免它),你必须传递代码执行的环境。使用您的简化示例,您可以这样做:

def test():
    exec("f= []", globals())
    f.append(2)
    print(f)

if __name__=="__main__":
 test()  # [2]

【讨论】:

  • 注意:这使得代码不可重入、线程不安全等,因为您使用的是共享的全局命名空间,而不是每次调用的本地变量。您可以将其他一些 dict 作为本地人传递,然后使用 otherdict['f'],但在 OP 的情况下,他们不妨完全跳过 exec
  • 我完全同意,这就是为什么我将免责声明放在开头,即 OP 不应使用 exec,这是一种不好的做法。但问题是如何让exec 在这种情况下工作。
【解决方案2】:

Locals 是一个编译时特性;即使使用exec,您也无法在运行时将real 局部变量添加到函数中。在编译时,计算固定大小的局部数组的大小,并为每个名称分配该数组中的索引;数组无法扩展,无法识别的名称没有映射。你可以用locals() 伪造它(它复制了真正的本地人,然后是一个可以变异的dict),但不要这样做。

只需使用dict 键入您关心的名称即可:

def predict(new_data):
 .
 .
 .
 model_list = fetch_needed_models() # ["f1","f2","f3"]

 models = {}
 for m in model_list:
      with open(f"./{m}.pkl","rb") as file:
          models[m] = pickle.load(file)

 # Try with list-comprehension
 f1_pred = [models['f1'].predict(x) for x in new_data]
 
 # Try with loop
 f1_pred = []
 for x in new_data:
    f1_pred.append(models['f1'].predict(x))

如果你知道 f1 会一直在里面,你可以直接提取它,但这会违背动态指定模型的目的:

def predict(new_data):
 .
 .
 .
 model_list = fetch_needed_models() # ["f1","f2","f3"]

 models = {}
 for m in model_list:
      with open(f"./{m}.pkl","rb") as file:
          models[m] = pickle.load(file)

 # We're sure f1 will be there, so we can make it a local manually    
 f1 = models['f1']

 # Try with list-comprehension
 f1_pred = [f1.predict(x) for x in new_data]
 
 # Try with loop
 f1_pred = []
 for x in new_data:
    f1_pred.append(f1.predict(x))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    相关资源
    最近更新 更多