python - 使用实例属性作为字典值
问题描述
预脚本:我已经搜索了很多关于 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.x
并self.y
没有被更新。
问题:
如何清理此代码以避免重复?
即使原始代码段被认为对功能“不是那么糟糕”,有没有办法以我想要的方式传递实例属性?
解决方案
在重构方面,有很多不同的方法可以做到这一点。就个人而言,我认为调用者应该负责确定该点应该做什么,而不是该点根据字符串输入确定该做什么。这里的问题是这种方法并没有使 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_x
和distance_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()
...
虽然更冗长,但这种方法有几个好处。
- 每个方向都包含在自己的功能中。这将允许子类轻松覆盖基点行为。例如,如果要跟踪每步向上移动 2 个网格空间的点,则可以扩展 BasePoint 并重写 move_up 函数,使其看起来像这样:
def move_up(steps=1):
super().move_up(distance=2, steps=steps)
- _move 函数更简洁,因为它不需要知道将 Point 移动到哪个方向。只需沿 x 和 y 轴移动距离,移动点并保存新位置。这使得类更具可扩展性,因为您可以通过添加新函数轻松地为点移动(即对角点)创建新方向
def move_diagonal(distance_x=1, distance_y=1, steps=1)
super()._move(distance=(distance_x, distance_y), steps=steps)
- 调用者控制点的调用方式。这允许调用者定义调用函数的规则
if action == 'U' and len(p.locations) < TOTAL_STEPS:
p.move_up()
else:
raise TooManyStepsException('You have exceeded the number of allowed steps')
希望这会有所帮助,并根据您的用例为您提供几种不同的方法。
推荐阅读
- kotlin - Kotlin 的 fold 是什么让我可以将操作函数放在右括号之后?
- google-cloud-platform - 谷歌云实例显示 SSH 而不是 RDP(Windows 而不是 Linux)
- javascript - 更新到 Codeigniter 3.x 后,为什么在 ajax 调用期间出现 json 解析错误?
- ruby-on-rails - 显示多行错误消息
- mysql - 从 db 表中获取行
- reactjs - 我可以在不违反 React 设计原则的情况下在 reducer 中验证令牌吗?
- java - 为什么java支持255维数组,而在现实生活中我们很少超过2D?
- python - Python Pong Game在1后没有增加点
- f# - F# - 以微秒为单位的时间转换为星期几
- macos - 无法从 MacOS 上的主机浏览器连接到 Docker 容器