【问题标题】:This Python class variable scope behavior is totally unexpected and frankly weird. What's going on?这个 Python 类变量作用域的行为是完全出乎意料的,坦率地说很奇怪。这是怎么回事?
【发布时间】:2018-03-01 14:50:39
【问题描述】:

好的,这个让我把头发扯掉了:

我有一个多进程程序,每个工作人员都在执行给定的任务。

当 KeyboardInterrupt 来临时,我希望每个工作人员将其内部状态保存到文件中,以便下次可以从中断的地方继续。

但是...

看起来包含状态信息的字典在这发生之前就消失了!

怎么样? exit() 函数正在访问一个更全局范围的字典版本......事实证明,各种 run()(以及从属于 run())函数一直在创建自己的变量版本。

这没什么好奇怪的……

除了...

他们都在使用自我。关键字。

如果我的理解是正确的,这应该意味着他们总是在访问变量的实例范围版本...而不是创建自己的!

这是代码的简化版本:

import multiprocessing
import atexit
import signal
import sys
import json

class Worker(multiprocessing.Process):
    def __init__(self, my_string_1, my_string_2):
        # Inherit the __init_ from Process, very important or we will get errors
        super(Worker, self).__init__()
        # Make sure we know what to do when called to exit
        atexit.register(self.exit)
        signal.signal(signal.SIGTERM, self.exit)
        self.my_dictionary = {
            'my_string_1' : my_string_1,
            'my_string_2' : my_string_2
            }
    def run(self):
        self.my_dictionary = {
            'new_string' : 'Watch me make weird stuff happen!'
            }
        try:
            while True:
                print(self.my_dictionary['my_string_1'] + " " + self.my_dictionary['my_string_2'])
        except (KeyboardInterrupt, SystemExit):
            self.exit()
    def exit(self):
        # Write the relevant data to file
        info_for_file = {
            'my_dictionary': self.my_dictionary
            }
        print(info_for_file) # For easier debugging
        save_file = open('save.log', 'w')
        json.dump(info_for_file, save_file)
        save_file.close()
        # Exit
        sys.exit()

if __name__ == '__main__':
    strings_list = ["Hello", "World", "Ehlo", "Wrld"]
    instances = []
    try:
        for i in range(len(strings_list) - 2):
            my_string_1 = strings_list[i]
            my_string_2 = strings_list[i + 1]
            instance = Worker(my_string_1, my_string_2)
            instances.append(instance)
            instance.start()
        for instance in instances:
            instance.join()
    except (KeyboardInterrupt, SystemExit):
        for instance in instances:
            instance.exit()
            instance.close()

在运行时,我们得到以下回溯...

Process Worker-2:
Process Worker-1:
Traceback (most recent call last):
  File "/usr/lib/python3.5/multiprocessing/process.py", line 249, in _bootstrap
    self.run()
Traceback (most recent call last):
  File "/usr/lib/python3.5/multiprocessing/process.py", line 249, in _bootstrap
    self.run()
  File "<stdin>", line 18, in run
  File "<stdin>", line 18, in run
KeyError: 'my_string_1'
KeyError: 'my_string_1'

换句话说,即使在 init 期间显式添加了键 my_string_1,run() 函数仍在访问不包含该键的新版本 self.my_dictionary!

同样,如果我们处理的是普通变量(my_dictionary 而不是 self.my_dictionary),这是可以预料的,但我认为 self.variables 始终是实例范围的......

这是怎么回事?

【问题讨论】:

    标签: python python-3.x class scope multiprocessing


    【解决方案1】:

    您的问题基本上可以用以下方式表示:

    class Test:
        def __init__(self):
            self.x = 1
    
        def run(self):
            self.x = 2
    
            if self.x != 1:
                print("self.x isn't 1!")
    
    t = Test()
    t.run()
    

    注意run 在做什么。

    您在写入时用不兼容的数据覆盖您的实例成员self.my_dictionary

    self.my_dictionary = {
                'new_string' : 'Watch me make weird stuff happen!'
    }
    

    然后在你说的时候尝试使用那些不兼容的数据

    print(self.my_dictionary['my_string_1']...
    

    当您覆盖 my_dictionary 时,您的意图并不清楚,但这就是您收到错误的原因。你需要重新思考你的逻辑。

    【讨论】:

    • 谢谢,我想你明白了,你说得对,我的代码本来可以更简单……我不确定多处理方面是否可能是问题的一部分。我的意思是设置 self.my_dictionary["new_string"] = '看我让奇怪的事情发生!' ...在原始演示代码中进行更正会产生预期的结果。不幸的是,在原始程序中进行相同的更正不会!我仍然得到“精神分裂症变量”的影响,当程序退出时,添加到字典的数据 run() 不存在......
    • @right2clicky 你一定在其他地方做了类似的事情。简而言之,您所描述的效果不仅会自行发生。尝试找出您的演示的不同之处。
    • 好吧,我在代码中搜索了与 self.my_dictionary 等效的变量,这就是我对它所做的:init: 初始化为 {} ... body 函数: len() ... body function: .append(somedata) ... body function: .pop(index) ... body function: .extend(somedata) ... ...以及一些要获取的东西来自变量的数据。而已!而且我不知道其中任何一个会如何破坏或覆盖它......
    • @right2clicky 我猜你在某种程度上混淆了主进程中的工作人员副本和衍生进程中的副本。
    猜你喜欢
    • 2015-02-27
    • 1970-01-01
    • 2020-02-29
    • 1970-01-01
    • 1970-01-01
    • 2015-03-30
    • 1970-01-01
    • 2020-09-14
    • 1970-01-01
    相关资源
    最近更新 更多