【问题标题】:How can I break up this py file in a DRY fashion?如何以 DRY 方式分解这个 py 文件?
【发布时间】:2021-02-07 17:59:16
【问题描述】:

在 Flask 应用程序的业务逻辑上下文中,我正在编写 大量 类的这些“定义”实例,将它们放在一个列表中,并在需要的地方导入该列表.在构建它之外,列表被视为静态的。

简化示例:

definitions.py

from my_object import MyObject

definition_registry = list()

# team 1, widget 1 definition
_definition = MyObject()
_definition.name = "team 1 widget 1"
_definition.coercer = str
definition_registry.append(_definition)

# team 1, widget 2 definition
_definition = MyObject()
_definition.name = "team 1 widget 2"
_definition.coercer = int
definition_registry.append(_definition)

# team 2, widget 1 definition
_definition = MyObject()
_definition.name = "team 2 widget 1"
_definition.coercer = float
definition_registry.append(_definition)

my_object.py:

class MyObject:
    def __init__(self):
        self.name = "unnamed"
        self.coercer = int

    def __repr__(self):
        return f"MyObject instance: {self.name} / {self.coercer}"

ma​​in.py

from definitions import definition_registry

if __name__ == '__main__':
    print(definition_registry)

输出:

[MyObject instance: team 1 widget 1 / <class 'str'>, MyObject instance: team 1 widget 2 / <class 'int'>, MyObject instance: team 2 widget 1 / <class 'float'>]

如何将definitions.py 拆分为多个文件(team_1.pyteam_2.py、...)?

重要提示:真正的 MyObject 的实例必须在 python 中定义。在我的示例中,coercer 属性旨在作为一个占位符来强化这一事实。

我曾考虑过使用exec,但这通常是一种不好的做法,而且这似乎不是该规则的一个好的例外。例如,将 definitions.py 的第 5 到 9 行放入 team1w1.py 并用 exec(open(team1w1.py).read()) 替换它们是可行的,但 PyCharm 的调试器不会逐行执行 team1w1.py

另一种方法是做类似的事情

from team1w1 import definition
definition_registry.append(definition)

from team1w2 import definition
definition_registry.append(definition)
...

这更好,但它仍然闻起来,因为

  • from ... import definition 在同一个文件中一遍又一遍地重复
  • import MyObject 必须为每个定义文件重复

【问题讨论】:

    标签: python dry


    【解决方案1】:

    有几种方法可以做到这一点。搜索实现插件的代码。这是一种方法:

    你的代码结构如下:

    /myproject
        main.py
        my_object.py
        definitions/
            __init__.py
            team_1.py
            team_2.py
    

    ma​​in.py

    这与您的代码基本相同,只是有一些额外的代码来显示正在发生的事情。

    import sys
    
    before = set(sys.modules.keys())
    
    import definitions
    
    after = set(sys.modules.keys())
    
    if __name__ == '__main__':
        print('\nRegistry:\n')
        for item in definitions.registry:
            print(f"    {item}")
        print()
    
        # this is just to show how to access things in team_1
        print(definitions.team_1.foo)
        print()
    
        # this shows that the modules 'definitions', 'definitions.team_1',
        # and 'definitions.team_2' have been imported (plus others)
        print(after - before)
    

    my_object.py

    正如其他人指出的那样,MyObject 可以将名称和强制器作为参数 到__init__(),注册表可以是一个类变量,注册由__init__()处理。

    class MyObject:
        registry = []
        
        def __init__(self, name="unnamed", coercer=str):
            self.name = name
            self.coercer = coercer
            
            MyObject.registry.append(self)
            
        def __repr__(self):
            return f"MyObject instance: {self.name} / {self.coercer}"
    

    定义/init.py

    这是技术的核心。导入包时,__init__.py 会运行,例如当main.py 具有import definitions 时。主要思想是使用pathlib.Path.glob()查找所有名称为team_*的文件并使用importlib.import_module()导入它们:

    import importlib
    import my_object
    import pathlib
    
    # this is an alias to the class variable so it can be referenced
    # like definitions.registry
    registry = my_object.MyObject.registry
    
    package_name = __package__
    package_path = pathlib.Path(__package__)
    
    print(f"importing {package_name} from {__file__}")
    
    for file_path in package_path.glob('team_*.py'):
        module_name = file_path.stem
        print(f"    importing {module_name} from {file_path}")
        importlib.import_module(f"{package_name}.{module_name}")
    
    print("    done")
    

    定义/team_1.py

    需要导入MyObject 才能创建实例。表明模块中可以实例化多个MyObjects,以及其他东西。

    import pathlib
    from my_object import MyObject
    
    file_name = pathlib.Path(__file__).stem
    
    print(f"        in {__package__}.{file_name}")
    
    # assign the object (can get it through registry or as team_1.widget_1
    widget_1 = MyObject("team 1 widget 1", str)
    
    # don't assign the object (can only get it through the registry)
    MyObject("team 1 widget 2", int)
    
    # can define other things too (variables, functions, classes, etc.)
    foo = 'this is team_1.foo'
    

    定义/team_2.py

    from my_object import MyObject
    
    print(f"        in {__package__}.{__file__}")
    
    # team 2, widget 1 definition
    MyObject("team 2 widget 1", float)
    

    其他东西

    如果你不能改变MyObject,也许你可以继承它并使用team_1.py中的子类等等。

    或者,定义一个make_myobject() 工厂函数:

    def make_myobject(name="unknown", coercer=str):
        definition = MyObject()
        definition.name = name
        definition.coercer = coercer
        registry.append(definition)
        return definition
    

    然后team_1.py 看起来像:

    from my_object import make_myobject
    
    make_myobject("team 1 widget 1", int)
    
    ....
    

    最后,intstr 以及其他类型、类等可以通过名称查找。因此,在您的简化示例中,MyObject()make_myobject() 可以取 coercer 的名称并进行查找。

    import sys
    
    def find_coercer(name):
        """Find the thing with the given name. If it is a dotted name, look
        it up in the named module. If it isn't a dotted name, look it up in
        the 'builtins' module.
        """
        module, _, name = name.strip().rpartition('.')
    
        if module == '':
            module = 'builtins'
    
        coercer = getattr(sys.modules[module], name)
    
        return coercer
    

    【讨论】:

      【解决方案2】:
      class MyObject():
      
          all_instances = []
      
          def __init__(name, coercer):
              self.name = name
              self.coercer = coercer
              all_instance.append(self)
      

      您的安装程序使用情况是

      import MyObject
      
      # Put your specifications into a readable list
      widget_specs = [
          ["Team 1 widget 1", str],
          ["Team 1 widget 2", float],
          ...
      ]
      for name, coercer in widget_specs:
          _ = MyObject(name, coercer)
      

      然后您访问MyObject.all_instances 以获得所需的小部件对象。

      这会解决问题吗,或者至少让你足够接近?

      【讨论】:

      • 谢谢,但我的问题是将definitions.py 分解为多个文件。
      【解决方案3】:

      我会这样安排:

      myobject.py

      class MyObject:
          def __init__(self, name="unnamed", coercer= int):
              self.name = name
              self.coercer = coercer
      
          def __repr__(self):
              return f"MyObject instance: {self.name} / {self.coercer}"
      

      definitions.py

      # just done for 2 items - If you want to distribute those definitions, 
      # you can define each element in a single file
      teams = [{"name": "team 1 widget 1", "coercer": str}, {"name": "team 1 widget 2", "coercer": int}]
      
      definition_registry = [MyObject(**element) for element in teams]
      

      【讨论】:

      • 谢谢,但我的问题的重点是将definitions.py 分解为多个文件。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-25
      • 1970-01-01
      • 2022-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多