【问题标题】:Call a function from different file where the file name and function name are read from a list从不同的文件调用函数,其中文件名和函数名是从列表中读取的
【发布时间】:2020-02-18 08:31:59
【问题描述】:

我有多个函数存储在不同的文件中,文件名和函数名都存储在列表中。是否有任何选项可以在没有条件语句的情况下调用所需的函数? 比如file1有函数function11function12

def function11():
    pass
def function12():
    pass

file2 有函数function21function22

def function21():
    pass
def function22():
    pass

我有清单

file_name = ["file1", "file2", "file1"]
function_name = ["function12", "function22", "funciton12"]

我将从不同的函数获取列表索引,基于我需要调用函数并获取输出。

【问题讨论】:

    标签: python arrays function


    【解决方案1】:

    如果其他函数会直接给你一个列表索引,那么你不需要将函数名作为字符串处理。相反,直接存储(不调用)列表中的函数:

    import file1, file2
    
    functions = [file1.function12, file2.function22, file1.function12]
    

    然后在获得索引后调用它们:

    function[index]()
    

    种方法可以在 Python 中执行所谓的“反射”并从字符串获取匹配命名的函数。但是它们解决的问题比您描述的更高级,而且更困难(尤其是如果您还必须使用模块名称)。


    如果您有一个允许从配置文件调用的函数和模块的“白名单”,但仍需要通过字符串查找它们,则可以使用 dict 显式创建映射:

    allowed_functions = {
        'file1': {
            'function11': file1.function11,
            'function12': file1.function12
        },
        'file2': {
            'function21': file2.function21,
            'function22': file2.function22
        }
    }
    

    然后调用函数:

    try:
        func = allowed_functions[module_name][function_name]
    except KeyError:
        raise ValueError("this function/module name is not allowed")
    else:
        func()
    

    最高级的方法是如果您需要从作者创建的“插件”模块加载代码。您可以使用标准库importlib 包,使用字符串名称查找要作为模块导入的文件,并动态导入。它看起来像:

    from importlib.util import spec_from_file_location, module_from_spec
    
    # Look for the file at the specified path, figure out the module name
    # from the base file name, import it and make a module object.
    def load_module(path):
        folder, filename = os.path.split(path)
        basename, extension = os.path.splitext(filename)
        spec = spec_from_file_location(basename, path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)
        assert module.__name__ == basename
        return module
    

    仍然不安全,因为它可以在文件系统的任何位置查找模块。最好自己指定文件夹,并且只允许在配置文件中使用文件名;但是您仍然必须通过在“文件名”中使用“..”和“/”之类的内容来防止黑客入侵路径。

    (我有一个项目可以做这样的事情。它从同样受用户控制的白名单中选择路径,所以我必须警告我的用户不要相互信任路径白名单文件。我也在目录中搜索模块,然后仅根据目录中的插件创建可能使用的插件白名单 - 所以没有带有“..”的有趣游戏。我仍然担心我忘记了什么。)

    一旦你有了一个模块名,你就可以通过名字来获取一个函数,比如:

    dynamic_module = load_module(some_path)
    try:
        func = getattr(dynamic_module, function_name)
    except AttributeError:
        raise ValueError("function not in module")
    

    无论如何,没有理由eval 任何东西,或者根据用户输入生成和导入代码。这是最不安全的。

    【讨论】:

    • 我会从配置文件中获取列表,不能这样存储!
    • 哦,那这个问题应该提到了。而且:谁来创建配置文件?如果您不想被调用的文件名函数存在潜在的安全风险(甚至可能不是来自您自己的代码,而是例如标准库,如果我以任何格式将"sys.exit" 放在配置文件中) .
    • 我会生成配置文件,但是我不能更改配置文件其他功能都依赖它
    • 我添加了更多方法来做到这一点。请小心,只允许用户真正需要的权力。
    • 你不会在一行中得到这个。
    【解决方案2】:

    我不确定我是否 100% 了解需求。也许问题中的更多细节。

    您正在寻找类似的东西吗?

    m = ["os"]
    f = ["getcwd"]
    command = ''.join([m[0], ".", f[0], "()"])
    # Put in some minimum sanity checking and sanitization!!!
    if ";" in command or <other dangerous string> in command:
            print("The line '{}' is suspicious. Will not run".format(command))
            sys.exit(1)
    print("This will error if the method isnt imported...")
    print(eval(''.join([m[0], ".", f[0], "()"])) )
    

    输出:

    This will error if the method isnt imported...
    /home/rightmire/eclipse-workspace/junkcode
    

    正如@KarlKnechtel 所指出的,命令来自外部文件是一个巨大的安全风险!

    【讨论】:

    • 如何传递参数来使用这个?
    • 您提到您从两个列表中获取导入名称。 file_name = ["file1", "file2", "file1"]function_name = ["function12", "function22", "funciton12"]。这也是我上面所说的,除了我叫file_name“m”(对于模块,这是文件名的正确术语。)我叫function_name“f”(对于函数):)
    • 我仍然强烈建议在运行任何源自外部输入的命令之前添加一些完整性检查和输入清理(也请参阅我的其他答案。)
    • 我没有从外部文件中获取导入名称,我只会得到函数名称和它所在的文件。
    • 谢谢您,无需更改任何内容,我的评论得到了答案。
    【解决方案3】:

    另一种选择。但是,这eval() 安全得多。

    有权访问您从配置文件中读取的列表的人可能会在您导入的列表中注入恶意代码。

    'from subprocess import call; subprocess.call(["rm", "-rf", "./*" stdout=/dev/null, stderr=/dev/null, shell=True)'
    

    代码:

    import re
    # You must first create a directory named "test_module"
    # You can do this with code if needed. 
    # Python recognizes a "module" as a module by the existence of an __init__.py
    # It will load that __init__.py at the "import" command, and you can access the methods it imports
    m = ["os", "sys", "subprocess"] # Modules to import from
    f = ["getcwd", "exit", "call; call('do', '---terrible-things')"] # Methods to import
    
    # Create an __init__.py
    with open("./test_module/__init__.py", "w") as FH:
        for count in range(0, len(m), 1):
            # Writes "from module import method" to __init.py 
            line = "from {} import {}\n".format(m[count], f[count])
            # !!!! SANITIZE THE LINE !!!!!
            if not re.match("^from [a-zA-Z0-9._]+ import [a-zA-Z0-9._]+$", line):
                print("The line '{}' is suspicious. Will not be entered into __init__.py!!".format(line))
                continue
            FH.write(line)
    
    import test_module
    print(test_module.getcwd())
    

    输出:

    The line 'from subprocess import call; call('do', '---terrible-things')' is suspicious. Will not be entered into __init__.py!!
    /home/rightmire/eclipse-workspace/junkcode
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-11
      • 1970-01-01
      • 2021-07-27
      • 2017-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多