【问题标题】:Is there a way to lazily vectorize a given class?有没有办法懒惰地矢量化给定的类?
【发布时间】:2013-11-04 23:50:31
【问题描述】:

在你反对之前:每个程序员都是懒惰的。否则你不会手动编程和做所有事情!


一个简单的例子。

我有一个类Line,它包含处理一条线所需的一切(例如使用两个顶点/点创建的对象)。这个类实际上非常复杂,为了简单、可维护和清晰起见,我想保持这样:我用两个顶点提供一个类,并输出一些困难的结果,例如两点之间的距离。

问题

现在,问题在于,虽然我需要跟踪这些单独的行,但有时我还希望将它们作为一个整体来处理。例如,我想计算由多条线组成的路径的长度。

目前的解决方案和不足

我创建了一个名为 Lines 的类,它还为此提供了一些方法。

Lines 目前是numpy.ndarray 的孩子,这不是很好:

  • 命名空间被 ndarray 的方法弄乱了;
  • 我正在使用ufuncs 在Lines 中围绕Line 的方法提供一个包装器,但是在这样的两个地方维护代码很繁琐。

问题

那么,你们将如何有效地“矢量化”Line 类,同时跟踪各个行?

我可以将所有内容都放在Lines 中,并将Line 视为一个特例,我试过了,但这确实会影响清晰度,并且使各个行的所有引用都很难实现和维护。


代码示例

import numpy as np
class Line:
    def __init__ (self, input_points):
        assert len(np.array(input_points).squeeze()) == 2
        self._points = np.array(input_points)

    def get_distance(self):
        return np.sqrt(((self._points[0]-self._points[1])**2).sum())

from itertools import combinations
class Lines(np.ndarray):

   _get_dists = np.frompyfunc(Line.get_distance, 1, 1)

   def __new__(cls, data):
       comb = [Line(el) for el in combinations(data, 2)]
       obj = np.asarray(comb).view(cls)
       obj = obj.squeeze()
       return obj

   def get_all_distances(self):
       return self._get_dists(self)

【问题讨论】:

  • Lines 只是Lines 的一般集合,还是它们以某种方式相互关联?另外,你能举一个你想“矢量化”的方法的具体例子吗?
  • 目前,所有数据都被加载并在Lines中拆分,以创建一组Line对象。这些对象是Lines 暂时保留的唯一数据。我目前正在研究一种将 all 数据作为 numpy 数组保存在内存中的方法。 Lines 将直接访问该数组,而对该数组子部分的引用将传递给 Line 实例。
  • 拥有一些代码(不是你的整个程序,只是一个小的、可运行的示例),而不必猜测……
  • 这个距离如何? (self._points[0] + self._points[1])/2
  • @Gael:嗯,你已经成功了一半。 ;)

标签: python numpy vectorization


【解决方案1】:

如果您希望 Lines 能够使用 ndarray 方法,但不让其公共命名空间与这些方法混淆,请使用委托而不是继承。换句话说,而不是这样:

class Lines(np.ndarray):
    def __init__(self, whatever):
        super().__init__(stuff)
    def dostuff(self, thingies):
        np.do_thingy(self.stuff(spam))
        return self.spam(eggs)

……这样做:

class Lines(object):
    def __init__(self, whatever):
        self.lines = np.array(stuff)
    def dostuff(self, thingies):
        np.do_thingy(self.lines.stuff(spam))
        return self.lines.spam(eggs)

同时,听起来您有一组 Line 方法,您希望在 Lines 中进行 ufuncify,并且您厌倦了重复自己。所以动态地这样做。下面是一个简单的例子来告诉你这个想法:

for name in 'bam', 'biff', 'pow', 'kazaam':
    func = getattr(Line, name)
    ufunc = np.frompyfunc(func, 1, 1)
    setattr(Lines, name, ufunc)

【讨论】:

  • 我以前从未听说过这种叫做“委托”的东西..但我立刻明白你的意思..+1!
  • @qwwqwwq:因为同一个词有太多其他含义,我已经逐渐停止使用这个名字了,但我不知道还有什么名字。特别是,在 .NET 语言和 ObjC 中显式构建绑定方法称为“委托”。至少在 C++ 中,它意味着原始含义的一个特定子集(它们笨拙地相当于 Python 在 3 行 __getattr__ 中所做的)。
  • 不错。这样,我什至可以稍微更改包装器中的方法名称。
  • @Gael:要记住的一件小事:如果您以新名称存储方法,您通常也希望设置其__name__ 属性,或者您的错误回溯等可以变得混乱。有关详细信息,请参阅functools.wraps(以及文档顶部的源链接,因为它也用作示例代码)。
【解决方案2】:

[写完这个答案后,我看到@abarnert 给出了相同的答案,但这个答案看起来不同,所以我发布它,以防有帮助]

您可以显式包装您需要的每个方法和属性(对方法使用一个 generic 包装函数,对属性使用一个包装函数),然后手动将包装结果分配给 Lines 类:

class Line(object):
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2
    def diff(self):
        return self.p2 - self.p1
    @property
    def point1(self):
        return self.p1

class Lines(object):
    def __init__(self, lines):
        self._lines = np.array(lines, dtype = object)

def _wrapped_method(mname):
    def f(self, *args, **kwargs):
        return np.array([ getattr(line, mname)(*args, **kwargs) for line in self._lines ])
    return f

def _wrapped_property(pname):
    def f(self):
        return np.array([ getattr(line, pname) for line in self._lines ])
    return property(f)

wrapped_methods = ( 'diff', )
for mname in wrapped_methods:
    setattr(Lines, mname, _wrapped_method(mname))

wrapped_properties = ( 'point1', )
for pname in wrapped_properties:
    setattr(Lines, pname, _wrapped_property(pname))

lines = Lines([ Line(3,5) ])  # 3,5 are not really points, but good enough for demonstration
print '%r' % lines.diff()
# array([2])
print '%r' % lines.point1
# array([3])

【讨论】:

  • 不错。与其他答案一样,这给了我一些探索的想法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
  • 1970-01-01
  • 2016-03-26
  • 1970-01-01
  • 1970-01-01
  • 2014-10-10
相关资源
最近更新 更多