在 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.py
、team_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)
....
最后,int
、str
和其他类型、类等都可以按名称查找。 因此,在您的简化示例中,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]