首页 > 解决方案 > 使用实例属性作为字典值

问题描述

预脚本:我已经搜索了很多关于 SO 的线程,但似乎没有人回答我的问题。

我制作了一个小脚本,它处理一个在网格周围移动的点,同时用它遍历的所有点更新一个集合。该move()方法的要点是:

# self.x and self.y are initialised to 0 at the time of object creation
# dir_ - direction in which to move
# steps - number of steps to move

def _move(self, dir_, steps):                
        if dir_ == 'U':
            for step in range(steps):
                self.y += 1
                self.locations.add((self.x, self.y))
        elif dir_ == 'R':
            for step in range(steps):
                self.x += 1
                self.locations.add((self.x, self.y))
        elif dir_ == 'L':
            for step in range(steps):
                self.x -= 1
                self.locations.add((self.x, self.y))
        elif dir_ == 'D':
            for step in range(steps):
                self.y -= 1
                self.locations.add((self.x, self.y))
        else:
            raise Exception("Invalid direction identifier.")

正如你所看到的,有很多重复。在我渴望清理东西的过程中,我尝试了这样的事情:

from operator import add, sub

def _move(self, dir_, steps):                
    dir_dict = {'U': (self.y, add), \
                'D': (self.y, sub), \
                'L': (self.x, sub), \
                'R': (self.x,add)}

    coord, func = dir_dict[dir_]
    for step in range(steps):
        coord = func(coord, 1)
        locations.add(self.x, self.y)

事实证明,我不能期望对对象属性的引用会像那样传递,因此,它self.xself.y没有被更新。

问题:

  1. 如何清理此代码以避免重复?

  2. 即使原始代码段被认为对功能“不是那么糟糕”,有没有办法以我想要的方式传递实例属性?

标签: pythondictionaryoopattributesinstance

解决方案


在重构方面,有很多不同的方法可以做到这一点。就个人而言,我认为调用者应该负责确定该点应该做什么,而不是该点根据字符串输入确定该做什么。这里的问题是这种方法并没有使 Point 真正可扩展(您不能更改基本功能而不必更改 _move 函数)

所以这是我的看法:

最初,我们可以像这样简化 _move 函数:

def _move(self, distances, steps=1):
    """Move the Point a given distance along the x,y axis, a given amount of times and save the new location"""
    distance_x, distance_y = distances
    for step in range(steps):
        self.x += distance_x
        self.y += distance_y
        self.locations.add((self.x, self.y))     

这是正在发生的事情。_move 函数现在期望沿 x 和 y 轴移动该点的距离,以及移动它的次数。distances在这种情况下是一个元组。为清楚起见,它被解压缩到distance_xdistance_y变量中。然后将距离添加到点的 x 和 y 值,然后保存到locations列表中。对于您的用例,调用者将查找该点应该做什么。

if action == 'U':
    point._move(0, 1)
elif action == 'D':
    point._move(0, -1)
...

现在,如果您想定义该点可以进行的特定运动,您可以执行以下操作:

def move_up(self, distance=1, steps=1):
    """Move the Point up a given distance, a given amount of times

    Precondition: The distance is positive
    """
    assert distance >= 0
    self._move((0, distance), steps)

def move_right(self, distance=1, steps=1):
    """Move the Point right a given distance, a given amount of times

    Precondition: The distance is positive
    """
    assert distance >= 0
    self._move((distance, 0), steps)

def move_down(self, distance=1, steps=1):
    """Move the Point down a given distance, a given amount of times

    Precondition: The distance is positive
    """
    assert distance <= 0 
    self._move((0, -distance), steps)

def move_left(self, distance=1, steps=1):
    """Move the Point left a given distance, a given amount of times

    Precondition: The distance is positive
    """    
    assert distance <= 0
    self._move((-distance, 0), steps)

在这里,每个函数都定义了点将如何在每个方向上移动。每个方向都有一个distance允许调用者定义在给定方向上移动点的网格空间数,以及数字steps。默认情况下,每个函数在每个方向上移动一个。断言在那里是因为能够使用负数移动某个方向似乎很奇怪(向右移动 -1 与向左移动 +1 相同),但根据您的用例,它们不是必需的。

调用者看起来像这样:

if action == 'U':
    point.move_up()
elif action == 'D':
    point.move_down()
...

虽然更冗长,但这种方法有几个好处。

  1. 每个方向都包含在自己的功能中。这将允许子类轻松覆盖基点行为。例如,如果要跟踪每步向上移动 2 个网格空间的点,则可以扩展 BasePoint 并重写 move_up 函数,使其看起来像这样:
def move_up(steps=1):
    super().move_up(distance=2, steps=steps)
  1. _move 函数更简洁,因为它不需要知道将 Point 移动到哪个方向。只需沿 x 和 y 轴移动距离,移动点并保存新位置。这使得类更具可扩展性,因为您可以通过添加新函数轻松地为点移动(即对角点)创建新方向
def move_diagonal(distance_x=1, distance_y=1, steps=1)
    super()._move(distance=(distance_x, distance_y), steps=steps)
  1. 调用者控制点的调用方式。这允许调用者定义调用函数的规则
if action == 'U' and len(p.locations) < TOTAL_STEPS:
    p.move_up()
else:
    raise TooManyStepsException('You have exceeded the number of allowed steps')

希望这会有所帮助,并根据您的用例为您提供几种不同的方法。


推荐阅读