我怎样才能以 DRY 方式分解这个 py 文件?
How can I break up this py file in a DRY fashion?
在 Flask 应用程序的业务逻辑上下文中,我正在编写 一吨 这些 class 的“定义”实例,将它们放入一个列表,并在需要的地方导入列表。在构建它之外,该列表被视为静态的。
简化示例:
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
、...)?
重要提示: 必须在 python 中定义真实 MyObject 的实例。在我的示例中,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
必须为每个定义文件重复
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]
有几种方法可以做到这一点。搜索代码以实现插件。这是一种方法:
您的代码结构如下:
/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
可以将 name 和 coercer 作为参数
__init__()
,注册表可以是 class 变量,注册由 __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")
definitions/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'
definitions/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
,也许你可以subclass它并使用team_1.py
中的subclass等
或者,定义一个 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
等类型,classes等可以按名称查找。因此,在您的简化示例中,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
在 Flask 应用程序的业务逻辑上下文中,我正在编写 一吨 这些 class 的“定义”实例,将它们放入一个列表,并在需要的地方导入列表。在构建它之外,该列表被视为静态的。
简化示例:
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
、...)?
重要提示: 必须在 python 中定义真实 MyObject 的实例。在我的示例中,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
必须为每个定义文件重复
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]
有几种方法可以做到这一点。搜索代码以实现插件。这是一种方法:
您的代码结构如下:
/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
可以将 name 和 coercer 作为参数
__init__()
,注册表可以是 class 变量,注册由 __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")
definitions/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'
definitions/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
,也许你可以subclass它并使用team_1.py
中的subclass等
或者,定义一个 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
等类型,classes等可以按名称查找。因此,在您的简化示例中,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