如何使用 freeze_support 从冻结的 sys.argv 调用中获取实际参数列表?

问题描述

我有一个独特的问题 - 我的解决方案,我希望使用 pyinstaller 打包,在启动时基于 sys.argv 对一些内容进行 JIT。在 Windows 上将 multiprocessingfreeze_support 一起使用时,多处理需要传入不同的参数来初始化新进程。原始 sys.argv 最终在调用目标函数时设置。如何在调用目标函数之前获取原始的 sys.argv?

import sys
import multiprocessing
print('ArgV:',sys.argv)


def print_argv():
    print(sys.argv)

if __name__ == '__main__':
    multiprocessing.freeze_support()
    print_argv()
    p = multiprocessing.Process(target=print_argv)
    p.start()
    p.join()

当用 pyinstaller 打包并用 --hello=True 运行时,产生:

ArgV: ['scratch.exe','--hello=True']
['scratch.exe','--hello=True']
ArgV: ['scratch.exe','--multiprocessing-fork','parent_pid=16096','pipe_handle=380']
['scratch.exe','--hello=True']

我想要一些神奇的代码,当sys.argv设置为--hello=True时,它可以提供我原来的sys.argv,即--multiprocessing-fork...

解决方法

我从未广泛使用冻结可执行文件,但我有几个想法......

看一下 multiprocessing.spawn._main(),复制原始 sys.argv 发生在这里:

preparation_data = reduction.pickle.load(from_parent)
prepare(preparation_data)

如果您覆盖 Process.__new__,您应该能够在 _bootstrap 之前运行代码(最终在进程对象上调用 run),但在收到 sys.argv 之后。

import sys
import multiprocessing
print('ArgV:',sys.argv)

def print_argv():
    print(sys.argv)
    
class myProcess(multiprocessing.Process):
    def __new__(cls,*args,**kwargs):
        if __name__ == "__mp_main__":
            print("hook",sys.argv)
        instance = super(myProcess,cls).__new__(cls) 
        instance.__init__(*args,**kwargs)
        return instance

if __name__ == '__main__':
    multiprocessing.freeze_support()
    print_argv()
    p = myProcess(target=print_argv)
    p.start()
    p.join()

另一个想法是通过覆盖 __getstate____setstate__ 来挂钩 unpickle 过程。

class myProcess(multiprocessing.Process):
    
    def __getstate__(self):
        return self.__dict__.copy()
    
    def __setstate__(self,state):
        print("hook",sys.argv)
        self.__dict__.update(state)

最后你可以钩住pickle寻找自定义类来解开时生成的审计事件:

class myProcess(multiprocessing.Process):
    pass

def hook(event_name,args):
    if "pickle.find_class" in event_name:
        if args[1] == myProcess.__name__:
            print("hook",sys.argv)
        
sys.addaudithook(hook)

所有这些在加载新进程的过程中大致同时发生,我不能说哪个是最健壮的...