3

当然,“除了明显的后果之外没有后果”是对我关于后果的问题的有效答案。

我帮助维护一个代码库,一些建议的代码结构大致如下:

# main.sh
export PYTHONPATH=$PYTHONPATH:$(pwd)
python main.py "brazil"
python main.py "france"
# main.py
from importlib.util import find_spec, module_from_spec
import sys

class BaseProcessor(object):
    # abstract base class
    pass

def run_for(country):
    spec = find_spec(country + ".processor")
    module = module_from_spec(spec)
    spec.loader.exec_module(module)
    processor = module.CountryProcessor()
    processor.do_something()

if __name__ == "__main__":
    run_for(sys.argv[1])
# brazil/processor.py
from main import BaseProcessor
class CountryProcessor(BaseProcessor):
    def do_something(self):
        print("This is the Brazil CountryProcessor")

# france/processor.py
from main import BaseProcessor
class CountryProcessor(BaseProcessor):
    def do_something(self):
        print("This is the France CountryProcessor")

中的代码run_for()使用 importlib 根据传入的字符串查找名为 processor 的模块country

输出如设计:

> ./main.sh 
This is the Brazil CountryProcessor
This is the France CountryProcessor

我们有一些类似的代码(但使用sys.path.insert())已经运行了几个月;我不知道同名类会导致问题的任何情况。

如果有的话,这种设计可能会产生什么意想不到的后果?

4

1 回答 1

0

在这里,我回答了我自己的问题,为以下潜在的意外后果提供了理由。我渴望听到关于现实世界影响的其他意见。可能只是我在床底下看到了怪物。

重要的是要规定,这是我们预计会出现一段时间的代码,整个组件当然比我的玩具示例复杂很多倍。

  1. 这种方法在以后会产生调试困难。
  • 传统的(例如可视的)调试器将显示加载了哪个类。如果所有类的名称都相同,我们就否认了调试帮助。
  • 因此,如果在导入过程中出现了一些错误,将更难捕获。我们本质上是在欺骗调试器。
  • 即使是基于日志的调试策略也容易出错。现在我们必须在每个类中维护不同的日志记录语句。复制/粘贴有忘记更改调试的风险。
  1. 直觉上,它似乎很容易出错
  • 这里有很多关于如何减轻来自两个不同库的意外名称冲突的 Q/A,通常是在导入时给它们不同的名称。为什么我们要与我们本来会努力避免的情况调情(如果我们不小心加载了其中两个)?
  • 如果一个天真的编辑器试图在我们的 importlib 魔术之后运行的方法中静态导入,这将导致问题。我确实不时在我们的代码中看到这样的静态导入。但我觉得还有一个更清晰的错误案例,我还不能清楚地表达出来。
  1. 它通常只是增加了复杂性,因此增加了维护成本。
  • 认知负荷很重要。“简单胜于复杂”(Tim Peters,“Python 之禅”)。
  • 与传统进口和工厂相比,这种进口机制增加了复杂性,但没有太多的补偿收益。(我在我的玩具示例中省略了它,但导入代码实际上分布在 3-4 个函数中。)我想,如果我们有 40 个国家/地区,假设的好处是不必进行 40 次静态导入,但有更直观的方法那。
  • 此代码不支持在同一个 Python 进程中加载​​多个国家/地区。估计现在不会发生。但谁能说它不能走下去呢?好的软件设计不会试图预测未来,而是尽可能长时间地保持灵活性。

在现实生活中,我认为这种方法反映了本可以部署到更持久解决方案的编程工作。既然工作已经完成,那么机会成本可能并不重要。(就上下文而言,建议使用此代码替换一些确实使用工厂和静态导入的代码,因此 IMO 的最终效果是在工作机制中引入了反模式。)


提议的替代方案

那么我将如何处理呢?如果导入的数量适中(例如,最多 40 或 50 个),我将使用简单的工厂类(甚至只是函数)对所有子类进行静态导入。工厂可以维护一个查找表,或者(更好)它可以推断类名。将蛇盒转换为骆驼盒几乎是微不足道的。

如果数字更大,我仍然认为没有理由不加载所有类。只需遍历子目录列表一次,将它们转换为驼峰式大小写,然后在转换为类名的驼峰式大小写后简单地导入它们。

Python 中额外导入的内存占用是如此微不足道,以至于我认为通常 Python 样式中的多余导入几乎没有缺点。如果出于某种原因我们想要更明智地导入,我们可以将选择性导入保留在工厂类中,但仍区分不同类的名称。再一次,只要我们遵循模式,构造名称几乎是微不足道的。

于 2021-02-28T18:02:23.133 回答