【发布时间】:2018-03-28 11:36:33
【问题描述】:
我正在研究一种方法,该方法通过查看一个或多个文件的某些内容来收集三个数量(称为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
这很容易理解并且工作正常(除了你可能想指出重复的appending 效率不高,但这不是问题的重点)但有产生 huge 列出了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() 方法来让应该使用它们的代码知道这些类?
【问题讨论】:
-
是什么阻止您使用三个简单的阅读器函数从文件中提取 A、B 和 C?您的代码中几乎没有类的情况,因为没有要存储的状态,真的。
-
好点是我故意遗漏了所有这些细节。拆分 A、B 和 C 的提取会导致计算时间增加 3 倍,因为当您计算另一个时,其中两个是免费的。此外,
self._analyze()确实 大量使用内部状态。更改类的属性基本上会改变您查看数据的方式。我明白你的意思,但我强烈支持拥有一个类(因为存在内部状态)和拥有一个方法(因为 A、B 和 C 是强耦合的)。 -
希望这不是一种读取 csv 文件的方式。 ) 您的设计可能是一个读取器类/函数,它将行流作为输入(它比读取内部文件更干净)并产生 A、B 和 C 以供进一步处理/累积。通过这种方式,我看到
self._analyze()变为Reader(incoming_stream).iterate()或yield_parsed_values(incoming_stream)取决于使用了多少内部状态。这限制了解析例程的责任并且更容易测试。您可以稍后将其集成到更大的类中。 -
不是 CSV,我可以向你保证。 :-) 实际上数据以某种二进制格式存储,其中的大块放入
numpy数组并处理以获得A、B 和C。据我所知,本质上是Reader(incoming_stream).iterate()方法。