使用实例属性作为字典值
Using Instance Attributes as Dictionary Values
前脚本:我在 SO 上搜索了很多线程,但 none 似乎回答了我的问题。
我制作了一个小脚本,用于处理围绕网格移动的点,同时使用它已遍历的所有这些点更新集合。 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
没有更新。
问题:
如何清理此代码以避免重复?
即使原始代码被认为是 "not all that bad" 的功能,有没有办法按照我打算的方式传递实例属性?
您的第一次重构肯定是在正确的轨道上。您看到的问题是 add
和 sub
return 新值,而不是现有值。 coord
与 self.x
或 self.y
不同。我会在这里使用属性查找
from operator import add, sub
def _move(self, dir_, steps):
dir_dict = {'U': ('y', self.y, add), \
'D': ('y', self.y, sub), \
'L': ('x', self.x, sub), \
'R': ('x', self.x, add)}
attr, coord, func = dir_dict[dir_]
for step in range(steps):
coord = func(coord, 1)
# set the attribute on self here
setattr(self, attr, coord)
locations.add(self.x, self.y)
这是我的做法:您希望字典值代表运动;这些是应该更新对象状态的操作,因此将它们表示为执行状态更新的函数是有意义的,而不是关于要执行哪些状态更新的数据。 (通过将 add
或 sub
存储为函数,您已经完成了一半。)
给出classmove_up
、move_down
、move_left
和move_right
方法,然后在字典中存储对这些方法的引用。
def move_up(self):
self.y += 1
def move_down(self):
self.y -= 1
def move_left(self):
self.x -= 1
def move_right(self):
self.x += 1
def _move(self, dir_, steps):
dir_dict = {'U': self.move_up,
'D': self.move_down,
'L': self.move_left,
'R': self.move_right}
func = dir_dict[dir_]
for step in range(steps):
func()
self.locations.add( (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()
...
虽然比较冗长,但这种方法有几个好处。
- 每个方向都包含在它自己的函数中。这将允许 subclasses 轻松覆盖基点行为。例如,如果您想要跟踪每步向上移动 2 个网格空间的点,您可以扩展 BasePoint 并重写 move_up 函数,使其看起来像这样:
def move_up(steps=1):
super().move_up(distance=2, steps=steps)
- _move函数更简洁,因为它不需要知道将Point移动到哪个方向。它只需要沿 x 和 y 轴移动的距离,移动点并保存新位置。这使得 class 更具可扩展性,因为您可以通过添加新函数
轻松地为要移动的点(即对角线点)创建新的方向
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')
希望这对您有所帮助,并根据您的用例为您提供几种不同的方法。
前脚本:我在 SO 上搜索了很多线程,但 none 似乎回答了我的问题。
我制作了一个小脚本,用于处理围绕网格移动的点,同时使用它已遍历的所有这些点更新集合。 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
没有更新。
问题:
如何清理此代码以避免重复?
即使原始代码被认为是 "not all that bad" 的功能,有没有办法按照我打算的方式传递实例属性?
您的第一次重构肯定是在正确的轨道上。您看到的问题是 add
和 sub
return 新值,而不是现有值。 coord
与 self.x
或 self.y
不同。我会在这里使用属性查找
from operator import add, sub
def _move(self, dir_, steps):
dir_dict = {'U': ('y', self.y, add), \
'D': ('y', self.y, sub), \
'L': ('x', self.x, sub), \
'R': ('x', self.x, add)}
attr, coord, func = dir_dict[dir_]
for step in range(steps):
coord = func(coord, 1)
# set the attribute on self here
setattr(self, attr, coord)
locations.add(self.x, self.y)
这是我的做法:您希望字典值代表运动;这些是应该更新对象状态的操作,因此将它们表示为执行状态更新的函数是有意义的,而不是关于要执行哪些状态更新的数据。 (通过将 add
或 sub
存储为函数,您已经完成了一半。)
给出classmove_up
、move_down
、move_left
和move_right
方法,然后在字典中存储对这些方法的引用。
def move_up(self):
self.y += 1
def move_down(self):
self.y -= 1
def move_left(self):
self.x -= 1
def move_right(self):
self.x += 1
def _move(self, dir_, steps):
dir_dict = {'U': self.move_up,
'D': self.move_down,
'L': self.move_left,
'R': self.move_right}
func = dir_dict[dir_]
for step in range(steps):
func()
self.locations.add( (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()
...
虽然比较冗长,但这种方法有几个好处。
- 每个方向都包含在它自己的函数中。这将允许 subclasses 轻松覆盖基点行为。例如,如果您想要跟踪每步向上移动 2 个网格空间的点,您可以扩展 BasePoint 并重写 move_up 函数,使其看起来像这样:
def move_up(steps=1):
super().move_up(distance=2, steps=steps)
- _move函数更简洁,因为它不需要知道将Point移动到哪个方向。它只需要沿 x 和 y 轴移动的距离,移动点并保存新位置。这使得 class 更具可扩展性,因为您可以通过添加新函数 轻松地为要移动的点(即对角线点)创建新的方向
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')
希望这对您有所帮助,并根据您的用例为您提供几种不同的方法。