使用父类的替代构造函数创建子类时出现问题



我有一个在2d板中创建块的类。我定义了它,这样它就可以在给定它们在板上的高度、长度和位置的情况下这样做,但也制作了一个替代构造函数,通过传递其在板中的坐标来创建块。


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}'

我正试图为黑板上的空白处创建一个儿童类。我认为使用from_words构造函数可以完成所有操作,但由于某种原因,我不理解创建的元素没有初始化,即没有name或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_words构造函数,但它运行良好

a = Block.from_coords("A",[(0,0),(1,0)])
print(a)
Name: A
Coords: [(0, 0), (1, 0)]

我知道我可以在SpacesBlock的init中定义名称和坐标,一切都很好,但我很好奇为什么它不能像我想象的那样工作。我错过了什么?

from_coords不是构造函数。这是一种工厂方法

__init__实际上初始化了一个已经存在(即已分配(的对象。因为它是一个普通的方法,接收一个self参数,所以它能够做到这一点。classmethod不能初始化一个已经存在的对象,除非你明确地为它提供一个。

代码中发生的情况是,super().from_coords(" ",coords)创建了Block单独实例,然后将其丢弃。SpacesBlockself实例没有得到其.name集合;另一个例子就是这样。

@classmethod(与@staticmethod相反(的要点是,因为您收到的参数是类的,所以即使您没有对象实例,它仍然可以表现为多态性。(正如您所发现的,您可以使用它使__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现在可以用于任何一个类。Block.from_grid创建Block实例,SpacesBlock.from_grid创建SpacesBlock实例——无论哪种情况,都使用(高度、长度、位置(方法。要直接从坐标列表中创建任一类,只需直接调用构造函数即可。

执行时
spaces = SpacesBlock([(0,0),(0,1)])

您创建了一个实例SpaceBlock。然后运行__init__来初始化这个对象。问题是,SpaceBlock中的__init__函数不会修改您刚刚创建的实例。相反,它创建并实例化另一个对象。这个新对象没有用于任何用途,您最初创建的对象没有进行任何修改,特别是没有namecoords属性。

您可以通过修改__init__来修复它,例如:

def __init__(self, coords):
x = super().from_coords("  ",coords)
self.name  = x.name
self.coords = x.coords

但这将是一个不必要的复杂代码。

最新更新