【问题标题】:Best way to lazily update calculated properties by hashing dependent attributes通过散列依赖属性延迟更新计算属性的最佳方法
【发布时间】:2014-07-19 12:24:02
【问题描述】:

我正在尝试制作一堆几何对象,这些对象具有其固有的几何属性(中心点、半径、长度等),以及有助于绘制它们的属性(例如 a 的 x、y、z 坐标)三角网格、圆弧分辨率等)。

由于计算 x、y、z 坐标对于某些形状来说是一项昂贵的任务(例如带有圆角的三棱柱),我不想在每次更改属性时都这样做,但只有当要求坐标。即便如此,如果形状的定义没有改变,则不需要重新计算它们。

所以我的解决方案是创建一个“哈希”,它只是定义形状“状态”的所有参数的元组。如果哈希值不变,则可以重新使用之前计算的坐标,否则必须重新计算坐标。所以我使用哈希作为存储形状定义的签名或指纹的一种方式。

我认为我的工作是有效的,但我想知道是否有更强大的方法可以利用__hash__ 或 id 之类的东西来处理这个问题。这对我来说有点矫枉过正,但我​​愿意接受建议。

这是我对球体的实现。我在最后使用 Mayavi 进行绘图,如果您没有 Mayavi,您可以跳过/忽略它。

#StdLib Imports
import os

#Numpy Imports
import numpy as np
from numpy import sin, cos, pi


class Sphere(object):
    """
    Class for a sphere
    """
    def __init__(self, c=None, r=None, n=None):
        super(Sphere, self).__init__()

        #Initial defaults
        self._coordinates = None
        self._c = np.array([0.0, 0.0, 0.0])
        self._r = 1.0
        self._n = 20
        self._hash = []

        #Assign Inputs
        if c is not None:
            self._c = c

        if r is not None:
            self._r = r

        if n is not None:
            self._n = n

    @property
    def c(self):
        return self._c

    @c.setter
    def c(self, val):
        self._c = val

    @property
    def r(self):
        return self._r

    @r.setter
    def r(self, val):
        self._r = val

    @property
    def n(self):
        return self._n

    @n.setter
    def n(self, val):
        self._n = val

    @property
    def coordinates(self):
        self._lazy_update()
        return self._coordinates

    def _lazy_update(self):
        new_hash = self._get_hash()
        old_hash = self._hash
        if new_hash != old_hash:
            self._update_coordinates()

    def _get_hash(self):
        return tuple(map(tuple, [self._c, [self._r, self._n]]))

    def _update_coordinates(self):

        c, r, n = self._c, self._r, self._n

        dphi, dtheta = pi / n, pi / n
        [phi, theta] = np.mgrid[0:pi + dphi*1.0:dphi,
                                0:2*pi + dtheta*1.0:dtheta]

        x = c[0] + r * cos(phi) * sin(theta)
        y = c[1] + r * sin(phi) * sin(theta)
        z = c[2] + r * cos(theta)

        self._coordinates = x, y, z
        self._hash = self._get_hash()


if __name__ == '__main__':

    from mayavi import mlab

    ns = [4, 6, 8, 10, 20, 50]

    sphere = Sphere()

    for i, n in enumerate(ns):
        sphere.c = [i*2.2, 0.0, 0.0]
        sphere.n = n

        mlab.mesh(*sphere.coordinates, representation='wireframe')

    mlab.show()

按照建议,这是一个使用字典将哈希存储为键的版本:

#StdLib Imports
import os

#Numpy Imports
import numpy as np
from numpy import sin, cos, pi


class Sphere(object):
    """
    Class for a sphere
    """
    def __init__(self, c=None, r=None, n=None):
        super(Sphere, self).__init__()

        #Initial defaults
        self._coordinates = {}
        self._c = np.array([0.0, 0.0, 0.0])
        self._r = 1.0
        self._n = 20

        #Assign Inputs
        if c is not None:
            self._c = c

        if r is not None:
            self._r = r

        if n is not None:
            self._n = n

    @property
    def c(self):
        return self._c

    @c.setter
    def c(self, val):
        self._c = val

    @property
    def r(self):
        return self._r

    @r.setter
    def r(self, val):
        self._r = val

    @property
    def n(self):
        return self._n

    @n.setter
    def n(self, val):
        self._n = val

    @property
    def _hash(self):
        return tuple(map(tuple, [self._c, [self._r, self._n]]))

    @property
    def coordinates(self):
        if self._hash not in self._coordinates:
            self._update_coordinates()

        return self._coordinates[self._hash]

    def _update_coordinates(self):

        c, r, n = self._c, self._r, self._n

        dphi, dtheta = pi / n, pi / n
        [phi, theta] = np.mgrid[0:pi + dphi*1.0:dphi,
                                0:2 * pi + dtheta*1.0:dtheta]

        x = c[0] + r * cos(phi) * sin(theta)
        y = c[1] + r * sin(phi) * sin(theta)
        z = c[2] + r * cos(theta)

        self._coordinates[self._hash] = x, y, z


if __name__ == '__main__':

    from mayavi import mlab

    ns = [4, 6, 8, 10, 20, 50]

    sphere = Sphere()

    for i, n in enumerate(ns):
        sphere.c = [i*2.2, 0.0, 0.0]
        sphere.n = n

        mlab.mesh(*sphere.coordinates, representation='wireframe')

    mlab.show()

【问题讨论】:

  • 你不只是重新实现一个字典,而不是保护自己免受哈希冲突吗?为什么不使用您的元组作为字典中的键?
  • 我使用集合的部分原因是哈希是一个不可变的副本,并且因为我最初将它存储为字典键(一次性值为 True)。然后我会做if self._get_hash() in self._hash_dict 来检查我是否需要重新计算。然后我意识到我可以只检查平等而不是检查 dict 成员资格......所以我基本上把它改成了if self._get_hash() != self._hash:,因为它更轻量级。我还关心什么哈希冲突?我的形状的整个状态都存储在元组中,因此如果相等,则根据定义,它是相同的形状。
  • 啊,忽略我的哈希冲突评论——我可以发誓我在那里看到了对 hash 函数的调用,但显然我只是文盲。不过,我不明白您不愿意使用 dict:使用 cache_dict 是标准模式(如果合适,也可以使用 lru_cache。)
  • 我猜是因为 dicts 用于将无序的键序列映射到值,我不需要所有这些...我也刚刚看了Alex Gaynor's 'Fast Python, Slow Python' talk,它警告不要滥用复杂的数据结构。
  • 已编辑以添加带有字典的实现......那更好/惯用/pythonic吗?

标签: python hash properties lazy-evaluation


【解决方案1】:

为什么不简单地保留一个标志:

class Sphere(object):

    def __init__(self, ...): 
        ...
        self._update_coordinates()

    ...

    @c.setter
    def c(self, val):
        self._changed = True
        self._c = val

    ...

    def _lazy_update(self):
        if self._changed:
            self._update_coordinates()

    def _update_coordinates(self):
        ...
        self._changed = False

【讨论】:

  • 我想过,但随后会为相同的几何图形重新计算坐标......就像这里:sphere.r = 1; x,y,z=sphere.coordinates sphere.r+=1; sphere.r=sphere.r/2; x,y,z = sphere.coordinates。第二组坐标不应触发重新计算,因为当前组基于 r=1,而 r 仍然等于 1。r=2 的事实不应导致不必要的重新计算(如果从未请求坐标) .
猜你喜欢
  • 1970-01-01
  • 2018-01-30
  • 2015-11-25
  • 2010-11-30
  • 1970-01-01
  • 2020-05-17
  • 1970-01-01
  • 2014-04-17
  • 2018-03-26
相关资源
最近更新 更多