Python 摘要 属性() "Can't instantiate abstract class [] with abstract methods",但我做到了
Python abstract property() "Can't instantiate abstract class [] with abstract methods", but I did
我正在尝试在 python 3.7.
中创建一个具有许多抽象 python 属性的基础 class
我使用 @属性、@abstractmethod、@property.setter 注释尝试了一种方法(请参阅下面的 'start')。这行得通,但如果 subclass 没有实现 setter,它不会引发异常。这就是我使用@abstract 的意义所在,所以这不好。
所以我尝试用另一种方式(参见下面的 'end')使用两个 @abstractmethod 方法和一个 'property()',它本身不是抽象的,但使用了这些方法。这种方法在实例化 subclass:
时会产生错误
# {TypeError}Can't instantiate abstract class FirstStep with abstract methods end
我明明是在实现抽象方法,所以我不明白这是什么意思。 'end' 属性 未标记为@abstract,但如果我将其注释掉,它会 运行 (但我没有得到我的 属性)。我还添加了测试非抽象方法 'test_elapsed_time' 以证明我拥有 class 结构和抽象权(有效)。
有没有可能我在做一些愚蠢的事情,或者 属性() 周围有什么特殊行为导致了这个?
class ParentTask(Task):
def get_first_step(self):
# {TypeError}Can't instantiate abstract class FirstStep with abstract methods end
return FirstStep(self)
class Step(ABC):
# __metaclass__ = ABCMeta
def __init__(self, task):
self.task = task
# First approach. Works, but no warnings if don't implement setter in subclass
@property
@abstractmethod
def start(self):
pass
@start.setter
@abstractmethod
def start(self, value):
pass
# Second approach. "This method for 'end' may look slight messier, but raises errors if not implemented.
@abstractmethod
def get_end(self):
pass
@abstractmethod
def set_end(self, value):
pass
end = property(get_end, set_end)
def test_elapsed_time(self):
return self.get_end() - self.start
class FirstStep(Step):
@property
def start(self):
return self.task.start_dt
# No warnings if this is commented out.
@start.setter
def start(self, value):
self.task.start_dt = value
def get_end(self):
return self.task.end_dt
def set_end(self, value):
self.task.end_dt = value
我怀疑这是抽象方法和属性交互中的错误。
在您的基地 class 中,会依次发生以下事情:
- 您定义了一个名为
start
的抽象方法。
- 您创建了一个新的 属性,它使用 1) 中的抽象方法作为它的 getter。名称
start
现在引用此 属性,唯一引用现在由 Self.start.fget
持有的原始名称。
- Python 保存对
start.setter
的临时引用,因为名称 start
即将绑定到另一个 object.
- 您创建了第二个名为
start
的抽象方法
- 来自 3) 的引用被赋予来自 4) 的抽象方法,以定义一个新的 属性 来替换当前绑定到名称
start
的曾经。此 属性 的 getter 是 1 中的方法,setter 是 4) 中的方法。现在start
指的是这个属性; start.fget
指1)中的方法); start.fset
参考4). 中的方法
此时,你有一个属性,它的组件函数是抽象方法。 属性 本身并没有被修饰为抽象,但是 property.__isabstractmethod__
的定义将其标记为抽象,因为它的所有组件方法都是抽象的。更重要的是,您在 Step.__abstractmethods__
中有以下条目:
start
, property
end
, property
set_end
,setter为end
gen_end
,getter为end
请注意,缺少 start
属性 的组件函数,因为 __abstractmethods__
存储 的名称,而不是对事物的引用需要被覆盖。使用 property
和由此产生的 属性 的 setter
方法作为装饰器重复替换名称 start
所指的内容。
现在,在您的 child class 中,您定义了一个名为 start
的 new 属性,隐藏继承的 属性,它有没有 setter和一个具体的方法作为它的getter。在这一点上,你是否为这个 属性 提供一个 setter 并不重要,因为就 abc
机器而言,你已经提供了它所要求的一切:
- 名字的具体方法
start
- 名称
get_end
和set_end
的具体方法
- 名称
end
的隐式具体定义,因为 属性 end
的所有基础函数都已提供具体定义。
@chepner 回答并解释得很好。基于此,我想出了一个解决方法,那就是……好吧……你来决定。充其量是偷偷摸摸的。但它实现了我的 3 个主要目标:
- 为 subclasses
中未实现的设置器引发异常
- 支持 python 属性 语义(相对于函数等)
- 避免样板文件重新声明每个子class中的每个属性,这仍然可能无法解决#1。
只需在基 class(而不是 属性)中声明抽象 get/set 函数。然后将 @class 方法初始值设定项添加到基础 class 中,它使用这些抽象方法创建实际属性,但到那时,它们将成为 subclass 上的具体方法。
它是在 subclass 声明之后的一行来初始化属性。没有任何东西强制执行该调用,因此它不是铁定的。在这个例子中并没有很大的节省,但我会有很多属性。最终结果看起来并不像我想象的那么脏。想听听对我忽略的事情的评论或警告。
from abc import abstractmethod, ABC
class ParentTask(object):
def __init__(self):
self.first_step = FirstStep(self)
self.second_step = SecondStep(self)
print(self.first_step.end)
print(self.second_step.end)
class Step(ABC):
def __init__(self, task):
self.task = task
@classmethod
def init_properties(cls):
cls.end = property(cls.get_end, cls.set_end)
@abstractmethod
def get_end(self):
pass
@abstractmethod
def set_end(self, value):
pass
class FirstStep(Step):
def get_end(self):
return 1
def set_end(self, value):
self.task.end = value
class SecondStep(Step):
def get_end(self):
return 2
def set_end(self, value):
self.task.end = value
FirstStep.init_properties()
SecondStep.init_properties()
ParentTask()
我正在尝试在 python 3.7.
中创建一个具有许多抽象 python 属性的基础 class我使用 @属性、@abstractmethod、@property.setter 注释尝试了一种方法(请参阅下面的 'start')。这行得通,但如果 subclass 没有实现 setter,它不会引发异常。这就是我使用@abstract 的意义所在,所以这不好。
所以我尝试用另一种方式(参见下面的 'end')使用两个 @abstractmethod 方法和一个 'property()',它本身不是抽象的,但使用了这些方法。这种方法在实例化 subclass:
时会产生错误 # {TypeError}Can't instantiate abstract class FirstStep with abstract methods end
我明明是在实现抽象方法,所以我不明白这是什么意思。 'end' 属性 未标记为@abstract,但如果我将其注释掉,它会 运行 (但我没有得到我的 属性)。我还添加了测试非抽象方法 'test_elapsed_time' 以证明我拥有 class 结构和抽象权(有效)。
有没有可能我在做一些愚蠢的事情,或者 属性() 周围有什么特殊行为导致了这个?
class ParentTask(Task):
def get_first_step(self):
# {TypeError}Can't instantiate abstract class FirstStep with abstract methods end
return FirstStep(self)
class Step(ABC):
# __metaclass__ = ABCMeta
def __init__(self, task):
self.task = task
# First approach. Works, but no warnings if don't implement setter in subclass
@property
@abstractmethod
def start(self):
pass
@start.setter
@abstractmethod
def start(self, value):
pass
# Second approach. "This method for 'end' may look slight messier, but raises errors if not implemented.
@abstractmethod
def get_end(self):
pass
@abstractmethod
def set_end(self, value):
pass
end = property(get_end, set_end)
def test_elapsed_time(self):
return self.get_end() - self.start
class FirstStep(Step):
@property
def start(self):
return self.task.start_dt
# No warnings if this is commented out.
@start.setter
def start(self, value):
self.task.start_dt = value
def get_end(self):
return self.task.end_dt
def set_end(self, value):
self.task.end_dt = value
我怀疑这是抽象方法和属性交互中的错误。
在您的基地 class 中,会依次发生以下事情:
- 您定义了一个名为
start
的抽象方法。 - 您创建了一个新的 属性,它使用 1) 中的抽象方法作为它的 getter。名称
start
现在引用此 属性,唯一引用现在由Self.start.fget
持有的原始名称。 - Python 保存对
start.setter
的临时引用,因为名称start
即将绑定到另一个 object. - 您创建了第二个名为
start
的抽象方法
- 来自 3) 的引用被赋予来自 4) 的抽象方法,以定义一个新的 属性 来替换当前绑定到名称
start
的曾经。此 属性 的 getter 是 1 中的方法,setter 是 4) 中的方法。现在start
指的是这个属性;start.fget
指1)中的方法);start.fset
参考4). 中的方法
此时,你有一个属性,它的组件函数是抽象方法。 属性 本身并没有被修饰为抽象,但是 property.__isabstractmethod__
的定义将其标记为抽象,因为它的所有组件方法都是抽象的。更重要的是,您在 Step.__abstractmethods__
中有以下条目:
start
,property
end
,property
set_end
,setter为end
gen_end
,getter为end
请注意,缺少 start
属性 的组件函数,因为 __abstractmethods__
存储 的名称,而不是对事物的引用需要被覆盖。使用 property
和由此产生的 属性 的 setter
方法作为装饰器重复替换名称 start
所指的内容。
现在,在您的 child class 中,您定义了一个名为 start
的 new 属性,隐藏继承的 属性,它有没有 setter和一个具体的方法作为它的getter。在这一点上,你是否为这个 属性 提供一个 setter 并不重要,因为就 abc
机器而言,你已经提供了它所要求的一切:
- 名字的具体方法
start
- 名称
get_end
和set_end
的具体方法
- 名称
end
的隐式具体定义,因为 属性end
的所有基础函数都已提供具体定义。
@chepner 回答并解释得很好。基于此,我想出了一个解决方法,那就是……好吧……你来决定。充其量是偷偷摸摸的。但它实现了我的 3 个主要目标:
- 为 subclasses 中未实现的设置器引发异常
- 支持 python 属性 语义(相对于函数等)
- 避免样板文件重新声明每个子class中的每个属性,这仍然可能无法解决#1。
只需在基 class(而不是 属性)中声明抽象 get/set 函数。然后将 @class 方法初始值设定项添加到基础 class 中,它使用这些抽象方法创建实际属性,但到那时,它们将成为 subclass 上的具体方法。
它是在 subclass 声明之后的一行来初始化属性。没有任何东西强制执行该调用,因此它不是铁定的。在这个例子中并没有很大的节省,但我会有很多属性。最终结果看起来并不像我想象的那么脏。想听听对我忽略的事情的评论或警告。
from abc import abstractmethod, ABC
class ParentTask(object):
def __init__(self):
self.first_step = FirstStep(self)
self.second_step = SecondStep(self)
print(self.first_step.end)
print(self.second_step.end)
class Step(ABC):
def __init__(self, task):
self.task = task
@classmethod
def init_properties(cls):
cls.end = property(cls.get_end, cls.set_end)
@abstractmethod
def get_end(self):
pass
@abstractmethod
def set_end(self, value):
pass
class FirstStep(Step):
def get_end(self):
return 1
def set_end(self, value):
self.task.end = value
class SecondStep(Step):
def get_end(self):
return 2
def set_end(self, value):
self.task.end = value
FirstStep.init_properties()
SecondStep.init_properties()
ParentTask()