【问题标题】:Call a function for every script inside a folder为文件夹中的每个脚本调用一个函数
【发布时间】:2021-02-21 10:23:04
【问题描述】:

有没有办法(仅使用 python。即:没有 bash 脚本或其他语言代码)在文件夹内的每个脚本中调用特定函数,而无需显式导入所有函数。

例如,假设这是我的结构:

main.py
modules/
    module1.py
    module2.py
    module3.py
    module4.py

每个moduleX.py 都有这个代码:

import os

def generic_function(caller):
    print('{} was called by {}'.format(os.path.basename(__file__), caller))

def internal_function():
    print('ERROR: Someone called an internal function')

main.py 有这个代码:

import modules
import os

for module in modules.some_magic_function():
    module.generic_function(os.path.basename(__file__))

所以如果我运行main.py,我应该得到这个输出:

module1.py was called by main.py
module2.py was called by main.py
module3.py was called by main.py
module4.py was called by main.py

*请注意internal_function()不应被调用(不像question)。另外,我不想显式声明每个模块文件,即使在 __init__.py

顺便说一句,我不介意为此使用类。事实上,它可能会更好。

【问题讨论】:

  • 你能澄清一下“不需要全部导入”的意思吗?你的意思是没有明确导入它们,例如import module1,或者你的意思是不导入它们根本,例如仅隐式导入已定义 generic_function 的那些?
  • @MisterMiyagi “不需要全部导入”我的意思是不需要做import module1, module2, module3, module4, module5(即显式导入它们),但我至少需要有一种控制权模块(例如,如果 generic_function() 为某个模块返回 True,则为同一模块调用另一个函数)

标签: python module


【解决方案1】:

虽然 sophros 的方法可以快速且足以隐式导入模块,但您可能会遇到与控制每个模块或复杂调用相关的问题(例如为每个调用设置条件)。所以我采用了另一种方法:

首先,我创建了一个声明了函数(现在是方法)的类。有了这个,我可以避免检查该方法是否存在,因为如果我没有声明它,我可以使用默认方法:

# main.py
class BaseModule:
    def __init__(self):
        # Any code
    
    def generic_function(self, caller):
        # This could be a Print (or default return value) or an Exception
        raise Exception('generic_function wasn\'t overridden or it was used with super')
    

然后我创建了另一个扩展 BaseModule 的类。遗憾的是,在不知道子类名称的情况下,我无法找到检查继承的好方法,因此我为每个模块使用了相同的名称:

# modules/moduleX.py
from main import BaseModule

class GenericModule(BaseModule):
    def __init__(self):
        BaseModule.__init__(self)
        # Any code
    
    def generic_function(self, caller):
        print('{} was called by {}'.format(os.path.basename(__file__), caller))

最后,在我的main.py 中,我使用importlib 动态导入模块并为每个模块保存一个实例,以便以后使用它们(为简单起见,我没有将它们保存在下面代码,但它很容易使用列表并在其上附加每个实例):

# main.py
import importlib
import os

if __name__ == '__main__':
    relPath = 'modules' # This has to be relative to the working directory

    for pyFile in os.listdir('./' + relPath):
        # just load python (.py) files except for __init__.py or similars
        if pyFile.endswith('.py') and not pyFile.startswith('__'):
            # each module has to be loaded with dots instead of slashes in the path and without the extension. Also, modules folder must have a __init___.py file
            module = importlib.import_module('{}.{}'.format(relPath, pyFile[:-3]))
            # we have to test if there is actually a class defined in the module. This was extracted from [1]
            try:
                moduleInstance = module.GenericModule(self)
                moduleInstance.generic_function(os.path.basename(__file__)) # You can actually do whatever you want here. You can save the moduleInstance in a list and call the function (method) later, or save its return value.
            except (AttributeError) as e:
                # NOTE: This will be fired if there is ANY AttributeError exception, including those that are related to a typo, so you should print or raise something here for diagnosting
                print('WARN:', pyFile, 'doesn\'t has GenericModule class or there was a typo in its content')

参考资料:

[1]Check for class existence

[2]Import module dynamically

[3]Method Overriding in Python

【讨论】:

    【解决方案2】:

    您可以使用execeval 来执行此操作。所以它会大致这样(exec):

    def magic_execute():
        import os
        import glob
        for pyfl in glob.glob(os.path(MYPATH, '*.py'):
            with open(pyfl, 'rt') as fh:
                pycode = fh.read()
                pycode += '\ngeneric_function({})'.format(__file__)
                exec(pycode)
    

    这里的假设是您根本不打算导入模块。

    请注意,有numerous security issues 与以这种不受限制的方式使用exec 有关。你可以increase security a bit

    【讨论】:

    • 对不起,我不清楚我想如何导入模块。 eval 可能是一个不错的选择,因为某些函数会返回一些值(主要是布尔值),但我担心读取和调用每个模块可能会产生内存泄漏或类似的东西,因为我的原始 main.py 无限期运行(对于为了问题简单起见,我没有告诉这个)。有没有办法拥有类似于您提议的代码的东西,但每个模块只导入一次,然后从列表或类似的东西中引用它们?
    • 这是一个毫无根据的担心 - 每个循环只会导入一次模块,并在执行后进行垃圾收集。这种方法不用担心内存泄漏。
    • 很高兴知道(我有预感,但我不确定)。我还有一个问题:如何为同一个模块调用两个函数,但取决于第一个函数的值。例如:如果generic_function() 返回true,则调用internal_function()(对于同一模块)并中断循环。否则,转到下一个模块并重复该过程。
    • 您可以添加一段代码来执行if 语句(类似于我添加执行您的generic_function。顺便说一句,这应该是一个不同的问题。
    猜你喜欢
    • 2021-09-03
    • 2020-07-04
    • 2020-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-03
    • 2023-01-19
    • 1970-01-01
    相关资源
    最近更新 更多