有没有办法懒惰地矢量化给定的类?

时间:2013-10-25 18:26:10

标签: python numpy vectorization

在你反对之前:每个程序员都很懒惰。否则你不会手动编程并做任何事情!


一个简单的例子。

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

问题

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

当前的解决方案和缺点

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

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

  • 命名空间被ndarray的方法混乱;
  • 我正在使用ufunc来围绕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)

2 个答案:

答案 0 :(得分:3)

如果您希望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 :(得分:1)

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

您可以显式地包装您需要的每个方法和属性(对方法使用一个泛型包装函数,为属性使用一个),并手动将包装结果分配给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])