如何以 DRY 方式分解此 py 文件?



在 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}"

main.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

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

你编码的结构是这样的:

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

main.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}"

定义/初始化.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
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

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

我会这样构建它:

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]

最新更新