我正在研究一种方法,通过查看一个或多个的某些内容来收集(统计)有关三个量的信息(此处称为A
,B
和C
)文件。整个事情或多或少等同于以下代码:
def get_statistics(self, file, some_option = True):
A_list = list()
B_list = list()
C_list = list()
with open(file, 'r') as f:
for line in f:
A, B, C = self._analyze(line, some_option)
A_list.append(A)
B_list.append(B)
C_list.append(C)
return A_list, B_list, C_list
这很容易理解并且工作正常(除了你可能想要指出重复的append
不是那么有效,但这不是问题的重点)但是产生的缺点是巨件列出A_list
,B_list
和C_list
。实际上,当循环遍历多个文件时,列表可能变得如此之大,以至于我们需要担心它们是否适合内存而不会引起问题。
通常,此方法的用户不会对完整数据集感兴趣,但只需要一些统计信息。也许用户只想要数量的均值和方差,或者有兴趣绘制直方图来可视化数据。嗯,很简单,让我们只修改方法的签名,并允许提供自定义类来收集感兴趣的数据。我只会通过调用append()
方法与这些类进行交互,用户可以决定该方法的作用。例如,它可以只对所有传入值求和并计算它们的数量,以便在需要时计算平均值。我修改过的方法可能如下所示:
def get_statistics(self, file, some_option = True, A_cls = list, B_cls = list, C_cls = list):
A_store = A_cls()
B_store = B_cls()
C_store = C_cls()
...
return A_store, B_store, C_store
这种方法使用户可以灵活地决定应该存储多少潜在的巨大数据集。如果省略A_cls
,B_cls
和C_cls
的参数,则会恢复第一个方法的功能。现在考虑想要绘制直方图的用户的示例,需要将初始化参数传递给那些类,例如bin大小或bin数。好的,让我们添加一种方法来将初始参数传递给这些类:
def get_statistics(self, file, some_option = True, A_cls = list, A_args = [], A_kwargs = {}, B_cls = list, B_args = [], B_kwargs = {}, C_cls = list, C_args = [], C_kwargs = {}):
A_store = A_cls(*A_args, **A_kwargs)
B_store = B_cls(*B_args, **B_kwargs)
C_store = C_cls(*C_args, **C_kwargs)
...
return A_store, B_store, C_store
哇,突然间,我用 11个参数加上self
使我最初易于阅读的功能变得臃肿!当然,他们中的大多数都有合理的默认值,但这仍然是编码和记录的噩梦。
那么如何保持我的界面有点整洁?一种选择是省略传递初始化参数的可能性,并且只有五个参数,我认为这是可以容忍的。然后,用户需要使用闭包来恢复此功能:
def make_A_helper(A_cls, *args, **kwargs):
def make_A():
return A_cls(*args, **kwargs)
return make_A
A_helper = make_A_helper(A_cls, *some_args, **some_other_kwargs)
然后将A_helper
传递给我的方法会隐藏在用户可能定义的some_args
和some_other_kwargs
中,但每次编码都会显得麻烦且烦人。我觉得用户自然会问,为什么他不能直接将some_args
和some_other_kwargs
传递给我的方法。那么处理这种情况的好方法是什么呢?
TL; DR: Python提供了让用户通过将类作为参数传递给函数来影响一段代码行为的机会,并将这些类的内部处理为黑盒子。在没有大量*args
和**kwargs
传递的情况下,使用此功能实现功能的最佳方法是什么?让用户将make函数编写为类生成器是否可以接受?是否有更好的方法,如提供某种register()
方法,使这些类知道应该使用它们的代码?
答案 0 :(得分:1)
这里最简单的答案是让用户传递对象而不是类,即:
to Only use plain FTP (insecure)
另一个解决方案是提供迭代器,让用户做class Something():
def get_statistics(self, file, some_option=True, acollector=None, bcollector=None, ccollector=None):
if acollector is None:
acollector = list()
if bcollector is None:
bcollector = list()
if ccollector is None:
ccollector = list()
with open(file, 'r') as f:
for line in f:
A, B, C = self._analyze(line, some_option)
acollector.append(A)
bcollector.append(B)
ccollector.append(C)
return acollector, bcollector, ccollector
A, B, C
然后用户可以自己收集信息:
def iterfile(self, file, some_option=True):
with open(file, 'r') as f:
for line in f:
yield self._analyze(line, some_option)
这里的好处是内存使用不再是你的问题,而是用户的xD
FWIW您可以通过基于something = Something()
for a, b, c in something.iterfile("a/file.ext"):
do_something_with(a, b, c)
重新实现get_statistics()
来结合两者,从而保持兼容性:
iterfile()