【问题标题】:Pythonic way to aggregate object properties in memory efficient way?以内存有效的方式聚合对象属性的 Pythonic 方法?
【发布时间】:2016-02-02 15:02:32
【问题描述】:

例如,我们有很多这样的对象:

class KeyStatisticEntry:
    def __init__(self, value=""):
        self.usedBytes = len(value)
        self.encoding = get_string_encoding(value)

    @property
    def total(self):
        overhead = get_object_overhead(self.usedBytes)
        if self.encoding == 'some value':
            return overhead
        else:
            return self.usedBytes + overhead
    @property
    def aligned(self):
        return some_func_with(self.usedBytes)

    # Here is lots of calculated properties on basis of existing properties

并且我们需要聚合关于这个对象的大量指标——它属性的最小值、最大值、总和、平均值、标准差值。目前我用这样的代码来做:

used_bytes = [] 
total_bytes = []
aligned_bytes = []
encodings = []

for obj in keys.items():
    used_bytes.append(obj.usedBytes)
    total_bytes.append(obj.total)
    aligned_bytes.append(obj.aligned)
    encodings.append(obj.encoding)

total_elements = len(used_bytes)
used_user = sum(used_bytes)
used_real = sum(total_bytes)
aligned = sum(aligned_bytes)
mean = statistics.mean(used_bytes)

问题:

这里是不是更“pythonic”的方式,具有更好的性能和内存使用?

【问题讨论】:

    标签: python list aggregate


    【解决方案1】:

    您可以使用operator.attrgetter 来获取对象的多个属性,然后使用itertools.zip_longest(Python 2.X 中的itertools.izip_longest)将相关属性附加在一起。

    from operator import attrgetter
    all_result = [attrgetter('usedBytes','total','aligned','encoding')(obj) for obj in keys.items()]
    

    或者使用生成器表达式来创建生成器而不是列表:

    all_result = (attrgetter('usedBytes','total','aligned','encoding')(obj) for obj in keys.items())
    

    然后使用zip_longest:

    used_bytes, total_bytes, aligned_bytes, encodings = zip_longest(*all_results)
    

    然后使用map 函数将sum 函数应用于需要求和的可迭代对象:

    used_user, used_real, aligned = map(sum,(used_bytes, total_bytes, aligned_bytes))
    

    lenmean 分别是:

    total_elements = len(used_bytes)
    mean = statistics.mean(used_bytes)
    

    如果您想将所有子列表作为生成器处理(在内存使用方面更优化,在运行时方面性能更低),您可以使用一个新类,以便使用生成器单独计算期望结果:

    from itertools import tee
    class Aggregator:
        def __init__(self, all_obj):
            self.obj = all_obj
            self.used_user, self.mean = self.getTotalBytesAndMean()
            self.total_elements = len(self.all_obj)
            self.aligned = self.getAligned()
    
        def getTotalBytesAndMean(self):
            iter_1, iter_2 = tee((obj.usedBytes for obj in self.all_obj))
            return sum(iter_1), statistics.mean(iter_2)
    
        def getTotal(self):
            return sum(obj.total for obj in self.all_obj)
    
        def getAligned(self):
            return sum(obj.aligned for obj in self.all_obj)
    
        def getEncoding(self):
            return (obj.encoding for obj in self.all_obj)
    

    那么你可以这样做:

    Agg = Aggregator(keys.items())
    
    # And simply access to attributes
    Agg.used_user
    

    【讨论】:

    • all_result 实际上是生成器列表吗?
    • @misterion 第一个不是生成器列表,因为attrgetter 不返回生成器。如果你想要一个生成器,你可以使用生成器表达式而不是列表推导。检查编辑。
    • tnx @Kasramvd,我是 python 新手——你能帮忙解决一下吗:如果不使用 zip_longest 实现数据,就无法聚合所有数据。我的意思是如果我有 3200 万个对象的列表,我会有 N 个 3200 万个属性的列表?这里没有办法用生成器进行计算吗?
    • @misterion 在这种情况下,我认为您需要使用另一个对象在生成器模式下聚合所有这些对象。让我更新答案。 ——
    • 太棒了! Tnx @Kasramvd!
    【解决方案2】:

    可能有更好的内存使用方法,使用(隐式)生成器而不是列表来获取所有信息。我不确定如果您在同一个列表上进行许多计算(例如 usedBytes)会更好。但是请注意,您不能在生成器上使用len(但长度无论如何都是输入列表的长度):

    total_elements = len(keys.items())
    used_user = sum(obj.usedBytes for obj in keys.items())
    used_real = sum(obj.total for obj in keys.items())
    aligned = sum(obj.aligned for obj in keys.items())
    mean = statistics.mean(obj.usedBytes for obj in keys.items())
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-03
      • 2018-11-10
      • 2012-01-18
      • 1970-01-01
      • 2016-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多