使用父项的替代构造函数创建子项 class 时出现问题
Problem creating a child class using an alternative constructor of parent
我有一个 class 可以在二维板上创建块。我定义了它,所以它在给定高度、长度和在板上的位置的情况下这样做,但也制作了一个替代构造函数,通过在板上传递其坐标来创建块。
class Block:
""" Create a block of given height and length in a starting location."""
def __init__(self, name: str, h: int, l: int, location: Tuple[int,int]):
self.name = name
self.coords = [tuple_sum(t1=location, t2=(i//l , i%l)) for i in range(h*l)]
# tuple_sum does (a, b)+(c, d) -> (a+c, b+d)
@classmethod
def from_coords(cls, name: str, coords: List[Tuple]):
block = cls.__new__(cls)
block.name = name
block.coords = coords
return block
...
def __str__(self) -> str:
return f'Name: {self.name}\nCoords: {self.coords}'
我正在尝试为版块中的空白区域创建子版 class。我认为使用 from_coords 构造函数可以完成所有操作,但出于某种原因我不明白创建的元素未初始化,即没有名称或坐标属性。
class SpacesBlock(Block):
""" Make a block of spaces from a list of coordenates """
def __init__(self, coords):
super().from_coords(" ",coords)
...
spaces = SpacesBlock([(0,0),(0,1)])
print(spaces)
AttributeError Traceback (most recent call last)
\block.py in <module>
space = SpacesBlock([(0,0),(0,1)])
----> print(spaces)
AttributeError: 'SpacesBlock' object has no attribute 'name'
我以为是 from_coords 构造函数,但它工作正常
a = Block.from_coords("A",[(0,0),(1,0)])
print(a)
Name: A
Coords: [(0, 0), (1, 0)]
我知道我可以在 SpacesBlock 的初始化中定义名称和坐标,一切都很好,但我很好奇为什么它不能像我想象的那样工作。我错过了什么?
当你执行
spaces = SpacesBlock([(0,0),(0,1)])
您创建了一个实例 SpaceBlock
。然后 __init__
运行以初始化此对象。问题是,SpaceBlock
中的 __init__
函数不会修改您刚刚创建的实例。相反,它创建并实例化另一个对象。这个新对象没有任何用途,您最初创建它的对象没有任何修改,特别是没有 name
和 coords
属性。
您可以通过修改 __init__
来修复它,例如如下:
def __init__(self, coords):
x = super().from_coords(" ",coords)
self.name = x.name
self.coords = x.coords
但这将是一个不必要的复杂代码。
from_coords
不是构造函数。这是一个工厂方法。
__init__
实际上初始化了一个已经存在的(即已分配的)对象。因为它是一个普通的方法,接收一个self
参数,所以它能够这样做。 classmethod
不能初始化一个已经存在的对象,除非你明确地提供它。
您的代码中发生的事情是 super().from_coords(" ",coords)
创建了 Block
的 单独 实例,然后将其丢弃。 SpacesBlock
的 self
实例没有设置 .name
;发生在另一个实例上。
@classmethod
(与 @staticmethod
相对)的要点是,因为您收到的参数是 ,所以 class,即使您没有对象实例,它仍然可以表现出多态性。 (正如您所发现的,您可以使用它来使 __new__
以多态方式工作。)
您的工厂方法已经可以表现出多态性:SpacesBlock.from_coords
将调用该方法,并将 SpacesBlock
作为 cls
传递,以便 cls.__new__
创建一个新的 SpacesBlock
实例。但是,__init__
不会以这种方式调用;并且根据您当前的组织结构,不清楚您将如何称呼它或通过什么。
__new__
真正合理的用途很少见。
使用工厂方法的正常方法是让它们调用 __init__
,方法是 确定要使用 的参数以进行 __init__
调用。在您的情况下,坐标列表可以是任意位置;但是宽度、高度和位置为您提供了一种指定多个坐标的方法。因此,使用实际构造函数进行坐标列表构造方法会更容易。
使用该设置,代码如下所示:
class Block:
def __init__(self, name: str, coords: List[Tuple]):
self.name = name
self.coords = coords
@classmethod
def from_grid(cls, name: str, h: int, l: int, location: Tuple[int,int]):
coords = [tuple_sum(location, (i//l , i%l)) for i in range(h*l)]
return cls(name, coords)
class SpacesBlock(Block):
pass # thus far, doesn't actually do anything different
这里要注意的重要一点是 from_grid
现在可以用于任何 class。 Block.from_grid
创建一个 Block
实例,而 SpacesBlock.from_grid
创建一个 SpacesBlock
实例 - 在任何一种情况下,都使用 (height, length, location) 方法。要直接从坐标列表创建 class,只需直接调用构造函数即可。
我有一个 class 可以在二维板上创建块。我定义了它,所以它在给定高度、长度和在板上的位置的情况下这样做,但也制作了一个替代构造函数,通过在板上传递其坐标来创建块。
class Block:
""" Create a block of given height and length in a starting location."""
def __init__(self, name: str, h: int, l: int, location: Tuple[int,int]):
self.name = name
self.coords = [tuple_sum(t1=location, t2=(i//l , i%l)) for i in range(h*l)]
# tuple_sum does (a, b)+(c, d) -> (a+c, b+d)
@classmethod
def from_coords(cls, name: str, coords: List[Tuple]):
block = cls.__new__(cls)
block.name = name
block.coords = coords
return block
...
def __str__(self) -> str:
return f'Name: {self.name}\nCoords: {self.coords}'
我正在尝试为版块中的空白区域创建子版 class。我认为使用 from_coords 构造函数可以完成所有操作,但出于某种原因我不明白创建的元素未初始化,即没有名称或坐标属性。
class SpacesBlock(Block):
""" Make a block of spaces from a list of coordenates """
def __init__(self, coords):
super().from_coords(" ",coords)
...
spaces = SpacesBlock([(0,0),(0,1)])
print(spaces)
AttributeError Traceback (most recent call last)
\block.py in <module>
space = SpacesBlock([(0,0),(0,1)])
----> print(spaces)
AttributeError: 'SpacesBlock' object has no attribute 'name'
我以为是 from_coords 构造函数,但它工作正常
a = Block.from_coords("A",[(0,0),(1,0)])
print(a)
Name: A
Coords: [(0, 0), (1, 0)]
我知道我可以在 SpacesBlock 的初始化中定义名称和坐标,一切都很好,但我很好奇为什么它不能像我想象的那样工作。我错过了什么?
当你执行
spaces = SpacesBlock([(0,0),(0,1)])
您创建了一个实例 SpaceBlock
。然后 __init__
运行以初始化此对象。问题是,SpaceBlock
中的 __init__
函数不会修改您刚刚创建的实例。相反,它创建并实例化另一个对象。这个新对象没有任何用途,您最初创建它的对象没有任何修改,特别是没有 name
和 coords
属性。
您可以通过修改 __init__
来修复它,例如如下:
def __init__(self, coords):
x = super().from_coords(" ",coords)
self.name = x.name
self.coords = x.coords
但这将是一个不必要的复杂代码。
from_coords
不是构造函数。这是一个工厂方法。
__init__
实际上初始化了一个已经存在的(即已分配的)对象。因为它是一个普通的方法,接收一个self
参数,所以它能够这样做。 classmethod
不能初始化一个已经存在的对象,除非你明确地提供它。
您的代码中发生的事情是 super().from_coords(" ",coords)
创建了 Block
的 单独 实例,然后将其丢弃。 SpacesBlock
的 self
实例没有设置 .name
;发生在另一个实例上。
@classmethod
(与 @staticmethod
相对)的要点是,因为您收到的参数是 ,所以 class,即使您没有对象实例,它仍然可以表现出多态性。 (正如您所发现的,您可以使用它来使 __new__
以多态方式工作。)
您的工厂方法已经可以表现出多态性:SpacesBlock.from_coords
将调用该方法,并将 SpacesBlock
作为 cls
传递,以便 cls.__new__
创建一个新的 SpacesBlock
实例。但是,__init__
不会以这种方式调用;并且根据您当前的组织结构,不清楚您将如何称呼它或通过什么。
__new__
真正合理的用途很少见。
使用工厂方法的正常方法是让它们调用 __init__
,方法是 确定要使用 的参数以进行 __init__
调用。在您的情况下,坐标列表可以是任意位置;但是宽度、高度和位置为您提供了一种指定多个坐标的方法。因此,使用实际构造函数进行坐标列表构造方法会更容易。
使用该设置,代码如下所示:
class Block:
def __init__(self, name: str, coords: List[Tuple]):
self.name = name
self.coords = coords
@classmethod
def from_grid(cls, name: str, h: int, l: int, location: Tuple[int,int]):
coords = [tuple_sum(location, (i//l , i%l)) for i in range(h*l)]
return cls(name, coords)
class SpacesBlock(Block):
pass # thus far, doesn't actually do anything different
这里要注意的重要一点是 from_grid
现在可以用于任何 class。 Block.from_grid
创建一个 Block
实例,而 SpacesBlock.from_grid
创建一个 SpacesBlock
实例 - 在任何一种情况下,都使用 (height, length, location) 方法。要直接从坐标列表创建 class,只需直接调用构造函数即可。